@arcote.tech/arc-workspace 0.4.10 → 0.5.0
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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcote.tech/arc-workspace",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Reusable workspace module for Arc framework — multi-workspace with dual token architecture",
|
|
7
7
|
"main": "./src/index.ts",
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"type-check": "tsc --noEmit"
|
|
11
11
|
},
|
|
12
12
|
"peerDependencies": {
|
|
13
|
-
"@arcote.tech/arc": "^0.
|
|
14
|
-
"@arcote.tech/arc-auth": "^0.
|
|
13
|
+
"@arcote.tech/arc": "^0.5.0",
|
|
14
|
+
"@arcote.tech/arc-auth": "^0.5.0",
|
|
15
15
|
"typescript": "^5.0.0"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
aggregate,
|
|
3
|
-
date,
|
|
4
|
-
string,
|
|
5
|
-
type ArcId,
|
|
6
|
-
} from "@arcote.tech/arc";
|
|
1
|
+
import { aggregate, date, string, type ArcId } from "@arcote.tech/arc";
|
|
7
2
|
import type { Token } from "@arcote.tech/arc-auth";
|
|
8
3
|
import type { WorkspaceToken } from "../tokens/workspace-token";
|
|
9
4
|
|
|
@@ -17,7 +12,8 @@ export type WorkspaceInvitationAggregateData = {
|
|
|
17
12
|
};
|
|
18
13
|
|
|
19
14
|
function generateInviteToken(): string {
|
|
20
|
-
const chars =
|
|
15
|
+
const chars =
|
|
16
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
21
17
|
let result = "";
|
|
22
18
|
for (let i = 0; i < 32; i++) {
|
|
23
19
|
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
@@ -39,7 +35,7 @@ export const createWorkspaceInvitationAggregate = <
|
|
|
39
35
|
} = data;
|
|
40
36
|
|
|
41
37
|
return aggregate(
|
|
42
|
-
`${data.name}WorkspaceInvitations`,
|
|
38
|
+
`${data.name as Data["name"]}WorkspaceInvitations`,
|
|
43
39
|
workspaceInvitationId,
|
|
44
40
|
{
|
|
45
41
|
workspaceId,
|
|
@@ -113,107 +109,115 @@ export const createWorkspaceInvitationAggregate = <
|
|
|
113
109
|
|
|
114
110
|
.mutateMethod(
|
|
115
111
|
"createInvitation",
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
112
|
+
(fn) =>
|
|
113
|
+
fn
|
|
114
|
+
.withParams({
|
|
115
|
+
workspaceId,
|
|
116
|
+
role: string(),
|
|
117
|
+
workspaceName: string(),
|
|
118
|
+
workspaceType: string(),
|
|
119
|
+
email: string().optional(),
|
|
120
|
+
})
|
|
121
|
+
.handle(
|
|
122
|
+
ONLY_SERVER &&
|
|
123
|
+
(async (ctx, params) => {
|
|
124
|
+
const invId = workspaceInvitationId.generate();
|
|
125
|
+
const token = generateInviteToken();
|
|
126
|
+
const expiresAt = new Date(
|
|
127
|
+
Date.now() + 7 * 24 * 60 * 60 * 1000,
|
|
128
|
+
); // 7 days
|
|
129
|
+
|
|
130
|
+
await ctx.invitationCreated.emit({
|
|
131
|
+
workspaceInvitationId: invId,
|
|
132
|
+
workspaceId: params.workspaceId,
|
|
133
|
+
invitedBy: ctx.$auth.params.accountId,
|
|
134
|
+
role: params.role,
|
|
135
|
+
token,
|
|
136
|
+
email: params.email,
|
|
137
|
+
workspaceName: params.workspaceName,
|
|
138
|
+
workspaceType: params.workspaceType,
|
|
139
|
+
expiresAt,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return { workspaceInvitationId: invId, token };
|
|
143
|
+
}),
|
|
144
|
+
),
|
|
145
145
|
)
|
|
146
146
|
|
|
147
147
|
.mutateMethod(
|
|
148
148
|
"acceptInvitation",
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
149
|
+
(fn) =>
|
|
150
|
+
fn.withParams({ token: string() }).handle(
|
|
151
|
+
ONLY_SERVER &&
|
|
152
|
+
(async (ctx, params) => {
|
|
153
|
+
const invitation = await ctx.$query.findOne({
|
|
154
|
+
token: params.token,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
if (!invitation) {
|
|
158
|
+
return { error: "INVITATION_NOT_FOUND" as const };
|
|
159
|
+
}
|
|
160
|
+
if (invitation.status !== "pending") {
|
|
161
|
+
return { error: "INVITATION_NOT_VALID" as const };
|
|
162
|
+
}
|
|
163
|
+
if (new Date(invitation.expiresAt) < new Date()) {
|
|
164
|
+
return { error: "INVITATION_EXPIRED" as const };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Emit accepted event — WorkspaceMember handles adding the member
|
|
168
|
+
await ctx.invitationAccepted.emit({
|
|
169
|
+
workspaceInvitationId: invitation._id,
|
|
170
|
+
workspaceId: invitation.workspaceId,
|
|
171
|
+
acceptedBy: ctx.$auth.params.accountId,
|
|
172
|
+
role: invitation.role,
|
|
173
|
+
workspaceName: invitation.workspaceName,
|
|
174
|
+
workspaceType: invitation.workspaceType,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Generate workspace token for the new member
|
|
178
|
+
const wsToken = workspaceToken.generateJWT({
|
|
179
|
+
accountId: ctx.$auth.params.accountId,
|
|
180
|
+
workspaceId: invitation.workspaceId,
|
|
181
|
+
role: invitation.role,
|
|
182
|
+
workspaceType: invitation.workspaceType,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
success: true,
|
|
187
|
+
token: wsToken,
|
|
188
|
+
workspaceId: invitation.workspaceId,
|
|
189
|
+
};
|
|
190
|
+
}),
|
|
191
|
+
),
|
|
190
192
|
)
|
|
191
193
|
|
|
192
194
|
.mutateMethod(
|
|
193
195
|
"revokeInvitation",
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
196
|
+
(fn) =>
|
|
197
|
+
fn.withParams({ workspaceInvitationId }).handle(
|
|
198
|
+
ONLY_SERVER &&
|
|
199
|
+
(async (ctx, params) => {
|
|
200
|
+
const invitation = await ctx.$query.findOne({
|
|
201
|
+
_id: params.workspaceInvitationId,
|
|
202
|
+
});
|
|
203
|
+
if (!invitation || invitation.status !== "pending") {
|
|
204
|
+
return { error: "INVITATION_NOT_VALID" as const };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
await ctx.invitationRevoked.emit({
|
|
208
|
+
workspaceInvitationId: params.workspaceInvitationId,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
return { success: true };
|
|
212
|
+
}),
|
|
213
|
+
),
|
|
210
214
|
)
|
|
211
215
|
|
|
212
|
-
.protectBy(userToken, (p
|
|
213
|
-
.clientQuery("getByWorkspace", async (ctx) => ctx.$query.find({}))
|
|
214
|
-
;
|
|
216
|
+
.protectBy(userToken, (p) => ({ invitedBy: p.accountId }))
|
|
217
|
+
.clientQuery("getByWorkspace", (fn) => fn.handle(async (ctx) => ctx.$query.find({})));
|
|
215
218
|
};
|
|
216
219
|
|
|
217
220
|
export type WorkspaceInvitationAggregate<
|
|
218
|
-
Data extends WorkspaceInvitationAggregateData =
|
|
221
|
+
Data extends WorkspaceInvitationAggregateData =
|
|
222
|
+
WorkspaceInvitationAggregateData,
|
|
219
223
|
> = ReturnType<typeof createWorkspaceInvitationAggregate<Data>>;
|
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
aggregate,
|
|
3
|
-
date,
|
|
4
|
-
string,
|
|
5
|
-
type ArcAggregateElement,
|
|
6
|
-
type ArcId,
|
|
7
|
-
} from "@arcote.tech/arc";
|
|
1
|
+
import { aggregate, date, string, type ArcId } from "@arcote.tech/arc";
|
|
8
2
|
import type { Token } from "@arcote.tech/arc-auth";
|
|
9
3
|
import type { WorkspaceToken } from "../tokens/workspace-token";
|
|
4
|
+
import type { createWorkspaceAggregate } from "./workspace";
|
|
5
|
+
import type { createWorkspaceInvitationAggregate } from "./workspace-invitation";
|
|
10
6
|
|
|
11
7
|
export type WorkspaceMemberAggregateData = {
|
|
12
8
|
name: string;
|
|
@@ -15,8 +11,10 @@ export type WorkspaceMemberAggregateData = {
|
|
|
15
11
|
accountId: ArcId<any>;
|
|
16
12
|
workspaceToken: WorkspaceToken;
|
|
17
13
|
userToken: Token;
|
|
18
|
-
WorkspaceAggregate:
|
|
19
|
-
WorkspaceInvitationAggregate
|
|
14
|
+
WorkspaceAggregate: ReturnType<typeof createWorkspaceAggregate>;
|
|
15
|
+
WorkspaceInvitationAggregate: ReturnType<
|
|
16
|
+
typeof createWorkspaceInvitationAggregate
|
|
17
|
+
>;
|
|
20
18
|
};
|
|
21
19
|
|
|
22
20
|
export const createWorkspaceMemberAggregate = <
|
|
@@ -34,226 +32,238 @@ export const createWorkspaceMemberAggregate = <
|
|
|
34
32
|
WorkspaceInvitationAggregate,
|
|
35
33
|
} = data;
|
|
36
34
|
|
|
37
|
-
const
|
|
35
|
+
const name = data.name as Data["name"];
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
`${
|
|
41
|
-
workspaceMemberId,
|
|
42
|
-
{
|
|
37
|
+
return (
|
|
38
|
+
aggregate(`${name}WorkspaceMembers`, workspaceMemberId, {
|
|
43
39
|
workspaceId,
|
|
44
40
|
accountId,
|
|
45
41
|
role: string(),
|
|
46
42
|
workspaceName: string(),
|
|
47
43
|
workspaceType: string(),
|
|
48
44
|
joinedAt: date(),
|
|
49
|
-
},
|
|
50
|
-
)
|
|
51
|
-
.publicEvent(
|
|
52
|
-
"workspaceMemberAdded",
|
|
53
|
-
{
|
|
54
|
-
workspaceMemberId,
|
|
55
|
-
workspaceId,
|
|
56
|
-
accountId,
|
|
57
|
-
role: string(),
|
|
58
|
-
workspaceName: string(),
|
|
59
|
-
workspaceType: string(),
|
|
60
|
-
},
|
|
61
|
-
async (ctx, event) => {
|
|
62
|
-
const {
|
|
63
|
-
workspaceMemberId: id,
|
|
64
|
-
workspaceId: wsId,
|
|
65
|
-
accountId: accId,
|
|
66
|
-
role,
|
|
67
|
-
workspaceName,
|
|
68
|
-
workspaceType,
|
|
69
|
-
} = event.payload;
|
|
70
|
-
await ctx.set(id, {
|
|
71
|
-
workspaceId: wsId,
|
|
72
|
-
accountId: accId,
|
|
73
|
-
role,
|
|
74
|
-
workspaceName,
|
|
75
|
-
workspaceType,
|
|
76
|
-
joinedAt: event.createdAt,
|
|
77
|
-
});
|
|
78
|
-
},
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
.publicEvent(
|
|
82
|
-
"workspaceMemberRemoved",
|
|
83
|
-
{ workspaceMemberId },
|
|
84
|
-
async (ctx, event) => {
|
|
85
|
-
await ctx.remove(event.payload.workspaceMemberId);
|
|
86
|
-
},
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
.publicEvent(
|
|
90
|
-
"workspaceMemberRoleChanged",
|
|
91
|
-
{
|
|
92
|
-
workspaceMemberId,
|
|
93
|
-
role: string(),
|
|
94
|
-
},
|
|
95
|
-
async (ctx, event) => {
|
|
96
|
-
await ctx.modify(event.payload.workspaceMemberId, {
|
|
97
|
-
role: event.payload.role,
|
|
98
|
-
});
|
|
99
|
-
},
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
// Auto-create owner membership when workspace is created
|
|
103
|
-
.handleEvent(workspaceCreatedEvent, async (ctx, event) => {
|
|
104
|
-
await ctx.set(event.payload.ownerMemberId, {
|
|
105
|
-
workspaceId: event.payload.workspaceId,
|
|
106
|
-
accountId: event.payload.ownerId,
|
|
107
|
-
role: "owner",
|
|
108
|
-
workspaceName: event.payload.name,
|
|
109
|
-
workspaceType: event.payload.type,
|
|
110
|
-
joinedAt: event.createdAt,
|
|
111
|
-
});
|
|
112
45
|
})
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
where: { workspaceId: event.payload.workspaceId },
|
|
118
|
-
});
|
|
119
|
-
for (const member of members) {
|
|
120
|
-
await ctx.remove(member._id);
|
|
121
|
-
}
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
// Sync workspace name to all members when workspace is renamed
|
|
125
|
-
.handleEvent(WorkspaceAggregate.getEvent("workspaceRenamed"), async (ctx, event) => {
|
|
126
|
-
const members = await ctx.find({
|
|
127
|
-
where: { workspaceId: event.payload.workspaceId },
|
|
128
|
-
});
|
|
129
|
-
for (const member of members) {
|
|
130
|
-
await ctx.modify(member._id, {
|
|
131
|
-
workspaceName: event.payload.name,
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
;
|
|
137
|
-
|
|
138
|
-
// Auto-add member when invitation is accepted
|
|
139
|
-
if (WorkspaceInvitationAggregate) {
|
|
140
|
-
const invitationAcceptedEvent = WorkspaceInvitationAggregate.getEvent("invitationAccepted");
|
|
141
|
-
Constructor = Constructor.handleEvent(invitationAcceptedEvent, async (ctx, event) => {
|
|
142
|
-
const p = event.payload;
|
|
143
|
-
const memberId = workspaceMemberId.generate();
|
|
144
|
-
await ctx.set(memberId, {
|
|
145
|
-
workspaceId: p.workspaceId,
|
|
146
|
-
accountId: p.acceptedBy,
|
|
147
|
-
role: p.role,
|
|
148
|
-
workspaceName: p.workspaceName,
|
|
149
|
-
workspaceType: p.workspaceType,
|
|
150
|
-
joinedAt: event.createdAt,
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
Constructor = Constructor.mutateMethod(
|
|
156
|
-
"addMember",
|
|
157
|
-
{
|
|
158
|
-
params: {
|
|
46
|
+
.publicEvent(
|
|
47
|
+
"memberAdded",
|
|
48
|
+
{
|
|
49
|
+
workspaceMemberId,
|
|
159
50
|
workspaceId,
|
|
160
51
|
accountId,
|
|
161
52
|
role: string(),
|
|
162
53
|
workspaceName: string(),
|
|
163
54
|
workspaceType: string(),
|
|
164
55
|
},
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
(async (ctx, params) => {
|
|
168
|
-
const existing = await ctx.$query.findOne({
|
|
169
|
-
workspaceId: params.workspaceId,
|
|
170
|
-
accountId: params.accountId,
|
|
171
|
-
});
|
|
172
|
-
if (existing) {
|
|
173
|
-
return { error: "ALREADY_A_MEMBER" as const };
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const id = workspaceMemberId.generate();
|
|
177
|
-
|
|
178
|
-
await ctx.workspaceMemberAdded.emit({
|
|
56
|
+
async (ctx, event) => {
|
|
57
|
+
const {
|
|
179
58
|
workspaceMemberId: id,
|
|
180
|
-
workspaceId:
|
|
181
|
-
accountId:
|
|
182
|
-
role
|
|
183
|
-
workspaceName
|
|
184
|
-
workspaceType
|
|
59
|
+
workspaceId: wsId,
|
|
60
|
+
accountId: accId,
|
|
61
|
+
role,
|
|
62
|
+
workspaceName,
|
|
63
|
+
workspaceType,
|
|
64
|
+
} = event.payload;
|
|
65
|
+
await ctx.set(id, {
|
|
66
|
+
workspaceId: wsId,
|
|
67
|
+
accountId: accId,
|
|
68
|
+
role,
|
|
69
|
+
workspaceName,
|
|
70
|
+
workspaceType,
|
|
71
|
+
joinedAt: event.createdAt,
|
|
185
72
|
});
|
|
73
|
+
},
|
|
74
|
+
)
|
|
186
75
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
ONLY_SERVER &&
|
|
195
|
-
(async (ctx, params) => {
|
|
196
|
-
await ctx.workspaceMemberRemoved.emit({
|
|
197
|
-
workspaceMemberId: params.workspaceMemberId,
|
|
198
|
-
});
|
|
199
|
-
}),
|
|
200
|
-
)
|
|
76
|
+
.publicEvent(
|
|
77
|
+
"memberRemoved",
|
|
78
|
+
{ workspaceMemberId },
|
|
79
|
+
async (ctx, event) => {
|
|
80
|
+
await ctx.remove(event.payload.workspaceMemberId);
|
|
81
|
+
},
|
|
82
|
+
)
|
|
201
83
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
params: {
|
|
84
|
+
.publicEvent(
|
|
85
|
+
"roleChanged",
|
|
86
|
+
{
|
|
206
87
|
workspaceMemberId,
|
|
207
88
|
role: string(),
|
|
208
89
|
},
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
await ctx.workspaceMemberRoleChanged.emit({
|
|
213
|
-
workspaceMemberId: params.workspaceMemberId,
|
|
214
|
-
role: params.role,
|
|
90
|
+
async (ctx, event) => {
|
|
91
|
+
await ctx.modify(event.payload.workspaceMemberId, {
|
|
92
|
+
role: event.payload.role,
|
|
215
93
|
});
|
|
216
|
-
}
|
|
217
|
-
|
|
94
|
+
},
|
|
95
|
+
)
|
|
218
96
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
97
|
+
// Auto-create owner membership when workspace is created
|
|
98
|
+
.handleEvent(
|
|
99
|
+
WorkspaceAggregate.getEvent("workspaceCreated"),
|
|
100
|
+
async (ctx, event) => {
|
|
101
|
+
await ctx.set(event.payload.ownerMemberId, {
|
|
102
|
+
workspaceId: event.payload.workspaceId,
|
|
103
|
+
accountId: event.payload.ownerId,
|
|
104
|
+
role: "owner",
|
|
105
|
+
workspaceName: event.payload.name,
|
|
106
|
+
workspaceType: event.payload.type,
|
|
107
|
+
joinedAt: event.createdAt,
|
|
226
108
|
});
|
|
109
|
+
},
|
|
110
|
+
)
|
|
227
111
|
|
|
228
|
-
|
|
229
|
-
|
|
112
|
+
// Remove all members when workspace is archived
|
|
113
|
+
.handleEvent(
|
|
114
|
+
WorkspaceAggregate.getEvent("workspaceArchived"),
|
|
115
|
+
async (ctx, event) => {
|
|
116
|
+
const members = await ctx.find({
|
|
117
|
+
where: { workspaceId: event.payload.workspaceId },
|
|
118
|
+
});
|
|
119
|
+
for (const member of members) {
|
|
120
|
+
await ctx.remove(member._id);
|
|
230
121
|
}
|
|
122
|
+
},
|
|
123
|
+
)
|
|
231
124
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
125
|
+
// Sync workspace name to all members when workspace is renamed
|
|
126
|
+
.handleEvent(
|
|
127
|
+
WorkspaceAggregate.getEvent("workspaceRenamed"),
|
|
128
|
+
async (ctx, event) => {
|
|
129
|
+
const members = await ctx.find({
|
|
130
|
+
where: { workspaceId: event.payload.workspaceId },
|
|
131
|
+
});
|
|
132
|
+
for (const member of members) {
|
|
133
|
+
await ctx.modify(member._id, {
|
|
134
|
+
workspaceName: event.payload.name,
|
|
135
|
+
});
|
|
237
136
|
}
|
|
137
|
+
},
|
|
138
|
+
)
|
|
238
139
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
140
|
+
// Auto-add member when invitation is accepted
|
|
141
|
+
.handleEvent(
|
|
142
|
+
WorkspaceInvitationAggregate.getEvent("invitationAccepted"),
|
|
143
|
+
async (ctx, event) => {
|
|
144
|
+
const p = event.payload;
|
|
145
|
+
const memberId = workspaceMemberId.generate();
|
|
146
|
+
await ctx.set(memberId, {
|
|
147
|
+
workspaceId: p.workspaceId,
|
|
148
|
+
accountId: p.acceptedBy,
|
|
149
|
+
role: p.role,
|
|
150
|
+
workspaceName: p.workspaceName,
|
|
151
|
+
workspaceType: p.workspaceType,
|
|
152
|
+
joinedAt: event.createdAt,
|
|
244
153
|
});
|
|
154
|
+
},
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
.mutateMethod(
|
|
158
|
+
"addMember",
|
|
159
|
+
(fn) =>
|
|
160
|
+
fn
|
|
161
|
+
.withParams({
|
|
162
|
+
workspaceId,
|
|
163
|
+
accountId,
|
|
164
|
+
role: string(),
|
|
165
|
+
workspaceName: string(),
|
|
166
|
+
workspaceType: string(),
|
|
167
|
+
})
|
|
168
|
+
.handle(
|
|
169
|
+
ONLY_SERVER &&
|
|
170
|
+
(async (ctx, params) => {
|
|
171
|
+
const existing = await ctx.$query.findOne({
|
|
172
|
+
workspaceId: params.workspaceId,
|
|
173
|
+
accountId: params.accountId,
|
|
174
|
+
});
|
|
175
|
+
if (existing) {
|
|
176
|
+
return { error: "ALREADY_A_MEMBER" as const };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const id = workspaceMemberId.generate();
|
|
180
|
+
|
|
181
|
+
await ctx.memberAdded.emit({
|
|
182
|
+
workspaceMemberId: id,
|
|
183
|
+
workspaceId: params.workspaceId,
|
|
184
|
+
accountId: params.accountId,
|
|
185
|
+
role: params.role,
|
|
186
|
+
workspaceName: params.workspaceName,
|
|
187
|
+
workspaceType: params.workspaceType,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
return { workspaceMemberId: id };
|
|
191
|
+
}),
|
|
192
|
+
),
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
.mutateMethod(
|
|
196
|
+
"removeMember",
|
|
197
|
+
(fn) =>
|
|
198
|
+
fn.withParams({ workspaceMemberId }).handle(
|
|
199
|
+
ONLY_SERVER &&
|
|
200
|
+
(async (ctx, params) => {
|
|
201
|
+
await ctx.memberRemoved.emit({
|
|
202
|
+
workspaceMemberId: params.workspaceMemberId,
|
|
203
|
+
});
|
|
204
|
+
}),
|
|
205
|
+
),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
.mutateMethod(
|
|
209
|
+
"changeRole",
|
|
210
|
+
(fn) =>
|
|
211
|
+
fn
|
|
212
|
+
.withParams({
|
|
213
|
+
workspaceMemberId,
|
|
214
|
+
role: string(),
|
|
215
|
+
})
|
|
216
|
+
.handle(
|
|
217
|
+
ONLY_SERVER &&
|
|
218
|
+
(async (ctx, params) => {
|
|
219
|
+
await ctx.roleChanged.emit({
|
|
220
|
+
workspaceMemberId: params.workspaceMemberId,
|
|
221
|
+
role: params.role,
|
|
222
|
+
});
|
|
223
|
+
}),
|
|
224
|
+
),
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
.mutateMethod(
|
|
228
|
+
"switchWorkspace",
|
|
229
|
+
(fn) =>
|
|
230
|
+
fn.withParams({ workspaceMemberId }).handle(
|
|
231
|
+
ONLY_SERVER &&
|
|
232
|
+
(async (ctx, params) => {
|
|
233
|
+
const member = await ctx.$query.findOne({
|
|
234
|
+
_id: params.workspaceMemberId,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
if (!member) {
|
|
238
|
+
return { error: "NOT_A_MEMBER" as const };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (
|
|
242
|
+
ctx.$auth.params.accountId &&
|
|
243
|
+
ctx.$auth.params.accountId !== member.accountId
|
|
244
|
+
) {
|
|
245
|
+
return { error: "NOT_A_MEMBER" as const };
|
|
246
|
+
}
|
|
245
247
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
248
|
+
const token = workspaceToken.generateJWT({
|
|
249
|
+
accountId: member.accountId,
|
|
250
|
+
workspaceId: member.workspaceId,
|
|
251
|
+
role: member.role,
|
|
252
|
+
workspaceType: member.workspaceType,
|
|
253
|
+
});
|
|
249
254
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
;
|
|
255
|
+
return { token };
|
|
256
|
+
}),
|
|
257
|
+
),
|
|
258
|
+
)
|
|
255
259
|
|
|
256
|
-
|
|
260
|
+
.protectBy(userToken, (params) => ({ accountId: params.accountId }))
|
|
261
|
+
.protectBy(workspaceToken, (params) => ({
|
|
262
|
+
workspaceId: params.workspaceId,
|
|
263
|
+
}))
|
|
264
|
+
.clientQuery("getAll", (fn) => fn.handle(async (ctx) => ctx.$query.find({})))
|
|
265
|
+
.clientQuery("getByWorkspace", (fn) => fn.handle(async (ctx) => ctx.$query.find({})))
|
|
266
|
+
);
|
|
257
267
|
};
|
|
258
268
|
|
|
259
269
|
export type WorkspaceMemberAggregate<
|
|
@@ -16,7 +16,7 @@ export const createWorkspaceAggregate = <
|
|
|
16
16
|
) => {
|
|
17
17
|
const { workspaceId, workspaceMemberId, accountId, workspaceToken } = data;
|
|
18
18
|
|
|
19
|
-
return aggregate(`${data.name}Workspaces`, workspaceId, {
|
|
19
|
+
return aggregate(`${data.name as Data["name"]}Workspaces`, workspaceId, {
|
|
20
20
|
name: string().minLength(1).maxLength(100),
|
|
21
21
|
type: string(),
|
|
22
22
|
ownerId: accountId,
|
|
@@ -59,95 +59,96 @@ export const createWorkspaceAggregate = <
|
|
|
59
59
|
|
|
60
60
|
.mutateMethod(
|
|
61
61
|
"create",
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
62
|
+
(fn) =>
|
|
63
|
+
fn
|
|
64
|
+
.withParams({
|
|
65
|
+
name: string().minLength(1).maxLength(100),
|
|
66
|
+
type: string(),
|
|
67
|
+
})
|
|
68
|
+
.handle(
|
|
69
|
+
ONLY_SERVER &&
|
|
70
|
+
(async (ctx, params) => {
|
|
71
|
+
const id = workspaceId.generate();
|
|
72
|
+
const ownerMemberId = workspaceMemberId.generate();
|
|
73
|
+
const ownerId = ctx.$auth.params.accountId;
|
|
74
|
+
|
|
75
|
+
await ctx.workspaceCreated.emit({
|
|
76
|
+
workspaceId: id,
|
|
77
|
+
ownerMemberId,
|
|
78
|
+
name: params.name,
|
|
79
|
+
type: params.type,
|
|
80
|
+
ownerId,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const token = workspaceToken.generateJWT({
|
|
84
|
+
accountId: ownerId,
|
|
85
|
+
workspaceId: id,
|
|
86
|
+
role: "owner",
|
|
87
|
+
workspaceType: params.type,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return { workspaceId: id, token, accountId: ownerId };
|
|
91
|
+
}),
|
|
92
|
+
),
|
|
91
93
|
)
|
|
92
94
|
|
|
93
95
|
.mutateMethod(
|
|
94
96
|
"rename",
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
97
|
+
(fn) =>
|
|
98
|
+
fn
|
|
99
|
+
.withParams({
|
|
100
|
+
workspaceId,
|
|
101
|
+
name: string().minLength(1).maxLength(100),
|
|
102
|
+
})
|
|
103
|
+
.handle(
|
|
104
|
+
ONLY_SERVER &&
|
|
105
|
+
(async (ctx, params) => {
|
|
106
|
+
const existing = await ctx.$query.findOne({
|
|
107
|
+
_id: params.workspaceId,
|
|
108
|
+
});
|
|
109
|
+
if (!existing) {
|
|
110
|
+
return { error: "WORKSPACE_NOT_FOUND" as const };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
await ctx.workspaceRenamed.emit({
|
|
114
|
+
workspaceId: params.workspaceId,
|
|
115
|
+
name: params.name,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return { success: true };
|
|
119
|
+
}),
|
|
120
|
+
),
|
|
117
121
|
)
|
|
118
122
|
|
|
119
|
-
.publicEvent(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
isArchived: true,
|
|
125
|
-
});
|
|
126
|
-
},
|
|
127
|
-
)
|
|
123
|
+
.publicEvent("workspaceArchived", { workspaceId }, async (ctx, event) => {
|
|
124
|
+
await ctx.modify(event.payload.workspaceId, {
|
|
125
|
+
isArchived: true,
|
|
126
|
+
});
|
|
127
|
+
})
|
|
128
128
|
|
|
129
129
|
.mutateMethod(
|
|
130
130
|
"archive",
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
131
|
+
(fn) =>
|
|
132
|
+
fn.withParams({ workspaceId }).handle(
|
|
133
|
+
ONLY_SERVER &&
|
|
134
|
+
(async (ctx, params) => {
|
|
135
|
+
const existing = await ctx.$query.findOne({
|
|
136
|
+
_id: params.workspaceId,
|
|
137
|
+
});
|
|
138
|
+
if (!existing) {
|
|
139
|
+
return { error: "WORKSPACE_NOT_FOUND" as const };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
await ctx.workspaceArchived.emit({
|
|
143
|
+
workspaceId: params.workspaceId,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
return { success: true };
|
|
147
|
+
}),
|
|
148
|
+
),
|
|
147
149
|
)
|
|
148
150
|
|
|
149
|
-
.clientQuery("getAll", async (ctx) => ctx.$query.find({}))
|
|
150
|
-
;
|
|
151
|
+
.clientQuery("getAll", (fn) => fn.handle(async (ctx) => ctx.$query.find({})));
|
|
151
152
|
};
|
|
152
153
|
|
|
153
154
|
export type WorkspaceAggregate<
|
package/src/workspace-builder.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
2
|
context,
|
|
3
|
-
type ArcAggregateElement,
|
|
4
3
|
type ArcContextElement,
|
|
5
4
|
} from "@arcote.tech/arc";
|
|
6
5
|
import type { AccountId, Token } from "@arcote.tech/arc-auth";
|
|
@@ -17,9 +16,9 @@ export class WorkspaceBuilder<
|
|
|
17
16
|
WsMemberId,
|
|
18
17
|
WsInvId,
|
|
19
18
|
WsToken,
|
|
20
|
-
Workspace
|
|
21
|
-
WorkspaceMember
|
|
22
|
-
WorkspaceInvitation
|
|
19
|
+
Workspace,
|
|
20
|
+
WorkspaceMember,
|
|
21
|
+
WorkspaceInvitation,
|
|
23
22
|
Types extends readonly string[],
|
|
24
23
|
Elements extends ArcContextElement<any>[],
|
|
25
24
|
> {
|