@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.
Files changed (64) hide show
  1. package/package.json +8 -4
  2. package/src/CreateWorkspacePage.jsx +7 -16
  3. package/src/Definition.js +1 -0
  4. package/src/GeneralSettings.jsx +77 -19
  5. package/src/Invitations.jsx +7 -7
  6. package/src/Invite.jsx +13 -25
  7. package/src/SchemasReadBootstrap.jsx +20 -0
  8. package/src/SelectWorkspacePage.jsx +5 -8
  9. package/src/Users.jsx +7 -7
  10. package/src/Users.page.jsx +3 -3
  11. package/src/accept-invitation.action.js +1 -1
  12. package/src/accept-invitation.api.js +46 -0
  13. package/src/accept-invitation.task.js +6 -5
  14. package/src/create-api-token.action.js +1 -1
  15. package/src/create-api-token.task.js +1 -1
  16. package/src/create.action.js +1 -1
  17. package/src/create.task.js +1 -1
  18. package/src/disable-service.action.js +1 -1
  19. package/src/disable-service.task.js +3 -15
  20. package/src/en.translations.json +36 -30
  21. package/src/enable-service.action.js +1 -1
  22. package/src/enable-service.task.js +3 -15
  23. package/src/entitlements.js +199 -0
  24. package/src/entitlements.spec.js +88 -0
  25. package/src/get-api-tokens.action.js +1 -1
  26. package/src/get-api-tokens.task.js +1 -1
  27. package/src/get-invitations.action.js +1 -1
  28. package/src/get-invitations.task.js +1 -1
  29. package/src/get-schemas.action.js +1 -0
  30. package/src/get-schemas.task.js +14 -0
  31. package/src/get-users.action.js +1 -1
  32. package/src/get-users.task.js +1 -1
  33. package/src/get.action.js +1 -1
  34. package/src/get.task.js +1 -1
  35. package/src/import-schemas.action.js +1 -0
  36. package/src/import-schemas.task.js +26 -0
  37. package/src/index.js +3 -2
  38. package/src/invite-user.action.js +1 -1
  39. package/src/invite-user.task.js +4 -3
  40. package/src/list.action.js +1 -1
  41. package/src/list.task.js +1 -1
  42. package/src/merge-workspace-schemas.js +13 -0
  43. package/src/merge-workspace-schemas.spec.js +16 -0
  44. package/src/remove-member.action.js +1 -1
  45. package/src/remove-member.task.js +1 -1
  46. package/src/schema-ids.js +3 -0
  47. package/src/select-workspace.api.js +50 -0
  48. package/src/selectWorkspace.js +11 -0
  49. package/src/service-keys.js +1 -0
  50. package/src/stories/Users.stories.jsx +35 -0
  51. package/src/stories/decorators.jsx +59 -0
  52. package/src/sv.translations.json +36 -30
  53. package/src/sync-workspace-membership.task.js +7 -6
  54. package/src/workspace-invitation.email.jsx +1 -1
  55. package/src/workspace-users.component.jsx +3 -3
  56. package/src/workspace.aggregate.js +35 -47
  57. package/src/workspace.schema.js +9 -0
  58. package/src/workspaces.events.js +61 -155
  59. package/src/{workspaces.spec.js → workspaces.integration.spec.js} +40 -38
  60. package/src/workspaces.queries.js +27 -12
  61. package/src/get-resource-templates.action.js +0 -1
  62. package/src/get-resource-templates.task.js +0 -18
  63. package/src/import-resource-templates.action.js +0 -1
  64. 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.2",
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.2",
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": "2b8745d57fee8b6c08787e2755b4df489291a5cd"
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 { patchUserWorkspaceId } from './patchUserWorkspaceId.js'
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
- return patchUserWorkspaceId(workspace.id).then((res) => {
29
- if (!res.ok) throw new Error('Could not select the new workspace')
30
- window.location.assign(
31
- router.getHref({ id: '@storage/home', params: { workspaceId: workspace.id } })
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
- <Title variant="primary" className="stack-m">{t('workspaces.create.title')}</Title>
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
@@ -4,4 +4,5 @@ export const Definition = {
4
4
  description: 'Workspace lifecycle, members, invitations, and services.',
5
5
  icon: 'users',
6
6
  statuses: ['beta'],
7
+ entitlementRequired: false,
7
8
  }
@@ -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, Title, Button, View, Text, useLocale } from '@ossy/design-system'
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
- <View layout="row" gap="s" justifyContent="space-between">
21
- <Title>{t('workspace.settings.detailsTitle')}</Title>
22
- <Switch on={flowStage}>
23
- <Switch.Case match={[FlowStage.View]}>
24
- <Button prefix={{ size: 's', name: 'pen' }} variant="cta" disabled>{t('workspace.settings.edit')}</Button>
25
- </Switch.Case>
26
- </Switch>
27
- </View>
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' }}>{t('workspace.settings.name')}</Text><Text>{workspace?.name}</Text></View>
33
- <View layout="row" gap="s"><Text style={{ fontWeight: 'bold' }}>{t('workspace.settings.created')}</Text><Text>{formatDate(workspace?.created)}</Text></View>
34
- <Title variant="tertiary">{t('workspace.settings.services')}</Title>
35
- <View gap="xs">
36
- {Object.entries(services).map(([key, enabled]) => (
37
- <Text key={key}>{key}: {enabled ? 'on' : 'off'}</Text>
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>
@@ -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 { Title, Text, Button, View, Icon, useLocale } from '@ossy/design-system'
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
- <View inset="s" gap="s">
17
- <View layout="row" justifyContent="space-between" alignItems="center">
18
- <Title>{t('workspace.invitations.title')}</Title>
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
- </View>
23
- <Text style={{ maxWidth: '400px' }}>{t('workspace.invitations.description')}</Text>
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
- <Title variant="primary">{t('workspace.invite.title')}</Title>
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={t('workspace.invite.emailPlaceholder')}
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()}>{t('design-system.cancel')}
72
- </Button>
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
- onClick={sendInvite}>{t('workspace.invite.submit')}
80
- </Button>
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={t('workspace.invite.successTitle')}
79
+ title="workspace.invite.successTitle"
90
80
  titleVariant="primary"
91
81
  >
92
82
  <Text>
93
- {t('workspace.invite.successBody')}
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 { Title, Text, Switch, Button, View, Guide, Icon, useLocale } from '@ossy/design-system'
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 { patchUserWorkspaceId } from './patchUserWorkspaceId.js'
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
- patchUserWorkspaceId(workspaceId).then((res) => {
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
- <Title variant="primary">
43
+ <Text variant="heading-primary" as="h1">
47
44
  {t('workspaces.select.title')}
48
- </Title>
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, Title, Button, useLocale } from '@ossy/design-system'
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
- <View inset="s" gap="s">
18
- <View layout="row" justifyContent="space-between" alignItems="center">
19
- <Title>{t('workspace.users.title')}</Title>
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
- </View>
24
- <Text style={{ maxWidth: '400px' }}>{t('workspace.users.description')}</Text>
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}>
@@ -1,5 +1,5 @@
1
1
  import React from 'react'
2
- import { View } from '@ossy/design-system'
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
- <View gap="m" surface="primary" style={{ padding: 'var(--space-m) var(--space-l)', height: '100%', overflowY: 'auto' }}>
15
+ <Page>
16
16
  <Users />
17
- </View>
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: 'User', 'state.email': email }).then(agg => agg?.state)
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.SignedUp({ email })
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
@@ -1 +1 @@
1
- export const metadata = { id: 'workspaces/create', access: 'authenticated' }
1
+ export const metadata = { id: '@ossy/workspaces/actions/create', access: 'authenticated' }
@@ -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))