@ossy/workspaces 1.17.0 → 1.17.2
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/README.md +1 -0
- package/package.json +5 -4
- package/src/AuthenticationGuard.jsx +17 -5
- package/src/CreateWorkspacePage.jsx +11 -7
- package/src/GeneralSettings.jsx +6 -3
- package/src/Invitations.jsx +4 -2
- package/src/Invite.jsx +6 -4
- package/src/SelectWorkspacePage.jsx +5 -3
- package/src/Users.jsx +4 -2
- package/src/accept-invitation.action.js +1 -85
- package/src/accept-invitation.task.js +84 -0
- package/src/create-api-token.action.js +1 -17
- package/src/create-api-token.task.js +16 -0
- package/src/create.action.js +1 -30
- package/src/create.task.js +29 -0
- package/src/disable-service.action.js +1 -28
- package/src/disable-service.task.js +27 -0
- package/src/en.translations.json +6 -6
- package/src/enable-service.action.js +1 -28
- package/src/enable-service.task.js +27 -0
- package/src/get-api-tokens.action.js +1 -11
- package/src/get-api-tokens.task.js +10 -0
- package/src/get-invitations.action.js +1 -11
- package/src/get-invitations.task.js +10 -0
- package/src/get-resource-templates.action.js +1 -19
- package/src/get-resource-templates.task.js +18 -0
- package/src/get-users.action.js +1 -14
- package/src/get-users.task.js +13 -0
- package/src/get.action.js +1 -14
- package/src/get.task.js +13 -0
- package/src/import-resource-templates.action.js +1 -27
- package/src/import-resource-templates.task.js +26 -0
- package/src/index.js +14 -2
- package/src/invite-user.action.js +1 -85
- package/src/invite-user.task.js +84 -0
- package/src/list.action.js +1 -9
- package/src/list.task.js +8 -0
- package/src/remove-member.action.js +1 -17
- package/src/remove-member.task.js +16 -0
- package/src/server.js +3 -0
- package/src/sv.translations.json +6 -6
- package/src/sync-workspace-membership.task.js +4 -3
- package/src/workspaces.spec.js +71 -80
- package/src/current.api.js +0 -30
- package/src/invitations.api.js +0 -46
- package/src/item.api.js +0 -32
- package/src/list.api.js +0 -35
- package/src/members.api.js +0 -42
- package/src/resource-templates.api.js +0 -35
- package/src/services-disable.api.js +0 -23
- package/src/services-enable.api.js +0 -23
- package/src/tokens.api.js +0 -35
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Aggregate } from '@ossy/event-store'
|
|
2
|
+
import { Workspace, WorkspacesEvents } from '@ossy/workspaces/server'
|
|
3
|
+
|
|
4
|
+
export const metadata = { id: 'workspaces/enable-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
|
+
]
|
|
14
|
+
|
|
15
|
+
export async function run({ payload, req }) {
|
|
16
|
+
const createdBy = payload?.userId ?? req?.userId
|
|
17
|
+
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
18
|
+
const service = payload?.service ?? payload
|
|
19
|
+
|
|
20
|
+
if (!AVAILABLE_SERVICES.includes(service)) {
|
|
21
|
+
throw Object.assign(new Error(`Service ${service} is not available`), { status: 400 })
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const event = WorkspacesEvents.ServiceEnabled({ createdBy, service })
|
|
25
|
+
await Aggregate.Of(Workspace, workspaceId).then(Aggregate.Add(event))
|
|
26
|
+
return null
|
|
27
|
+
}
|
|
@@ -1,11 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { Workspace } from '@ossy/workspaces'
|
|
3
|
-
|
|
4
|
-
export const id = 'workspaces/get-api-tokens'
|
|
5
|
-
export const access = 'workspace'
|
|
6
|
-
|
|
7
|
-
export async function run({ payload, req }) {
|
|
8
|
-
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
9
|
-
const workspace = await Aggregate.Of(Workspace, workspaceId).then(Aggregate.View())
|
|
10
|
-
return workspace.apiTokens
|
|
11
|
-
}
|
|
1
|
+
export const metadata = { id: 'workspaces/get-api-tokens', access: 'workspace' }
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Aggregate } from '@ossy/event-store'
|
|
2
|
+
import { Workspace } from '@ossy/workspaces/server'
|
|
3
|
+
|
|
4
|
+
export const metadata = { id: 'workspaces/get-api-tokens' }
|
|
5
|
+
|
|
6
|
+
export async function run({ payload, req }) {
|
|
7
|
+
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
8
|
+
const workspace = await Aggregate.Of(Workspace, workspaceId).then(Aggregate.View())
|
|
9
|
+
return workspace.apiTokens
|
|
10
|
+
}
|
|
@@ -1,11 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { Workspace } from '@ossy/workspaces'
|
|
3
|
-
|
|
4
|
-
export const id = 'workspaces/get-invitations'
|
|
5
|
-
export const access = 'workspace'
|
|
6
|
-
|
|
7
|
-
export async function run({ payload, req }) {
|
|
8
|
-
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
9
|
-
const workspace = await Aggregate.Of(Workspace, workspaceId).then(Aggregate.View())
|
|
10
|
-
return workspace.invitations
|
|
11
|
-
}
|
|
1
|
+
export const metadata = { id: 'workspaces/get-invitations', access: 'workspace' }
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Aggregate } from '@ossy/event-store'
|
|
2
|
+
import { Workspace } from '@ossy/workspaces/server'
|
|
3
|
+
|
|
4
|
+
export const metadata = { id: 'workspaces/get-invitations' }
|
|
5
|
+
|
|
6
|
+
export async function run({ payload, req }) {
|
|
7
|
+
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
8
|
+
const workspace = await Aggregate.Of(Workspace, workspaceId).then(Aggregate.View())
|
|
9
|
+
return workspace.invitations
|
|
10
|
+
}
|
|
@@ -1,19 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { Workspace } from '@ossy/workspaces'
|
|
3
|
-
import { getSystemResourceTemplates } from '@ossy/platform'
|
|
4
|
-
|
|
5
|
-
export const id = 'workspaces/get-resource-templates'
|
|
6
|
-
export const access = 'workspace'
|
|
7
|
-
|
|
8
|
-
export async function run({ payload, req }) {
|
|
9
|
-
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
10
|
-
|
|
11
|
-
const workspace = await Aggregate.Of(Workspace, workspaceId).then(Aggregate.View())
|
|
12
|
-
const systemTemplates = getSystemResourceTemplates()
|
|
13
|
-
const workspaceTemplates = workspace.resourceTemplates || []
|
|
14
|
-
const workspaceIds = new Set(workspaceTemplates.map(t => t.id))
|
|
15
|
-
return [
|
|
16
|
-
...systemTemplates.filter(t => !workspaceIds.has(t.id)),
|
|
17
|
-
...workspaceTemplates,
|
|
18
|
-
]
|
|
19
|
-
}
|
|
1
|
+
export const metadata = { id: 'workspaces/get-resource-templates', access: 'workspace' }
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Aggregate } from '@ossy/event-store'
|
|
2
|
+
import { Workspace } from '@ossy/workspaces/server'
|
|
3
|
+
import { getSystemResourceTemplates } from '@ossy/platform'
|
|
4
|
+
|
|
5
|
+
export const metadata = { id: 'workspaces/get-resource-templates' }
|
|
6
|
+
|
|
7
|
+
export async function run({ payload, req }) {
|
|
8
|
+
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
9
|
+
|
|
10
|
+
const workspace = await Aggregate.Of(Workspace, workspaceId).then(Aggregate.View())
|
|
11
|
+
const systemTemplates = getSystemResourceTemplates()
|
|
12
|
+
const workspaceTemplates = workspace.resourceTemplates || []
|
|
13
|
+
const workspaceIds = new Set(workspaceTemplates.map(t => t.id))
|
|
14
|
+
return [
|
|
15
|
+
...systemTemplates.filter(t => !workspaceIds.has(t.id)),
|
|
16
|
+
...workspaceTemplates,
|
|
17
|
+
]
|
|
18
|
+
}
|
package/src/get-users.action.js
CHANGED
|
@@ -1,14 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { Workspace } from '@ossy/workspaces'
|
|
3
|
-
import { User } from '@ossy/users'
|
|
4
|
-
|
|
5
|
-
export const id = 'workspaces/get-users'
|
|
6
|
-
export const access = 'workspace'
|
|
7
|
-
|
|
8
|
-
export async function run({ payload, req }) {
|
|
9
|
-
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
10
|
-
const workspace = await Aggregate.Of(Workspace, workspaceId).then(Aggregate.View())
|
|
11
|
-
return Promise.all(
|
|
12
|
-
workspace.users.map(userId => Aggregate.Of(User, userId).then(Aggregate.View(User.UserForWorkspace)))
|
|
13
|
-
)
|
|
14
|
-
}
|
|
1
|
+
export const metadata = { id: 'workspaces/get-users', access: 'workspace' }
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Aggregate } from '@ossy/event-store'
|
|
2
|
+
import { Workspace } from '@ossy/workspaces/server'
|
|
3
|
+
import { User } from '@ossy/users/server'
|
|
4
|
+
|
|
5
|
+
export const metadata = { id: 'workspaces/get-users' }
|
|
6
|
+
|
|
7
|
+
export async function run({ payload, req }) {
|
|
8
|
+
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
9
|
+
const workspace = await Aggregate.Of(Workspace, workspaceId).then(Aggregate.View())
|
|
10
|
+
return Promise.all(
|
|
11
|
+
workspace.users.map(userId => Aggregate.Of(User, userId).then(Aggregate.View(User.UserForWorkspace)))
|
|
12
|
+
)
|
|
13
|
+
}
|
package/src/get.action.js
CHANGED
|
@@ -1,14 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { Workspace } from '@ossy/workspaces'
|
|
3
|
-
|
|
4
|
-
export const id = 'workspaces/get'
|
|
5
|
-
export const access = 'workspace'
|
|
6
|
-
|
|
7
|
-
export async function run({ payload, req }) {
|
|
8
|
-
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
9
|
-
if (!workspaceId) throw Object.assign(new Error('workspaceId is required'), { status: 404 })
|
|
10
|
-
|
|
11
|
-
const workspace = await Aggregate.Of(Workspace, workspaceId).then(Aggregate.View())
|
|
12
|
-
if (!workspace?.id) throw Object.assign(new Error('Workspace not found'), { status: 404 })
|
|
13
|
-
return workspace
|
|
14
|
-
}
|
|
1
|
+
export const metadata = { id: 'workspaces/get', access: 'workspace' }
|
package/src/get.task.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Aggregate } from '@ossy/event-store'
|
|
2
|
+
import { Workspace } from '@ossy/workspaces/server'
|
|
3
|
+
|
|
4
|
+
export const metadata = { id: 'workspaces/get' }
|
|
5
|
+
|
|
6
|
+
export async function run({ payload, req }) {
|
|
7
|
+
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
8
|
+
if (!workspaceId) throw Object.assign(new Error('workspaceId is required'), { status: 404 })
|
|
9
|
+
|
|
10
|
+
const workspace = await Aggregate.Of(Workspace, workspaceId).then(Aggregate.View())
|
|
11
|
+
if (!workspace?.id) throw Object.assign(new Error('Workspace not found'), { status: 404 })
|
|
12
|
+
return workspace
|
|
13
|
+
}
|
|
@@ -1,27 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { Workspace, WorkspacesEvents } from '@ossy/workspaces'
|
|
3
|
-
import { validateResourceTemplatesForImport } from '@ossy/platform'
|
|
4
|
-
|
|
5
|
-
export const id = 'workspaces/import-resource-templates'
|
|
6
|
-
export const access = 'workspace'
|
|
7
|
-
|
|
8
|
-
export async function run({ payload, req }) {
|
|
9
|
-
const userId = payload?.userId ?? req?.userId
|
|
10
|
-
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
11
|
-
const templates = payload?.templates ?? payload
|
|
12
|
-
|
|
13
|
-
if (!Array.isArray(templates)) {
|
|
14
|
-
throw Object.assign(new Error('templates must be an array'), { status: 400 })
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const reservedIds = new Set([])
|
|
18
|
-
const validation = validateResourceTemplatesForImport(templates, reservedIds)
|
|
19
|
-
if (!validation.ok) {
|
|
20
|
-
throw Object.assign(new Error(validation.message), { status: 400, type: validation.code })
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const event = WorkspacesEvents.ResourceTemplatesImported({ createdBy: userId, templates })
|
|
24
|
-
|
|
25
|
-
await Aggregate.Of(Workspace, workspaceId).then(Aggregate.Add(event))
|
|
26
|
-
return event.payload.templates
|
|
27
|
-
}
|
|
1
|
+
export const metadata = { id: 'workspaces/import-resource-templates', access: 'workspace' }
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Aggregate } from '@ossy/event-store'
|
|
2
|
+
import { Workspace, WorkspacesEvents } from '@ossy/workspaces/server'
|
|
3
|
+
import { validateResourceTemplatesForImport } from '@ossy/platform'
|
|
4
|
+
|
|
5
|
+
export const metadata = { id: 'workspaces/import-resource-templates' }
|
|
6
|
+
|
|
7
|
+
export async function run({ payload, req }) {
|
|
8
|
+
const userId = payload?.userId ?? req?.userId
|
|
9
|
+
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
10
|
+
const templates = payload?.templates ?? payload
|
|
11
|
+
|
|
12
|
+
if (!Array.isArray(templates)) {
|
|
13
|
+
throw Object.assign(new Error('templates must be an array'), { status: 400 })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const reservedIds = new Set([])
|
|
17
|
+
const validation = validateResourceTemplatesForImport(templates, reservedIds)
|
|
18
|
+
if (!validation.ok) {
|
|
19
|
+
throw Object.assign(new Error(validation.message), { status: 400, type: validation.code })
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const event = WorkspacesEvents.ResourceTemplatesImported({ createdBy: userId, templates })
|
|
23
|
+
|
|
24
|
+
await Aggregate.Of(Workspace, workspaceId).then(Aggregate.Add(event))
|
|
25
|
+
return event.payload.templates
|
|
26
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
export { metadata as ListWorkspaces } from './list.action.js'
|
|
2
|
+
export { metadata as GetWorkspace } from './get.action.js'
|
|
3
|
+
export { metadata as CreateWorkspace } from './create.action.js'
|
|
4
|
+
export { metadata as InviteUser } from './invite-user.action.js'
|
|
5
|
+
export { metadata as AcceptInvitation } from './accept-invitation.action.js'
|
|
6
|
+
export { metadata as GetInvitations } from './get-invitations.action.js'
|
|
7
|
+
export { metadata as GetUsers } from './get-users.action.js'
|
|
8
|
+
export { metadata as RemoveMember } from './remove-member.action.js'
|
|
9
|
+
export { metadata as CreateWorkspaceApiToken } from './create-api-token.action.js'
|
|
10
|
+
export { metadata as GetWorkspaceApiTokens } from './get-api-tokens.action.js'
|
|
11
|
+
export { metadata as GetResourceTemplates } from './get-resource-templates.action.js'
|
|
12
|
+
export { metadata as ImportResourceTemplates } from './import-resource-templates.action.js'
|
|
13
|
+
export { metadata as EnableService } from './enable-service.action.js'
|
|
14
|
+
export { metadata as DisableService } from './disable-service.action.js'
|
|
1
15
|
export { CreateWorkspacePage } from './CreateWorkspacePage.jsx'
|
|
2
16
|
export { SelectWorkspacePage } from './SelectWorkspacePage.jsx'
|
|
3
17
|
export { GeneralSettings } from './GeneralSettings.jsx'
|
|
@@ -7,5 +21,3 @@ export { Users } from './Users.jsx'
|
|
|
7
21
|
export { AuthenticationGuard } from './AuthenticationGuard.jsx'
|
|
8
22
|
export { patchUserWorkspaceId } from './patchUserWorkspaceId.js'
|
|
9
23
|
export { Definition } from './Definition.js'
|
|
10
|
-
export { Workspace } from './workspace.aggregate.js'
|
|
11
|
-
export { WorkspacesEvents } from './workspaces.events.js'
|
|
@@ -1,85 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { Aggregate } from '@ossy/event-store'
|
|
3
|
-
import { Workspace, WorkspacesEvents } from '@ossy/workspaces'
|
|
4
|
-
import { Token, TokenEvents } from '@ossy/tokens'
|
|
5
|
-
import { EmailRenderer } from '@ossy/email'
|
|
6
|
-
import { ConfigService } from '@ossy/platform'
|
|
7
|
-
import { createLogger } from '@ossy/observability'
|
|
8
|
-
import WorkspaceInvitationEmail, { subject as workspaceInvitationSubject } from './workspace-invitation.email.jsx'
|
|
9
|
-
|
|
10
|
-
const log = createLogger('workspaces/invite-user')
|
|
11
|
-
|
|
12
|
-
const isEmailLike = maybeEmail => /.@.+\.../.test(maybeEmail)
|
|
13
|
-
|
|
14
|
-
function createVerificationToken(workspaceId) {
|
|
15
|
-
const expiresIn = ConfigService.TokenValidity
|
|
16
|
-
return jwt.sign({ aud: workspaceId }, ConfigService.TokenSecret, { expiresIn })
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export const id = 'workspaces/invite-user'
|
|
20
|
-
export const access = 'workspace'
|
|
21
|
-
|
|
22
|
-
export async function run({ payload, integrations, req }) {
|
|
23
|
-
const createdBy = payload?.userId ?? req?.userId
|
|
24
|
-
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
25
|
-
const email = payload?.email ?? req?.body
|
|
26
|
-
const inviterEmail = payload?.inviterEmail ?? req?.user?.email
|
|
27
|
-
|
|
28
|
-
if (!isEmailLike(email)) {
|
|
29
|
-
throw Object.assign(new Error('Invalid email'), { status: 400 })
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const workspace = await Aggregate.Of(Workspace, workspaceId).then(Aggregate.View())
|
|
33
|
-
|
|
34
|
-
const userInvitation = workspace.invitations.find(inv => inv.email === email)
|
|
35
|
-
if (userInvitation && userInvitation.expiresAt > Date.now()) {
|
|
36
|
-
throw Object.assign(new Error('User already invited'), { status: 400 })
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const expiresIn = ConfigService.TokenValidity
|
|
40
|
-
const expiresAt = Date.now() + expiresIn * 1000
|
|
41
|
-
const inviteEvent = WorkspacesEvents.UserInvited({ createdBy, email, expiresAt })
|
|
42
|
-
|
|
43
|
-
const rawToken = createVerificationToken(workspaceId)
|
|
44
|
-
const tokenEvent = TokenEvents.Created({
|
|
45
|
-
type: 'Verification',
|
|
46
|
-
createdBy,
|
|
47
|
-
name: 'Workspace Invitation',
|
|
48
|
-
subject: email,
|
|
49
|
-
audience: workspace.id,
|
|
50
|
-
token: rawToken,
|
|
51
|
-
expiresAt,
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
const existingUser = await Aggregate.Collection.findOne({ type: 'User', 'state.email': email }).then(agg => agg?.state)
|
|
55
|
-
if (existingUser && workspace.users.includes(existingUser.id)) {
|
|
56
|
-
throw Object.assign(new Error('User already in workspace'), { status: 400 })
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
await Promise.all([
|
|
60
|
-
Aggregate.Of(Workspace, workspace.id).then(Aggregate.Add(inviteEvent)),
|
|
61
|
-
Aggregate.Of(Token, tokenEvent),
|
|
62
|
-
])
|
|
63
|
-
|
|
64
|
-
const baseUrl = ConfigService.getWebClientBaseUrl(req)
|
|
65
|
-
const { html, text } = EmailRenderer.render(WorkspaceInvitationEmail, {
|
|
66
|
-
workspace,
|
|
67
|
-
verificationToken: rawToken,
|
|
68
|
-
invitedBy: inviterEmail,
|
|
69
|
-
baseUrl,
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
const emailClient = integrations?.get?.('email')
|
|
73
|
-
if (emailClient) {
|
|
74
|
-
await emailClient.send({ to: email, from: 'noreply@ossy.se', subject: workspaceInvitationSubject, html, text })
|
|
75
|
-
} else if (ConfigService.BuildEnvironment === 'local') {
|
|
76
|
-
log.info('-----------YOU GOT MAIL-----------')
|
|
77
|
-
log.info(`To: ${email}`)
|
|
78
|
-
log.info(`From: noreply@ossy.se`)
|
|
79
|
-
log.info(`Subject: ${workspaceInvitationSubject}`)
|
|
80
|
-
log.info(text || html)
|
|
81
|
-
log.info('----------------------------------')
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return ''
|
|
85
|
-
}
|
|
1
|
+
export const metadata = { id: 'workspaces/invite-user', access: 'workspace' }
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import jwt from 'jsonwebtoken'
|
|
2
|
+
import { Aggregate } from '@ossy/event-store'
|
|
3
|
+
import { Workspace, WorkspacesEvents } from '@ossy/workspaces/server'
|
|
4
|
+
import { Token, TokenEvents } from '@ossy/tokens/server'
|
|
5
|
+
import { EmailRenderer } from '@ossy/email'
|
|
6
|
+
import { ConfigService } from '@ossy/platform'
|
|
7
|
+
import { createLogger } from '@ossy/observability'
|
|
8
|
+
import WorkspaceInvitationEmail, { subject as workspaceInvitationSubject } from './workspace-invitation.email.jsx'
|
|
9
|
+
|
|
10
|
+
export const metadata = { id: 'workspaces/invite-user' }
|
|
11
|
+
|
|
12
|
+
const log = createLogger('workspaces/invite-user')
|
|
13
|
+
|
|
14
|
+
const isEmailLike = maybeEmail => /.@.+\.../.test(maybeEmail)
|
|
15
|
+
|
|
16
|
+
function createVerificationToken(workspaceId) {
|
|
17
|
+
const expiresIn = ConfigService.TokenValidity
|
|
18
|
+
return jwt.sign({ aud: workspaceId }, ConfigService.TokenSecret, { expiresIn })
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function run({ payload, integrations, req }) {
|
|
22
|
+
const createdBy = payload?.userId ?? req?.userId
|
|
23
|
+
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
24
|
+
const email = payload?.email ?? req?.body
|
|
25
|
+
const inviterEmail = payload?.inviterEmail ?? req?.user?.email
|
|
26
|
+
|
|
27
|
+
if (!isEmailLike(email)) {
|
|
28
|
+
throw Object.assign(new Error('Invalid email'), { status: 400 })
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const workspace = await Aggregate.Of(Workspace, workspaceId).then(Aggregate.View())
|
|
32
|
+
|
|
33
|
+
const userInvitation = workspace.invitations.find(inv => inv.email === email)
|
|
34
|
+
if (userInvitation && userInvitation.expiresAt > Date.now()) {
|
|
35
|
+
throw Object.assign(new Error('User already invited'), { status: 400 })
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const expiresIn = ConfigService.TokenValidity
|
|
39
|
+
const expiresAt = Date.now() + expiresIn * 1000
|
|
40
|
+
const inviteEvent = WorkspacesEvents.UserInvited({ createdBy, email, expiresAt })
|
|
41
|
+
|
|
42
|
+
const rawToken = createVerificationToken(workspaceId)
|
|
43
|
+
const tokenEvent = TokenEvents.Created({
|
|
44
|
+
type: 'Verification',
|
|
45
|
+
createdBy,
|
|
46
|
+
name: 'Workspace Invitation',
|
|
47
|
+
subject: email,
|
|
48
|
+
audience: workspace.id,
|
|
49
|
+
token: rawToken,
|
|
50
|
+
expiresAt,
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const existingUser = await Aggregate.Collection.findOne({ type: 'User', 'state.email': email }).then(agg => agg?.state)
|
|
54
|
+
if (existingUser && workspace.users.includes(existingUser.id)) {
|
|
55
|
+
throw Object.assign(new Error('User already in workspace'), { status: 400 })
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
await Promise.all([
|
|
59
|
+
Aggregate.Of(Workspace, workspace.id).then(Aggregate.Add(inviteEvent)),
|
|
60
|
+
Aggregate.Of(Token, tokenEvent),
|
|
61
|
+
])
|
|
62
|
+
|
|
63
|
+
const baseUrl = ConfigService.getWebClientBaseUrl(req)
|
|
64
|
+
const { html, text } = EmailRenderer.render(WorkspaceInvitationEmail, {
|
|
65
|
+
workspace,
|
|
66
|
+
verificationToken: rawToken,
|
|
67
|
+
invitedBy: inviterEmail,
|
|
68
|
+
baseUrl,
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const emailClient = integrations?.get?.('email')
|
|
72
|
+
if (emailClient) {
|
|
73
|
+
await emailClient.send({ to: email, from: 'noreply@ossy.se', subject: workspaceInvitationSubject, html, text })
|
|
74
|
+
} else if (ConfigService.BuildEnvironment === 'local') {
|
|
75
|
+
log.info('-----------YOU GOT MAIL-----------')
|
|
76
|
+
log.info(`To: ${email}`)
|
|
77
|
+
log.info(`From: noreply@ossy.se`)
|
|
78
|
+
log.info(`Subject: ${workspaceInvitationSubject}`)
|
|
79
|
+
log.info(text || html)
|
|
80
|
+
log.info('----------------------------------')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return ''
|
|
84
|
+
}
|
package/src/list.action.js
CHANGED
|
@@ -1,9 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export const id = 'workspaces/list'
|
|
4
|
-
export const access = 'authenticated'
|
|
5
|
-
|
|
6
|
-
export async function run({ payload, req }) {
|
|
7
|
-
const userId = payload?.userId ?? req?.userId
|
|
8
|
-
return WorkspacesQueries.getAllByUserIncluded(userId)
|
|
9
|
-
}
|
|
1
|
+
export const metadata = { id: 'workspaces/list', access: 'authenticated' }
|
package/src/list.task.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { WorkspacesQueries } from './workspaces.queries.js'
|
|
2
|
+
|
|
3
|
+
export const metadata = { id: 'workspaces/list' }
|
|
4
|
+
|
|
5
|
+
export async function run({ payload, req }) {
|
|
6
|
+
const userId = payload?.userId ?? req?.userId
|
|
7
|
+
return WorkspacesQueries.getAllByUserIncluded(userId)
|
|
8
|
+
}
|
|
@@ -1,17 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { Workspace, WorkspacesEvents } from '@ossy/workspaces'
|
|
3
|
-
|
|
4
|
-
export const id = 'workspaces/remove-member'
|
|
5
|
-
export const access = 'workspace'
|
|
6
|
-
|
|
7
|
-
export async function run({ payload, req }) {
|
|
8
|
-
const createdBy = payload?.userId ?? req?.userId
|
|
9
|
-
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
10
|
-
const memberId = payload?.memberId ?? req?.memberId
|
|
11
|
-
|
|
12
|
-
if (!memberId) throw Object.assign(new Error('memberId is required'), { status: 400 })
|
|
13
|
-
|
|
14
|
-
const event = WorkspacesEvents.UserRemoved({ createdBy, userId: memberId })
|
|
15
|
-
await Aggregate.Of(Workspace, workspaceId).then(Aggregate.Add(event))
|
|
16
|
-
return null
|
|
17
|
-
}
|
|
1
|
+
export const metadata = { id: 'workspaces/remove-member', access: 'workspace' }
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Aggregate } from '@ossy/event-store'
|
|
2
|
+
import { Workspace, WorkspacesEvents } from '@ossy/workspaces/server'
|
|
3
|
+
|
|
4
|
+
export const metadata = { id: 'workspaces/remove-member' }
|
|
5
|
+
|
|
6
|
+
export async function run({ payload, req }) {
|
|
7
|
+
const createdBy = payload?.userId ?? req?.userId
|
|
8
|
+
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
9
|
+
const memberId = payload?.memberId ?? req?.memberId
|
|
10
|
+
|
|
11
|
+
if (!memberId) throw Object.assign(new Error('memberId is required'), { status: 400 })
|
|
12
|
+
|
|
13
|
+
const event = WorkspacesEvents.UserRemoved({ createdBy, userId: memberId })
|
|
14
|
+
await Aggregate.Of(Workspace, workspaceId).then(Aggregate.Add(event))
|
|
15
|
+
return null
|
|
16
|
+
}
|
package/src/server.js
ADDED
package/src/sv.translations.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
"workspaces
|
|
2
|
+
"profile/workspaces/create.documentTitle": "Skapa workspace",
|
|
3
3
|
"workspaces.create.title": "Skapa ett nytt workspace",
|
|
4
4
|
"workspaces.create.namePlaceholder": "Workspace-namn",
|
|
5
5
|
"workspaces.create.cancel": "Avbryt",
|
|
6
6
|
"workspaces.create.submit": "Skapa",
|
|
7
|
-
"workspaces.
|
|
7
|
+
"profile/workspaces.documentTitle": "Välj workspace",
|
|
8
8
|
"workspaces.select.title": "Mina workspaces",
|
|
9
9
|
"workspaces.select.newWorkspace": "Nytt workspace",
|
|
10
10
|
"workspaces.select.loading": "Laddar...",
|
|
@@ -43,15 +43,15 @@
|
|
|
43
43
|
"workspaces/remove-member.description": "Ta bort en användare från workspace",
|
|
44
44
|
"workspaces/list.label": "Lista workspaces",
|
|
45
45
|
"workspaces/list.description": "Lista workspaces för den inloggade användaren",
|
|
46
|
-
"workspace
|
|
46
|
+
"workspace/settings.documentTitle": "Workspace-inställningar",
|
|
47
47
|
"workspace.settings.title": "Workspace-inställningar",
|
|
48
|
-
"workspace
|
|
48
|
+
"workspace/users.documentTitle": "Användare",
|
|
49
49
|
"workspace.users.title": "Användare",
|
|
50
50
|
"workspace.users.description": "Hantera användare i ditt workspace.",
|
|
51
51
|
"workspace.users.invite": "Bjud in användare",
|
|
52
52
|
"workspace.users.loading": "Laddar",
|
|
53
|
-
"workspace
|
|
54
|
-
"workspace
|
|
53
|
+
"workspace/invitations.documentTitle": "Inbjudningar",
|
|
54
|
+
"workspace/invitations/add.documentTitle": "Bjud in användare",
|
|
55
55
|
"workspace.invitations.title": "Inbjudningar",
|
|
56
56
|
"workspace.settings.detailsTitle": "Workspace-detaljer",
|
|
57
57
|
"workspace.settings.edit": "Redigera",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Aggregate } from '@ossy/event-store'
|
|
2
|
-
import { User, UsersEvents } from '@ossy/users'
|
|
2
|
+
import { User, UsersEvents } from '@ossy/users/server'
|
|
3
|
+
import { JoinWorkspace, LeaveWorkspace } from '@ossy/users'
|
|
3
4
|
|
|
4
5
|
export const metadata = {
|
|
5
6
|
id: 'sync-workspace-membership',
|
|
@@ -17,7 +18,7 @@ export async function run ({ event, sdk }) {
|
|
|
17
18
|
const userId = event.payload.userId
|
|
18
19
|
|
|
19
20
|
if (sdk) {
|
|
20
|
-
await sdk.
|
|
21
|
+
await sdk.invoke(LeaveWorkspace, { userId, workspaceId })
|
|
21
22
|
return
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -30,7 +31,7 @@ export async function run ({ event, sdk }) {
|
|
|
30
31
|
const userId = event.createdBy
|
|
31
32
|
|
|
32
33
|
if (sdk) {
|
|
33
|
-
await sdk.
|
|
34
|
+
await sdk.invoke(JoinWorkspace, { userId, workspaceId, createdBy: userId })
|
|
34
35
|
return
|
|
35
36
|
}
|
|
36
37
|
|