@ossy/workspaces 1.16.7 → 1.17.1
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 +23 -10
- package/src/CreateWorkspacePage.jsx +18 -12
- package/src/Definition.js +1 -17
- package/src/GeneralSettings.jsx +27 -70
- package/src/Invitations.jsx +11 -19
- package/src/Invite.jsx +17 -14
- package/src/SelectWorkspacePage.jsx +17 -22
- package/src/Users.jsx +10 -15
- 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 +72 -0
- 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 +72 -0
- 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,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
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"profile/workspaces/create.documentTitle": "Skapa workspace",
|
|
3
|
+
"workspaces.create.title": "Skapa ett nytt workspace",
|
|
4
|
+
"workspaces.create.namePlaceholder": "Workspace-namn",
|
|
5
|
+
"workspaces.create.cancel": "Avbryt",
|
|
6
|
+
"workspaces.create.submit": "Skapa",
|
|
7
|
+
"profile/workspaces.documentTitle": "Välj workspace",
|
|
8
|
+
"workspaces.select.title": "Mina workspaces",
|
|
9
|
+
"workspaces.select.newWorkspace": "Nytt workspace",
|
|
10
|
+
"workspaces.select.loading": "Laddar...",
|
|
11
|
+
"workspaces.select.description": "Workspaces är separata miljöer där du kan hantera resurser, mallar, tjänster och fakturering.",
|
|
12
|
+
"workspaces.select.errorTitle": "Vi kunde inte ladda dina workspaces",
|
|
13
|
+
"workspaces.select.errorText": "Något gick fel när dina workspaces skulle laddas. Kanske har din session gått ut, eller så har vi klantat till det. Prova att börja om från startsidan.",
|
|
14
|
+
"workspaces.select.errorAction": "Gå till startsidan",
|
|
15
|
+
"workspaces.select.emptyTitle": "Nu sätter vi igång",
|
|
16
|
+
"workspaces.select.emptyText": "För att komma igång behöver vi skapa ett workspace. Ett workspace kapslar in resurser, resursmallar, faktureringsinformation och vilka användare som har tillgång.",
|
|
17
|
+
"workspaces.select.emptyAction": "Skapa ett workspace",
|
|
18
|
+
"workspaces/create.label": "Skapa workspace",
|
|
19
|
+
"workspaces/create.description": "Skapa ett nytt workspace för den inloggade användaren",
|
|
20
|
+
"workspaces/accept-invitation.label": "Acceptera inbjudan",
|
|
21
|
+
"workspaces/accept-invitation.description": "Acceptera en workspace-inbjudan",
|
|
22
|
+
"workspaces/create-api-token.label": "Skapa workspace API-nyckel",
|
|
23
|
+
"workspaces/create-api-token.description": "Skapa en API-nyckel kopplad till workspace",
|
|
24
|
+
"workspaces/disable-service.label": "Inaktivera tjänst",
|
|
25
|
+
"workspaces/disable-service.description": "Inaktivera en tjänst för workspace",
|
|
26
|
+
"workspaces/enable-service.label": "Aktivera tjänst",
|
|
27
|
+
"workspaces/enable-service.description": "Aktivera en tjänst för workspace",
|
|
28
|
+
"workspaces/get-api-tokens.label": "Hämta workspace API-nycklar",
|
|
29
|
+
"workspaces/get-api-tokens.description": "Lista API-nycklar för workspace",
|
|
30
|
+
"workspaces/get-invitations.label": "Hämta inbjudningar",
|
|
31
|
+
"workspaces/get-invitations.description": "Lista väntande workspace-inbjudningar",
|
|
32
|
+
"workspaces/get-resource-templates.label": "Hämta resursmallar",
|
|
33
|
+
"workspaces/get-resource-templates.description": "Lista resursmallar tillgängliga för workspace",
|
|
34
|
+
"workspaces/get-users.label": "Hämta workspace-användare",
|
|
35
|
+
"workspaces/get-users.description": "Lista medlemmar i workspace",
|
|
36
|
+
"workspaces/get.label": "Hämta workspace",
|
|
37
|
+
"workspaces/get.description": "Ladda ett workspace via id",
|
|
38
|
+
"workspaces/import-resource-templates.label": "Importera resursmallar",
|
|
39
|
+
"workspaces/import-resource-templates.description": "Importera resursmallar till workspace",
|
|
40
|
+
"workspaces/invite-user.label": "Bjud in användare",
|
|
41
|
+
"workspaces/invite-user.description": "Skicka en workspace-inbjudan till en användare",
|
|
42
|
+
"workspaces/remove-member.label": "Ta bort medlem",
|
|
43
|
+
"workspaces/remove-member.description": "Ta bort en användare från workspace",
|
|
44
|
+
"workspaces/list.label": "Lista workspaces",
|
|
45
|
+
"workspaces/list.description": "Lista workspaces för den inloggade användaren",
|
|
46
|
+
"workspace/settings.documentTitle": "Workspace-inställningar",
|
|
47
|
+
"workspace.settings.title": "Workspace-inställningar",
|
|
48
|
+
"workspace/users.documentTitle": "Användare",
|
|
49
|
+
"workspace.users.title": "Användare",
|
|
50
|
+
"workspace.users.description": "Hantera användare i ditt workspace.",
|
|
51
|
+
"workspace.users.invite": "Bjud in användare",
|
|
52
|
+
"workspace.users.loading": "Laddar",
|
|
53
|
+
"workspace/invitations.documentTitle": "Inbjudningar",
|
|
54
|
+
"workspace/invitations/add.documentTitle": "Bjud in användare",
|
|
55
|
+
"workspace.invitations.title": "Inbjudningar",
|
|
56
|
+
"workspace.settings.detailsTitle": "Workspace-detaljer",
|
|
57
|
+
"workspace.settings.edit": "Redigera",
|
|
58
|
+
"workspace.settings.name": "Namn",
|
|
59
|
+
"workspace.settings.created": "Skapad",
|
|
60
|
+
"workspace.settings.services": "Tjänster",
|
|
61
|
+
"workspace.settings.loading": "Laddar...",
|
|
62
|
+
"workspace.invitations.description": "Bjud in användare till ditt workspace för att samarbeta.",
|
|
63
|
+
"workspace.invitations.invite": "Bjud in användare",
|
|
64
|
+
"workspace.invitations.empty": "Inga inbjudningar",
|
|
65
|
+
"workspace.invite.title": "Bjud in användare",
|
|
66
|
+
"workspace.invite.description": "Bjud in en användare att hantera och bidra med innehåll i detta workspace",
|
|
67
|
+
"workspace.invite.emailPlaceholder": "E-postadress",
|
|
68
|
+
"workspace.invite.submit": "Skicka inbjudan",
|
|
69
|
+
"workspace.invite.successTitle": "Klart",
|
|
70
|
+
"workspace.invite.successBody": "Vi skickade en inbjudan via e-post. När de har accepterat visas de i användarlistan.",
|
|
71
|
+
"workspace.invite.error": "Något gick fel — försök igen om några minuter"
|
|
72
|
+
}
|
|
@@ -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
|
|
package/src/workspaces.spec.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import casual from 'casual'
|
|
2
2
|
import { TestUtil } from '@ossy/platform/test'
|
|
3
3
|
|
|
4
|
+
function authHeaders(token) {
|
|
5
|
+
return {
|
|
6
|
+
'Content-Type': 'application/json',
|
|
7
|
+
Authorization: token,
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
4
11
|
function workspaceHeaders(userToken, workspaceId) {
|
|
5
12
|
return {
|
|
6
13
|
'Content-Type': 'application/json',
|
|
@@ -9,12 +16,10 @@ function workspaceHeaders(userToken, workspaceId) {
|
|
|
9
16
|
}
|
|
10
17
|
}
|
|
11
18
|
|
|
12
|
-
describe('[/
|
|
19
|
+
describe('[workspaces/create]', () => {
|
|
13
20
|
|
|
14
|
-
TestUtil.
|
|
15
|
-
|
|
16
|
-
method: 'POST',
|
|
17
|
-
headers: { 'Content-Type': 'application/json' },
|
|
21
|
+
TestUtil.AssertActionAuthenticationNeeded({
|
|
22
|
+
actionId: 'workspaces/create',
|
|
18
23
|
})
|
|
19
24
|
|
|
20
25
|
describe('given a workspace name', () => {
|
|
@@ -22,11 +27,10 @@ describe('[/workspaces][POST]', () => {
|
|
|
22
27
|
const user = await TestUtil.GetAuthenticatedTestUser()
|
|
23
28
|
const workspaceName = casual.word
|
|
24
29
|
|
|
25
|
-
const response = await TestUtil.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
body: JSON.stringify({ name: workspaceName }),
|
|
30
|
+
const response = await TestUtil.InvokeAction({
|
|
31
|
+
actionId: 'workspaces/create',
|
|
32
|
+
headers: authHeaders(user.token),
|
|
33
|
+
payload: { name: workspaceName },
|
|
30
34
|
})
|
|
31
35
|
|
|
32
36
|
const body = await response.json()
|
|
@@ -53,28 +57,24 @@ describe('[/workspaces][POST]', () => {
|
|
|
53
57
|
|
|
54
58
|
})
|
|
55
59
|
|
|
56
|
-
describe('[/
|
|
60
|
+
describe('[workspaces/list]', () => {
|
|
57
61
|
|
|
58
|
-
TestUtil.
|
|
59
|
-
|
|
60
|
-
method: 'GET',
|
|
61
|
-
headers: { 'Content-Type': 'application/json' },
|
|
62
|
+
TestUtil.AssertActionAuthenticationNeeded({
|
|
63
|
+
actionId: 'workspaces/list',
|
|
62
64
|
})
|
|
63
65
|
|
|
64
66
|
it('must return a minified list of workspaces the user have created', async () => {
|
|
65
67
|
const user = await TestUtil.GetAuthenticatedTestUser()
|
|
66
68
|
|
|
67
|
-
const workspace = await TestUtil.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
body: JSON.stringify({ name: casual.word }),
|
|
69
|
+
const workspace = await TestUtil.InvokeAction({
|
|
70
|
+
actionId: 'workspaces/create',
|
|
71
|
+
headers: authHeaders(user.token),
|
|
72
|
+
payload: { name: casual.word },
|
|
72
73
|
}).then(response => response.json())
|
|
73
74
|
|
|
74
|
-
const response = await TestUtil.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
headers: { 'Content-Type': 'application/json', 'Authorization': user.token },
|
|
75
|
+
const response = await TestUtil.InvokeAction({
|
|
76
|
+
actionId: 'workspaces/list',
|
|
77
|
+
headers: authHeaders(user.token),
|
|
78
78
|
})
|
|
79
79
|
|
|
80
80
|
const responseBody = await response.json()
|
|
@@ -89,28 +89,26 @@ describe('[/workspaces][GET]', () => {
|
|
|
89
89
|
|
|
90
90
|
})
|
|
91
91
|
|
|
92
|
-
describe('[/
|
|
92
|
+
describe('[workspaces/get]', () => {
|
|
93
93
|
|
|
94
|
-
TestUtil.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
headers: { 'Content-Type': 'application/json' },
|
|
94
|
+
TestUtil.AssertWorkspaceActionAuthenticationNeeded({
|
|
95
|
+
actionId: 'workspaces/get',
|
|
96
|
+
payload: { workspaceId: '123' },
|
|
98
97
|
})
|
|
99
98
|
|
|
100
99
|
it('must return the requested workspace', async () => {
|
|
101
100
|
const user = await TestUtil.GetAuthenticatedTestUser()
|
|
102
101
|
|
|
103
|
-
const workspace = await TestUtil.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
body: JSON.stringify({ name: casual.word }),
|
|
102
|
+
const workspace = await TestUtil.InvokeAction({
|
|
103
|
+
actionId: 'workspaces/create',
|
|
104
|
+
headers: authHeaders(user.token),
|
|
105
|
+
payload: { name: casual.word },
|
|
108
106
|
}).then(response => response.json())
|
|
109
107
|
|
|
110
|
-
const response = await TestUtil.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
108
|
+
const response = await TestUtil.InvokeAction({
|
|
109
|
+
actionId: 'workspaces/get',
|
|
110
|
+
headers: workspaceHeaders(user.token, workspace.id),
|
|
111
|
+
payload: { workspaceId: workspace.id },
|
|
114
112
|
})
|
|
115
113
|
|
|
116
114
|
const responseBody = await response.json()
|
|
@@ -122,22 +120,21 @@ describe('[/workspaces/:workspaceId][GET]', () => {
|
|
|
122
120
|
|
|
123
121
|
})
|
|
124
122
|
|
|
125
|
-
describe('[/resource-templates]
|
|
123
|
+
describe('[workspaces/import-resource-templates]', () => {
|
|
126
124
|
|
|
127
|
-
TestUtil.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
headers: {
|
|
125
|
+
TestUtil.AssertWorkspaceActionAuthenticationNeeded({
|
|
126
|
+
actionId: 'workspaces/import-resource-templates',
|
|
127
|
+
payload: { templates: [] },
|
|
128
|
+
headers: { workspaceId: 'ws-test' },
|
|
131
129
|
})
|
|
132
130
|
|
|
133
131
|
it('must return the saved templates and create a ResourceTemplatesImported event', async () => {
|
|
134
132
|
const user = await TestUtil.GetAuthenticatedTestUser()
|
|
135
133
|
|
|
136
|
-
const workspace = await TestUtil.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
body: JSON.stringify({ name: casual.word }),
|
|
134
|
+
const workspace = await TestUtil.InvokeAction({
|
|
135
|
+
actionId: 'workspaces/create',
|
|
136
|
+
headers: authHeaders(user.token),
|
|
137
|
+
payload: { name: casual.word },
|
|
141
138
|
}).then(response => response.json())
|
|
142
139
|
|
|
143
140
|
const imported = [
|
|
@@ -148,11 +145,10 @@ describe('[/resource-templates][POST]', () => {
|
|
|
148
145
|
},
|
|
149
146
|
]
|
|
150
147
|
|
|
151
|
-
const response = await TestUtil.
|
|
152
|
-
|
|
153
|
-
method: 'POST',
|
|
148
|
+
const response = await TestUtil.InvokeAction({
|
|
149
|
+
actionId: 'workspaces/import-resource-templates',
|
|
154
150
|
headers: workspaceHeaders(user.token, workspace.id),
|
|
155
|
-
|
|
151
|
+
payload: { templates: imported },
|
|
156
152
|
})
|
|
157
153
|
|
|
158
154
|
const responseBody = await response.json()
|
|
@@ -172,22 +168,20 @@ describe('[/resource-templates][POST]', () => {
|
|
|
172
168
|
|
|
173
169
|
})
|
|
174
170
|
|
|
175
|
-
describe('[/resource-templates]
|
|
171
|
+
describe('[workspaces/get-resource-templates]', () => {
|
|
176
172
|
|
|
177
|
-
TestUtil.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
headers: { 'Content-Type': 'application/json', 'workspaceId': 'ws-test' },
|
|
173
|
+
TestUtil.AssertWorkspaceActionAuthenticationNeeded({
|
|
174
|
+
actionId: 'workspaces/get-resource-templates',
|
|
175
|
+
headers: { workspaceId: 'ws-test' },
|
|
181
176
|
})
|
|
182
177
|
|
|
183
178
|
it('must return the saved templates event', async () => {
|
|
184
179
|
const user = await TestUtil.GetAuthenticatedTestUser()
|
|
185
180
|
|
|
186
|
-
const workspace = await TestUtil.
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
body: JSON.stringify({ name: casual.word }),
|
|
181
|
+
const workspace = await TestUtil.InvokeAction({
|
|
182
|
+
actionId: 'workspaces/create',
|
|
183
|
+
headers: authHeaders(user.token),
|
|
184
|
+
payload: { name: casual.word },
|
|
191
185
|
}).then(response => response.json())
|
|
192
186
|
|
|
193
187
|
const imported = [
|
|
@@ -198,11 +192,10 @@ describe('[/resource-templates][GET]', () => {
|
|
|
198
192
|
},
|
|
199
193
|
]
|
|
200
194
|
|
|
201
|
-
const response = await TestUtil.
|
|
202
|
-
|
|
203
|
-
method: 'POST',
|
|
195
|
+
const response = await TestUtil.InvokeAction({
|
|
196
|
+
actionId: 'workspaces/import-resource-templates',
|
|
204
197
|
headers: workspaceHeaders(user.token, workspace.id),
|
|
205
|
-
|
|
198
|
+
payload: { templates: imported },
|
|
206
199
|
})
|
|
207
200
|
|
|
208
201
|
const responseBody = await response.json()
|
|
@@ -214,29 +207,27 @@ describe('[/resource-templates][GET]', () => {
|
|
|
214
207
|
|
|
215
208
|
})
|
|
216
209
|
|
|
217
|
-
describe('[/
|
|
210
|
+
describe('[workspaces/create-api-token]', () => {
|
|
218
211
|
|
|
219
|
-
TestUtil.
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
headers: {
|
|
212
|
+
TestUtil.AssertWorkspaceActionAuthenticationNeeded({
|
|
213
|
+
actionId: 'workspaces/create-api-token',
|
|
214
|
+
payload: { description: 'Api token for GitHub Actions' },
|
|
215
|
+
headers: { workspaceId: 'ws-test' },
|
|
223
216
|
})
|
|
224
217
|
|
|
225
218
|
it('must return an api token and create an ApiTokenCreated event', async () => {
|
|
226
219
|
const user = await TestUtil.GetAuthenticatedTestUser()
|
|
227
220
|
|
|
228
|
-
const workspace = await TestUtil.
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
body: JSON.stringify({ name: casual.word }),
|
|
221
|
+
const workspace = await TestUtil.InvokeAction({
|
|
222
|
+
actionId: 'workspaces/create',
|
|
223
|
+
headers: authHeaders(user.token),
|
|
224
|
+
payload: { name: casual.word },
|
|
233
225
|
}).then(response => response.json())
|
|
234
226
|
|
|
235
|
-
const response = await TestUtil.
|
|
236
|
-
|
|
237
|
-
method: 'POST',
|
|
227
|
+
const response = await TestUtil.InvokeAction({
|
|
228
|
+
actionId: 'workspaces/create-api-token',
|
|
238
229
|
headers: workspaceHeaders(user.token, workspace.id),
|
|
239
|
-
|
|
230
|
+
payload: { description: 'Api token for GitHub Actions' },
|
|
240
231
|
})
|
|
241
232
|
|
|
242
233
|
const responseBody = await response.json()
|