UNPKG

@stackend/api

Version:

JS bindings to api.stackend.com

1,343 lines (1,219 loc) 32 kB
import { Request } from '../request'; import { getCurrentCommunityPermalink, getJson, post, Thunk, XcapJsonResult } from '../api'; import ModerationStatus from '../api/ModerationStatus'; import Order from '../api/Order'; import { fetchModules } from './moduleAction'; import { hasElevatedPrivilege, User } from '../user'; import { PaginatedCollection } from '../api/PaginatedCollection'; import { CurrentUserType } from '../login/loginReducer'; import { PrivilegeTypeId, PrivilegeTypeIds } from '../user/privileges'; import { Image } from '../media'; import XcapObject from '../api/XcapObject'; import NameAware from '../api/NameAware'; import DescriptionAware from '../api/DescriptionAware'; import PermalinkAware from '../api/PermalinkAware'; import CreatorUserIdAware from '../api/CreatorUserIdAware'; import CreatedDateAware from '../api/CreatedDateAware'; import ModerationStatusAware from '../api/ModerationStatusAware'; import ExpirationDateAware from '../api/ExpirationDateAware'; /** * Stackend API constants and methods. * * @since 20 apr 2017 */ /** * CommunityStatus * @type {{VISIBLE: string, HIDDEN: string, REMOVED: string}} */ export enum CommunityStatus { VISIBLE = 'VISIBLE', HIDDEN = 'HIDDEN', REMOVED = 'REMOVED' } /** * Xcap community name for stackend. * @type {string} */ export const STACKEND_COMMUNITY = 'stackend'; /** * Commmunity permalink used for stackend.com news and examples * @type {string} */ export const STACKEND_DOT_COM_COMMUNITY = 'stackend-com'; /** * Definition of a community */ export interface Community extends XcapObject, NameAware, DescriptionAware, PermalinkAware, CreatorUserIdAware, CreatedDateAware, ModerationStatusAware, ExpirationDateAware { __type: 'se.josh.xcap.community.Community'; status: string /** CommunityStatus */; logotype: Image | null; domains: Array<string>; adminUserIds: Array<number>; adminsUserRef: Array<User>; moderatorUserIds: Array<number>; moderatorUserRef: Array<User>; locale: string; xcapCommunityName: string; theme: string; settings: any; style: any; defaultUserId: number; openAIApiKey: string; } /** * Community setting keys */ export const CommmunitySettings = { LOGIN_ENABLED: 'loginEnabled', REGISTER_ENABLED: 'registerEnabled', TERMS_AND_CONDITIONS_LINK: 'termsAndConditionsLink', FACEBOOK_LOGIN: 'facebookLogin', GOOGLE_LOGIN: 'googleLogin', OAUTH2_LOGIN: 'oauth2Login' }; /** * A module / function */ export interface Module extends XcapObject, NameAware { __type: 'se.josh.xcap.community.Module'; /** Community owning this module */ communityId: number; /** If set to true, the module will be visible on site */ enabled: boolean; /** XCAP community context for the component */ componentContext: string; /** XCAP component class name */ componentClass: string; /** Component name, for example "comments" */ componentName: string; /** * Does the module have categories? */ hasCategories: boolean; /** * Does the module have additional comments? */ hasComments: boolean; obfuscatedReference: string; /** Related object key, if any */ objectRef: null | string; /** Rule type id used when applying rules */ ruleTypeId: number; /** * Additional module specific settings */ settings: { [key: string]: any; }; /** * Additional style settings */ style: { [key: string]: string; }; } export interface ModuleRule { /** Create privilege */ createPrivilege: PrivilegeTypeIds; moderationStatus: ModerationStatus; postModerationTtlMinutes: number; contentFiltering: boolean; trustedUsers: Array<User>; } export interface ModuleStats { numberOfMembers: number /** -1 for unknown */; numberOfPosts: number /** -1 for unknown */; numberOfComments: number /** -1 for unknown */; } export interface CommunityStats { numberOfModules: number; objectsAwaitingModeration: number; numberOfUsers: number; numberOfActiveUsers: number; mediaFileSize: number; numberOfPosts: number; } /** * A community theme */ export const Theme = { STACKEND: 'stackend' }; /** * Community search sort ordering */ export enum OrderBy { NAME = 'NAME', CREATED_DATE = 'CREATED_DATE' } /** * Given a theme, get a human readable label * @param theme * @returns {string} */ export function getThemeLabel(theme: string): string { if (!theme || theme.length === 0) { return ''; } const r = theme.replace(/_/g, ' '); return r.charAt(0).toUpperCase() + r.substring(1).toLowerCase(); } /** * CommunityManager context * @type {string} */ export const COMMUNITY_MANAGER_CONTEXT = 'community'; /** * CommunityManager component class * @type {string} */ export const COMPONENT_CLASS = 'se.josh.xcap.community.CommunityManager'; /** * Community permalink reserverd for news and documentation on stackend.com * @type {string} */ export const STACKEND_COM_COMMUNITY_PERMALINK = 'stackend-com'; export interface GetCommunityResult extends XcapJsonResult { communityFromDomain: boolean; stackendCommunity: Community | null; /** * Number of objects waiting for moderation. Available to admins only. */ objectsRequiringModeration: number; } /** * Get a community. * If no parameter is present, the domain is taken from the referer header. * * @param id {String} * @param permalink {String} * @param domain {String} * */ export function getCommunity({ id, permalink, domain }: { id?: number; permalink?: string; domain?: string; }): Thunk<Promise<GetCommunityResult>> { return getJson({ url: '/stackend/community/get', parameters: { id, permalink, domain }, community: STACKEND_COMMUNITY }); } export interface ValidateCommunityPermalinkResult extends XcapJsonResult { valid: boolean; suggestions: Array<string>; } /** * Validate a community permalink and get suggestions based on the permalink/name. * * @param permalink {String} * @param name {String} generate permalink suggestions based on this name (Optional) * */ export function validateCommunityPermalink({ permalink, name }: { permalink: string; name?: string; }): Thunk<Promise<ValidateCommunityPermalinkResult>> { return getJson({ url: '/stackend/community/validate-permalink', parameters: { permalink, name }, community: STACKEND_COMMUNITY }); } /** * Create, but do not store a new community object that can be used to store * @param name * @param permalink */ export function newCommunity(name: string, permalink: string): any { return { id: 0, permalink, name, description: '', status: CommunityStatus.VISIBLE, locale: 'en_US', domains: [], logotypeId: 0, admins: [], moderators: [], theme: Theme.STACKEND, openAIApiKey: '' }; } export interface StoreCommunityResult extends XcapJsonResult { storedCommunity: Community; } /** * Edit/create a community. * * @param id {String} (optional, only when editing) * @param permalink {String} Required * @param name {String} Name * @param description {String} * @param status {CommunityStatus} * @param locale {String} Locale (default: en_US) * @param domains {string[]} List of valid domains * @param settings {String} Implementation specific settings data (typically JS) for front end. * @param logotypeId {number} Media id of logotype image * @param admins {number[]} List of admin user ids * @param moderators {number[]} List of moderator user ids * @param theme {String} Name of theme to use * @param style {String} Implementation specific style data (typically CSS) for front end. * @param defaultUserId * @param openAIApiKey */ export function storeCommunity({ id, permalink, name, description, status = CommunityStatus.VISIBLE, locale = 'en_US', domains = [], settings, logotypeId, admins = [], moderators = [], theme, style = undefined, defaultUserId, openAIApiKey }: { id?: number; permalink?: string; name?: string; description?: string; status?: any; locale?: string; domains?: Array<string>; settings?: any; logotypeId?: number; admins?: Array<number>; moderators?: Array<number>; theme?: string; style?: any; defaultUserId?: number; openAIApiKey?: string; }): Thunk<Promise<StoreCommunityResult>> { return post({ url: '/stackend/community/store', parameters: { id, permalink, name, description, status, locale, domains, logotypeId, admins, moderators, theme, style: style ? JSON.stringify(style) : '{}', settings: settings ? JSON.stringify(settings) : '{}', defaultUserId, openAIApiKey }, community: STACKEND_COMMUNITY }); } /** * Set all community settings without affecting anything else. * * @param id {number} * @param settings {any} Settings */ export function setCommunitySettings({ id, settings }: { id: number; settings: any; }): Thunk<Promise<StoreCommunityResult>> { return post({ url: '/stackend/community/set-settings', parameters: { id, settings: JSON.stringify(settings) }, community: STACKEND_COMMUNITY }); } /** * Set a single community setting without affecting anything else. * * @param id {number} * @param name {string} * @param value {any} */ export function setCommunitySetting({ id, name, value }: { id: number; name: string; value: any; }): Thunk<Promise<StoreCommunityResult>> { return post({ url: '/stackend/community/set-setting', parameters: { id, name, value: JSON.stringify(value) }, community: STACKEND_COMMUNITY }); } /** * Set a single community setting without affecting anything else. * * @param id {number} * @param theme StackendTheme */ export function storeCommunityTheme({ id, theme }: { id: number; theme: any }): Thunk<Promise<StoreCommunityResult>> { return post({ url: '/stackend/community/store-theme', parameters: { id, theme: typeof theme === 'string' ? theme : JSON.stringify(theme) }, community: STACKEND_COMMUNITY }); } export type GetCommunityPrivateSettingsResult = XcapJsonResult; /** * Get the community's private settings that are not exposed to the frontend * @param key Get a specific setting * @param prefix Get settings with a specific prefix ("" for all) * @param community */ export function getCommunityPrivateSettings({ key, prefix, community }: { key?: string | null; prefix?: string | null; community?: string | null; }): Thunk<Promise<GetCommunityPrivateSettingsResult>> { return getJson({ url: '/stackend/community/private/get-settings', parameters: { key, prefix }, community: community }); } /** * Store the communitys private settings that are not exposed to the frontend. * Store a single key/value or a set of values. * @param key * @param value * @param values * @param community * @returns {Thunk<XcapJsonResult>} */ export function storeCommunityPrivateSettings({ key, value, values, community }: { key?: string | null; value?: any | null; values?: { [name: string]: any }; community?: string | null; }): Thunk<Promise<XcapJsonResult>> { const x = { key: key, values: values ? JSON.stringify(values) : null }; return post({ url: '/stackend/community/private/store-settings', parameters: x, community: community }); } /** * Set visible / hidden status of a community */ export function setCommunityStatus({ id, status }: { id?: number; status: CommunityStatus.VISIBLE | CommunityStatus.HIDDEN; }): Thunk<Promise<StoreCommunityResult>> { return post({ url: '/stackend/community/set-status', parameters: { id, status }, community: STACKEND_COMMUNITY }); } export interface RemoveCommunityResult extends XcapJsonResult { dataRemoved: boolean; } /** * Remove a community. If new or empty, the data will also be removed. * You can force data to be removed by setting removeData. That requires back office access however. * * @param id {String} * @param removeData {boolean} Remove the data, even if the community is not empty. Requires back office access. */ export function removeCommunity({ id, removeData }: { id: number; removeData: boolean; }): Thunk<Promise<RemoveCommunityResult>> { return post({ url: '/stackend/community/remove', parameters: { id, removeData }, community: STACKEND_COMMUNITY }); } export interface SearchCommunityResult extends XcapJsonResult { results: PaginatedCollection<Community>; statistics: { [id: string]: CommunityStats }; } /** * Search for a communities. * @param myCommunities {boolean} Search the current users communities only * @param creatorUserId {number} find communities created by this user only * @param status * @param q Search string * @param p * @param pageSize * @param orderBy * @param order */ export function searchCommunity({ myCommunities = true, creatorUserId, status = CommunityStatus.VISIBLE, q, p = 1, pageSize, orderBy = OrderBy.NAME, order = Order.ASCENDING }: { myCommunities?: boolean; creatorUserId?: number; status?: any; q?: number; p?: number; pageSize?: number; orderBy?: OrderBy; order?: Order; }): Thunk<Promise<SearchCommunityResult>> { return getJson({ url: '/stackend/community/search', parameters: { myCommunities, creatorUserId, status, q, p, pageSize, orderBy, order }, community: STACKEND_COMMUNITY }); } /** * Get the current user (with privileges from stackend rather than the current community) */ export function getCurrentStackendUser(): Thunk<Promise<XcapJsonResult>> { return getJson({ url: '/user/get', community: STACKEND_COMMUNITY }); } /** * In a list of communities, find the one that matches the permalink * @return {Community} may return null, */ export function getCurrentCommunity(communities: Array<Community>): Thunk<Community | null> { return (dispatch: any): Community | null => { if (typeof communities === 'undefined' || communities === null || communities.length === 0) { return null; } const currentCommunityPermalink = dispatch(getCurrentCommunityPermalink()); if (currentCommunityPermalink === null) { // FIXME: Fall back to url like _getCurrentCommunity return null; } for (let i = 0; i < communities.length; i++) { const community = communities[i]; if (community.permalink === currentCommunityPermalink) { return community; } } return null; }; } /** * Check if the potential community url permalink is blocked. * @param communityUrl * @returns {boolean} */ export function isCommunityUrlBlocked(communityUrl: string): boolean { const blockedUrls: { [url: string]: boolean } = { create: true, 'my-settings': true, contact: true, stacks: true, user: true, register: true, billing: true, oauth2: true, google: true, facebook: true, shopify: true, 'shopify-app': true, healthcheck: true }; // Remove inital / let u = communityUrl; if (u.startsWith('/')) { u = u.substring(1); } // Remove extra path const i = u.indexOf('/'); if (i !== -1) { u = u.substring(0, i); } const t = blockedUrls[u]; return typeof t === 'undefined' ? false : t; } /** * From the request url try to get the communityPermalink * @return {Community} may return null, */ export function _getCurrentCommunityPermalinkFromUrl(request: Request): any { const p = request.location.pathname; // Needs to work with: // - /XXX // - /stacks/XXX // - /contextPath/XXX // - /contextPath/stacks/XXX const re = new RegExp('^' + request.contextPath + '(:?/stacks)?/([^/]+).*'); const r = re.exec(p); if (!r) { return null; } const currentCommunityPermalink = r[2]; if (isCommunityUrlBlocked(currentCommunityPermalink)) { return null; } // FIXME: Fall back to domain return currentCommunityPermalink; } /** * In a list of communities, find the one that matches the permalink * @return {Community} may return null, */ export function _getCurrentCommunity(communities: Array<Community>, request: Request): Community | null { if (typeof communities === 'undefined' || communities === null || communities.length === 0) { return null; } const currentCommunityPermalink = _getCurrentCommunityPermalinkFromUrl(request); if (currentCommunityPermalink === null) { return null; } for (let i = 0; i < communities.length; i++) { const community = communities[i]; if (community.permalink === currentCommunityPermalink) { return community; } } return null; } /** * Check if the user is a community moderator, but not admin * @param community * @param userId * @returns {boolean} */ export function isCommunityModerator(community: Community | null, userId: number): boolean { if (!(community && community.id)) { return false; } if (!userId) { return false; } if (typeof community.moderatorUserIds !== 'undefined' && community.moderatorUserIds.includes(userId)) { return true; } return false; } /** * Check if a user is admin * @param community * @param userId * @returns {boolean} */ export function isCommunityAdmin(community: Community | null, userId?: number | null): boolean { if (typeof community === 'undefined' || community === null || !userId) { return false; } if (userId === community.creatorUserId) { return true; } return typeof community.adminUserIds !== 'undefined' && community.adminUserIds.includes(userId as number); } /** * Check if the user has stackend admin access (any community/stack). * @param currentUser * @returns {boolean} */ export function hasStackendAdminAccess(currentUser: CurrentUserType | User | null): boolean { return hasElevatedPrivilege(currentUser, COMMUNITY_MANAGER_CONTEXT, COMPONENT_CLASS, PrivilegeTypeId.ADMIN); } /** * Check if there is any way a user may see the current community * @param community * @param currentUser */ export function hasCommunityAdminOrModeratorAccess(community: Community | null, currentUser: CurrentUserType): boolean { if (!(community && community.id)) { return false; } if (!(currentUser && currentUser.user)) { return false; } const userId = currentUser.user.id; if (userId === community.creatorUserId) { return true; } if (typeof community.adminUserIds !== 'undefined' && community.adminUserIds.includes(userId)) { return true; } if (typeof community.moderatorUserIds !== 'undefined' && community.moderatorUserIds.includes(userId)) { return true; } return hasStackendAdminAccess(currentUser); } /** * Check if the current user has community admin access * @param community * @param currentUser * @returns {boolean} */ export function hasCommunityAdminAccess(community: Community | null, currentUser: CurrentUserType): boolean { if (!(currentUser && currentUser.user)) { return false; } if (hasStackendAdminAccess(currentUser)) { return true; } return isCommunityAdmin(community, currentUser.user.id); } /** * Check if the user has stackend access and may create new stacks. * @param currentUser * @returns {boolean} */ export function hasStackendCreateAccess(currentUser: CurrentUserType): boolean { return hasElevatedPrivilege(currentUser, COMMUNITY_MANAGER_CONTEXT, COMPONENT_CLASS, PrivilegeTypeId.TRUSTED); } export type SupportedModuleContext = { context: string; componentClass: string; supportsMultipleModules: boolean; }; export interface GetModulesResult extends XcapJsonResult { modules: Array<Module>; supportedModuleContexts: Array<SupportedModuleContext>; stats: { [id: string]: ModuleStats }; } /** * Get the modules of a community. * * @param communityId * @returns {Promise} */ export function getModules({ communityId }: { communityId: number }): Thunk<Promise<GetModulesResult>> { return getJson({ url: '/stackend/module/list', parameters: { communityId, pageSize: 1000 }, community: STACKEND_COMMUNITY }); } export interface GetModuleResult extends XcapJsonResult { module: Module | null; rule: ModuleRule | null; commentRule: ModuleRule | null; stats: ModuleStats | null; } /** * Get a module of a community. * * @param communityId * @param moduleId * @returns {Thunk<GetModuleResult>} */ export function getModule({ communityId, moduleId }: { communityId: number; moduleId: number; }): Thunk<Promise<GetModuleResult>> { return getJson({ url: '/stackend/module/get', parameters: { communityId, moduleId }, community: STACKEND_COMMUNITY }); } /** * Get a singleton module given it's component class * @param communityId * @param componentClass * @param componentContext * @returns {Thunk<GetModuleResult>} */ export function getSingletonModule({ communityId, componentClass, componentContext }: { communityId: number; componentClass: string; componentContext: string; }): Thunk<Promise<GetModuleResult>> { return getJson({ url: '/stackend/module/get-singleton', parameters: { communityId, singletonComponentClass: componentClass, singletonComponentContext: componentContext }, community: STACKEND_COMMUNITY }); } /** * Construct the object that can be passed to storeModule() * @param communityId * @param componentClass * @param componentContext * @param name */ export function newModule({ communityId, componentClass, componentContext, name }: { communityId: number; componentClass: string; componentContext: string; name: string; }): any { if (!communityId) { throw Error('communityId required'); } if (!componentClass) { throw Error('componentClass required'); } if (!componentContext) { throw Error('componentContext required'); } return { id: 0, communityId, name, enabled: true, componentClass, componentContext, ruleTypeId: 0, settings: {}, style: {}, extraData: {} }; } export interface StoreModuleResult extends XcapJsonResult { module: Module | null; } /** * Store a module of a community. * * When creating a new module, this will also set up the module data, for example the blog. * * @param id * @param communityId * @param name * @param enabled * @param componentClass * @param componentContext * @param ruleTypeId * @param settings Implementation specific (js) string (Max 64KB) * @param style Implementation specific (css) string (Max 64KB) * @param extraData Component specific extra JSON data. * * Supported optional extra data: * <dl> * <dt>permalink</dt> * <dt>description</dt> * <dt>forumAnonymity</dt><dd>ForumAnonymityLevel</dd> * <dt>groupVisibile</dt><dd>true/false</dd> * <dt>groupContentVisibile</dt><dd>true/false</dd> * <dt>body<dt> * <dt>teaser</dt> * <dl> * @returns {Promise} */ export function storeModule({ id, communityId, name, enabled, componentClass, componentContext, ruleTypeId, settings, style, extraData }: { id?: number; communityId: number; name?: string; enabled?: boolean; componentClass?: string; componentContext?: string; ruleTypeId?: number; settings?: any; style?: any; extraData?: any; }): Thunk<Promise<StoreModuleResult>> { return async (dispatch: any): Promise<StoreModuleResult> => { const module = await dispatch( post({ url: '/stackend/module/store', parameters: { id, communityId, name, enabled, componentClass, componentContext, ruleTypeId, settings: settings ? JSON.stringify(settings) : '{}', style: style ? JSON.stringify(style) : '{}', extraData: extraData ? JSON.stringify(extraData) : '{}' }, community: STACKEND_COMMUNITY }) ); dispatch(fetchModules({ communityId })); // FIXME: Update state without re-fetch return module; }; } interface RuleSetup { createPrivilege: PrivilegeTypeIds; moderationStatus: ModerationStatus; contentFiltering: boolean; postModerationTtlMinutes: number; } /** * Store rules for a module * @param moduleId * @param communityId * @param rule * @param commentRule * @param trustedUsers */ export function storeModuleRules({ communityId, moduleId, rule, commentRule, trustedUsers }: { communityId: number; moduleId: number; rule: RuleSetup; commentRule: RuleSetup; trustedUsers: Array<number>; }): Thunk<Promise<XcapJsonResult>> { const r = { createPrivilege: rule.createPrivilege, moderationStatus: rule.moderationStatus, contentFiltering: rule.contentFiltering, postModerationTtlMinutes: rule.postModerationTtlMinutes }; let cr = null; if (commentRule) { cr = { createPrivilege: commentRule.createPrivilege, moderationStatus: commentRule.moderationStatus, contentFiltering: commentRule.contentFiltering, postModerationTtlMinutes: commentRule.postModerationTtlMinutes }; } return post({ url: '/stackend/module/rules/store', parameters: { communityId, moduleId, trustedUsers, rule: JSON.stringify(r), commentRule: JSON.stringify(cr) }, community: STACKEND_COMMUNITY }); } /** * Remove a module. * * @param id * @param communityId */ export function removeModule({ id, communityId }: { id: number; communityId: number }): Thunk<Promise<XcapJsonResult>> { return post({ url: '/stackend/module/remove', parameters: { id, communityId }, community: STACKEND_COMMUNITY }); } /** * Detect modules by inspecting existing data. Developer tool. * @param communityId */ export function detectModules({ communityId }: { communityId: number }): Thunk<Promise<XcapJsonResult>> { return post({ url: '/stackend/modules/update', parameters: { communityId }, community: STACKEND_COMMUNITY }); } /** * Translates component class names to human readable names. */ const COMPONENT_CLASS_TO_MODULE_NAME: { [name: string]: string } = { 'se.josh.xcap.comment.impl.CommentManagerImpl': 'Comments', 'se.josh.xcap.comment.CommentManager': 'Comments', 'net.josh.community.blog.BlogManager': 'Blog', 'net.josh.community.forum.impl.ForumManagerImpl': 'Forum', 'net.josh.community.forum.ForumManager': 'Forum', 'se.josh.xcap.cms.CmsManager': 'CMS', 'se.josh.xcap.cms.impl.CmsManagerImpl': 'CMS', 'se.josh.xcap.like.impl.LikeManagerImpl': 'Like', 'net.josh.community.group.GroupManager': 'Group', 'net.josh.community.category.CategoryManager': 'Page', 'com.stackend.live.LiveEventManager': 'Live Event' }; const MODULE_TYPE_TO_COMPONENT_CLASS: { [name: string]: string } = { comment: 'se.josh.xcap.comment.impl.CommentManagerImpl', blog: 'net.josh.community.blog.BlogManager', forum: 'net.josh.community.forum.impl.ForumManagerImpl', cms: 'se.josh.xcap.cms.impl.CmsManagerImpl', group: 'net.josh.community.group.GroupManager', page: 'net.josh.community.category.CategoryManager', live: 'com.stackend.live.LiveEventManager' }; /** * Get a human readable component name * @param componentClass class */ export function getComponentLabel(componentClass: string): string { const t = COMPONENT_CLASS_TO_MODULE_NAME[componentClass]; if (typeof t === 'undefined') { return 'Unknown'; } return t; } /** * Get a component class * @param moduleType */ export function getComponentClassFromModuleType(moduleType: string): string { const t = MODULE_TYPE_TO_COMPONENT_CLASS[moduleType]; if (typeof t === 'undefined') { return 'Unknown'; } return t; } export interface ListAdminUsersResult extends XcapJsonResult { users: Array<User>; } /** * List users with stackend admin status. * Requires stackend admin status. * @param privilege {number} PrivilegeType */ export function listAdminUsers({ privilege }: { privilege: number }): Thunk<Promise<ListAdminUsersResult>> { return getJson({ url: '/stackend/user/list-admins', parameters: { privilege }, community: STACKEND_COMMUNITY }); } /** * Grant/revoke stackend admin status for a user. * Requires stackend admin status. * Any privilegeType lower than PrivilegeType.VERIFIED will remove the grants. * @param userId {number} User id * @param privilege {number} PrivilegeType */ export function setAdminStatus({ userId, privilege }: { userId: number; privilege: PrivilegeTypeIds; }): Thunk<Promise<XcapJsonResult>> { return post({ url: '/stackend/user/set-admin-status', parameters: { userId, privilege }, community: STACKEND_COMMUNITY }); } export interface SetCommunityAccessResult extends XcapJsonResult { stackendCommunity: Community; communityPrivilegeType: number; communityId: number; } /** * Make a user moderator or admin status from a community. Or remove that status * @param communityId * @param userId * @param communityPrivilegeType Privilege: ADMIN for admins, TRUSTED for moderators. All other privs will revoke the access. */ export function setCommunityAccess({ communityId, userId, privilegeType }: { communityId: number; userId: number; privilegeType: number; }): Thunk<Promise<SetCommunityAccessResult>> { return post({ url: '/stackend/user/set-community-access', parameters: arguments, community: STACKEND_COMMUNITY }); } /** * Invite a user as administrator or moderator of this community. * The user may or may not already be registered. If not, an email is sent inviting the user to manage the community. * * @param email Users email * @param communityId Id of community * @param communityPrivilegeType Privilege. Supports PrivilegeType.ADMIN (admin) and PrivilegeType.TRUSTED (moderator) * @param message Optional welcome message */ export function inviteUserToCommunity({ email, communityId, communityPrivilegeType, message }: { email: string; communityId: number; communityPrivilegeType: PrivilegeTypeIds; message: string; }): Thunk<Promise<XcapJsonResult>> { return post({ url: '/stackend/user/invite', parameters: arguments, community: STACKEND_COMMUNITY }); } /** * Get the domain (excluding www) and path of an url. * "http://www.josh.se/test" would return "josh.se/test" * "/test" would return "/test" * @param url * @returns {*|string} */ export function getReferenceUrl(url: string): string { if (window !== undefined) { // Shopify specific for reference url normalization // @ts-ignore if (window?.Shopify?.routes?.root !== undefined) { // @ts-ignore url = url.replace(window.Shopify.routes.root.slice(0, -1), ''); } } const r = /(?:https|http)?(?::\/\/)?(?:www\.)?([^?#]*)/.exec(url); return r ? r[1] : url; } /** * Get the url to a stackend community and module * @param request * @param community * @param module * @param path * @returns {string} */ export function getStackendUrl({ request, community, module, path }: { request: Request; community?: Community | null; module?: Module | null; path: string; }): string { let s: string = request.contextPath; if (community) { s += '/stacks/' + community.permalink; if (module) { s += '/module/' + module.id; } } if (path) { if (!s.endsWith('/') && !path.startsWith('/')) { s += '/'; } s += path; } return s; } /** * Remove a user. Requires stackend admin status. Fails if the user has communities. * @param userId * @returns {Thunk<XcapJsonResult>} */ export function removeUser({ userId }: { userId: number }): Thunk<Promise<XcapJsonResult>> { return post({ url: '/stackend/user/remove', parameters: arguments }); }