Skip to content

Commit

Permalink
Merge pull request #141 from nightly-labs/team-invites
Browse files Browse the repository at this point in the history
Team invites
  • Loading branch information
Giems authored Mar 25, 2024
2 parents 1bbb668 + 93c1dbe commit 719f2c8
Show file tree
Hide file tree
Showing 53 changed files with 1,635 additions and 98 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/connect-test-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ jobs:
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
default: true
override: true
- name: install packages
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: |
Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ log = "0.4.20"
jsonwebtoken = "9.2.0"
pwhash = "1.0.0"
futures = "0.3.30"
axum = { version = "0.7.4", features = ["ws", "macros"] }
axum = { version = "0.7.5", features = ["ws", "macros"] }
tower = { version = "0.4.13", features = [
"util",
"timeout",
"load-shed",
"limit",
] }
tower-http = { version = "0.5.1", features = ["cors", "trace"] }
tower-http = { version = "0.5.2", features = ["cors", "trace"] }
reqwest = {version = "0.11.24", features = ["json"]}
tokio = { version = "1.35.1", features = ["full"] }
async-trait = "0.1.77"
Expand Down
2 changes: 1 addition & 1 deletion database/migrations/0011_events_index.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
CREATE TABLE events(
event_id SERIAL PRIMARY KEY,
event_id BIGSERIAL PRIMARY KEY,
app_id TEXT NOT NULL,
event_type event_type_enum NOT NULL,
creation_timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW()
Expand Down
11 changes: 11 additions & 0 deletions database/migrations/0013_team_invites.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
CREATE TABLE team_invites(
invite_id BIGSERIAL PRIMARY KEY,
team_id TEXT NOT NULL REFERENCES team(team_id),
user_email TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
accepted_at TIMESTAMPTZ,
cancelled_at TIMESTAMPTZ
);

CREATE INDEX team_invites_user_email_idx ON team_invites(user_email);
CREATE INDEX team_invites_team_id_idx ON team_invites(team_id);
File renamed without changes.
File renamed without changes.
7 changes: 5 additions & 2 deletions database/src/tables/grafana_users/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ use crate::tables::grafana_users::table_struct::GRAFANA_USERS_TABLE_NAME;
use sqlx::query_as;

impl Db {
pub async fn get_user_by_user_id(&self, user_id: &String) -> Result<GrafanaUser, DbError> {
pub async fn get_user_by_user_id(
&self,
user_id: &String,
) -> Result<Option<GrafanaUser>, DbError> {
let query = format!("SELECT * FROM {GRAFANA_USERS_TABLE_NAME} WHERE user_id = $1");
let typed_query = query_as::<_, GrafanaUser>(&query);

return typed_query
.bind(&user_id)
.fetch_one(&self.connection_pool)
.fetch_optional(&self.connection_pool)
.await
.map_err(|e| e.into());
}
Expand Down
2 changes: 1 addition & 1 deletion database/src/tables/grafana_users/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,6 @@ mod tests {
db.add_new_user(&user).await.unwrap();

let user_result = db.get_user_by_user_id(&user.user_id).await.unwrap();
assert_eq!(user_result, user);
assert_eq!(user_result, Some(user));
}
}
1 change: 1 addition & 0 deletions database/src/tables/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod registered_app;
pub mod session_public_keys;
pub mod sessions;
pub mod team;
pub mod team_invites;
pub mod test_utils;
pub mod user_app_privileges;
pub mod utils;
3 changes: 3 additions & 0 deletions database/src/tables/team_invites/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod select;
pub mod table_struct;
pub mod update;
65 changes: 65 additions & 0 deletions database/src/tables/team_invites/select.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use super::table_struct::TeamInvite;
use crate::db::Db;
use crate::structs::db_error::DbError;
use crate::tables::team_invites::table_struct::TEAM_INVITES_TABLE_NAME;

impl Db {
pub async fn get_invites_by_team_id(
&self,
team_id: &String,
active_invites: bool,
) -> Result<Vec<TeamInvite>, DbError> {
let additional_filter = if active_invites {
"AND ti.accepted_at IS NULL AND ti.cancelled_at IS NULL"
} else {
""
};

let query = format!(
"SELECT ti.invite_id, ti.team_id, ti.user_email, ti.created_at,
ti.accepted_at, ti.cancelled_at, t.team_name, gu.email AS admin_email
FROM {TEAM_INVITES_TABLE_NAME} ti
INNER JOIN team t ON ti.team_id = t.team_id
INNER JOIN grafana_users gu ON t.team_admin_id = gu.user_id
WHERE ti.team_id = $1 {additional_filter}
ORDER BY ti.created_at DESC",
);

let typed_query = sqlx::query_as::<_, TeamInvite>(&query).bind(team_id);

return typed_query
.fetch_all(&self.connection_pool)
.await
.map_err(|e| e.into());
}

pub async fn get_invites_by_user_email(
&self,
user_email: &String,
active_invites: bool,
) -> Result<Vec<TeamInvite>, DbError> {
let additional_filter = if active_invites {
"AND ti.accepted_at IS NULL AND ti.cancelled_at IS NULL"
} else {
""
};

let query = format!(
"SELECT ti.invite_id, ti.team_id, ti.user_email, ti.created_at, \
ti.accepted_at, ti.cancelled_at, t.team_name, gu.email AS admin_email \
FROM {TEAM_INVITES_TABLE_NAME} ti \
INNER JOIN team t ON ti.team_id = t.team_id \
INNER JOIN grafana_users gu ON t.team_admin_id = gu.user_id \
WHERE ti.user_email = $1 {additional_filter} \
ORDER BY ti.created_at DESC",
TEAM_INVITES_TABLE_NAME = TEAM_INVITES_TABLE_NAME
);

let typed_query = sqlx::query_as::<_, TeamInvite>(&query).bind(user_email);

return typed_query
.fetch_all(&self.connection_pool)
.await
.map_err(|e| e.into());
}
}
37 changes: 37 additions & 0 deletions database/src/tables/team_invites/table_struct.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use sqlx::{
postgres::PgRow,
types::chrono::{DateTime, Utc},
FromRow, Row,
};

pub const TEAM_INVITES_TABLE_NAME: &str = "team_invites";
pub const TEAM_INVITES_KEYS: &str =
"invite_id, team_id, user_email, created_at, accepted_at, cancelled_at";

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TeamInvite {
pub invite_id: i64,
pub team_id: String,
pub user_email: String,
pub created_at: DateTime<Utc>,
pub accepted_at: Option<DateTime<Utc>>,
pub cancelled_at: Option<DateTime<Utc>>,
// Not present in the table, queried from the team table
pub team_name: String,
pub admin_email: String,
}

impl FromRow<'_, PgRow> for TeamInvite {
fn from_row(row: &sqlx::postgres::PgRow) -> std::result::Result<Self, sqlx::Error> {
Ok(TeamInvite {
invite_id: row.get("invite_id"),
team_id: row.get("team_id"),
user_email: row.get("user_email"),
created_at: row.get("created_at"),
accepted_at: row.get("accepted_at"),
cancelled_at: row.get("cancelled_at"),
team_name: row.get("team_name"),
admin_email: row.get("admin_email"),
})
}
}
74 changes: 74 additions & 0 deletions database/src/tables/team_invites/update.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use super::table_struct::{TEAM_INVITES_KEYS, TEAM_INVITES_TABLE_NAME};
use crate::db::Db;
use crate::structs::db_error::DbError;
use crate::tables::utils::get_current_datetime;
use sqlx::query;

impl Db {
pub async fn create_new_team_invite(
&self,
team_id: &String,
user_email: &String,
) -> Result<(), DbError> {
let query_body = format!(
"INSERT INTO {TEAM_INVITES_TABLE_NAME} ({TEAM_INVITES_KEYS}) VALUES (DEFAULT, $1, $2, $3, NULL, NULL)"
);

let query_result = query(&query_body)
.bind(&team_id)
.bind(&user_email)
.bind(&get_current_datetime())
.execute(&self.connection_pool)
.await;

match query_result {
Ok(_) => Ok(()),
Err(e) => Err(e).map_err(|e| e.into()),
}
}

pub async fn accept_team_invite(
&self,
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
team_id: &String,
user_email: &String,
) -> Result<(), DbError> {
let query_body = format!(
"UPDATE {TEAM_INVITES_TABLE_NAME} SET accepted_at = $1 WHERE team_id = $2 AND user_email = $3 AND accepted_at IS NULL AND cancelled_at IS NULL"
);

let query_result = query(&query_body)
.bind(&get_current_datetime())
.bind(&team_id)
.bind(&user_email)
.execute(&mut **tx)
.await;

match query_result {
Ok(_) => Ok(()),
Err(e) => Err(e).map_err(|e| e.into()),
}
}

pub async fn cancel_team_invite(
&self,
team_id: &String,
user_email: &String,
) -> Result<(), DbError> {
let query_body = format!(
"UPDATE {TEAM_INVITES_TABLE_NAME} SET cancelled_at = $1 WHERE team_id = $2 AND user_email = $3 AND accepted_at IS NULL AND cancelled_at IS NULL"
);

let query_result = query(&query_body)
.bind(&get_current_datetime())
.bind(&team_id)
.bind(&user_email)
.execute(&self.connection_pool)
.await;

match query_result {
Ok(_) => Ok(()),
Err(e) => Err(e).map_err(|e| e.into()),
}
}
}
13 changes: 8 additions & 5 deletions database/src/tables/user_app_privileges/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ impl Db {

pub async fn add_user_to_the_team(
&self,
tx: &mut Transaction<'_, sqlx::Postgres>,
user_id: &String,
team_id: &String,
) -> Result<(), DbError> {
Expand Down Expand Up @@ -98,9 +99,7 @@ impl Db {
values_str
);

sqlx::query(&insert_query)
.execute(&self.connection_pool)
.await?;
sqlx::query(&insert_query).execute(&mut **tx).await?;

Ok(())
}
Expand Down Expand Up @@ -294,9 +293,11 @@ mod tests {
db.register_new_app(&app).await.unwrap();
}

db.add_user_to_the_team(&user.user_id, &team_id)
let mut tx = db.connection_pool.begin().await.unwrap();
db.add_user_to_the_team(&mut tx, &user.user_id, &team_id)
.await
.unwrap();
tx.commit().await.unwrap();

let get_by_user_id = db.get_privileges_by_user_id(&user.user_id).await.unwrap();
assert!(get_by_user_id.len() == 8);
Expand Down Expand Up @@ -331,9 +332,11 @@ mod tests {
}

// Add user to the new team
db.add_user_to_the_team(&user.user_id, &team_id)
let mut tx = db.connection_pool.begin().await.unwrap();
db.add_user_to_the_team(&mut tx, &user.user_id, &team_id)
.await
.unwrap();
tx.commit().await.unwrap();

let get_by_user_id = db.get_privileges_by_user_id(&user.user_id).await.unwrap();
assert!(get_by_user_id.len() == 10);
Expand Down
2 changes: 1 addition & 1 deletion server/bindings/CloudApiErrors.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type CloudApiErrors = "TeamDoesNotExist" | "UserDoesNotExist" | "CloudFeatureDisabled" | "InsufficientPermissions" | "TeamHasNoRegisteredApps" | "DatabaseError" | "MaximumUsersPerTeamReached" | "UserAlreadyBelongsToTheTeam" | "IncorrectPassword" | "AccessTokenFailure" | "RefreshTokenFailure" | "AppAlreadyExists" | "MaximumAppsPerTeamReached" | "TeamAlreadyExists" | "PersonalTeamAlreadyExists" | "EmailAlreadyExists" | "InternalServerError" | "UserDoesNotBelongsToTheTeam" | "InvalidName" | "UnauthorizedOriginError" | "AppDoesNotExist";
export type CloudApiErrors = "TeamDoesNotExist" | "UserDoesNotExist" | "CloudFeatureDisabled" | "InsufficientPermissions" | "TeamHasNoRegisteredApps" | "DatabaseError" | "MaximumUsersPerTeamReached" | "UserAlreadyBelongsToTheTeam" | "IncorrectPassword" | "AccessTokenFailure" | "RefreshTokenFailure" | "AppAlreadyExists" | "MaximumAppsPerTeamReached" | "TeamAlreadyExists" | "PersonalTeamAlreadyExists" | "EmailAlreadyExists" | "InternalServerError" | "UserDoesNotBelongsToTheTeam" | "InvalidName" | "UnauthorizedOriginError" | "AppDoesNotExist" | "UserAlreadyInvitedToTheTeam" | "MaximumInvitesPerTeamReached" | "InviteNotFound" | "ActionForbiddenForPersonalTeam" | "InviteDoesNotExist";
3 changes: 3 additions & 0 deletions server/bindings/HttpAcceptTeamInviteRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export interface HttpAcceptTeamInviteRequest { teamId: string, }
3 changes: 3 additions & 0 deletions server/bindings/HttpAcceptTeamInviteResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type HttpAcceptTeamInviteResponse = null;
3 changes: 3 additions & 0 deletions server/bindings/HttpCancelTeamUserInviteRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export interface HttpCancelTeamUserInviteRequest { teamId: string, userEmail: string, }
3 changes: 3 additions & 0 deletions server/bindings/HttpCancelTeamUserInviteResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type HttpCancelTeamUserInviteResponse = null;
2 changes: 1 addition & 1 deletion server/bindings/HttpCloudEndpoint.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type HttpCloudEndpoint = "/register_new_app" | "/register_with_password" | "/login_with_password" | "/register_new_team" | "/add_user_to_team" | "/remove_user_from_team" | "/get_user_joined_teams" | "/events";
export type HttpCloudEndpoint = "/register_new_app" | "/register_with_password" | "/login_with_password" | "/login_with_google" | "/register_new_team" | "/remove_user_from_team" | "/get_user_joined_teams" | "/events" | "/invite_user_to_team" | "/accept_team_invite" | "/get_team_user_invites" | "/get_user_team_invites" | "/cancel_team_invite";
3 changes: 3 additions & 0 deletions server/bindings/HttpGetTeamUserInvitesRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export interface HttpGetTeamUserInvitesRequest { teamId: string, }
4 changes: 4 additions & 0 deletions server/bindings/HttpGetTeamUserInvitesResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { TeamInvite } from "./TeamInvite";

export interface HttpGetTeamUserInvitesResponse { teamInvites: Array<TeamInvite>, }
2 changes: 1 addition & 1 deletion server/bindings/HttpGetUserJoinedTeamsResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ import type { AppInfo } from "./AppInfo";
import type { JoinedTeam } from "./JoinedTeam";
import type { UserPrivilege } from "./UserPrivilege";

export interface HttpGetUserJoinedTeamsResponse { teams: Record<string, JoinedTeam>, teams_apps: Record<string, Array<AppInfo>>, user_privileges: Record<string, Record<string, UserPrivilege>>, }
export interface HttpGetUserJoinedTeamsResponse { teams: Record<string, JoinedTeam>, teamsApps: Record<string, Array<AppInfo>>, userPrivileges: Record<string, Record<string, UserPrivilege>>, }
4 changes: 4 additions & 0 deletions server/bindings/HttpGetUserTeamInvitesResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { TeamInvite } from "./TeamInvite";

export interface HttpGetUserTeamInvitesResponse { teamInvites: Array<TeamInvite>, }
3 changes: 3 additions & 0 deletions server/bindings/HttpInviteUserToTeamRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export interface HttpInviteUserToTeamRequest { teamId: string, userEmail: string, }
3 changes: 3 additions & 0 deletions server/bindings/HttpInviteUserToTeamResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type HttpInviteUserToTeamResponse = null;
2 changes: 1 addition & 1 deletion server/bindings/HttpLoginResponse.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export interface HttpLoginResponse { user_id: string, auth_token: string, refresh_token: string, }
export interface HttpLoginResponse { userId: string, authToken: string, refreshToken: string, }
3 changes: 3 additions & 0 deletions server/bindings/HttpLoginWithGoogleRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export interface HttpLoginWithGoogleRequest { oauthToken: string, email: string, enforceIp: boolean, }
2 changes: 1 addition & 1 deletion server/bindings/HttpRegisterNewAppResponse.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export interface HttpRegisterNewAppResponse { app_id: string, }
export interface HttpRegisterNewAppResponse { appId: string, }
Loading

0 comments on commit 719f2c8

Please sign in to comment.