UNPKG

@datalayer/core

Version:
1,353 lines (1,352 loc) 112 kB
/* * Copyright (c) 2023-2025 Datalayer, Inc. * Distributed under the terms of the Modified BSD License. */ import { URLExt } from '@jupyterlab/coreutils'; import { BOOTSTRAP_USER_ONBOARDING, LinkedInUser, asContact, asDatasource, asInbound, asInvite, asOrganization, asOutbound, asPage, asSecret, asSpace, asSurvey, asTeam, asToken, asUsage, asUser, } from "../models"; import { useCoreStore, useIAMStore } from '../state'; import { asDisplayName, namesAsInitials, asArray } from "../utils"; import { IAMProvidersSpecs } from '../models'; import { newUserMock } from './../mocks'; import { useDatalayer } from "./useDatalayer"; import { useAuthorization } from "./useAuthorization"; import { OUTPUTSHOT_PLACEHOLDER_DEFAULT_SVG } from './assets'; const CONTACTS_BY_HANDLE = new Map(); const CONTACTS_BY_ID = new Map(); const COURSES_BY_ID = new Map(); const COURSES_ENROLLMENTS_BY_ID = new Map(); const COURSES_INSTRUCTORS_BY_ID = new Map(); const DATASOURCES_BY_ID = new Map(); const INBOUNDS_BY_HANDLE = new Map(); const INBOUNDS_BY_ID = new Map(); const INVITES_BY_TOKEN = new Map(); const ORGANISATIONS_BY_HANDLE = new Map(); const ORGANISATIONS_BY_ID = new Map(); const ORGANISATIONS_FOR_USER_BY_ID = new Map(); const OUTBOUNDS_BY_ID = new Map(); const PAGES_BY_ID = new Map(); const PUBLIC_COURSES_BY_ID = new Map(); const PUBLIC_ITEMS_BY_ID = new Map(); const SCHOOLS_BY_ID = new Map(); const SECRETS_BY_ID = new Map(); const SPACES_BY_HANDLE_BY_ORGANISATION_HANDLE = new Map(); const SPACES_BY_ID_BY_ORGANISATION_ID = new Map(); const SPACES_FOR_USER_BY_HANDLE = new Map(); const SPACES_FOR_USER_BY_ID = new Map(); const SPACE_ASSIGNMENTS_BY_ID = new Map(); const SPACE_CELLS_BY_ID = new Map(); const SPACE_DATASETS_BY_ID = new Map(); const SPACE_DOCUMENTS_BY_ID = new Map(); const SPACE_ENVIRONMENTS_BY_ID = new Map(); const SPACE_EXERCISES_BY_ID = new Map(); const SPACE_ITEMS_CACHE = new Map(); const SPACE_LESSONS_BY_ID = new Map(); const SPACE_NOTEBOOKS_BY_ID = new Map(); const STUDENTS_BY_ID = new Map(); const STUDENT_ASSIGNMENTS_BY_ID = new Map(); const TEAMS_BY_HANDLE = new Map(); const TEAMS_BY_ID = new Map(); const TEAMS_BY_ORGANIZATION_BY_ID = new Map(); const TOKENS_BY_ID = new Map(); const USERS_BY_HANDLE = new Map(); const USERS_BY_ID = new Map(); const DEFAULT_SEARCH_OPTS = { q: '*', types: ['page'], max: 3, public: true, }; /** * Callbacks to RUN service. * * It assumes to be used within a {@link Router} component. If not * you must set the options `loginRoute` to `null` (raise an error _Unauthorized_ * instead of redirecting to the login page). */ export const useCache = ({ loginRoute = '/login' } = {}) => { const { configuration } = useCoreStore(); const { user } = useIAMStore(); const { requestDatalayer } = useDatalayer({ loginRoute }); const { checkIsOrganizationMember } = useAuthorization(); // Caches ------------------------------------------------------------------- const clearAllCaches = () => { CONTACTS_BY_HANDLE.clear(); CONTACTS_BY_ID.clear(); COURSES_BY_ID.clear(); COURSES_ENROLLMENTS_BY_ID.clear(); COURSES_INSTRUCTORS_BY_ID.clear(); DATASOURCES_BY_ID.clear(); INBOUNDS_BY_HANDLE.clear(); INBOUNDS_BY_ID.clear(); INVITES_BY_TOKEN.clear(); ORGANISATIONS_BY_HANDLE.clear(); ORGANISATIONS_BY_ID.clear(); ORGANISATIONS_FOR_USER_BY_ID.clear(); PAGES_BY_ID.clear(); OUTBOUNDS_BY_ID.clear(); PUBLIC_COURSES_BY_ID.clear(); PUBLIC_ITEMS_BY_ID.clear(); SCHOOLS_BY_ID.clear(); SECRETS_BY_ID.clear(); SPACES_BY_HANDLE_BY_ORGANISATION_HANDLE.clear(); SPACES_BY_ID_BY_ORGANISATION_ID.clear(); SPACES_FOR_USER_BY_HANDLE.clear(); SPACES_FOR_USER_BY_ID.clear(); SPACE_ASSIGNMENTS_BY_ID.clear(); SPACE_CELLS_BY_ID.clear(); SPACE_DATASETS_BY_ID.clear(); SPACE_DOCUMENTS_BY_ID.clear(); SPACE_ENVIRONMENTS_BY_ID.clear(); SPACE_EXERCISES_BY_ID.clear(); SPACE_ITEMS_CACHE.clear(); SPACE_LESSONS_BY_ID.clear(); SPACE_NOTEBOOKS_BY_ID.clear(); STUDENTS_BY_ID.clear(); STUDENT_ASSIGNMENTS_BY_ID.clear(); TOKENS_BY_ID.clear(); USERS_BY_HANDLE.clear(); USERS_BY_ID.clear(); }; const clearCachedItems = () => { PUBLIC_ITEMS_BY_ID.clear(); SPACE_ASSIGNMENTS_BY_ID.clear(); SPACE_DATASETS_BY_ID.clear(); SPACE_DOCUMENTS_BY_ID.clear(); SPACE_ENVIRONMENTS_BY_ID.clear(); SPACE_EXERCISES_BY_ID.clear(); SPACE_ITEMS_CACHE.clear(); SPACE_ITEMS_CACHE.clear(); SPACE_LESSONS_BY_ID.clear(); SPACE_NOTEBOOKS_BY_ID.clear(); SPACE_CELLS_BY_ID.clear(); SPACE_CELLS_BY_ID.clear(); }; // Authentication ------------------------------------------------------------------ const login = (handle, password) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/login`, method: 'POST', body: { handle, password, } }); }; const logout = () => { clearAllCaches(); return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/logout`, method: 'GET', }); }; // Join ------------------------------------------------------------------ const requestJoin = (handle, email, firstName, lastName, password, passwordConfirm) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/join/request`, method: 'POST', body: { handle, email, firstName, lastName, password, passwordConfirm, } }); }; const requestJoinToken = (handle, email, firstName, lastName, password, passwordConfirm) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/join/request/token`, method: 'POST', body: { handle, email, firstName, lastName, password, passwordConfirm, } }); }; const joinWithInvite = (formValues, token) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/join/invites/token`, method: 'POST', body: { ...formValues, token, }, }); }; const confirmJoinWithToken = (userHandle, token) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/join/users/${userHandle}/tokens/${token}`, method: 'GET', }); }; // Password ------------------------------------------------------------------ const changePassword = (handle, password, passwordConfirm) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/password`, method: 'PUT', body: { handle, password, passwordConfirm, } }); }; const createTokenForPasswordChange = (handle, password, passwordConfirm) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/password/token`, method: 'POST', body: { handle, password, passwordConfirm, } }); }; const confirmPassworkWithToken = (userHandle, token) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/password/confirm/users/${userHandle}/tokens/${token}`, method: 'PUT', }); }; // OAuth2 ------------------------------------------------------------------- const getOAuth2AuthorizationURL = async (queryArgs) => { return requestDatalayer({ url: URLExt.join(configuration.iamRunUrl, 'api/iam/v1/oauth2/authz/url') + URLExt.objectToQueryString(queryArgs), notifyOnError: false, }); }; const getOAuth2AuthorizationLinkURL = async (queryArgs) => { return requestDatalayer({ url: URLExt.join(configuration.iamRunUrl, 'api/iam/v1/oauth2/authz/url/link') + URLExt.objectToQueryString(queryArgs), }); }; // IAM Providers ------------------------------------------------------------ const getGitHubProfile = async (accessToken) => { return fetch(IAMProvidersSpecs.GitHub.userInfoURL, { method: 'GET', headers: { 'Accept': 'application/vnd.github+json', 'Authorization': `Bearer ${accessToken}`, 'X-GitHub-Api-Version': '2022-11-28', }, }).then(resp => resp.json()); }; /* * CORS is not supported @see https://github.com/linkedin-developers/linkedin-api-js-client */ const getLinkedinProfile = async (accessToken) => { return proxyGET(IAMProvidersSpecs.LinkedIn.userInfoURL, accessToken).then(resp => { return new LinkedInUser(resp.response); }); }; const postLinkedinShare = async (linkedinUser, postText, accessToken) => { const POST_SHARE_REQUEST = { "author": linkedinUser.getUrn(), "lifecycleState": "PUBLISHED", "specificContent": { "com.linkedin.ugc.ShareContent": { "shareCommentary": { "text": postText }, "shareMediaCategory": "NONE" } }, "visibility": { "com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC" } }; return proxyPOST(IAMProvidersSpecs.LinkedIn.postShareURL, POST_SHARE_REQUEST, accessToken); }; const postLinkedinShareWithUpload = async (linkedinUser, postText, uploadObject, accessToken) => { const REGISTER_UPLOAD_REQUEST = { "registerUploadRequest": { "recipes": [ "urn:li:digitalmediaRecipe:feedshare-image" ], "owner": linkedinUser.getUrn(), "serviceRelationships": [ { "relationshipType": "OWNER", "identifier": "urn:li:userGeneratedContent" } ] } }; return proxyPOST(IAMProvidersSpecs.LinkedIn.registerUploadURL, REGISTER_UPLOAD_REQUEST, accessToken) .then(registerUploadReponse => { /* { "value": { "uploadMechanism": { "com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest": { "headers": {}, "uploadUrl": "https://api.linkedin.com/mediaUpload/C5522AQGTYER3k3ByHQ/feedshare-uploadedImage/0?ca=vector_feedshare&cn=uploads&m=AQJbrN86Zm265gAAAWemyz2pxPSgONtBiZdchrgG872QltnfYjnMdb2j3A&app=1953784&sync=0&v=beta&ut=2H-IhpbfXrRow1" } }, "mediaArtifact": "urn:li:digitalmediaMediaArtifact:(urn:li:digitalmediaAsset:C5522AQGTYER3k3ByHQ,urn:li:digitalmediaMediaArtifactClass:feedshare-uploadedImage)", "asset": "urn:li:digitalmediaAsset:C5522AQGTYER3k3ByHQ" } } */ const asset = registerUploadReponse.response.value.asset; const uploadURL = registerUploadReponse.response.value.uploadMechanism['com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest'].uploadUrl; const UPLOAD_OBJECT_REQUEST = { uploadURL: uploadURL, content: uploadObject, userURN: linkedinUser.getUrn(), }; return proxyPUT(uploadURL, UPLOAD_OBJECT_REQUEST, accessToken) .then(resp => { const share = { "author": linkedinUser.getUrn(), "lifecycleState": "PUBLISHED", "specificContent": { "com.linkedin.ugc.ShareContent": { "shareCommentary": { "text": postText }, "shareMediaCategory": "IMAGE", "media": [ { "status": "READY", "description": { "text": "Datalayer Notebook" }, "media": asset, "title": { "text": "Datalayer Notebook" } } ] } }, "visibility": { "com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC" } }; return proxyPOST(IAMProvidersSpecs.LinkedIn.postShareURL, share, accessToken); }); }); }; // Proxy ------------------------------------------------------------------- const proxyGET = async (url, token) => { return requestDatalayer({ url: URLExt.join(configuration.iamRunUrl, 'api/iam/v1/proxy/request'), method: 'POST', body: { request_method: 'GET', request_url: url, request_token: token, } }); }; const proxyPOST = async (url, body, token) => { return requestDatalayer({ url: URLExt.join(configuration.iamRunUrl, 'api/iam/v1/proxy/request'), method: 'POST', body: { request_method: 'POST', request_url: url, request_token: token, request_body: body, } }); }; const proxyPUT = async (url, body, token) => { return requestDatalayer({ url: URLExt.join(configuration.iamRunUrl, 'api/iam/v1/proxy/request'), method: 'POST', body: { request_method: 'PUT', request_url: url, request_token: token, request_body: body, } }); }; // Waiting List ------------------------------------------------------------- const registerToWaitingList = (formData) => { requestDatalayer({ url: `${configuration.growthRunUrl}/api/growth/v1/waitinglist/register`, method: 'POST', body: { firstName: formData.firstName, lastName: formData.lastName, email: formData.email, affiliation: formData.affiliation || '' } }) .then(resp => { // Special case, make the error very explicit to the user... if (!resp.success) { alert('Sorry, something has gone wrong... Please send an email to eric@datalayer.io to register to the waiting list.'); } }) .catch(err => { // Special case, make the error very explicit to the user... console.error(err); alert('Sorry, something has gone wrong... Please send an email to eric@datalayer.io to register to the waiting list.'); }); }; // Profile ------------------------------------------------------------------ const getMe = async (token) => { const resp = await requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/me`, method: 'GET', token }); const me = resp.me; if (me) { const user = asUser(me); return user; } return null; }; const updateMe = (email, firstName, lastName) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/me`, method: 'PUT', body: { email, firstName, lastName, } }); }; const whoami = () => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/whoami`, method: 'GET', }); }; const requestEmailUpdate = (email) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/me/email`, method: 'PUT', body: { email, } }); }; const confirmEmailUpdate = (token) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/me/email`, method: 'POST', body: { token, } }); }; // Onboarding --------------------------------------------------------------- const updateUserOnboarding = (onboarding) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/onboardings`, method: 'PUT', body: { onboarding, } }); }; // Settings ----------------------------------------------------------------- const updateUserSettings = (userId, settings) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/users/${userId}/settings`, method: 'PUT', body: { aiagents_url_s: settings.aiAgentsUrl, can_invite_b: settings.canInvite || false, }, }); }; // Pages ------------------------------------------------------------------ const toPage = (s) => { if (s) { const page = asPage(s); PAGES_BY_ID.set(s.uid, page); return page; } }; const createPage = (page) => { return requestDatalayer({ url: `${configuration.libraryRunUrl}/api/library/v1/pages`, method: 'POST', body: { ...page } }).then(resp => { if (resp.success) { if (resp.page) { const pageId = resp.page.uid; PAGES_BY_ID.set(pageId, { ...page, id: pageId, }); } } return resp; }); }; const updatePage = (page) => { return requestDatalayer({ url: `${configuration.libraryRunUrl}/api/library/v1/pages/${page.id}`, method: 'PUT', body: { name: page.name, description: page.description, tags: page.tags, }, }).then(resp => { if (resp.success) { if (resp.page) { toPage(resp.page); } } return resp; }); }; const deletePage = (page) => { return requestDatalayer({ url: `${configuration.libraryRunUrl}/api/library/v1/pages/${page.id}`, method: 'DELETE', }); }; const getPage = (pageId) => PAGES_BY_ID.get(pageId); const clearCachedPages = () => PAGES_BY_ID.clear(); const refreshPage = (pageId) => { return requestDatalayer({ url: `${configuration.libraryRunUrl}/api/library/v1/pages/${pageId}`, method: 'GET', }).then(resp => { if (resp.success) { if (resp.page) { toPage(resp.page); } } return resp; }); }; const getPages = () => { return Array.from(PAGES_BY_ID.values()); }; const refreshPages = () => { return requestDatalayer({ url: `${configuration.libraryRunUrl}/api/library/v1/pages`, method: 'GET', }).then(resp => { if (resp.success) { const pages = resp.pages; if (pages) { PAGES_BY_ID.clear(); pages.forEach(page => { toPage(page); }); } } return resp; }); }; // Datasources ------------------------------------------------------------------ const toDatasource = (s) => { if (s) { const datasource = asDatasource(s); DATASOURCES_BY_ID.set(s.uid, datasource); return datasource; } }; const createDatasource = (datasource) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/datasources`, method: 'POST', body: { ...datasource } }).then(resp => { if (resp.success) { if (resp.datasource) { toDatasource(resp.datasource); } } return resp; }); }; const updateDatasource = (datasource) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/datasources/${datasource.id}`, method: 'PUT', body: { ...datasource } }).then(resp => { if (resp.success) { if (resp.datasource) { toDatasource(resp.datasource); } } return resp; }); }; const getDatasource = (datasourceId) => DATASOURCES_BY_ID.get(datasourceId); const clearCachedDatasources = () => DATASOURCES_BY_ID.clear(); const refreshDatasource = (datasourceId) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/datasources/${datasourceId}`, method: 'GET', }).then(resp => { if (resp.success) { if (resp.datasource) { toDatasource(resp.datasource); } } return resp; }); }; const getDatasources = () => { return Array.from(DATASOURCES_BY_ID.values()); }; const refreshDatasources = () => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/datasources`, method: 'GET', }).then(resp => { if (resp.success) { const datasources = resp.datasources; if (datasources) { SECRETS_BY_ID.clear(); datasources.forEach(datasource => { toDatasource(datasource); }); } } return resp; }); }; // Secrets ------------------------------------------------------------------ const toSecret = (s) => { if (s) { const secret = asSecret(s); SECRETS_BY_ID.set(s.uid, secret); return secret; } }; const createSecret = (secret) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/secrets`, method: 'POST', body: { ...secret } }).then(resp => { if (resp.success) { if (resp.secret) { toSecret(resp.secret); } } return resp; }); }; const updateSecret = (secret) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/secrets/${secret.id}`, method: 'PUT', body: { ...secret } }).then(resp => { if (resp.success) { if (resp.secret) { toSecret(resp.secret); } } return resp; }); }; const deleteSecret = (secret) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/secrets/${secret.id}`, method: 'DELETE', }); }; const getSecret = (secretId) => SECRETS_BY_ID.get(secretId); const clearCachedSecrets = () => SECRETS_BY_ID.clear(); const refreshSecret = (secretId) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/secrets/${secretId}`, method: 'GET', }).then(resp => { if (resp.success) { if (resp.secret) { toSecret(resp.secret); } } return resp; }); }; const getSecrets = () => { return Array.from(SECRETS_BY_ID.values()); }; const refreshSecrets = () => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/secrets`, method: 'GET', }).then(resp => { if (resp.success) { const secrets = resp.secrets; if (secrets) { SECRETS_BY_ID.clear(); secrets.forEach(secret => { toSecret(secret); }); } } return resp; }); }; // Tokens ------------------------------------------------------------------ const toToken = (s) => { if (s) { const token = asToken(s); TOKENS_BY_ID.set(s.uid, token); return token; } }; const createToken = (token) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/tokens`, method: 'POST', body: { ...token, expirationDate: token.expirationDate.getTime(), } }).then(resp => { if (resp.success) { if (resp.token) { toToken(resp.token); } } return resp; }); }; const updateToken = (token) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/tokens/${token.id}`, method: 'PUT', body: { ...token } }).then(resp => { if (resp.success) { if (resp.token) { toToken(resp.token); } } return resp; }); }; const getToken = (tokenId) => TOKENS_BY_ID.get(tokenId); const clearCachedTokens = () => TOKENS_BY_ID.clear(); const refreshToken = (tokenId) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/tokens/${tokenId}`, method: 'GET', }).then(resp => { if (resp.success) { if (resp.token) { toToken(resp.token); } } return resp; }); }; const getTokens = () => { return Array.from(TOKENS_BY_ID.values()); }; const refreshTokens = () => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/tokens`, method: 'GET', }).then(resp => { if (resp.success) { const tokens = resp.tokens; if (tokens) { TOKENS_BY_ID.clear(); tokens.forEach(token => { toToken(token); }); } } return resp; }); }; // Layout ------------------------------------------------------------------- const refreshLayout = (accountHandle, spaceHandle, user) => { return requestDatalayer({ url: `${configuration.spacerRunUrl}/api/spacer/v1/layouts/accounts/${accountHandle}${spaceHandle !== undefined ? '/spaces/' + spaceHandle : ''}`, method: 'GET', }).then(resp => { if (resp.success) { if (resp.user) { toUser(resp.user); } let organization = undefined; if (resp.organization) { organization = toOrganization(resp.organization); if (user && checkIsOrganizationMember(user, organization)) { ORGANISATIONS_FOR_USER_BY_ID.set(organization.id, organization); } } if (resp.space) { const space = toSpace(resp.space); if (organization) { let osById = SPACES_BY_ID_BY_ORGANISATION_ID.get(organization.id); if (!osById) { osById = new Map(); SPACES_BY_ID_BY_ORGANISATION_ID.set(organization.id, osById); } osById.set(space.id, space); let osByHandle = SPACES_BY_HANDLE_BY_ORGANISATION_HANDLE.get(organization.handle); if (!osByHandle) { osByHandle = new Map(); SPACES_BY_HANDLE_BY_ORGANISATION_HANDLE.set(organization.handle, osByHandle); } osByHandle.set(space.handle, space); } else { SPACES_FOR_USER_BY_HANDLE.set(space.handle, space); SPACES_FOR_USER_BY_ID.set(space.id, space); } } } return resp; }); }; // Invites ------------------------------------------------------------------- const requestInvite = (firstName, lastName, email, socialUrl) => { return requestDatalayer({ url: `${configuration.growthRunUrl}/api/growth/v1/invites/request`, method: 'POST', body: { first_name: firstName, last_name: lastName, email: email, social_url: socialUrl, } }); }; const sendInvite = (invite) => { return requestDatalayer({ url: `${configuration.growthRunUrl}/api/growth/v1/invites`, method: 'POST', body: { email: invite.to.email, firstName: invite.to.firstName, lastName: invite.to.lastName, message: invite.message, brand: invite.brand, } }); }; const getInvite = (token) => INVITES_BY_TOKEN.get(token); const clearCachedInvites = () => INVITES_BY_TOKEN.clear(); const refreshInvite = (token) => { return requestDatalayer({ url: `${configuration.growthRunUrl}/api/growth/v1/invites/tokens/${token}`, method: 'GET', }).then(resp => { if (resp.success) { const i = resp.invite; if (i) { const invite = asInvite(i); if (invite.token) { INVITES_BY_TOKEN.set(invite.token, invite); } } } return resp; }); }; const getInvites = () => { return Array.from(INVITES_BY_TOKEN.values()); }; const refreshInvites = (accountId) => { return requestDatalayer({ url: `${configuration.growthRunUrl}/api/growth/v1/invites/users/${accountId}`, method: 'GET', }).then(resp => { if (resp.success) { resp.invites.forEach(i => { const invite = asInvite(i); if (invite.token) { INVITES_BY_TOKEN.set(invite.token, invite); } }); } return resp; }); }; const putInvite = (token) => { return requestDatalayer({ url: `${configuration.growthRunUrl}/api/growth/v1/invites/tokens/${token}`, method: 'PUT', }); }; // Accounts ------------------------------------------------------------------- const refreshAccount = (accountHandle) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/accounts/${accountHandle}`, method: 'GET', }).then(resp => { if (resp.success) { if (resp.user) { toUser(resp.user); } if (resp.organization) { toOrganization(resp.organization); } } return resp; }); }; // Contacts --------------------------------------------------------------------- const toContact = (c) => { if (c) { const contact = asContact(c); CONTACTS_BY_ID.set(contact.id, contact); CONTACTS_BY_HANDLE.set(contact.handle, contact); return contact; } }; const getContactById = (contactId) => CONTACTS_BY_ID.get(contactId); const getContactByHandle = (contactHandle) => CONTACTS_BY_HANDLE.get(contactHandle); const createContact = (contact) => { return requestDatalayer({ url: `${configuration.growthRunUrl}/api/growth/v1/contacts`, method: 'POST', body: { contact, } }).then(resp => { if (resp.success) { toContact(resp.contact); } return resp; }); }; const updateContact = (contactId, contact) => { return requestDatalayer({ url: `${configuration.growthRunUrl}/api/growth/v1/contacts/${contactId}`, method: 'PUT', body: { contact, } }).then(resp => { if (resp.success) { toContact(resp.contact); } return resp; }); }; const refreshContact = (contactId) => { return requestDatalayer({ url: `${configuration.growthRunUrl}/api/growth/v1/contacts/${contactId}`, method: 'GET', }).then(resp => { if (resp.success) { toContact(resp.contact); } return resp; }); }; const searchContacts = (query) => { return requestDatalayer({ url: `${configuration.growthRunUrl}/api/growth/v1/contacts/search`, method: 'POST', body: { query, } }).then(resp => { if (resp.success) { const contacts = resp.contacts.map(contact => toContact(contact)); resp.contacts = contacts; } return resp; }); }; const assignTagToContact = (contactId, tagName) => { return requestDatalayer({ url: `${configuration.growthRunUrl}/api/growth/v1/contacts/${contactId}/tags/${tagName}`, method: 'POST', }); }; const unassignTagFromContact = (contactId, tagName) => { return requestDatalayer({ url: `${configuration.growthRunUrl}/api/growth/v1/contacts/${contactId}/tags/${tagName}`, method: 'DELETE', }); }; const deleteContact = (contactId) => { return requestDatalayer({ url: `${configuration.growthRunUrl}/api/growth/v1/contacts/${contactId}`, method: 'DELETE', }); }; const sendInviteToContact = (contact, message) => { return requestDatalayer({ url: `${configuration.growthRunUrl}/api/growth/v1/contacts/invites`, method: 'POST', body: { contactId: contact.id, message, } }); }; // Contacts Enrich ---------------------------------------------------------- const enrichContactEmail = (contactId, useDomain) => { return requestDatalayer({ url: `${configuration.growthRunUrl}/api/growth/v1/contacts/${contactId}/enrich/email?useDomain=${useDomain}`, method: 'GET', }); }; const enrichContactLinkedin = (contactId) => { return requestDatalayer({ url: `${configuration.growthRunUrl}/api/growth/v1/contacts/${contactId}/enrich/linkedin`, method: 'GET', }); }; const sendLinkedinConnectionRequest = (contact, message) => { return requestDatalayer({ url: `${configuration.growthRunUrl}/api/growth/v1/contacts/${contact.id}/connect/linkedin`, method: 'POST', body: { message, } }).then(resp => { if (resp.success) { toContact(resp.contact); } return resp; }); }; // Contacts Links ----------------------------------------------------------- const linkUserWithContact = (userId, contactId) => { return requestDatalayer({ url: `${configuration.growthRunUrl}/api/growth/v1/users/${userId}/contacts/${contactId}/link`, method: 'POST', }); }; const unlinkUserFromContact = (userId, contactId) => { return requestDatalayer({ url: `${configuration.growthRunUrl}/api/growth/v1/users/${userId}/contacts/${contactId}/link`, method: 'DELETE', }); }; // Users -------------------------------------------------------------------- const toUser = (u) => { if (u) { const user = asUser(u); USERS_BY_ID.set(user.id, user); USERS_BY_HANDLE.set(user.handle, user); return user; } }; const getUser = (id) => USERS_BY_ID.get(id); const getUserByHandle = (handle) => USERS_BY_HANDLE.get(handle); const refreshUser = (userId) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/users/${userId}`, method: 'GET', }).then(resp => { if (resp.success) { toUser(resp.user); } return resp; }); }; const searchUsers = (namingPattern) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/users/search`, method: 'POST', body: { namingPattern } }).then(resp => { if (resp.success) { const users = resp.users.map(user => toUser(user)); resp.users = users; } return resp; }); }; // User Roles --------------------------------------------------------------- const assignRoleToUser = (userId, roleName) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/users/${userId}/roles/${roleName}`, method: 'POST', }); }; const unassignRoleFromUser = (userId, roleName) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/users/${userId}/roles/${roleName}`, method: 'DELETE', }); }; // Organizations ------------------------------------------------------------------- const toOrganization = (org) => { const organization = asOrganization(org); ORGANISATIONS_BY_ID.set(organization.id, organization); ORGANISATIONS_BY_HANDLE.set(organization.handle, organization); return organization; }; const createOrganization = (organization) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/organizations`, method: 'POST', body: { handle: organization.handle, name: organization.name, description: organization.description, } }).then(resp => { const organization = toOrganization(resp.organization); ORGANISATIONS_FOR_USER_BY_ID.set(organization.id, organization); return resp; }); }; const getOrganizationById = (organizationId) => ORGANISATIONS_BY_ID.get(organizationId); const getOrganizationByHandle = (organizationHandle) => ORGANISATIONS_BY_HANDLE.get(organizationHandle); const clearCachedOrganizations = () => { ORGANISATIONS_BY_HANDLE.clear(); ORGANISATIONS_BY_ID.clear(); ORGANISATIONS_FOR_USER_BY_ID.clear(); }; const refreshOrganization = (user, organizationId) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/organizations/${organizationId}`, method: 'GET', }).then(resp => { if (resp.success) { const org = resp.organization; if (org) { const organization = toOrganization(org); if (checkIsOrganizationMember(user, organization)) { ORGANISATIONS_FOR_USER_BY_ID.set(organizationId, organization); } } } return resp; }); }; const updateOrganization = (organization) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/organizations/${organization.id}`, method: 'PUT', body: { name: organization.name, description: organization.description, } }).then(resp => { if (resp.success) { const org = getOrganizationById(organization.id); if (org) { org.name = organization.name; org.description = organization.description; } } return resp; }); }; const getUserOrganizations = () => Array.from(ORGANISATIONS_FOR_USER_BY_ID.values()); const getUserOrganizationById = (organizationId) => ORGANISATIONS_FOR_USER_BY_ID.get(organizationId); const refreshUserOrganizations = (user) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/organizations`, method: 'GET', }).then(resp => { if (resp.success) { resp.organizations.forEach(org => { const organization = toOrganization(org); if (checkIsOrganizationMember(user, organization)) { ORGANISATIONS_FOR_USER_BY_ID.set(organization.id, organization); } }); } return resp; }); }; const addMemberToOrganization = (organizationId, userId) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/organizations/${organizationId}/members/${userId}`, method: 'POST', }); }; const removeMemberFromOrganization = (organizationId, userId) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/organizations/${organizationId}/members/${userId}`, method: 'DELETE', }); }; const addRoleToOrganizationMember = (organizationId, userId, roleName) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/organizations/${organizationId}/members/${userId}/roles/${roleName}`, method: 'POST', }); }; const removeRoleFromOrganizationMember = (organizationId, userId, roleName) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/organizations/${organizationId}/members/${userId}/roles/${roleName}`, method: 'DELETE', }); }; // Teams ------------------------------------------------------------------- const toTeam = (org, organizationId) => { const team = asTeam(org, organizationId); TEAMS_BY_ID.set(team.id, team); TEAMS_BY_HANDLE.set(team.handle, team); return team; }; const createTeam = (team, organization) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/teams`, method: 'POST', body: { handle: team.handle, name: team.name, description: team.description, organizationId: organization.id, } }).then(resp => { const team = toTeam(resp.team, organization.id); TEAMS_BY_HANDLE.set(team.handle, team); TEAMS_BY_ID.set(team.id, team); return resp; }); }; const getTeamById = (teamId) => TEAMS_BY_ID.get(teamId); const getTeamByHandle = (teamHandle) => TEAMS_BY_HANDLE.get(teamHandle); const clearCachedTeams = () => { TEAMS_BY_HANDLE.clear(); TEAMS_BY_ID.clear(); }; const refreshTeam = (teamId, organizationId) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/teams/${teamId}`, method: 'GET', }).then(resp => { if (resp.success) { const t = resp.team; if (t) { const team = toTeam(t, organizationId); TEAMS_BY_HANDLE.set(team.handle, team); TEAMS_BY_ID.set(team.id, team); } } return resp; }); }; const updateTeam = (team) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/teams/${team.id}`, method: 'PUT', body: { name: team.name, description: team.description, } }).then(resp => { if (resp.success) { const t = resp.team; if (t) { const tt = toTeam(t, team.organization.id); TEAMS_BY_HANDLE.set(team.handle, tt); TEAMS_BY_ID.set(team.id, tt); } } return resp; }); }; const getTeamsByOrganizationId = (organizationId) => TEAMS_BY_ORGANIZATION_BY_ID.get(organizationId); const refreshTeams = (organizationId) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/organizations/${organizationId}/teams`, method: 'GET', }).then(resp => { if (resp.success) { const teams = resp.teams.map(t => { const team = toTeam(t, organizationId); TEAMS_BY_HANDLE.set(team.handle, team); TEAMS_BY_ID.set(team.id, team); return team; }); TEAMS_BY_ORGANIZATION_BY_ID.set(organizationId, teams); } return resp; }); }; const addMemberToTeam = (teamId, userId) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/teams/${teamId}/members/${userId}`, method: 'POST', }); }; const removeMemberFromTeam = (teamId, userId) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/teams/${teamId}/members/${userId}`, method: 'DELETE', }); }; const addRoleToTeamMember = (teamId, userId, roleName) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/teams/${teamId}/members/${userId}/roles/${roleName}`, method: 'POST', }); }; const removeRoleFromTeamMember = (teamId, userId, roleName) => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/teams/${teamId}/members/${userId}/roles/${roleName}`, method: 'DELETE', }); }; // Schools ------------------------------------------------------------------- const getSchools = () => { return Array.from(SCHOOLS_BY_ID.values()); }; const refreshSchools = () => { return requestDatalayer({ url: `${configuration.iamRunUrl}/api/iam/v1/organizations/schools`, method: 'GET', }).then(resp => { if (resp.success) { resp.orgs.forEach(s => { const dean = undefined; const students = new Array(); const members = new Array(); const courses = new Array(); const school = { id: s.uid, type: 'school', handle: s.handle_s, name: s.name_t, description: s.description_t, dean, members, students, courses, public: s.public_b, creationDate: new Date(s.creation_ts_dt), setMembers(members) { this.members = members; } }; SCHOOLS_BY_ID.set(school.id, school); }); } return resp; }); }; // Spaces ------------------------------------------------------------------- const toSpace = (spc) => { const space = asSpace(spc); return space; }; const createSpace = (space, organization) => { const seedSpaceId = (space.variant === 'course') ? space.seedSpace?.id : undefined; return requestDatalayer({ url: `${configuration.spacerRunUrl}/api/spacer/v1/spaces`, method: 'POST', body: { name: space.name, description: space.description, variant: space.variant,