@ossy/workspaces 1.17.2 → 1.17.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -4
- package/src/CreateWorkspacePage.jsx +7 -16
- package/src/Definition.js +1 -0
- package/src/GeneralSettings.jsx +77 -19
- package/src/Invitations.jsx +7 -7
- package/src/Invite.jsx +13 -25
- package/src/SchemasReadBootstrap.jsx +20 -0
- package/src/SelectWorkspacePage.jsx +5 -8
- package/src/Users.jsx +7 -7
- package/src/Users.page.jsx +3 -3
- package/src/accept-invitation.action.js +1 -1
- package/src/accept-invitation.api.js +46 -0
- package/src/accept-invitation.task.js +6 -5
- package/src/create-api-token.action.js +1 -1
- package/src/create-api-token.task.js +1 -1
- package/src/create.action.js +1 -1
- package/src/create.task.js +1 -1
- package/src/disable-service.action.js +1 -1
- package/src/disable-service.task.js +3 -15
- package/src/en.translations.json +36 -30
- package/src/enable-service.action.js +1 -1
- package/src/enable-service.task.js +3 -15
- package/src/entitlements.js +199 -0
- package/src/entitlements.spec.js +88 -0
- package/src/get-api-tokens.action.js +1 -1
- package/src/get-api-tokens.task.js +1 -1
- package/src/get-invitations.action.js +1 -1
- package/src/get-invitations.task.js +1 -1
- package/src/get-schemas.action.js +1 -0
- package/src/get-schemas.task.js +14 -0
- package/src/get-users.action.js +1 -1
- package/src/get-users.task.js +1 -1
- package/src/get.action.js +1 -1
- package/src/get.task.js +1 -1
- package/src/import-schemas.action.js +1 -0
- package/src/import-schemas.task.js +26 -0
- package/src/index.js +3 -2
- package/src/invite-user.action.js +1 -1
- package/src/invite-user.task.js +4 -3
- package/src/list.action.js +1 -1
- package/src/list.task.js +1 -1
- package/src/merge-workspace-schemas.js +13 -0
- package/src/merge-workspace-schemas.spec.js +16 -0
- package/src/remove-member.action.js +1 -1
- package/src/remove-member.task.js +1 -1
- package/src/schema-ids.js +3 -0
- package/src/select-workspace.api.js +50 -0
- package/src/selectWorkspace.js +11 -0
- package/src/service-keys.js +1 -0
- package/src/stories/Users.stories.jsx +35 -0
- package/src/stories/decorators.jsx +59 -0
- package/src/sv.translations.json +36 -30
- package/src/sync-workspace-membership.task.js +7 -6
- package/src/workspace-invitation.email.jsx +1 -1
- package/src/workspace-users.component.jsx +3 -3
- package/src/workspace.aggregate.js +35 -47
- package/src/workspace.schema.js +9 -0
- package/src/workspaces.events.js +61 -155
- package/src/{workspaces.spec.js → workspaces.integration.spec.js} +40 -38
- package/src/workspaces.queries.js +27 -12
- package/src/get-resource-templates.action.js +0 -1
- package/src/get-resource-templates.task.js +0 -18
- package/src/import-resource-templates.action.js +0 -1
- package/src/import-resource-templates.task.js +0 -26
package/package.json
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/workspaces",
|
|
3
3
|
"description": "Workspaces feature package - create, select, and manage workspaces, users, and invitations",
|
|
4
|
-
"version": "1.17.
|
|
4
|
+
"version": "1.17.3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"module": "./src/index.js",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": "./src/index.js",
|
|
10
10
|
"./server": "./src/server.js",
|
|
11
|
+
"./entitlements": "./src/entitlements.js",
|
|
12
|
+
"./merge-workspace-schemas": "./src/merge-workspace-schemas.js",
|
|
13
|
+
"./SchemasReadBootstrap": "./src/SchemasReadBootstrap.jsx",
|
|
11
14
|
"./workspace-invitation.email.jsx": "./src/workspace-invitation.email.jsx"
|
|
12
15
|
},
|
|
13
16
|
"scripts": {
|
|
14
|
-
"test": "NODE_OPTIONS=--experimental-vm-modules jest --verbose"
|
|
17
|
+
"test": "NODE_OPTIONS=--experimental-vm-modules jest --verbose",
|
|
18
|
+
"test:integration": "NODE_OPTIONS=--experimental-vm-modules jest --config jest.integration.config.js --verbose"
|
|
15
19
|
},
|
|
16
20
|
"author": "Ossy <yourfriends@ossy.se> (https://ossy.se)",
|
|
17
21
|
"license": "MIT",
|
|
@@ -26,7 +30,7 @@
|
|
|
26
30
|
},
|
|
27
31
|
"devDependencies": {
|
|
28
32
|
"@jest/globals": "^30.2.0",
|
|
29
|
-
"@ossy/platform": "^1.39.
|
|
33
|
+
"@ossy/platform": "^1.39.3",
|
|
30
34
|
"casual": "^1.6.2",
|
|
31
35
|
"jest": "^30.2.0"
|
|
32
36
|
},
|
|
@@ -38,5 +42,5 @@
|
|
|
38
42
|
"/src",
|
|
39
43
|
"README.md"
|
|
40
44
|
],
|
|
41
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "a0d89185a17f8de8ce328c3a648c108ff1d61d8f"
|
|
42
46
|
}
|
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
2
|
import { ListWorkspaces, CreateWorkspace } from '@ossy/workspaces'
|
|
3
3
|
import { useSdk, cacheKey } from '@ossy/sdk-react'
|
|
4
|
-
import {
|
|
5
|
-
Button,
|
|
6
|
-
useInputValue,
|
|
7
|
-
Title,
|
|
8
|
-
Input,
|
|
9
|
-
View,
|
|
10
|
-
useLocale,
|
|
11
|
-
} from '@ossy/design-system'
|
|
4
|
+
import { Button, useInputValue, Input, View, useLocale } from '@ossy/design-system'
|
|
12
5
|
import { useRouter } from '@ossy/router-react'
|
|
13
6
|
import { AuthenticationGuard } from './AuthenticationGuard.jsx'
|
|
14
|
-
import {
|
|
7
|
+
import { selectWorkspace } from './selectWorkspace.js'
|
|
15
8
|
import { metadata as CreateWorkspaceMeta } from './create.action.js'
|
|
16
9
|
|
|
17
10
|
export const CreateWorkspacePage = () => {
|
|
@@ -25,12 +18,10 @@ export const CreateWorkspacePage = () => {
|
|
|
25
18
|
sdk.invoke(CreateWorkspace, { name: workspaceName })
|
|
26
19
|
.then((workspace) => {
|
|
27
20
|
sdk.invalidate(cacheKey(ListWorkspaces))
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
)
|
|
33
|
-
})
|
|
21
|
+
selectWorkspace(
|
|
22
|
+
workspace.id,
|
|
23
|
+
router.getHref({ id: '@storage/home', params: { workspaceId: workspace.id } })
|
|
24
|
+
)
|
|
34
25
|
})
|
|
35
26
|
.catch(err => { setError(err.message) })
|
|
36
27
|
}
|
|
@@ -40,7 +31,7 @@ export const CreateWorkspacePage = () => {
|
|
|
40
31
|
<View gap="m">
|
|
41
32
|
|
|
42
33
|
<title>{t('profile/workspaces/create.documentTitle')}</title>
|
|
43
|
-
<
|
|
34
|
+
<Text variant="heading-primary" className="stack-m" as="h1" text="workspaces.create.title" />
|
|
44
35
|
|
|
45
36
|
<Input
|
|
46
37
|
id="workspaceName"
|
package/src/Definition.js
CHANGED
package/src/GeneralSettings.jsx
CHANGED
|
@@ -1,42 +1,100 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
|
-
import { GetWorkspace } from '@ossy/workspaces'
|
|
2
|
+
import { GetWorkspace, EnableService, DisableService } from '@ossy/workspaces'
|
|
3
3
|
import { GetCurrentUser } from '@ossy/authentication'
|
|
4
|
-
import { useSdk, AsyncStatus } from '@ossy/sdk-react'
|
|
5
|
-
import { Switch,
|
|
4
|
+
import { useSdk, AsyncStatus, cacheKey } from '@ossy/sdk-react'
|
|
5
|
+
import { Switch, Button, View, Text, ContentHeader, useLocale } from '@ossy/design-system'
|
|
6
6
|
|
|
7
7
|
const formatDate = (timestamp) => timestamp ? new Date(timestamp).toLocaleDateString() : ''
|
|
8
8
|
const FlowStage = { View: 'View', Edit: 'Edit' }
|
|
9
9
|
|
|
10
|
+
/** Built-in capabilities a workspace can enable (package name → locale label key). */
|
|
11
|
+
const MANAGED_SERVICES = []
|
|
12
|
+
|
|
13
|
+
function isServiceOn (services, packageName) {
|
|
14
|
+
return services?.[packageName]?.enabled === true
|
|
15
|
+
}
|
|
16
|
+
|
|
10
17
|
export const GeneralSettings = () => {
|
|
11
18
|
const { t } = useLocale()
|
|
12
19
|
const sdk = useSdk()
|
|
13
|
-
const { data: workspace } = sdk.read(GetWorkspace)
|
|
20
|
+
const { data: workspace, refetch: refetchWorkspace } = sdk.read(GetWorkspace)
|
|
14
21
|
const { status: userStatus } = sdk.read(GetCurrentUser)
|
|
15
22
|
const [flowStage, setFlowStage] = useState(FlowStage.View)
|
|
23
|
+
const [busyPackage, setBusyPackage] = useState(null)
|
|
16
24
|
const services = workspace?.services || {}
|
|
17
25
|
|
|
26
|
+
const toggleService = async (packageName, enable) => {
|
|
27
|
+
setBusyPackage(packageName)
|
|
28
|
+
try {
|
|
29
|
+
const action = enable ? EnableService : DisableService
|
|
30
|
+
await sdk.invoke(action, { service: packageName })
|
|
31
|
+
sdk.invalidate(cacheKey(GetWorkspace))
|
|
32
|
+
await refetchWorkspace()
|
|
33
|
+
} finally {
|
|
34
|
+
setBusyPackage(null)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
18
38
|
return (
|
|
19
39
|
<View gap="l" inset="m">
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
40
|
+
<ContentHeader
|
|
41
|
+
title="workspace.settings.detailsTitle"
|
|
42
|
+
description="workspace.settings.detailsDescription"
|
|
43
|
+
inset="none"
|
|
44
|
+
far={(
|
|
45
|
+
<Switch on={flowStage}>
|
|
46
|
+
<Switch.Case match={[FlowStage.View]}>
|
|
47
|
+
<Button prefix={{ size: 's', name: 'pen' }} variant="cta" disabled label="workspace.settings.edit" />
|
|
48
|
+
</Switch.Case>
|
|
49
|
+
</Switch>
|
|
50
|
+
)}
|
|
51
|
+
/>
|
|
28
52
|
{userStatus === AsyncStatus.Loading ? (
|
|
29
53
|
<Text>{t('workspace.settings.loading')}</Text>
|
|
30
54
|
) : (
|
|
31
55
|
<View gap="s">
|
|
32
|
-
<View layout="row" gap="s"><Text style={{ fontWeight: 'bold' }}
|
|
33
|
-
<View layout="row" gap="s"><Text style={{ fontWeight: 'bold' }}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
)
|
|
56
|
+
<View layout="row" gap="s"><Text style={{ fontWeight: 'bold' }} text="workspace.settings.name" /><Text>{workspace?.name}</Text></View>
|
|
57
|
+
<View layout="row" gap="s"><Text style={{ fontWeight: 'bold' }} text="workspace.settings.created" /><Text>{formatDate(workspace?.created)}</Text></View>
|
|
58
|
+
{MANAGED_SERVICES.length > 0 && (
|
|
59
|
+
<>
|
|
60
|
+
<Text variant="heading-tertiary" as="h3" text="workspace.settings.services" />
|
|
61
|
+
<View gap="m">
|
|
62
|
+
{MANAGED_SERVICES.map(({ package: packageName, labelKey }) => {
|
|
63
|
+
const enabled = isServiceOn(services, packageName)
|
|
64
|
+
return (
|
|
65
|
+
<View key={packageName} layout="row" gap="m" alignItems="center" justifyContent="space-between">
|
|
66
|
+
<Text data-service-status={packageName}>
|
|
67
|
+
{t(labelKey)}: {enabled ? t('workspace.settings.service.on') : t('workspace.settings.service.off')}
|
|
68
|
+
</Text>
|
|
69
|
+
{enabled ? (
|
|
70
|
+
<Button
|
|
71
|
+
variant="neutral"
|
|
72
|
+
size="s"
|
|
73
|
+
data-action={DisableService.id}
|
|
74
|
+
data-service={packageName}
|
|
75
|
+
disabled={busyPackage === packageName}
|
|
76
|
+
onClick={() => toggleService(packageName, false)}
|
|
77
|
+
>
|
|
78
|
+
{t('workspace.settings.service.disable')}
|
|
79
|
+
</Button>
|
|
80
|
+
) : (
|
|
81
|
+
<Button
|
|
82
|
+
variant="primary"
|
|
83
|
+
size="s"
|
|
84
|
+
data-action={EnableService.id}
|
|
85
|
+
data-service={packageName}
|
|
86
|
+
disabled={busyPackage === packageName}
|
|
87
|
+
onClick={() => toggleService(packageName, true)}
|
|
88
|
+
>
|
|
89
|
+
{t('workspace.settings.service.enable')}
|
|
90
|
+
</Button>
|
|
91
|
+
)}
|
|
92
|
+
</View>
|
|
93
|
+
)
|
|
94
|
+
})}
|
|
39
95
|
</View>
|
|
96
|
+
</>
|
|
97
|
+
)}
|
|
40
98
|
</View>
|
|
41
99
|
)}
|
|
42
100
|
</View>
|
package/src/Invitations.jsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { GetWorkspace } from '@ossy/workspaces'
|
|
3
3
|
import { useSdk } from '@ossy/sdk-react'
|
|
4
|
-
import {
|
|
4
|
+
import { Text, Button, View, Icon, ContentHeader, useLocale } from '@ossy/design-system'
|
|
5
5
|
import { useRouter } from '@ossy/router-react'
|
|
6
6
|
|
|
7
7
|
export const Invitations = () => {
|
|
@@ -13,15 +13,15 @@ export const Invitations = () => {
|
|
|
13
13
|
|
|
14
14
|
return (
|
|
15
15
|
<View gap="m">
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
<ContentHeader
|
|
17
|
+
title="workspace.invitations.title"
|
|
18
|
+
description="workspace.invitations.description"
|
|
19
|
+
far={(
|
|
19
20
|
<Button variant="cta" href={router.getHref('@workspace/invitations/add')} prefix="user-add">
|
|
20
21
|
{t('workspace.invitations.invite')}
|
|
21
22
|
</Button>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
</View>
|
|
23
|
+
)}
|
|
24
|
+
/>
|
|
25
25
|
<View gap="xs">
|
|
26
26
|
{invitations.map(invite => (
|
|
27
27
|
<View layout="row" roundness="s" alignItems="center" inset="m" gap="m" selectable key={invite.email}>
|
package/src/Invite.jsx
CHANGED
|
@@ -2,17 +2,7 @@ import React, { useState, useRef } from 'react'
|
|
|
2
2
|
import { InviteUser } from '@ossy/workspaces'
|
|
3
3
|
import { useSdk } from '@ossy/sdk-react'
|
|
4
4
|
import { useRouter } from '@ossy/router-react'
|
|
5
|
-
import {
|
|
6
|
-
useInputValue,
|
|
7
|
-
Button,
|
|
8
|
-
Title,
|
|
9
|
-
Input,
|
|
10
|
-
Switch,
|
|
11
|
-
Guide,
|
|
12
|
-
Text,
|
|
13
|
-
View,
|
|
14
|
-
useLocale,
|
|
15
|
-
} from '@ossy/design-system'
|
|
5
|
+
import { useInputValue, Button, Input, Switch, Guide, Text, View, useLocale } from '@ossy/design-system'
|
|
16
6
|
|
|
17
7
|
const FlowStage = {
|
|
18
8
|
EnterEmail: 'EnterEmail',
|
|
@@ -42,10 +32,8 @@ export const Invite = () => {
|
|
|
42
32
|
|
|
43
33
|
<Switch.Case match={[FlowStage.EnterEmail]}>
|
|
44
34
|
<View gap="m">
|
|
45
|
-
<
|
|
46
|
-
<Text
|
|
47
|
-
{t('workspace.invite.description')}
|
|
48
|
-
</Text>
|
|
35
|
+
<Text variant="heading-primary" as="h1" text="workspace.invite.title" />
|
|
36
|
+
<Text text="workspace.invite.description" />
|
|
49
37
|
<form
|
|
50
38
|
ref={formRef}
|
|
51
39
|
onSubmit={sendInvite}
|
|
@@ -57,7 +45,7 @@ export const Invite = () => {
|
|
|
57
45
|
id="registerField"
|
|
58
46
|
className="stack-m"
|
|
59
47
|
type="email"
|
|
60
|
-
placeholder=
|
|
48
|
+
placeholder="workspace.invite.emailPlaceholder"
|
|
61
49
|
required
|
|
62
50
|
value={email}
|
|
63
51
|
onChange={e => setEmail(e.target.value)}
|
|
@@ -68,16 +56,18 @@ export const Invite = () => {
|
|
|
68
56
|
<Button
|
|
69
57
|
variant="link"
|
|
70
58
|
id="cancelButton"
|
|
71
|
-
href={() => router.back()}
|
|
72
|
-
|
|
59
|
+
href={() => router.back()}
|
|
60
|
+
label="design-system.cancel"
|
|
61
|
+
/>
|
|
73
62
|
|
|
74
63
|
<Button
|
|
75
64
|
variant="cta"
|
|
76
65
|
id="registerButton"
|
|
77
66
|
data-action={InviteUser.id}
|
|
78
67
|
type="submit"
|
|
79
|
-
|
|
80
|
-
|
|
68
|
+
label="workspace.invite.submit"
|
|
69
|
+
onClick={sendInvite}
|
|
70
|
+
/>
|
|
81
71
|
</View>
|
|
82
72
|
</View>
|
|
83
73
|
</form>
|
|
@@ -86,11 +76,11 @@ export const Invite = () => {
|
|
|
86
76
|
|
|
87
77
|
<Switch.Case match={[FlowStage.Success]}>
|
|
88
78
|
<Guide
|
|
89
|
-
title=
|
|
79
|
+
title="workspace.invite.successTitle"
|
|
90
80
|
titleVariant="primary"
|
|
91
81
|
>
|
|
92
82
|
<Text>
|
|
93
|
-
|
|
83
|
+
<Text as="span" text="workspace.invite.successBody" />
|
|
94
84
|
<br/>
|
|
95
85
|
✌️
|
|
96
86
|
</Text>
|
|
@@ -98,9 +88,7 @@ export const Invite = () => {
|
|
|
98
88
|
</Switch.Case>
|
|
99
89
|
|
|
100
90
|
<Switch.Case match={[FlowStage.Error]}>
|
|
101
|
-
<Text
|
|
102
|
-
{t('workspace.invite.error')}
|
|
103
|
-
</Text>
|
|
91
|
+
<Text text="workspace.invite.error" />
|
|
104
92
|
</Switch.Case>
|
|
105
93
|
|
|
106
94
|
</Switch>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React, { useEffect } from 'react'
|
|
2
|
+
import { AsyncStatus, cacheKey, useReadCacheStore } from '@ossy/sdk-react'
|
|
3
|
+
import { metadata as GetSchemas } from './get-schemas.action.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Seed the GetSchemas read cache from SSR props so schema lookups work on first paint.
|
|
7
|
+
*/
|
|
8
|
+
export function SchemasReadBootstrap ({ schemas }) {
|
|
9
|
+
const store = useReadCacheStore()
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (!Array.isArray(schemas) || schemas.length === 0) return
|
|
13
|
+
const key = cacheKey(GetSchemas)
|
|
14
|
+
const entry = store.getEntry(key)
|
|
15
|
+
if (entry.status === AsyncStatus.Success) return
|
|
16
|
+
store.setEntry(key, { status: AsyncStatus.Success, data: schemas, error: null })
|
|
17
|
+
}, [schemas, store])
|
|
18
|
+
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { ListWorkspaces } from '@ossy/workspaces'
|
|
3
3
|
import { useSdk, AsyncStatus } from '@ossy/sdk-react'
|
|
4
|
-
import {
|
|
4
|
+
import { Text, Switch, Button, View, Guide, Icon, useLocale } from '@ossy/design-system'
|
|
5
5
|
import { useRouter } from '@ossy/router-react'
|
|
6
6
|
import { AuthenticationGuard } from './AuthenticationGuard.jsx'
|
|
7
|
-
import {
|
|
7
|
+
import { selectWorkspace } from './selectWorkspace.js'
|
|
8
8
|
|
|
9
9
|
const PageFlow = {
|
|
10
10
|
Error: AsyncStatus.Error,
|
|
@@ -24,10 +24,7 @@ export const SelectWorkspacePage = () => {
|
|
|
24
24
|
: asyncStatus
|
|
25
25
|
|
|
26
26
|
const setWorkspace = workspaceId => {
|
|
27
|
-
|
|
28
|
-
if (!res.ok) return
|
|
29
|
-
window.location.assign(router.getHref('@home/home'))
|
|
30
|
-
})
|
|
27
|
+
selectWorkspace(workspaceId, router.getHref('@home/home'))
|
|
31
28
|
}
|
|
32
29
|
|
|
33
30
|
return (
|
|
@@ -43,9 +40,9 @@ export const SelectWorkspacePage = () => {
|
|
|
43
40
|
alignItems: 'center',
|
|
44
41
|
marginBottom: 'var(--space-m)',
|
|
45
42
|
}}>
|
|
46
|
-
<
|
|
43
|
+
<Text variant="heading-primary" as="h1">
|
|
47
44
|
{t('workspaces.select.title')}
|
|
48
|
-
</
|
|
45
|
+
</Text>
|
|
49
46
|
<Button
|
|
50
47
|
prefix="math-plus"
|
|
51
48
|
variant="cta"
|
package/src/Users.jsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { GetUsers } from '@ossy/workspaces'
|
|
3
3
|
import { useSdk, AsyncStatus } from '@ossy/sdk-react'
|
|
4
|
-
import { Text, View, Icon,
|
|
4
|
+
import { Text, View, Icon, Button, ContentHeader, useLocale } from '@ossy/design-system'
|
|
5
5
|
import { useRouter } from '@ossy/router-react'
|
|
6
6
|
|
|
7
7
|
export const Users = () => {
|
|
@@ -14,15 +14,15 @@ export const Users = () => {
|
|
|
14
14
|
|
|
15
15
|
return (
|
|
16
16
|
<View gap="m">
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
<ContentHeader
|
|
18
|
+
title="workspace.users.title"
|
|
19
|
+
description="workspace.users.description"
|
|
20
|
+
far={(
|
|
20
21
|
<Button variant="cta" href={router.getHref('@workspace/invitations/add')} prefix="user-add">
|
|
21
22
|
{t('workspace.users.invite')}
|
|
22
23
|
</Button>
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
</View>
|
|
24
|
+
)}
|
|
25
|
+
/>
|
|
26
26
|
<View gap="xs">
|
|
27
27
|
{users.map(user => (
|
|
28
28
|
<View layout="row" roundness="s" alignItems="center" inset="m" gap="m" selectable key={user.id}>
|
package/src/Users.page.jsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import { Page } from '@ossy/design-system'
|
|
3
3
|
import { Users } from './Users.jsx'
|
|
4
4
|
|
|
5
5
|
export const metadata = {
|
|
@@ -12,9 +12,9 @@ export const metadata = {
|
|
|
12
12
|
|
|
13
13
|
export const UsersPage = () => {
|
|
14
14
|
return (
|
|
15
|
-
<
|
|
15
|
+
<Page>
|
|
16
16
|
<Users />
|
|
17
|
-
</
|
|
17
|
+
</Page>
|
|
18
18
|
)
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const metadata = { id: 'workspaces/accept-invitation', access: 'public' }
|
|
1
|
+
export const metadata = { id: '@ossy/workspaces/actions/accept-invitation', access: 'public' }
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ActionService,
|
|
3
|
+
mergeUserAppSettingsCookie,
|
|
4
|
+
setAuthCookie,
|
|
5
|
+
} from '@ossy/platform'
|
|
6
|
+
|
|
7
|
+
export const metadata = {
|
|
8
|
+
id: 'users.accept-invitation',
|
|
9
|
+
path: '/api/v0/users/accept-invitation',
|
|
10
|
+
action: '@ossy/workspaces/actions/accept-invitation',
|
|
11
|
+
method: 'GET',
|
|
12
|
+
query: ['token', 'workspaceId', 'redirect'],
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default async function handle (req, res) {
|
|
16
|
+
if (req.method !== 'GET') {
|
|
17
|
+
res.setHeader('Allow', 'GET')
|
|
18
|
+
res.status(405).json({ error: 'Method Not Allowed' })
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const { authToken, workspaceId } = await ActionService.invoke('@ossy/workspaces/actions/accept-invitation', {
|
|
24
|
+
payload: {
|
|
25
|
+
token: req.query.token,
|
|
26
|
+
workspaceId: req.query.workspaceId,
|
|
27
|
+
},
|
|
28
|
+
req,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
setAuthCookie(res, authToken)
|
|
32
|
+
if (workspaceId) {
|
|
33
|
+
mergeUserAppSettingsCookie(req, res, { workspaceId })
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const redirect = req.query.redirect
|
|
37
|
+
if (redirect && typeof redirect === 'string') {
|
|
38
|
+
res.redirect(302, redirect)
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
res.status(200).json('')
|
|
43
|
+
} catch (err) {
|
|
44
|
+
res.status(err.status || 401).json('')
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import jwt from 'jsonwebtoken'
|
|
2
2
|
import { Aggregate } from '@ossy/event-store'
|
|
3
|
+
import { UserSchema } from '@ossy/users/server'
|
|
3
4
|
import { Workspace, WorkspacesEvents } from '@ossy/workspaces/server'
|
|
4
5
|
import { User, UsersEvents } from '@ossy/users/server'
|
|
5
6
|
import { Token, TokenEvents } from '@ossy/tokens/server'
|
|
6
7
|
import { ConfigService } from '@ossy/platform'
|
|
7
8
|
import { createLogger } from '@ossy/observability'
|
|
8
9
|
|
|
9
|
-
export const metadata = { id: 'workspaces/accept-invitation' }
|
|
10
|
+
export const metadata = { id: '@ossy/workspaces/tasks/accept-invitation' }
|
|
10
11
|
|
|
11
|
-
const log = createLogger('workspaces/accept-invitation')
|
|
12
|
+
const log = createLogger('@ossy/workspaces/tasks/accept-invitation')
|
|
12
13
|
|
|
13
14
|
function verifyToken(token) {
|
|
14
15
|
return new Promise((resolve, reject) => {
|
|
@@ -32,7 +33,7 @@ export async function run({ payload, req }) {
|
|
|
32
33
|
|
|
33
34
|
if (!email) throw Object.assign(new Error('No email found in token payload'), { status: 401 })
|
|
34
35
|
|
|
35
|
-
const existingUser = await Aggregate.Collection.findOne({ type:
|
|
36
|
+
const existingUser = await Aggregate.Collection.findOne({ type: UserSchema.user, 'state.email': email }).then(agg => agg?.state)
|
|
36
37
|
|
|
37
38
|
if (existingUser && workspace.users.includes(existingUser.id)) {
|
|
38
39
|
throw Object.assign(new Error('User already in workspace'), { status: 400 })
|
|
@@ -60,7 +61,7 @@ export async function run({ payload, req }) {
|
|
|
60
61
|
Aggregate.Of(Workspace, resolvedWorkspaceId).then(Aggregate.Add(userInvitationAccepted)),
|
|
61
62
|
])
|
|
62
63
|
} else {
|
|
63
|
-
const signUpEvent = UsersEvents.
|
|
64
|
+
const signUpEvent = UsersEvents.Created({ email })
|
|
64
65
|
userId = signUpEvent.createdBy
|
|
65
66
|
const signInExpiresIn = ConfigService.TokenValidity
|
|
66
67
|
signInExpiresAt = Date.now() + signInExpiresIn * 1000
|
|
@@ -80,5 +81,5 @@ export async function run({ payload, req }) {
|
|
|
80
81
|
])
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
return { authToken: signInToken, authExpiresAt: signInExpiresAt }
|
|
84
|
+
return { authToken: signInToken, authExpiresAt: signInExpiresAt, workspaceId: resolvedWorkspaceId }
|
|
84
85
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const metadata = { id: 'workspaces/create-api-token', access: 'workspace' }
|
|
1
|
+
export const metadata = { id: '@ossy/workspaces/actions/create-api-token', access: 'workspace' }
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Aggregate } from '@ossy/event-store'
|
|
2
2
|
import { Workspace, WorkspacesEvents } from '@ossy/workspaces/server'
|
|
3
3
|
|
|
4
|
-
export const metadata = { id: 'workspaces/create-api-token' }
|
|
4
|
+
export const metadata = { id: '@ossy/workspaces/tasks/create-api-token' }
|
|
5
5
|
|
|
6
6
|
export async function run({ payload, req }) {
|
|
7
7
|
const createdBy = payload?.userId ?? req?.userId
|
package/src/create.action.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const metadata = { id: 'workspaces/create', access: 'authenticated' }
|
|
1
|
+
export const metadata = { id: '@ossy/workspaces/actions/create', access: 'authenticated' }
|
package/src/create.task.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Aggregate } from '@ossy/event-store'
|
|
|
2
2
|
import { Workspace, WorkspacesEvents } from '@ossy/workspaces/server'
|
|
3
3
|
import { ConfigService } from '@ossy/platform'
|
|
4
4
|
|
|
5
|
-
export const metadata = { id: 'workspaces/create' }
|
|
5
|
+
export const metadata = { id: '@ossy/workspaces/tasks/create' }
|
|
6
6
|
|
|
7
7
|
export async function run({ payload, req }) {
|
|
8
8
|
const createdBy = payload?.userId ?? req?.userId
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const metadata = { id: 'workspaces/disable-service', access: 'workspace' }
|
|
1
|
+
export const metadata = { id: '@ossy/workspaces/actions/disable-service', access: 'workspace' }
|
|
@@ -1,25 +1,13 @@
|
|
|
1
1
|
import { Aggregate } from '@ossy/event-store'
|
|
2
2
|
import { Workspace, WorkspacesEvents } from '@ossy/workspaces/server'
|
|
3
|
+
import { assertEnableableService } from './service-keys.js'
|
|
3
4
|
|
|
4
|
-
export const metadata = { id: 'workspaces/disable-service' }
|
|
5
|
-
|
|
6
|
-
const AVAILABLE_SERVICES = [
|
|
7
|
-
'@ossy/tasks/visual-content-descriptors',
|
|
8
|
-
'@ossy/tasks/resize-common-web',
|
|
9
|
-
'@ossy/resumes',
|
|
10
|
-
'@ossy/consultancy',
|
|
11
|
-
'@ossy/apps',
|
|
12
|
-
'@ossy/domains',
|
|
13
|
-
]
|
|
5
|
+
export const metadata = { id: '@ossy/workspaces/tasks/disable-service' }
|
|
14
6
|
|
|
15
7
|
export async function run({ payload, req }) {
|
|
16
8
|
const createdBy = payload?.userId ?? req?.userId
|
|
17
9
|
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
18
|
-
const service = payload?.service
|
|
19
|
-
|
|
20
|
-
if (!AVAILABLE_SERVICES.includes(service)) {
|
|
21
|
-
throw Object.assign(new Error(`Service ${service} is not available`), { status: 400 })
|
|
22
|
-
}
|
|
10
|
+
const service = assertEnableableService(payload?.service)
|
|
23
11
|
|
|
24
12
|
const event = WorkspacesEvents.ServiceDisabled({ createdBy, service })
|
|
25
13
|
await Aggregate.Of(Workspace, workspaceId).then(Aggregate.Add(event))
|