Initial work on payouts (badges, perms, splits) (#440)

* Initial work on payouts (badges, perms, splits)

* Fix clippy error, bitflag consistency
This commit is contained in:
Geometrically
2022-09-02 12:38:58 -07:00
committed by GitHub
parent 4c1dca73c4
commit e7c3f8bf47
13 changed files with 1030 additions and 801 deletions

View File

@@ -0,0 +1,7 @@
ALTER TABLE team_members ADD COLUMN payouts_split REAL NOT NULL DEFAULT 0;
UPDATE team_members
SET permissions = 1023, payouts_split = 100
WHERE role = 'Owner';
ALTER TABLE users ADD COLUMN badges bigint default 0 NOT NULL;

File diff suppressed because it is too large Load Diff

View File

@@ -28,8 +28,6 @@ pub enum DatabaseError {
Database(#[from] sqlx::error::Error), Database(#[from] sqlx::error::Error),
#[error("Error while trying to generate random ID")] #[error("Error while trying to generate random ID")]
RandomId, RandomId,
#[error("Invalid permissions bitflag!")]
Bitflag,
#[error("A database request failed")] #[error("A database request failed")]
Other(String), Other(String),
} }

View File

@@ -1,6 +1,7 @@
use super::ids::*; use super::ids::*;
use crate::database::models::User; use crate::database::models::User;
use crate::models::teams::Permissions; use crate::models::teams::Permissions;
use crate::models::users::Badges;
pub struct TeamBuilder { pub struct TeamBuilder {
pub members: Vec<TeamMemberBuilder>, pub members: Vec<TeamMemberBuilder>,
@@ -10,6 +11,7 @@ pub struct TeamMemberBuilder {
pub role: String, pub role: String,
pub permissions: Permissions, pub permissions: Permissions,
pub accepted: bool, pub accepted: bool,
pub payouts_split: f32,
} }
impl TeamBuilder { impl TeamBuilder {
@@ -41,6 +43,7 @@ impl TeamBuilder {
role: member.role, role: member.role,
permissions: member.permissions, permissions: member.permissions,
accepted: member.accepted, accepted: member.accepted,
payouts_split: member.payouts_split,
}; };
sqlx::query!( sqlx::query!(
@@ -78,6 +81,7 @@ pub struct TeamMember {
pub role: String, pub role: String,
pub permissions: Permissions, pub permissions: Permissions,
pub accepted: bool, pub accepted: bool,
pub payouts_split: f32,
} }
/// A member of a team /// A member of a team
@@ -89,6 +93,7 @@ pub struct QueryTeamMember {
pub role: String, pub role: String,
pub permissions: Permissions, pub permissions: Permissions,
pub accepted: bool, pub accepted: bool,
pub payouts_split: f32,
} }
impl TeamMember { impl TeamMember {
@@ -104,7 +109,7 @@ impl TeamMember {
let team_members = sqlx::query!( let team_members = sqlx::query!(
" "
SELECT id, user_id, role, permissions, accepted SELECT id, user_id, role, permissions, accepted, payouts_split
FROM team_members FROM team_members
WHERE team_id = $1 WHERE team_id = $1
", ",
@@ -113,19 +118,16 @@ impl TeamMember {
.fetch_many(executor) .fetch_many(executor)
.try_filter_map(|e| async { .try_filter_map(|e| async {
if let Some(m) = e.right() { if let Some(m) = e.right() {
let permissions = Permissions::from_bits(m.permissions as u64); Ok(Some(Ok(TeamMember {
if let Some(perms) = permissions { id: TeamMemberId(m.id),
Ok(Some(Ok(TeamMember { team_id: id,
id: TeamMemberId(m.id), user_id: UserId(m.user_id),
team_id: id, role: m.role,
user_id: UserId(m.user_id), permissions: Permissions::from_bits(m.permissions as u64)
role: m.role, .unwrap_or_default(),
permissions: perms, accepted: m.accepted,
accepted: m.accepted, payouts_split: m.payouts_split,
}))) })))
} else {
Ok(Some(Err(super::DatabaseError::Bitflag)))
}
} else { } else {
Ok(None) Ok(None)
} }
@@ -152,10 +154,10 @@ impl TeamMember {
let team_members = sqlx::query!( let team_members = sqlx::query!(
" "
SELECT tm.id id, tm.role member_role, tm.permissions permissions, tm.accepted accepted, SELECT tm.id id, tm.role member_role, tm.permissions permissions, tm.accepted accepted, tm.payouts_split payouts_split,
u.id user_id, u.github_id github_id, u.name user_name, u.email email, 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.avatar_url avatar_url, u.username username, u.bio bio,
u.created created, u.role user_role u.created created, u.role user_role, u.badges badges
FROM team_members tm FROM team_members tm
INNER JOIN users u ON u.id = tm.user_id INNER JOIN users u ON u.id = tm.user_id
WHERE tm.team_id = $1 WHERE tm.team_id = $1
@@ -165,13 +167,12 @@ impl TeamMember {
.fetch_many(executor) .fetch_many(executor)
.try_filter_map(|e| async { .try_filter_map(|e| async {
if let Some(m) = e.right() { if let Some(m) = e.right() {
let permissions = Permissions::from_bits(m.permissions as u64);
if let Some(perms) = permissions {
Ok(Some(Ok(QueryTeamMember { Ok(Some(Ok(QueryTeamMember {
id: TeamMemberId(m.id), id: TeamMemberId(m.id),
team_id: id, team_id: id,
role: m.member_role, role: m.member_role,
permissions: perms, permissions: Permissions::from_bits(m.permissions as u64).unwrap_or_default(),
accepted: m.accepted, accepted: m.accepted,
user: User { user: User {
id: UserId(m.user_id), id: UserId(m.user_id),
@@ -183,11 +184,10 @@ impl TeamMember {
bio: m.bio, bio: m.bio,
created: m.created, created: m.created,
role: m.user_role, role: m.user_role,
badges: Badges::from_bits(m.badges as u64).unwrap_or_default(),
}, },
payouts_split: m.payouts_split
}))) })))
} else {
Ok(Some(Err(super::DatabaseError::Bitflag)))
}
} else { } else {
Ok(None) Ok(None)
} }
@@ -216,10 +216,10 @@ impl TeamMember {
let teams = sqlx::query!( let teams = sqlx::query!(
" "
SELECT tm.id id, tm.team_id team_id, tm.role member_role, tm.permissions permissions, tm.accepted accepted, SELECT tm.id id, tm.team_id team_id, tm.role member_role, tm.permissions permissions, tm.accepted accepted, tm.payouts_split payouts_split,
u.id user_id, u.github_id github_id, u.name user_name, u.email email, 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.avatar_url avatar_url, u.username username, u.bio bio,
u.created created, u.role user_role u.created created, u.role user_role, u.badges badges
FROM team_members tm FROM team_members tm
INNER JOIN users u ON u.id = tm.user_id INNER JOIN users u ON u.id = tm.user_id
WHERE tm.team_id = ANY($1) WHERE tm.team_id = ANY($1)
@@ -230,13 +230,12 @@ impl TeamMember {
.fetch_many(exec) .fetch_many(exec)
.try_filter_map(|e| async { .try_filter_map(|e| async {
if let Some(m) = e.right() { if let Some(m) = e.right() {
let permissions = Permissions::from_bits(m.permissions as u64);
if let Some(perms) = permissions {
Ok(Some(Ok(QueryTeamMember { Ok(Some(Ok(QueryTeamMember {
id: TeamMemberId(m.id), id: TeamMemberId(m.id),
team_id: TeamId(m.team_id), team_id: TeamId(m.team_id),
role: m.member_role, role: m.member_role,
permissions: perms, permissions: Permissions::from_bits(m.permissions as u64).unwrap_or_default(),
accepted: m.accepted, accepted: m.accepted,
user: User { user: User {
id: UserId(m.user_id), id: UserId(m.user_id),
@@ -248,11 +247,10 @@ impl TeamMember {
bio: m.bio, bio: m.bio,
created: m.created, created: m.created,
role: m.user_role, role: m.user_role,
badges: Badges::from_bits(m.badges as u64).unwrap_or_default(),
}, },
payouts_split: m.payouts_split
}))) })))
} else {
Ok(Some(Err(super::DatabaseError::Bitflag)))
}
} else { } else {
Ok(None) Ok(None)
} }
@@ -279,7 +277,7 @@ impl TeamMember {
let team_members = sqlx::query!( let team_members = sqlx::query!(
" "
SELECT id, team_id, role, permissions, accepted SELECT id, team_id, role, permissions, accepted, payouts_split
FROM team_members FROM team_members
WHERE (user_id = $1 AND accepted = TRUE) WHERE (user_id = $1 AND accepted = TRUE)
", ",
@@ -288,19 +286,16 @@ impl TeamMember {
.fetch_many(executor) .fetch_many(executor)
.try_filter_map(|e| async { .try_filter_map(|e| async {
if let Some(m) = e.right() { if let Some(m) = e.right() {
let permissions = Permissions::from_bits(m.permissions as u64); Ok(Some(Ok(TeamMember {
if let Some(perms) = permissions { id: TeamMemberId(m.id),
Ok(Some(Ok(TeamMember { team_id: TeamId(m.team_id),
id: TeamMemberId(m.id), user_id: id,
team_id: TeamId(m.team_id), role: m.role,
user_id: id, permissions: Permissions::from_bits(m.permissions as u64)
role: m.role, .unwrap_or_default(),
permissions: perms, accepted: m.accepted,
accepted: m.accepted, payouts_split: m.payouts_split,
}))) })))
} else {
Ok(Some(Err(super::DatabaseError::Bitflag)))
}
} else { } else {
Ok(None) Ok(None)
} }
@@ -327,7 +322,7 @@ impl TeamMember {
let team_members = sqlx::query!( let team_members = sqlx::query!(
" "
SELECT id, team_id, role, permissions, accepted SELECT id, team_id, role, permissions, accepted, payouts_split
FROM team_members FROM team_members
WHERE user_id = $1 WHERE user_id = $1
", ",
@@ -336,19 +331,16 @@ impl TeamMember {
.fetch_many(executor) .fetch_many(executor)
.try_filter_map(|e| async { .try_filter_map(|e| async {
if let Some(m) = e.right() { if let Some(m) = e.right() {
let permissions = Permissions::from_bits(m.permissions as u64); Ok(Some(Ok(TeamMember {
if let Some(perms) = permissions { id: TeamMemberId(m.id),
Ok(Some(Ok(TeamMember { team_id: TeamId(m.team_id),
id: TeamMemberId(m.id), user_id: id,
team_id: TeamId(m.team_id), role: m.role,
user_id: id, permissions: Permissions::from_bits(m.permissions as u64)
role: m.role, .unwrap_or_default(),
permissions: perms, accepted: m.accepted,
accepted: m.accepted, payouts_split: m.payouts_split,
}))) })))
} else {
Ok(Some(Err(super::DatabaseError::Bitflag)))
}
} else { } else {
Ok(None) Ok(None)
} }
@@ -374,7 +366,7 @@ impl TeamMember {
{ {
let result = sqlx::query!( let result = sqlx::query!(
" "
SELECT id, user_id, role, permissions, accepted SELECT id, user_id, role, permissions, accepted, payouts_split
FROM team_members FROM team_members
WHERE (team_id = $1 AND user_id = $2 AND accepted = TRUE) WHERE (team_id = $1 AND user_id = $2 AND accepted = TRUE)
", ",
@@ -391,8 +383,9 @@ impl TeamMember {
user_id, user_id,
role: m.role, role: m.role,
permissions: Permissions::from_bits(m.permissions as u64) permissions: Permissions::from_bits(m.permissions as u64)
.ok_or(super::DatabaseError::Bitflag)?, .unwrap_or_default(),
accepted: m.accepted, accepted: m.accepted,
payouts_split: m.payouts_split,
})) }))
} else { } else {
Ok(None) Ok(None)
@@ -415,7 +408,7 @@ impl TeamMember {
let team_members = sqlx::query!( let team_members = sqlx::query!(
" "
SELECT id, team_id, user_id, role, permissions, accepted SELECT id, team_id, user_id, role, permissions, accepted, payouts_split
FROM team_members FROM team_members
WHERE (team_id = ANY($1) AND user_id = $2 AND accepted = TRUE) WHERE (team_id = ANY($1) AND user_id = $2 AND accepted = TRUE)
", ",
@@ -425,19 +418,15 @@ impl TeamMember {
.fetch_many(executor) .fetch_many(executor)
.try_filter_map(|e| async { .try_filter_map(|e| async {
if let Some(m) = e.right() { if let Some(m) = e.right() {
let permissions = Permissions::from_bits(m.permissions as u64);
if let Some(perms) = permissions {
Ok(Some(Ok(TeamMember { Ok(Some(Ok(TeamMember {
id: TeamMemberId(m.id), id: TeamMemberId(m.id),
team_id: TeamId(m.team_id), team_id: TeamId(m.team_id),
user_id, user_id,
role: m.role, role: m.role,
permissions: perms, permissions: Permissions::from_bits(m.permissions as u64).unwrap_or_default(),
accepted: m.accepted, accepted: m.accepted,
payouts_split: m.payouts_split
}))) })))
} else {
Ok(Some(Err(super::DatabaseError::Bitflag)))
}
} else { } else {
Ok(None) Ok(None)
} }
@@ -463,7 +452,7 @@ impl TeamMember {
{ {
let result = sqlx::query!( let result = sqlx::query!(
" "
SELECT id, user_id, role, permissions, accepted SELECT id, user_id, role, permissions, accepted, payouts_split
FROM team_members FROM team_members
WHERE (team_id = $1 AND user_id = $2) WHERE (team_id = $1 AND user_id = $2)
", ",
@@ -480,8 +469,9 @@ impl TeamMember {
user_id, user_id,
role: m.role, role: m.role,
permissions: Permissions::from_bits(m.permissions as u64) permissions: Permissions::from_bits(m.permissions as u64)
.ok_or(super::DatabaseError::Bitflag)?, .unwrap_or_default(),
accepted: m.accepted, accepted: m.accepted,
payouts_split: m.payouts_split,
})) }))
} else { } else {
Ok(None) Ok(None)
@@ -550,6 +540,7 @@ impl TeamMember {
new_permissions: Option<Permissions>, new_permissions: Option<Permissions>,
new_role: Option<String>, new_role: Option<String>,
new_accepted: Option<bool>, new_accepted: Option<bool>,
new_payouts_split: Option<f32>,
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<(), super::DatabaseError> { ) -> Result<(), super::DatabaseError> {
if let Some(permissions) = new_permissions { if let Some(permissions) = new_permissions {
@@ -598,6 +589,21 @@ impl TeamMember {
} }
} }
if let Some(payouts_split) = new_payouts_split {
sqlx::query!(
"
UPDATE team_members
SET payouts_split = $1
WHERE (team_id = $2 AND user_id = $3)
",
payouts_split,
id as TeamId,
user_id as UserId,
)
.execute(&mut *transaction)
.await?;
}
Ok(()) Ok(())
} }
@@ -611,7 +617,7 @@ impl TeamMember {
{ {
let result = sqlx::query!( let result = sqlx::query!(
" "
SELECT tm.id, tm.team_id, tm.user_id, tm.role, tm.permissions, tm.accepted FROM mods m SELECT tm.id, tm.team_id, tm.user_id, tm.role, tm.permissions, tm.accepted, tm.payouts_split FROM mods m
INNER JOIN team_members tm ON tm.team_id = m.team_id AND user_id = $2 AND accepted = TRUE INNER JOIN team_members tm ON tm.team_id = m.team_id AND user_id = $2 AND accepted = TRUE
WHERE m.id = $1 WHERE m.id = $1
", ",
@@ -628,8 +634,9 @@ impl TeamMember {
user_id, user_id,
role: m.role, role: m.role,
permissions: Permissions::from_bits(m.permissions as u64) permissions: Permissions::from_bits(m.permissions as u64)
.ok_or(super::DatabaseError::Bitflag)?, .unwrap_or_default(),
accepted: m.accepted, accepted: m.accepted,
payouts_split: m.payouts_split,
})) }))
} else { } else {
Ok(None) Ok(None)
@@ -646,7 +653,7 @@ impl TeamMember {
{ {
let result = sqlx::query!( let result = sqlx::query!(
" "
SELECT tm.id, tm.team_id, tm.user_id, tm.role, tm.permissions, tm.accepted FROM versions v SELECT tm.id, tm.team_id, tm.user_id, tm.role, tm.permissions, tm.accepted, tm.payouts_split FROM versions v
INNER JOIN mods m ON m.id = v.mod_id 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 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 WHERE v.id = $1
@@ -664,8 +671,9 @@ impl TeamMember {
user_id, user_id,
role: m.role, role: m.role,
permissions: Permissions::from_bits(m.permissions as u64) permissions: Permissions::from_bits(m.permissions as u64)
.ok_or(super::DatabaseError::Bitflag)?, .unwrap_or_default(),
accepted: m.accepted, accepted: m.accepted,
payouts_split: m.payouts_split,
})) }))
} else { } else {
Ok(None) Ok(None)

View File

@@ -1,4 +1,5 @@
use super::ids::{ProjectId, UserId}; use super::ids::{ProjectId, UserId};
use crate::models::users::Badges;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
pub struct User { pub struct User {
@@ -11,6 +12,7 @@ pub struct User {
pub bio: Option<String>, pub bio: Option<String>,
pub created: DateTime<Utc>, pub created: DateTime<Utc>,
pub role: String, pub role: String,
pub badges: Badges,
} }
impl User { impl User {
@@ -54,7 +56,7 @@ impl User {
" "
SELECT u.github_id, u.name, u.email, SELECT u.github_id, u.name, u.email,
u.avatar_url, u.username, u.bio, u.avatar_url, u.username, u.bio,
u.created, u.role u.created, u.role, u.badges
FROM users u FROM users u
WHERE u.id = $1 WHERE u.id = $1
", ",
@@ -74,6 +76,8 @@ impl User {
bio: row.bio, bio: row.bio,
created: row.created, created: row.created,
role: row.role, role: row.role,
badges: Badges::from_bits(row.badges as u64)
.unwrap_or_default(),
})) }))
} else { } else {
Ok(None) Ok(None)
@@ -91,7 +95,7 @@ impl User {
" "
SELECT u.id, u.name, u.email, SELECT u.id, u.name, u.email,
u.avatar_url, u.username, u.bio, u.avatar_url, u.username, u.bio,
u.created, u.role u.created, u.role, u.badges
FROM users u FROM users u
WHERE u.github_id = $1 WHERE u.github_id = $1
", ",
@@ -111,6 +115,8 @@ impl User {
bio: row.bio, bio: row.bio,
created: row.created, created: row.created,
role: row.role, role: row.role,
badges: Badges::from_bits(row.badges as u64)
.unwrap_or_default(),
})) }))
} else { } else {
Ok(None) Ok(None)
@@ -128,7 +134,7 @@ impl User {
" "
SELECT u.id, u.github_id, u.name, u.email, SELECT u.id, u.github_id, u.name, u.email,
u.avatar_url, u.username, u.bio, u.avatar_url, u.username, u.bio,
u.created, u.role u.created, u.role, u.badges
FROM users u FROM users u
WHERE LOWER(u.username) = LOWER($1) WHERE LOWER(u.username) = LOWER($1)
", ",
@@ -148,6 +154,8 @@ impl User {
bio: row.bio, bio: row.bio,
created: row.created, created: row.created,
role: row.role, role: row.role,
badges: Badges::from_bits(row.badges as u64)
.unwrap_or_default(),
})) }))
} else { } else {
Ok(None) Ok(None)
@@ -169,7 +177,8 @@ impl User {
" "
SELECT u.id, u.github_id, u.name, u.email, SELECT u.id, u.github_id, u.name, u.email,
u.avatar_url, u.username, u.bio, u.avatar_url, u.username, u.bio,
u.created, u.role FROM users u u.created, u.role, u.badges
FROM users u
WHERE u.id = ANY($1) WHERE u.id = ANY($1)
", ",
&user_ids_parsed &user_ids_parsed
@@ -186,6 +195,7 @@ impl User {
bio: u.bio, bio: u.bio,
created: u.created, created: u.created,
role: u.role, role: u.role,
badges: Badges::from_bits(u.badges as u64).unwrap_or_default(),
})) }))
}) })
.try_collect::<Vec<User>>() .try_collect::<Vec<User>>()

View File

@@ -34,7 +34,9 @@ bitflags::bitflags! {
const REMOVE_MEMBER = 1 << 5; const REMOVE_MEMBER = 1 << 5;
const EDIT_MEMBER = 1 << 6; const EDIT_MEMBER = 1 << 6;
const DELETE_PROJECT = 1 << 7; const DELETE_PROJECT = 1 << 7;
const ALL = 0b11111111; const VIEW_ANALYTICS = 1 << 8;
const VIEW_PAYOUTS = 1 << 9;
const ALL = 0b1111111111;
} }
} }
@@ -57,6 +59,9 @@ pub struct TeamMember {
pub permissions: Option<Permissions>, pub permissions: Option<Permissions>,
/// Whether the user has joined the team or is just invited to it /// Whether the user has joined the team or is just invited to it
pub accepted: bool, pub accepted: bool,
/// Payouts split. This is a weighted average. For example. if a team has two members with this
/// value set to 25.0 for both members, they split revenue 50/50
pub payouts_split: Option<f32>,
} }
impl TeamMember { impl TeamMember {
@@ -71,6 +76,11 @@ impl TeamMember {
Some(data.permissions) Some(data.permissions)
}, },
accepted: data.accepted, accepted: data.accepted,
payouts_split: if override_permissions {
None
} else {
Some(data.payouts_split)
},
} }
} }
} }

View File

@@ -9,6 +9,29 @@ pub struct UserId(pub u64);
pub const DELETED_USER: UserId = UserId(127155982985829); pub const DELETED_USER: UserId = UserId(127155982985829);
bitflags::bitflags! {
#[derive(Serialize, Deserialize)]
#[serde(transparent)]
pub struct Badges: u64 {
const MIDAS = 1 << 0;
const EARLY_MODPACK_ADOPTER = 1 << 1;
const EARLY_RESPACK_ADOPTER = 1 << 2;
const EARLY_PLUGIN_ADOPTER = 1 << 3;
const ALPHA_TESTER = 1 << 4;
const CONTRIBUTOR = 1 << 5;
const TRANSLATOR = 1 << 6;
const ALL = 0b1111111;
const NONE = 0b0;
}
}
impl Default for Badges {
fn default() -> Badges {
Badges::NONE
}
}
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
pub struct User { pub struct User {
pub id: UserId, pub id: UserId,
@@ -20,6 +43,7 @@ pub struct User {
pub bio: Option<String>, pub bio: Option<String>,
pub created: DateTime<Utc>, pub created: DateTime<Utc>,
pub role: Role, pub role: Role,
pub badges: Badges,
} }
use crate::database::models::user_item::User as DBUser; use crate::database::models::user_item::User as DBUser;
@@ -35,6 +59,7 @@ impl From<DBUser> for User {
bio: data.bio, bio: data.bio,
created: data.created, created: data.created,
role: Role::from_string(&*data.role), role: Role::from_string(&*data.role),
badges: data.badges,
} }
} }
} }

View File

@@ -15,7 +15,7 @@ use crate::database::models::{generate_state_id, User};
use crate::models::error::ApiError; use crate::models::error::ApiError;
use crate::models::ids::base62_impl::{parse_base62, to_base62}; use crate::models::ids::base62_impl::{parse_base62, to_base62};
use crate::models::ids::DecodingError; use crate::models::ids::DecodingError;
use crate::models::users::Role; use crate::models::users::{Badges, Role};
use crate::parse_strings_from_var; use crate::parse_strings_from_var;
use crate::util::auth::get_github_user_from_token; use crate::util::auth::get_github_user_from_token;
use actix_web::http::StatusCode; use actix_web::http::StatusCode;
@@ -272,6 +272,7 @@ pub async fn auth_callback(
bio: user.bio, bio: user.bio,
created: Utc::now(), created: Utc::now(),
role: Role::Developer.to_string(), role: Role::Developer.to_string(),
badges: Badges::default(),
} }
.insert(&mut transaction) .insert(&mut transaction)
.await?; .await?;

View File

@@ -628,6 +628,7 @@ pub async fn project_create_inner(
role: crate::models::teams::OWNER_ROLE.to_owned(), role: crate::models::teams::OWNER_ROLE.to_owned(),
permissions: crate::models::teams::Permissions::ALL, permissions: crate::models::teams::Permissions::ALL,
accepted: true, accepted: true,
payouts_split: 100.0,
}], }],
}; };

View File

@@ -189,6 +189,7 @@ pub async fn join_team(
None, None,
None, None,
Some(true), Some(true),
None,
&mut transaction, &mut transaction,
) )
.await?; .await?;
@@ -214,6 +215,8 @@ pub struct NewTeamMember {
pub role: String, pub role: String,
#[serde(default = "Permissions::default")] #[serde(default = "Permissions::default")]
pub permissions: Permissions, pub permissions: Permissions,
#[serde(default)]
pub payouts_split: f32,
} }
#[post("{id}/members")] #[post("{id}/members")]
@@ -255,6 +258,13 @@ pub async fn add_team_member(
"The `Owner` role is restricted to one person".to_string(), "The `Owner` role is restricted to one person".to_string(),
)); ));
} }
if !(0.0..=5000.0).contains(&new_member.payouts_split) {
return Err(ApiError::InvalidInput(
"Payouts split must be between 0 and 5000!".to_string(),
));
}
let request = crate::database::models::team_item::TeamMember::get_from_user_id_pending( let request = crate::database::models::team_item::TeamMember::get_from_user_id_pending(
team_id, team_id,
new_member.user_id.into(), new_member.user_id.into(),
@@ -291,6 +301,7 @@ pub async fn add_team_member(
role: new_member.role.clone(), role: new_member.role.clone(),
permissions: new_member.permissions, permissions: new_member.permissions,
accepted: false, accepted: false,
payouts_split: new_member.payouts_split,
} }
.insert(&mut transaction) .insert(&mut transaction)
.await?; .await?;
@@ -349,6 +360,7 @@ pub async fn add_team_member(
pub struct EditTeamMember { pub struct EditTeamMember {
pub permissions: Option<Permissions>, pub permissions: Option<Permissions>,
pub role: Option<String>, pub role: Option<String>,
pub payouts_split: Option<f32>,
} }
#[patch("{id}/members/{user_id}")] #[patch("{id}/members/{user_id}")]
@@ -406,6 +418,14 @@ pub async fn edit_team_member(
} }
} }
if let Some(payouts_split) = edit_member.payouts_split {
if !(0.0..=5000.0).contains(&payouts_split) {
return Err(ApiError::InvalidInput(
"Payouts split must be between 0 and 5000!".to_string(),
));
}
}
if edit_member.role.as_deref() == Some(crate::models::teams::OWNER_ROLE) { if edit_member.role.as_deref() == Some(crate::models::teams::OWNER_ROLE) {
return Err(ApiError::InvalidInput( return Err(ApiError::InvalidInput(
"The `Owner` role is restricted to one person".to_string(), "The `Owner` role is restricted to one person".to_string(),
@@ -418,6 +438,7 @@ pub async fn edit_team_member(
edit_member.permissions, edit_member.permissions,
edit_member.role.clone(), edit_member.role.clone(),
None, None,
edit_member.payouts_split,
&mut transaction, &mut transaction,
) )
.await?; .await?;
@@ -491,6 +512,7 @@ pub async fn transfer_ownership(
None, None,
Some(crate::models::teams::DEFAULT_ROLE.to_string()), Some(crate::models::teams::DEFAULT_ROLE.to_string()),
None, None,
None,
&mut transaction, &mut transaction,
) )
.await?; .await?;
@@ -501,6 +523,7 @@ pub async fn transfer_ownership(
Some(Permissions::ALL), Some(Permissions::ALL),
Some(crate::models::teams::OWNER_ROLE.to_string()), Some(crate::models::teams::OWNER_ROLE.to_string()),
None, None,
None,
&mut transaction, &mut transaction,
) )
.await?; .await?;

View File

@@ -2,7 +2,7 @@ use crate::database::models::User;
use crate::file_hosting::FileHost; use crate::file_hosting::FileHost;
use crate::models::notifications::Notification; use crate::models::notifications::Notification;
use crate::models::projects::{Project, ProjectStatus}; use crate::models::projects::{Project, ProjectStatus};
use crate::models::users::{Role, UserId}; use crate::models::users::{Badges, Role, UserId};
use crate::routes::ApiError; use crate::routes::ApiError;
use crate::util::auth::get_user_from_headers; use crate::util::auth::get_user_from_headers;
use crate::util::routes::read_from_payload; use crate::util::routes::read_from_payload;
@@ -154,6 +154,7 @@ pub struct EditUser {
#[validate(length(max = 160))] #[validate(length(max = 160))]
pub bio: Option<Option<String>>, pub bio: Option<Option<String>>,
pub role: Option<Role>, pub role: Option<Role>,
pub badges: Option<Badges>,
} }
#[patch("{id}")] #[patch("{id}")]
@@ -277,6 +278,27 @@ pub async fn user_edit(
.await?; .await?;
} }
if let Some(badges) = &new_user.badges {
if !user.role.is_admin() {
return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit the badges of this user!"
.to_string(),
));
}
sqlx::query!(
"
UPDATE users
SET badges = $1
WHERE (id = $2)
",
badges.bits() as i64,
id as crate::database::models::ids::UserId,
)
.execute(&mut *transaction)
.await?;
}
transaction.commit().await?; transaction.commit().await?;
Ok(HttpResponse::NoContent().body("")) Ok(HttpResponse::NoContent().body(""))
} else { } else {

View File

@@ -72,6 +72,7 @@ where
bio: result.bio, bio: result.bio,
created: result.created, created: result.created,
role: Role::from_string(&result.role), role: Role::from_string(&result.role),
badges: result.badges,
}), }),
None => Err(AuthenticationError::InvalidCredentials), None => Err(AuthenticationError::InvalidCredentials),
} }

View File

@@ -8,7 +8,7 @@ pub struct LiteLoaderValidator;
impl super::Validator for LiteLoaderValidator { impl super::Validator for LiteLoaderValidator {
fn get_file_extensions(&self) -> &[&str] { fn get_file_extensions(&self) -> &[&str] {
&["litemod"] &["litemod", "jar"]
} }
fn get_project_types(&self) -> &[&str] { fn get_project_types(&self) -> &[&str] {