You've already forked AstralRinth
forked from didirus/AstralRinth
Team routes (#92)
* Team routes template * More work on teams * Updating routes WIP * Edit routes * Fixes * Run prepare, prevent non-members from seeing perms * More fixes * Finish team routes * More fixes * Unpushed changes * Some more fixes and error handling * Fix sqlx prepare, formatting Co-authored-by: Aeledfyr <aeledfyr@gmail.com>
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1612,6 +1612,7 @@ dependencies = [
|
||||
"actix-web",
|
||||
"async-trait",
|
||||
"base64 0.13.0",
|
||||
"bitflags",
|
||||
"chrono",
|
||||
"dotenv",
|
||||
"env_logger",
|
||||
|
||||
@@ -27,6 +27,7 @@ chrono = { version = "0.4", features = ["serde"] }
|
||||
rand = "0.7.3"
|
||||
base64 = "0.13.0"
|
||||
sha1 = { version = "0.6.0", features = ["std"] }
|
||||
bitflags = "1.2.1"
|
||||
|
||||
gumdrop = "0.8.0"
|
||||
dotenv = "0.15"
|
||||
|
||||
5
migrations/20201109200208_edit-teams.sql
Normal file
5
migrations/20201109200208_edit-teams.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
-- Add migration script here
|
||||
ALTER TABLE team_members
|
||||
ADD COLUMN permissions bigint default 0 NOT NULL;
|
||||
ALTER TABLE team_members
|
||||
ADD COLUMN accepted boolean default false NOT NULL;
|
||||
438
sqlx-data.json
438
sqlx-data.json
@@ -1,25 +1,5 @@
|
||||
{
|
||||
"db": "PostgreSQL",
|
||||
"02d8895627dfe108735a6e10ad63239348b71b3322b4734526a2646f17aedf05": {
|
||||
"query": "\n SELECT status FROM statuses\n WHERE id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "status",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"03209c5bda2d704e688439919a7b3903db6ad7caebf7ddafb3ea52d312d47bfb": {
|
||||
"query": "\n INSERT INTO users (\n id, github_id, username, name, email,\n avatar_url, bio, created\n )\n VALUES (\n $1, $2, $3, $4, $5,\n $6, $7, $8\n )\n ",
|
||||
"describe": {
|
||||
@@ -39,6 +19,47 @@
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"0739834cfbef869855ed4e1aea7e1f7601f6519867ee48c573ee901c4498e04c": {
|
||||
"query": "\n UPDATE team_members\n SET permissions = $1\n WHERE (team_id = $2 AND user_id = $3 AND NOT role = $4)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8",
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"07e72d55c2f18744bbfffee9920866d4aacd680f316058ec734735c173a7f16b": {
|
||||
"query": "\n SELECT DISTINCT gv.version, gv.created FROM versions\n INNER JOIN game_versions_versions gvv ON gvv.joining_version_id=versions.id\n INNER JOIN game_versions gv ON gvv.game_version_id=gv.id\n WHERE versions.mod_id = $1\n ORDER BY gv.created ASC\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "version",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "created",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"0ca11a32b2860e4f5c3d20892a5be3cb419e084f42ba0f98e09b9995027fcc4e": {
|
||||
"query": "\n SELECT id FROM statuses\n WHERE status = $1\n ",
|
||||
"describe": {
|
||||
@@ -194,22 +215,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"15b2a2f1bbbbab4f1d99e5e428b2ffba77c83814b936fa6e10e2703b207f6e9a": {
|
||||
"query": "\n INSERT INTO team_members (id, team_id, user_id, member_name, role)\n VALUES ($1, $2, $3, $4, $5)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8",
|
||||
"Int8",
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"17e6d30c3693e9bd9f772f3dc4e2eafe75fdeecfdcf2746eac641f77ced6b8a8": {
|
||||
"query": "\n SELECT u.id, u.github_id, u.name, u.email,\n u.avatar_url, u.username, u.bio,\n u.created, u.role FROM users u\n WHERE u.id IN (SELECT * FROM UNNEST($1::bigint[]))\n ",
|
||||
"describe": {
|
||||
@@ -384,6 +389,56 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"21d268dbad5ffd34d476998aea4475cdb071e8cfbb245c4853ee5f4c44b0c8ae": {
|
||||
"query": "\n SELECT id, user_id, member_name, role, permissions, accepted\n FROM team_members\n WHERE (team_id = $1 AND accepted = TRUE)\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "user_id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "member_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "role",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "permissions",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "accepted",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"225597042db9c2d95296ea6bbeda4e99ffc9ddfab3991c8637ac3f4749ece6f3": {
|
||||
"query": "\n SELECT m.id, m.title, m.description, m.downloads, m.icon_url, m.body_url, m.published, m.updated, m.team_id\n FROM mods m\n WHERE id = $1\n ",
|
||||
"describe": {
|
||||
@@ -547,6 +602,20 @@
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"3b52d9f68ba23d1e3764f8df9f28bcaec0741101f6afd0c7c234b7f1b91054a4": {
|
||||
"query": "\n UPDATE team_members\n SET accepted = TRUE\n WHERE (team_id = $1 AND user_id = $2 AND NOT role = $3)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"42e072309779598d0c213280dd8052d1b4889cb24ef5204ca13b74f693b94328": {
|
||||
"query": "\n SELECT user_id FROM team_members tm\n INNER JOIN mods ON mods.team_id = tm.team_id\n WHERE mods.id = $1\n ",
|
||||
"describe": {
|
||||
@@ -826,6 +895,72 @@
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"6c2299a7b7ab22f83049bc41fb5dd380adea3579e7b00df7d16fb6747a0a7313": {
|
||||
"query": "\n UPDATE team_members\n SET role = $1\n WHERE (team_id = $2 AND user_id = $3 AND NOT role = $4)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Int8",
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"6d8f1863579977d367784a5b457e21c841886834afe69a964de039d6f92795d4": {
|
||||
"query": "\n SELECT id, user_id, member_name, role, permissions, accepted\n FROM team_members\n WHERE (team_id = $1 AND user_id = $2)\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "user_id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "member_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "role",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "permissions",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "accepted",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"71db1bc306ff6da3a92544e1585aa11c5627b50d95b15e794b2fa5dc838ea1a3": {
|
||||
"query": "\n SELECT mod_id, version_number, author_id\n FROM versions\n WHERE id = $1\n ",
|
||||
"describe": {
|
||||
@@ -920,6 +1055,56 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"733a9569fef2aa48d1d3b1b02d7fa893174d523adc57fb22995b1ea8897f3abf": {
|
||||
"query": "\n SELECT id, team_id, member_name, role, permissions, accepted\n FROM team_members\n WHERE (user_id = $1 AND accepted = TRUE)\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "team_id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "member_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "role",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "permissions",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "accepted",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"73bdd6c9e7cd8c1ed582261aebdee0f8fd2734e712ef288a2608564c918009cb": {
|
||||
"query": "\n DELETE FROM versions WHERE id = $1\n ",
|
||||
"describe": {
|
||||
@@ -952,20 +1137,8 @@
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"8f706d78ac4235ea04c59e2c220a4791e1d08fdf287b783b4aaef36fd2445467": {
|
||||
"query": "\n DELETE FROM loaders\n WHERE loader = $1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"91d7e437c6dfb9b95e68aca92154bd2af4de13eeb9611e08dcad2717d0c41ed9": {
|
||||
"query": "\n SELECT id, user_id, member_name, role\n FROM team_members\n WHERE team_id = $1\n ",
|
||||
"7bbbeecf3246a8e07ad073a07f7d057e0990a810d69ae18cec41de60b704b174": {
|
||||
"query": "\n SELECT id, user_id, member_name, role, permissions, accepted\n FROM team_members\n WHERE (team_id = $1 AND user_id = $2 AND accepted = TRUE)\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -987,14 +1160,27 @@
|
||||
"ordinal": 3,
|
||||
"name": "role",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "permissions",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "accepted",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
@@ -1002,6 +1188,32 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"8ba2b2c38958f1c542e514fc62ab4682f58b0b442ac1842d20625420698e34ec": {
|
||||
"query": "\n DELETE FROM team_members\n WHERE (team_id = $1 AND user_id = $2 AND NOT role = $3)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"8f706d78ac4235ea04c59e2c220a4791e1d08fdf287b783b4aaef36fd2445467": {
|
||||
"query": "\n DELETE FROM loaders\n WHERE loader = $1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"9a41d6c1d5c250df6114157edf5621a88bc336c5c628ba89182ba999e0af3ba8": {
|
||||
"query": "\n SELECT id, title, description, downloads,\n icon_url, body_url, published,\n updated, status,\n issues_url, source_url, wiki_url,\n team_id\n FROM mods\n WHERE id IN (SELECT * FROM UNNEST($1::bigint[]))\n ",
|
||||
"describe": {
|
||||
@@ -1121,6 +1333,24 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"9ef0577ee4845091a0d29bad6d1130223767e6d6140cafb9013fdb428d5159dd": {
|
||||
"query": "\n INSERT INTO team_members (id, team_id, user_id, member_name, role, permissions, accepted)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8",
|
||||
"Int8",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Int8",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"a2a99a640468a9fb8f0718e5aea6740cf5b33dafd5e038c154d6a13674fa999b": {
|
||||
"query": "\n INSERT INTO mods (\n id, team_id, title, description, body_url,\n published, downloads, icon_url, issues_url,\n source_url, wiki_url, status\n )\n VALUES (\n $1, $2, $3, $4, $5,\n $6, $7, $8, $9,\n $10, $11, $12\n )\n ",
|
||||
"describe": {
|
||||
@@ -1262,6 +1492,32 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"ad273daadd249e93b500c339a62aac48a497ebcc15164776ad20860a4d232896": {
|
||||
"query": "\n SELECT DISTINCT gv.version, gv.created FROM versions\n INNER JOIN game_versions_versions gvv ON gvv.joining_version_id=versions.id\n INNER JOIN game_versions gv ON gvv.game_version_id=gv.id\n WHERE versions.mod_id = $1\n ORDER BY gv.created ASC\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "version",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "created",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"b0e3d1c70b87bb54819e3fac04b684a9b857aeedb4dcb7cb400c2af0dbb12922": {
|
||||
"query": "\n DELETE FROM teams\n WHERE id = $1\n ",
|
||||
"describe": {
|
||||
@@ -1700,6 +1956,73 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"d99c8f5a2d8f73f6c91ac7e72e352e03e608522142aab1b569ef5eced5ec18f8": {
|
||||
"query": "\n INSERT INTO team_members (\n id, user_id, member_name, role, permissions, accepted\n )\n VALUES (\n $1, $2, $3, $4, $5,\n $6\n )\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Int8",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"dd16be7b85d2a4bd77eccc780d27031b964d4d346d8899b4dd0e2f47ba86d5fd": {
|
||||
"query": "\n SELECT id, team_id, member_name, role, permissions, accepted\n FROM team_members\n WHERE user_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "team_id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "member_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "role",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "permissions",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "accepted",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"deb81673526789bca38d39e64303f61d2a63febfdfb68136e58517af9f7792bc": {
|
||||
"query": "\n SELECT category FROM mods_categories\n INNER JOIN categories ON joining_category_id = id\n WHERE joining_mod_id = $1\n ",
|
||||
"describe": {
|
||||
@@ -1740,6 +2063,21 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"e53013562a3a9659df5c56185b92cf9cc30ce7e409f4762be98662161af593b9": {
|
||||
"query": "\n UPDATE team_members\n SET member_name = $1\n WHERE (team_id = $2 AND user_id = $3 AND NOT role = $4)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Int8",
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"e673006d1355fa91ba5739d7cf569eec5e1ec501f7b1dc2b431f0b1c25ac07d5": {
|
||||
"query": "\n DELETE FROM game_versions\n WHERE version = $1\n ",
|
||||
"describe": {
|
||||
|
||||
@@ -30,6 +30,10 @@ pub enum DatabaseError {
|
||||
alphanumeric characters or '_-'."
|
||||
)]
|
||||
InvalidIdentifier(String),
|
||||
#[error("Invalid permissions bitflag!")]
|
||||
BitflagError,
|
||||
#[error("A database request failed")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl ids::ChannelId {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::ids::*;
|
||||
use crate::models::teams::Permissions;
|
||||
|
||||
pub struct TeamBuilder {
|
||||
pub members: Vec<TeamMemberBuilder>,
|
||||
@@ -7,6 +8,8 @@ pub struct TeamMemberBuilder {
|
||||
pub user_id: UserId,
|
||||
pub name: String,
|
||||
pub role: String,
|
||||
pub permissions: Permissions,
|
||||
pub accepted: bool,
|
||||
}
|
||||
|
||||
impl TeamBuilder {
|
||||
@@ -36,18 +39,22 @@ impl TeamBuilder {
|
||||
user_id: member.user_id,
|
||||
name: member.name,
|
||||
role: member.role,
|
||||
permissions: member.permissions,
|
||||
accepted: member.accepted,
|
||||
};
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
INSERT INTO team_members (id, team_id, user_id, member_name, role)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
INSERT INTO team_members (id, team_id, user_id, member_name, role, permissions, accepted)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
",
|
||||
team_member.id as TeamMemberId,
|
||||
team_member.team_id as TeamId,
|
||||
team_member.user_id as UserId,
|
||||
team_member.name,
|
||||
team_member.role,
|
||||
team_member.permissions.bits() as i64,
|
||||
team_member.accepted,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
@@ -72,9 +79,12 @@ pub struct TeamMember {
|
||||
/// The name of the user
|
||||
pub name: String,
|
||||
pub role: String,
|
||||
pub permissions: Permissions,
|
||||
pub accepted: bool,
|
||||
}
|
||||
|
||||
impl TeamMember {
|
||||
/// Lists the members of a team
|
||||
pub async fn get_from_team<'a, 'b, E>(
|
||||
id: TeamId,
|
||||
executor: E,
|
||||
@@ -86,25 +96,339 @@ impl TeamMember {
|
||||
|
||||
let team_members = sqlx::query!(
|
||||
"
|
||||
SELECT id, user_id, member_name, role
|
||||
SELECT id, user_id, member_name, role, permissions, accepted
|
||||
FROM team_members
|
||||
WHERE team_id = $1
|
||||
WHERE (team_id = $1 AND accepted = TRUE)
|
||||
",
|
||||
id as TeamId,
|
||||
)
|
||||
.fetch_many(executor)
|
||||
.try_filter_map(|e| async {
|
||||
Ok(e.right().map(|m| TeamMember {
|
||||
id: TeamMemberId(m.id),
|
||||
team_id: id,
|
||||
user_id: UserId(m.user_id),
|
||||
name: m.member_name,
|
||||
role: m.role,
|
||||
}))
|
||||
if let Some(m) = e.right() {
|
||||
let permissions = Permissions::from_bits(m.permissions as u64);
|
||||
if let Some(perms) = permissions {
|
||||
Ok(Some(TeamMember {
|
||||
id: TeamMemberId(m.id),
|
||||
team_id: id,
|
||||
user_id: UserId(m.user_id),
|
||||
name: m.member_name,
|
||||
role: m.role,
|
||||
permissions: perms,
|
||||
accepted: m.accepted,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
.try_collect::<Vec<TeamMember>>()
|
||||
.await?;
|
||||
|
||||
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,
|
||||
executor: E,
|
||||
) -> Result<Vec<TeamMember>, super::DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
use futures::stream::TryStreamExt;
|
||||
|
||||
let team_members = sqlx::query!(
|
||||
"
|
||||
SELECT id, team_id, member_name, role, permissions, accepted
|
||||
FROM team_members
|
||||
WHERE (user_id = $1 AND accepted = TRUE)
|
||||
",
|
||||
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(TeamMember {
|
||||
id: TeamMemberId(m.id),
|
||||
team_id: TeamId(m.team_id),
|
||||
user_id: id,
|
||||
name: m.member_name,
|
||||
role: m.role,
|
||||
permissions: perms,
|
||||
accepted: m.accepted,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
.try_collect::<Vec<TeamMember>>()
|
||||
.await?;
|
||||
|
||||
Ok(team_members)
|
||||
}
|
||||
|
||||
/// Lists the team members for a user. Includes pending requests.
|
||||
pub async fn get_from_user_private<'a, 'b, E>(
|
||||
id: UserId,
|
||||
executor: E,
|
||||
) -> Result<Vec<TeamMember>, super::DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
use futures::stream::TryStreamExt;
|
||||
|
||||
let team_members = sqlx::query!(
|
||||
"
|
||||
SELECT id, team_id, member_name, role, permissions, accepted
|
||||
FROM team_members
|
||||
WHERE user_id = $1
|
||||
",
|
||||
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: id,
|
||||
name: m.member_name,
|
||||
role: m.role,
|
||||
permissions: perms,
|
||||
accepted: m.accepted,
|
||||
})))
|
||||
} else {
|
||||
Ok(Some(Err(super::DatabaseError::BitflagError)))
|
||||
}
|
||||
} 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. Does not return pending members.
|
||||
pub async fn get_from_user_id<'a, 'b, E>(
|
||||
id: TeamId,
|
||||
user_id: UserId,
|
||||
executor: E,
|
||||
) -> Result<Option<Self>, super::DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
SELECT id, user_id, member_name, role, permissions, accepted
|
||||
FROM team_members
|
||||
WHERE (team_id = $1 AND user_id = $2 AND accepted = TRUE)
|
||||
",
|
||||
id as TeamId,
|
||||
user_id as UserId
|
||||
)
|
||||
.fetch_optional(executor)
|
||||
.await?;
|
||||
|
||||
if let Some(m) = result {
|
||||
Ok(Some(TeamMember {
|
||||
id: TeamMemberId(m.id),
|
||||
team_id: id,
|
||||
user_id,
|
||||
name: m.member_name,
|
||||
role: m.role,
|
||||
permissions: Permissions::from_bits(m.permissions as u64)
|
||||
.ok_or_else(|| super::DatabaseError::BitflagError)?,
|
||||
accepted: m.accepted,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
user_id: UserId,
|
||||
executor: E,
|
||||
) -> Result<Option<Self>, super::DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
SELECT id, user_id, member_name, role, permissions, accepted
|
||||
FROM team_members
|
||||
WHERE (team_id = $1 AND user_id = $2)
|
||||
",
|
||||
id as TeamId,
|
||||
user_id as UserId
|
||||
)
|
||||
.fetch_optional(executor)
|
||||
.await?;
|
||||
|
||||
if let Some(m) = result {
|
||||
Ok(Some(TeamMember {
|
||||
id: TeamMemberId(m.id),
|
||||
team_id: id,
|
||||
user_id,
|
||||
name: m.member_name,
|
||||
role: m.role,
|
||||
permissions: Permissions::from_bits(m.permissions as u64)
|
||||
.ok_or_else(|| super::DatabaseError::BitflagError)?,
|
||||
accepted: m.accepted,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn insert(
|
||||
&self,
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<(), sqlx::error::Error> {
|
||||
sqlx::query!(
|
||||
"
|
||||
INSERT INTO team_members (
|
||||
id, user_id, member_name, role, permissions, accepted
|
||||
)
|
||||
VALUES (
|
||||
$1, $2, $3, $4, $5,
|
||||
$6
|
||||
)
|
||||
",
|
||||
self.id as TeamMemberId,
|
||||
self.user_id as UserId,
|
||||
self.name,
|
||||
self.role,
|
||||
self.permissions.bits() as i64,
|
||||
self.accepted,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete<'a, 'b, E>(
|
||||
id: TeamId,
|
||||
user_id: UserId,
|
||||
executor: E,
|
||||
) -> Result<(), super::DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
use sqlx::Done;
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
DELETE FROM team_members
|
||||
WHERE (team_id = $1 AND user_id = $2 AND NOT role = $3)
|
||||
",
|
||||
id as TeamId,
|
||||
user_id as UserId,
|
||||
crate::models::teams::OWNER_ROLE,
|
||||
)
|
||||
.execute(executor)
|
||||
.await?;
|
||||
|
||||
if result.rows_affected() != 1 {
|
||||
return Err(super::DatabaseError::Other(format!(
|
||||
"Deleting a member failed; {} rows deleted",
|
||||
result.rows_affected()
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn edit_team_member(
|
||||
id: TeamId,
|
||||
user_id: UserId,
|
||||
new_permissions: Option<Permissions>,
|
||||
new_role: Option<String>,
|
||||
new_accepted: Option<bool>,
|
||||
new_name: Option<String>,
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<(), super::DatabaseError> {
|
||||
if let Some(permissions) = new_permissions {
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE team_members
|
||||
SET permissions = $1
|
||||
WHERE (team_id = $2 AND user_id = $3 AND NOT role = $4)
|
||||
",
|
||||
permissions.bits() as i64,
|
||||
id as TeamId,
|
||||
user_id as UserId,
|
||||
crate::models::teams::OWNER_ROLE,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(role) = new_role {
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE team_members
|
||||
SET role = $1
|
||||
WHERE (team_id = $2 AND user_id = $3 AND NOT role = $4)
|
||||
",
|
||||
role,
|
||||
id as TeamId,
|
||||
user_id as UserId,
|
||||
crate::models::teams::OWNER_ROLE,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(accepted) = new_accepted {
|
||||
if accepted {
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE team_members
|
||||
SET accepted = TRUE
|
||||
WHERE (team_id = $1 AND user_id = $2 AND NOT role = $3)
|
||||
",
|
||||
id as TeamId,
|
||||
user_id as UserId,
|
||||
crate::models::teams::OWNER_ROLE,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(name) = new_name {
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE team_members
|
||||
SET member_name = $1
|
||||
WHERE (team_id = $2 AND user_id = $3 AND NOT role = $4)
|
||||
",
|
||||
name,
|
||||
id as TeamId,
|
||||
user_id as UserId,
|
||||
crate::models::teams::OWNER_ROLE,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,27 @@ pub struct Team {
|
||||
pub members: Vec<TeamMember>,
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Permissions: u64 {
|
||||
const UPLOAD_VERSION = 1 << 0;
|
||||
const DELETE_VERSION = 1 << 1;
|
||||
const EDIT_DETAILS = 1 << 2;
|
||||
const EDIT_BODY = 1 << 3;
|
||||
const MANAGE_INVITES = 1 << 4;
|
||||
const REMOVE_MEMBER = 1 << 5;
|
||||
const EDIT_MEMBER = 1 << 6;
|
||||
const DELETE_MOD = 1 << 7;
|
||||
const ALL = 0b11111111;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Permissions {
|
||||
fn default() -> Permissions {
|
||||
Permissions::UPLOAD_VERSION | Permissions::DELETE_VERSION
|
||||
}
|
||||
}
|
||||
|
||||
/// A member of a team
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct TeamMember {
|
||||
@@ -29,4 +50,6 @@ pub struct TeamMember {
|
||||
pub name: String,
|
||||
/// The role of the user in the team
|
||||
pub role: String,
|
||||
/// A bitset containing the user's permissions in this team
|
||||
pub permissions: Permissions,
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ pub struct User {
|
||||
pub role: Role,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Role {
|
||||
Developer,
|
||||
|
||||
@@ -49,12 +49,20 @@ pub fn users_config(cfg: &mut web::ServiceConfig) {
|
||||
web::scope("user")
|
||||
.service(users::user_get)
|
||||
.service(users::mods_list)
|
||||
.service(users::user_delete),
|
||||
.service(users::user_delete)
|
||||
.service(users::teams),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn teams_config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(web::scope("team").service(teams::team_members_get));
|
||||
cfg.service(
|
||||
web::scope("team")
|
||||
.service(teams::team_members_get)
|
||||
.service(teams::edit_team_member)
|
||||
.service(teams::add_team_member)
|
||||
.service(teams::join_team)
|
||||
.service(teams::remove_team_member),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
@@ -63,8 +71,12 @@ pub enum ApiError {
|
||||
DatabaseError(#[from] crate::database::models::DatabaseError),
|
||||
#[error("Deserialization error: {0}")]
|
||||
JsonError(#[from] serde_json::Error),
|
||||
#[error("Authentication Error")]
|
||||
AuthenticationError,
|
||||
#[error("Authentication Error: {0}")]
|
||||
AuthenticationError(#[from] crate::auth::AuthenticationError),
|
||||
#[error("Authentication Error: {0}")]
|
||||
CustomAuthenticationError(String),
|
||||
#[error("Invalid Input: {0}")]
|
||||
InvalidInputError(String),
|
||||
#[error("Search Error: {0}")]
|
||||
SearchError(#[from] meilisearch_sdk::errors::Error),
|
||||
}
|
||||
@@ -73,9 +85,11 @@ impl actix_web::ResponseError for ApiError {
|
||||
fn status_code(&self) -> actix_web::http::StatusCode {
|
||||
match self {
|
||||
ApiError::DatabaseError(..) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
ApiError::AuthenticationError => actix_web::http::StatusCode::UNAUTHORIZED,
|
||||
ApiError::AuthenticationError(..) => actix_web::http::StatusCode::UNAUTHORIZED,
|
||||
ApiError::CustomAuthenticationError(..) => actix_web::http::StatusCode::UNAUTHORIZED,
|
||||
ApiError::JsonError(..) => actix_web::http::StatusCode::BAD_REQUEST,
|
||||
ApiError::SearchError(..) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
ApiError::InvalidInputError(..) => actix_web::http::StatusCode::BAD_REQUEST,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,9 +98,11 @@ impl actix_web::ResponseError for ApiError {
|
||||
crate::models::error::ApiError {
|
||||
error: match self {
|
||||
ApiError::DatabaseError(..) => "database_error",
|
||||
ApiError::AuthenticationError => "unauthorized",
|
||||
ApiError::AuthenticationError(..) => "unauthorized",
|
||||
ApiError::CustomAuthenticationError(..) => "unauthorized",
|
||||
ApiError::JsonError(..) => "json_error",
|
||||
ApiError::SearchError(..) => "search_error",
|
||||
ApiError::InvalidInputError(..) => "invalid_input",
|
||||
},
|
||||
description: &self.to_string(),
|
||||
},
|
||||
|
||||
@@ -437,6 +437,8 @@ async fn mod_create_inner(
|
||||
user_id: current_user.id.into(),
|
||||
name: current_user.username.clone(),
|
||||
role: crate::models::teams::OWNER_ROLE.to_owned(),
|
||||
permissions: crate::models::teams::Permissions::ALL,
|
||||
accepted: true,
|
||||
}],
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use super::ApiError;
|
||||
use crate::auth::check_is_moderator_from_headers;
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::database;
|
||||
use crate::models;
|
||||
use crate::models::mods::SearchRequest;
|
||||
use crate::models::teams::Permissions;
|
||||
use crate::models::users::Role;
|
||||
use crate::search::{search_for_mod, SearchConfig, SearchError};
|
||||
use actix_web::{delete, get, web, HttpRequest, HttpResponse};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -92,17 +94,30 @@ pub async fn mod_delete(
|
||||
pool: web::Data<PgPool>,
|
||||
config: web::Data<SearchConfig>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
check_is_moderator_from_headers(
|
||||
req.headers(),
|
||||
&mut *pool
|
||||
.acquire()
|
||||
.await
|
||||
.map_err(|e| ApiError::DatabaseError(e.into()))?,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| ApiError::AuthenticationError)?;
|
||||
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let id = info.into_inner().0;
|
||||
|
||||
if user.role != Role::Moderator || user.role != Role::Admin {
|
||||
let mod_item = database::models::Mod::get(id.into(), &**pool)
|
||||
.await
|
||||
.map_err(|e| ApiError::DatabaseError(e.into()))?
|
||||
.ok_or_else(|| ApiError::InvalidInputError("Invalid Mod ID specified!".to_string()))?;
|
||||
let team_member = database::models::TeamMember::get_from_user_id(
|
||||
mod_item.team_id,
|
||||
user.id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await
|
||||
.map_err(ApiError::DatabaseError)?
|
||||
.ok_or_else(|| ApiError::InvalidInputError("Invalid Mod ID specified!".to_string()))?;
|
||||
|
||||
if !team_member.permissions.contains(Permissions::DELETE_MOD) {
|
||||
return Err(ApiError::CustomAuthenticationError(
|
||||
"You don't have permission to delete this mod".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let result = database::models::Mod::remove_full(id.into(), &**pool)
|
||||
.await
|
||||
.map_err(|e| ApiError::DatabaseError(e.into()))?;
|
||||
|
||||
@@ -41,8 +41,7 @@ pub async fn category_create(
|
||||
.await
|
||||
.map_err(|e| ApiError::DatabaseError(e.into()))?,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| ApiError::AuthenticationError)?;
|
||||
.await?;
|
||||
|
||||
let name = category.into_inner().0;
|
||||
|
||||
@@ -64,8 +63,7 @@ pub async fn category_delete(
|
||||
.await
|
||||
.map_err(|e| ApiError::DatabaseError(e.into()))?,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| ApiError::AuthenticationError)?;
|
||||
.await?;
|
||||
|
||||
let name = category.into_inner().0;
|
||||
let mut transaction = pool.begin().await.map_err(models::DatabaseError::from)?;
|
||||
@@ -103,8 +101,7 @@ pub async fn loader_create(
|
||||
.await
|
||||
.map_err(|e| ApiError::DatabaseError(e.into()))?,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| ApiError::AuthenticationError)?;
|
||||
.await?;
|
||||
|
||||
let name = loader.into_inner().0;
|
||||
|
||||
@@ -126,8 +123,7 @@ pub async fn loader_delete(
|
||||
.await
|
||||
.map_err(|e| ApiError::DatabaseError(e.into()))?,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| ApiError::AuthenticationError)?;
|
||||
.await?;
|
||||
|
||||
let name = loader.into_inner().0;
|
||||
let mut transaction = pool.begin().await.map_err(models::DatabaseError::from)?;
|
||||
@@ -187,8 +183,7 @@ pub async fn game_version_create(
|
||||
.await
|
||||
.map_err(|e| ApiError::DatabaseError(e.into()))?,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| ApiError::AuthenticationError)?;
|
||||
.await?;
|
||||
|
||||
let name = game_version.into_inner().0;
|
||||
|
||||
@@ -221,8 +216,7 @@ pub async fn game_version_delete(
|
||||
.await
|
||||
.map_err(|e| ApiError::DatabaseError(e.into()))?,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| ApiError::AuthenticationError)?;
|
||||
.await?;
|
||||
|
||||
let name = game_version.into_inner().0;
|
||||
let mut transaction = pool.begin().await.map_err(models::DatabaseError::from)?;
|
||||
|
||||
@@ -1,25 +1,363 @@
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::database::models::TeamMember;
|
||||
use crate::models::teams::TeamId;
|
||||
use crate::models::teams::{Permissions, TeamId};
|
||||
use crate::models::users::UserId;
|
||||
use crate::routes::ApiError;
|
||||
use actix_web::{get, web, HttpResponse};
|
||||
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
|
||||
#[get("{id}/members")]
|
||||
pub async fn team_members_get(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(TeamId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let id = info.into_inner().0;
|
||||
let members_data = TeamMember::get_from_team(id.into(), &**pool).await?;
|
||||
|
||||
let current_user = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||
|
||||
if let Some(user) = current_user {
|
||||
let team_member = TeamMember::get_from_user_id(id.into(), user.id.into(), &**pool)
|
||||
.await
|
||||
.map_err(ApiError::DatabaseError)?;
|
||||
|
||||
if team_member.is_some() {
|
||||
let team_members: Vec<crate::models::teams::TeamMember> = members_data
|
||||
.into_iter()
|
||||
.map(|data| crate::models::teams::TeamMember {
|
||||
user_id: data.user_id.into(),
|
||||
name: data.name,
|
||||
role: data.role,
|
||||
permissions: data.permissions,
|
||||
})
|
||||
.collect();
|
||||
|
||||
return Ok(HttpResponse::Ok().json(team_members));
|
||||
}
|
||||
}
|
||||
|
||||
let team_members: Vec<crate::models::teams::TeamMember> = members_data
|
||||
.into_iter()
|
||||
.map(|data| crate::models::teams::TeamMember {
|
||||
user_id: data.user_id.into(),
|
||||
name: data.name,
|
||||
role: data.role,
|
||||
permissions: Permissions::default(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(HttpResponse::Ok().json(team_members))
|
||||
}
|
||||
|
||||
#[post("{id}/join")]
|
||||
pub async fn join_team(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(TeamId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let team_id = info.into_inner().0.into();
|
||||
let current_user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
|
||||
let member =
|
||||
TeamMember::get_from_user_id_pending(team_id, current_user.id.into(), &**pool).await?;
|
||||
|
||||
if let Some(member) = member {
|
||||
if member.accepted {
|
||||
return Err(ApiError::InvalidInputError(
|
||||
"You are already a member of this team".to_string(),
|
||||
));
|
||||
}
|
||||
let mut transaction = pool
|
||||
.begin()
|
||||
.await
|
||||
.map_err(|e| ApiError::DatabaseError(e.into()))?;
|
||||
|
||||
// Edit Team Member to set Accepted to True
|
||||
TeamMember::edit_team_member(
|
||||
team_id,
|
||||
current_user.id.into(),
|
||||
None,
|
||||
None,
|
||||
Some(true),
|
||||
None,
|
||||
&mut transaction,
|
||||
)
|
||||
.await?;
|
||||
|
||||
transaction
|
||||
.commit()
|
||||
.await
|
||||
.map_err(|e| ApiError::DatabaseError(e.into()))?;
|
||||
} else {
|
||||
return Err(ApiError::InvalidInputError(
|
||||
"There is no pending request from this team".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(HttpResponse::Ok().body(""))
|
||||
}
|
||||
|
||||
fn default_role() -> String {
|
||||
"Member".to_string()
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct NewTeamMember {
|
||||
pub user_id: UserId,
|
||||
#[serde(default = "default_role")]
|
||||
pub role: String,
|
||||
#[serde(default = "Permissions::default")]
|
||||
pub permissions: Permissions,
|
||||
}
|
||||
|
||||
#[post("{id}/members")]
|
||||
pub async fn add_team_member(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(TeamId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
new_member: web::Json<NewTeamMember>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let team_id = info.into_inner().0.into();
|
||||
|
||||
let mut transaction = pool
|
||||
.begin()
|
||||
.await
|
||||
.map_err(|e| ApiError::DatabaseError(e.into()))?;
|
||||
|
||||
let current_user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let team_member =
|
||||
TeamMember::get_from_user_id(team_id, current_user.id.into(), &**pool).await?;
|
||||
|
||||
let member = match team_member {
|
||||
Some(m) => m,
|
||||
None => {
|
||||
return Err(ApiError::CustomAuthenticationError(
|
||||
"You don't have permission to invite users to this team".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
if !member.permissions.contains(Permissions::MANAGE_INVITES) {
|
||||
return Err(ApiError::CustomAuthenticationError(
|
||||
"You don't have permission to invite users to this team".to_string(),
|
||||
));
|
||||
}
|
||||
if !member.permissions.contains(new_member.permissions) {
|
||||
return Err(ApiError::InvalidInputError(
|
||||
"The new member has permissions that you don't have".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if new_member.role == crate::models::teams::OWNER_ROLE {
|
||||
return Err(ApiError::InvalidInputError(
|
||||
"The `Owner` role is restricted to one person".to_string(),
|
||||
));
|
||||
}
|
||||
let request = crate::database::models::team_item::TeamMember::get_from_user_id_pending(
|
||||
team_id,
|
||||
member.user_id,
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(req) = request {
|
||||
if req.accepted {
|
||||
return Err(ApiError::InvalidInputError(
|
||||
"The user is already a member of that team".to_string(),
|
||||
));
|
||||
} else {
|
||||
return Err(ApiError::InvalidInputError(
|
||||
"There is already a pending member request for this user".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let new_user = crate::database::models::User::get(member.user_id, &**pool)
|
||||
.await
|
||||
.map_err(|e| ApiError::DatabaseError(e.into()))?
|
||||
.ok_or_else(|| ApiError::InvalidInputError("An invalid User ID specified".to_string()))?;
|
||||
|
||||
let new_id = crate::database::models::ids::generate_team_member_id(&mut transaction).await?;
|
||||
TeamMember {
|
||||
id: new_id,
|
||||
team_id,
|
||||
user_id: new_member.user_id.into(),
|
||||
name: new_user.username,
|
||||
role: new_member.role.clone(),
|
||||
permissions: new_member.permissions,
|
||||
accepted: false,
|
||||
}
|
||||
.insert(&mut transaction)
|
||||
.await
|
||||
.map_err(|e| ApiError::DatabaseError(e.into()))?;
|
||||
|
||||
transaction
|
||||
.commit()
|
||||
.await
|
||||
.map_err(|e| ApiError::DatabaseError(e.into()))?;
|
||||
|
||||
Ok(HttpResponse::Ok().body(""))
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct EditTeamMember {
|
||||
pub permissions: Option<Permissions>,
|
||||
pub role: Option<String>,
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
#[patch("{id}/members/{user_id}")]
|
||||
pub async fn edit_team_member(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(TeamId, UserId)>,
|
||||
pool: web::Data<PgPool>,
|
||||
edit_member: web::Json<EditTeamMember>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let ids = info.into_inner();
|
||||
let id = ids.0.into();
|
||||
let user_id = ids.1.into();
|
||||
|
||||
let current_user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let team_member = TeamMember::get_from_user_id(id, current_user.id.into(), &**pool).await?;
|
||||
|
||||
let mut transaction = pool
|
||||
.begin()
|
||||
.await
|
||||
.map_err(|e| ApiError::DatabaseError(e.into()))?;
|
||||
|
||||
let member = match team_member {
|
||||
Some(m) => m,
|
||||
None => {
|
||||
return Err(ApiError::CustomAuthenticationError(
|
||||
"You don't have permission to edit members of this team".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
// If the only thing being modified is the name, a user can
|
||||
// modify their own member without extra permissions.
|
||||
if user_id == current_user.id.into()
|
||||
&& edit_member.permissions.is_none()
|
||||
&& edit_member.role.is_none()
|
||||
{
|
||||
TeamMember::edit_team_member(
|
||||
id,
|
||||
user_id,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
edit_member.name.clone(),
|
||||
&mut transaction,
|
||||
)
|
||||
.await?;
|
||||
|
||||
transaction
|
||||
.commit()
|
||||
.await
|
||||
.map_err(|e| ApiError::DatabaseError(e.into()))?;
|
||||
|
||||
return Ok(HttpResponse::Ok().body(""));
|
||||
}
|
||||
|
||||
if !member.permissions.contains(Permissions::EDIT_MEMBER) {
|
||||
return Err(ApiError::CustomAuthenticationError(
|
||||
"You don't have permission to edit members of this team".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(new_permissions) = edit_member.permissions {
|
||||
if !member.permissions.contains(new_permissions) {
|
||||
return Err(ApiError::InvalidInputError(
|
||||
"The new permissions have permissions that you don't have".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if edit_member.role.as_deref() == Some(crate::models::teams::OWNER_ROLE) {
|
||||
return Err(ApiError::InvalidInputError(
|
||||
"The `Owner` role is restricted to one person".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
TeamMember::edit_team_member(
|
||||
id,
|
||||
user_id,
|
||||
edit_member.permissions,
|
||||
edit_member.role.clone(),
|
||||
None,
|
||||
edit_member.name.clone(),
|
||||
&mut transaction,
|
||||
)
|
||||
.await?;
|
||||
|
||||
transaction
|
||||
.commit()
|
||||
.await
|
||||
.map_err(|e| ApiError::DatabaseError(e.into()))?;
|
||||
|
||||
Ok(HttpResponse::Ok().body(""))
|
||||
}
|
||||
|
||||
#[delete("{id}/members/{user_id}")]
|
||||
pub async fn remove_team_member(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(TeamId, UserId)>,
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let ids = info.into_inner();
|
||||
let id = ids.0.into();
|
||||
let user_id = ids.1.into();
|
||||
|
||||
let current_user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let team_member = TeamMember::get_from_user_id(id, current_user.id.into(), &**pool).await?;
|
||||
|
||||
let member = match team_member {
|
||||
Some(m) => m,
|
||||
None => {
|
||||
return Err(ApiError::CustomAuthenticationError(
|
||||
"You don't have permission to remove members from this team".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let delete_member = TeamMember::get_from_user_id(id, user_id, &**pool).await?;
|
||||
|
||||
if let Some(delete_member) = delete_member {
|
||||
if delete_member.role == crate::models::teams::OWNER_ROLE {
|
||||
// The owner cannot be removed from a team
|
||||
return Err(ApiError::CustomAuthenticationError(
|
||||
"The owner can't be removed from a team".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if delete_member.accepted {
|
||||
// Members other than the owner can either leave the team, or be
|
||||
// removed by a member with the REMOVE_MEMBER permission.
|
||||
if delete_member.user_id == member.user_id
|
||||
|| member.permissions.contains(Permissions::REMOVE_MEMBER)
|
||||
{
|
||||
TeamMember::delete(id, user_id, &**pool).await?;
|
||||
} else {
|
||||
return Err(ApiError::CustomAuthenticationError(
|
||||
"You do not have permission to remove a member from this team".to_string(),
|
||||
));
|
||||
}
|
||||
} else if delete_member.user_id == member.user_id
|
||||
|| member.permissions.contains(Permissions::MANAGE_INVITES)
|
||||
{
|
||||
// This is a pending invite rather than a member, so the
|
||||
// user being invited or team members with the MANAGE_INVITES
|
||||
// permission can remove it.
|
||||
TeamMember::delete(id, user_id, &**pool).await?;
|
||||
} else {
|
||||
return Err(ApiError::CustomAuthenticationError(
|
||||
"You do not have permission to cancel a team invite".to_string(),
|
||||
));
|
||||
}
|
||||
Ok(HttpResponse::Ok().body(""))
|
||||
} else {
|
||||
Ok(HttpResponse::NotFound().body(""))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::auth::{check_is_moderator_from_headers, get_user_from_headers};
|
||||
use crate::database::models::User;
|
||||
use crate::database::models::{TeamMember, User};
|
||||
use crate::models::teams::Permissions;
|
||||
use crate::models::users::{Role, UserId};
|
||||
use crate::routes::ApiError;
|
||||
use actix_web::{delete, get, web, HttpRequest, HttpResponse};
|
||||
@@ -19,8 +20,7 @@ pub async fn user_auth_get(
|
||||
.await
|
||||
.map_err(|e| ApiError::DatabaseError(e.into()))?,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| ApiError::AuthenticationError)?,
|
||||
.await?,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -121,6 +121,47 @@ pub async fn mods_list(
|
||||
}
|
||||
}
|
||||
|
||||
#[get("teams")]
|
||||
pub async fn teams(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(UserId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let id: crate::database::models::UserId = info.into_inner().0.into();
|
||||
|
||||
let current_user = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||
|
||||
let results;
|
||||
let mut same_user = false;
|
||||
|
||||
if let Some(user) = current_user {
|
||||
if user.id.0 == id.0 as u64 {
|
||||
results = TeamMember::get_from_user_private(id, &**pool).await?;
|
||||
same_user = true;
|
||||
} else {
|
||||
results = TeamMember::get_from_user_public(id, &**pool).await?;
|
||||
}
|
||||
} else {
|
||||
results = TeamMember::get_from_user_public(id, &**pool).await?;
|
||||
}
|
||||
|
||||
let team_members: Vec<crate::models::teams::TeamMember> = results
|
||||
.into_iter()
|
||||
.map(|data| crate::models::teams::TeamMember {
|
||||
user_id: data.user_id.into(),
|
||||
name: data.name,
|
||||
role: data.role,
|
||||
permissions: if same_user {
|
||||
data.permissions
|
||||
} else {
|
||||
Permissions::default()
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(HttpResponse::Ok().json(team_members))
|
||||
}
|
||||
|
||||
// TODO: Make this actually do stuff
|
||||
#[delete("{id}")]
|
||||
pub async fn user_delete(
|
||||
@@ -135,8 +176,7 @@ pub async fn user_delete(
|
||||
.await
|
||||
.map_err(|e| ApiError::DatabaseError(e.into()))?,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| ApiError::AuthenticationError)?;
|
||||
.await?;
|
||||
|
||||
let _id = info.0;
|
||||
let result = Some(());
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use super::ApiError;
|
||||
use crate::auth::check_is_moderator_from_headers;
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::database;
|
||||
use crate::models;
|
||||
use crate::models::teams::Permissions;
|
||||
use crate::models::users::Role;
|
||||
use actix_web::{delete, get, web, HttpRequest, HttpResponse};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
@@ -142,21 +144,44 @@ fn convert_version(data: database::models::version_item::QueryVersion) -> models
|
||||
#[delete("{version_id}")]
|
||||
pub async fn version_delete(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(models::ids::ModId, models::ids::VersionId)>,
|
||||
info: web::Path<(models::ids::VersionId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
check_is_moderator_from_headers(
|
||||
req.headers(),
|
||||
&mut *pool
|
||||
.acquire()
|
||||
.await
|
||||
.map_err(|e| ApiError::DatabaseError(e.into()))?,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| ApiError::AuthenticationError)?;
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let id = info.into_inner().0;
|
||||
|
||||
if user.role != Role::Moderator || user.role != Role::Admin {
|
||||
let version = database::models::Version::get(id.into(), &**pool)
|
||||
.await
|
||||
.map_err(|e| ApiError::DatabaseError(e.into()))?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInputError("Invalid Version ID specified!".to_string())
|
||||
})?;
|
||||
let mod_item = database::models::Mod::get(version.mod_id, &**pool)
|
||||
.await
|
||||
.map_err(|e| ApiError::DatabaseError(e.into()))?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInputError("Invalid Version ID specified!".to_string())
|
||||
})?;
|
||||
let team_member = database::models::TeamMember::get_from_user_id(
|
||||
mod_item.team_id,
|
||||
user.id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await
|
||||
.map_err(ApiError::DatabaseError)?
|
||||
.ok_or_else(|| ApiError::InvalidInputError("Invalid Version ID specified!".to_string()))?;
|
||||
|
||||
if !team_member
|
||||
.permissions
|
||||
.contains(Permissions::DELETE_VERSION)
|
||||
{
|
||||
return Err(ApiError::CustomAuthenticationError(
|
||||
"You don't have permission to delete versions in this team".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: check if the mod exists and matches the version id
|
||||
let id = info.1;
|
||||
let result = database::models::Version::remove_full(id.into(), &**pool)
|
||||
.await
|
||||
.map_err(|e| ApiError::DatabaseError(e.into()))?;
|
||||
|
||||
Reference in New Issue
Block a user