import { PostgreSQL } from "./types" ; import { is_array , is_valid_uuid_string } from "@cocalc/util/misc" ; import { callback2 } from "@cocalc/util/async-utils" ; const GROUPS = [ "owner" , "collaborator" ] as const ; export async function add_collaborators_to_projects ( db , account_id , accounts , projects , tokens ? ) { try { await verify_write_access_to_projects ( db , account_id , projects , tokens ) ; } catch ( err ) { if ( accounts . length == 1 && account_id == accounts [ 0 ] && tokens == null ) { await verify_course_access_to_project ( db , account_id , projects [ 0 ] ) ; } else { throw err ; } } verify_types ( account_id , accounts , projects ) ; for ( const i in projects ) { const project_id = projects [ i ] ; const account_id = accounts [ i ] ; const token_id = tokens ?. [ i ] ; if ( await callback2 ( db . user_is_collaborator , { project_id , account_id } ) ) { continue ; } await callback2 ( db . add_user_to_project , { project_id , account_id , } ) ; if ( token_id != null ) { await increment_project_invite_token_counter ( db , token_id ) ; } } } async function verify_write_access_to_projects ( db , account_id , projects , tokens ? ) { if ( tokens != null ) { for ( let i = 0 ; i < projects . length ; i ++ ) { if ( tokens [ i ] == null ) { throw Error ( "If tokens are specified, they must all be non-null." ) ; } const { project_id , error } = await project_invite_token_project_id ( db , tokens [ i ] ) ; if ( error || ! project_id ) { throw Error ( `Project invite token is not valid - ${ error }` ) ; } projects [ i ] = project_id ; } return ; } for ( const project_id of new Set ( projects ) ) { if ( ! ( await callback2 ( db . user_is_in_project_group , { project_id , account_id , groups : GROUPS , } ) ) ) { throw Error ( `user ${ account_id } does not have write access to project ${ project_id }` ) ; } } } function verify_types ( account_id , accounts , projects ) { if ( ! is_valid_uuid_string ( account_id ) ) throw Error ( `account_id (="${ account_id }") must be a valid uuid string (type=${ typeof account_id })` ) ; if ( ! is_array ( accounts ) ) { throw Error ( "accounts must be an array" ) ; } if ( ! is_array ( projects ) ) { throw Error ( "projects must be an array" ) ; } if ( accounts . length != projects . length ) { throw Error ( `accounts (of length ${ accounts . length }) and projects (of length ${ projects . length }) must be arrays of the same length` ) ; } for ( const x of accounts ) { if ( ! is_valid_uuid_string ( x ) ) throw Error ( `all account id's must be valid uuid's, but "${ x }" is not` ) ; } for ( const x of projects ) { if ( x != "" && ! is_valid_uuid_string ( x ) ) throw Error ( `all project id's must be valid uuid's (or empty), but "${ x }" is not` ) ; } } async function project_invite_token_project_id ( db , token ) { let v ; try { v = await db . async_query ( { table : "project_invite_tokens" , select : [ "expires" , "counter" , "usage_limit" , "project_id" ] , where : { token } , } ) ; } catch ( err ) { return { error : `problem querying the database -- ${ err }` } ; } if ( v . rows . length == 0 ) return { error : "no such token" } ; const { expires , counter , usage_limit , project_id } = v . rows [ 0 ] ; if ( expires != null && expires <= new Date ( ) ) { return { error : "the token already expired" } ; } if ( usage_limit != null && counter >= usage_limit ) { return { error : `the token can only be used ${ usage_limit } times` } ; } return { project_id } ; } async function increment_project_invite_token_counter ( db , token ) { await db . async_query ( { query : "UPDATE project_invite_tokens SET counter=coalesce(counter, 0)+1 WHERE token=$1" , params : [ token ] , } ) ; } async function verify_course_access_to_project ( db , account_id , project_id ) { const v = await db . async_query ( { query : "SELECT course FROM projects WHERE project_id=$1" , params : [ project_id ] , } ) ; if ( v . rows . length == 0 ) { throw Error ( `no project with id "${ project_id }"` ) ; } const course_id = v . rows [ 0 ] . course ?. project_id ; if ( ! is_valid_uuid_string ( course_id ) ) { throw Error ( `cannot add self to "${ project_id }" -- must be an admin` ) ; } if ( ! is_valid_uuid_string ( account_id ) ) { throw Error ( `account_id ${ account_id } must be a valid uuid` ) ; } const w = await db . async_query ( { query : `SELECT users#>'{${ account_id },group}' AS group FROM projects WHERE project_id=\$1` , params : [ course_id ] , } ) ; const group = w . rows [ 0 ] ?. group ; if ( group != "owner" && group != "collaborator" ) { throw Error ( `cannot add self to "${ project_id }" -- must be owner or collaborator on course project` ) ; } }