@fonderie/workspaces 5.3.2 → 6.0.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/brain/outcomes.md +14 -14
- package/brain/signatures.md +4 -0
- package/dist/index.cjs +222 -118
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -4
- package/dist/index.d.ts +10 -4
- package/dist/index.js +222 -119
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.cjs
CHANGED
|
@@ -29,6 +29,7 @@ __export(index_exports, {
|
|
|
29
29
|
importMembership: () => importMembership,
|
|
30
30
|
importRole: () => importRole,
|
|
31
31
|
importWorkspace: () => importWorkspace,
|
|
32
|
+
requireManager: () => requireManager,
|
|
32
33
|
requireWorkspace: () => requireWorkspace,
|
|
33
34
|
schemas: () => schemas_exports,
|
|
34
35
|
toInvitationDTO: () => toInvitationDTO,
|
|
@@ -51,6 +52,7 @@ var EVENT_KEYS = {
|
|
|
51
52
|
|
|
52
53
|
// src/routes.ts
|
|
53
54
|
var import_middlewares = require("@fonderie/core/middlewares");
|
|
55
|
+
var import_rate_limit = require("@fonderie/rate-limit");
|
|
54
56
|
|
|
55
57
|
// src/schemas.ts
|
|
56
58
|
var schemas_exports = {};
|
|
@@ -107,7 +109,10 @@ var createInvitationsSchema = import_zod.z.union([
|
|
|
107
109
|
inviteEntry,
|
|
108
110
|
import_zod.z.array(inviteEntry).min(1, "at least one invite is required")
|
|
109
111
|
]);
|
|
110
|
-
var acceptInvitationSchema = import_zod.z.
|
|
112
|
+
var acceptInvitationSchema = import_zod.z.union([
|
|
113
|
+
import_zod.z.object({ pin: import_zod.z.string().trim().min(1, "pin is required") }),
|
|
114
|
+
import_zod.z.object({ token: import_zod.z.string().trim().min(1, "token is required") })
|
|
115
|
+
]);
|
|
111
116
|
var createRoleSchema = import_zod.z.object({
|
|
112
117
|
name,
|
|
113
118
|
description: import_zod.z.string().max(1e3).optional()
|
|
@@ -460,8 +465,45 @@ function withWorkspace(store, ctx, next) {
|
|
|
460
465
|
return handler;
|
|
461
466
|
}
|
|
462
467
|
|
|
463
|
-
// src/
|
|
468
|
+
// src/middlewares/require-manager.ts
|
|
464
469
|
var import_core2 = require("@fonderie/core");
|
|
470
|
+
function requireManager(store, config) {
|
|
471
|
+
const managerRoles = config.managerRoles ?? ["ADMIN"];
|
|
472
|
+
return async (ctx, next) => {
|
|
473
|
+
if (config.management === "any-member") return next();
|
|
474
|
+
if (!ctx.user) {
|
|
475
|
+
return (0, import_core2.setApiResponse)(import_core2.HTTP.UNAUTHORIZED, "UNAUTHORIZED", "Unauthorized");
|
|
476
|
+
}
|
|
477
|
+
if (!ctx.workspace) return next();
|
|
478
|
+
const ownerId = ctx.workspace.ownerId;
|
|
479
|
+
if (ownerId && ownerId === ctx.user.id) return next();
|
|
480
|
+
const [row] = await store.query(
|
|
481
|
+
`SELECT 1 AS ok
|
|
482
|
+
FROM fonderie_role_user_workspaces ruw
|
|
483
|
+
JOIN fonderie_roles r ON r.id = ruw.role_id
|
|
484
|
+
WHERE ruw.user_id = $1
|
|
485
|
+
AND ruw.workspace_id = $2
|
|
486
|
+
AND ruw.removed = false
|
|
487
|
+
AND ruw.suspended = false
|
|
488
|
+
AND r.is_system = true
|
|
489
|
+
AND r.active = true
|
|
490
|
+
AND r.name = ANY($3)
|
|
491
|
+
LIMIT 1`,
|
|
492
|
+
[ctx.user.id, ctx.workspace.id, managerRoles]
|
|
493
|
+
);
|
|
494
|
+
if (!row) {
|
|
495
|
+
return (0, import_core2.setApiResponse)(
|
|
496
|
+
import_core2.HTTP.FORBIDDEN,
|
|
497
|
+
"MANAGER_REQUIRED",
|
|
498
|
+
"This action requires the workspace owner or an admin role"
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
return next();
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// src/controllers/workspace.controller.ts
|
|
506
|
+
var import_core3 = require("@fonderie/core");
|
|
465
507
|
|
|
466
508
|
// src/models/workspace.model.ts
|
|
467
509
|
var WorkspaceModel = class {
|
|
@@ -739,7 +781,11 @@ function toInvitationDTO(inv) {
|
|
|
739
781
|
workspaceId: (0, import_parser.stringOrEmpty)(inv.workspaceId),
|
|
740
782
|
email: (0, import_parser.stringOrEmpty)(inv.email),
|
|
741
783
|
roleId: (0, import_parser.stringOrEmpty)(inv.roleId),
|
|
742
|
-
token
|
|
784
|
+
// The accept token is a bearer credential delivered to the INVITEE by
|
|
785
|
+
// email — never expose it through the API. Listing invitations used to
|
|
786
|
+
// leak it, letting any member accept (hijack) someone else's pending
|
|
787
|
+
// invite. The field stays for DTO-shape compatibility, always empty.
|
|
788
|
+
token: "",
|
|
743
789
|
status: (0, import_parser.stringOrEmpty)(inv.status),
|
|
744
790
|
expiresAt: (0, import_parser.dateOrEmpty)(inv.expiresAt),
|
|
745
791
|
createdAt: (0, import_parser.dateOrEmpty)(inv.createdAt)
|
|
@@ -763,9 +809,9 @@ function workspaceController(store, config) {
|
|
|
763
809
|
return {
|
|
764
810
|
async list(ctx) {
|
|
765
811
|
if (!ctx.user)
|
|
766
|
-
return (0,
|
|
812
|
+
return (0, import_core3.setApiResponse)(import_core3.HTTP.UNAUTHORIZED, "UNAUTHORIZED", "Authentication required");
|
|
767
813
|
const list = await workspaces.findByUserId(ctx.user.id);
|
|
768
|
-
return (0,
|
|
814
|
+
return (0, import_core3.setApiResponse)(import_core3.HTTP.OK, "WORKSPACES_FETCHED", "Workspaces retrieved successfully.", {
|
|
769
815
|
workspaces: list.map(toWorkspaceDTO)
|
|
770
816
|
});
|
|
771
817
|
},
|
|
@@ -775,11 +821,11 @@ function workspaceController(store, config) {
|
|
|
775
821
|
const description = body?.["description"];
|
|
776
822
|
const type = body?.["type"];
|
|
777
823
|
if (typeof name2 !== "string" || name2.trim().length === 0) {
|
|
778
|
-
return (0,
|
|
824
|
+
return (0, import_core3.setApiResponse)(import_core3.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "name is required");
|
|
779
825
|
}
|
|
780
826
|
if (type === "PERSONAL") {
|
|
781
|
-
return (0,
|
|
782
|
-
|
|
827
|
+
return (0, import_core3.setApiResponse)(
|
|
828
|
+
import_core3.HTTP.UNPROCESSABLE,
|
|
783
829
|
"INVALID_PARAMETER",
|
|
784
830
|
"Personal workspaces are created automatically"
|
|
785
831
|
);
|
|
@@ -802,18 +848,18 @@ function workspaceController(store, config) {
|
|
|
802
848
|
await memModel.add({ userId: ctx.user.id, workspaceId: ws.id, roleId: adminRole.id });
|
|
803
849
|
return ws;
|
|
804
850
|
});
|
|
805
|
-
return (0,
|
|
851
|
+
return (0, import_core3.setApiResponse)(import_core3.HTTP.CREATED, "WORKSPACE_CREATED", "Workspace created successfully.", {
|
|
806
852
|
workspace: toWorkspaceDTO(workspace)
|
|
807
853
|
});
|
|
808
854
|
},
|
|
809
855
|
async get(ctx) {
|
|
810
|
-
if (!ctx.workspace) return (0,
|
|
811
|
-
return (0,
|
|
856
|
+
if (!ctx.workspace) return (0, import_core3.setApiResponse)(import_core3.HTTP.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
857
|
+
return (0, import_core3.setApiResponse)(import_core3.HTTP.OK, "WORKSPACE_FETCHED", "Workspace retrieved successfully.", {
|
|
812
858
|
workspace: toWorkspaceDTO(ctx.workspace)
|
|
813
859
|
});
|
|
814
860
|
},
|
|
815
861
|
async update(ctx) {
|
|
816
|
-
if (!ctx.workspace) return (0,
|
|
862
|
+
if (!ctx.workspace) return (0, import_core3.setApiResponse)(import_core3.HTTP.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
817
863
|
const body = ctx.meta["body"];
|
|
818
864
|
const opts = {};
|
|
819
865
|
if (typeof body?.["name"] === "string") opts.name = body["name"].trim();
|
|
@@ -828,33 +874,33 @@ function workspaceController(store, config) {
|
|
|
828
874
|
if (body?.["address"] !== void 0 && typeof body["address"] === "object")
|
|
829
875
|
opts.address = body["address"] ?? null;
|
|
830
876
|
const workspace = await workspaces.update(ctx.workspace.id, opts);
|
|
831
|
-
if (!workspace) return (0,
|
|
832
|
-
return (0,
|
|
877
|
+
if (!workspace) return (0, import_core3.setApiResponse)(import_core3.HTTP.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
878
|
+
return (0, import_core3.setApiResponse)(import_core3.HTTP.OK, "WORKSPACE_UPDATED", "Workspace updated successfully.", {
|
|
833
879
|
workspace: toWorkspaceDTO(workspace)
|
|
834
880
|
});
|
|
835
881
|
},
|
|
836
882
|
async archive(ctx) {
|
|
837
|
-
if (!ctx.workspace) return (0,
|
|
883
|
+
if (!ctx.workspace) return (0, import_core3.setApiResponse)(import_core3.HTTP.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
838
884
|
if (ctx.workspace.isPersonal) {
|
|
839
|
-
return (0,
|
|
840
|
-
|
|
885
|
+
return (0, import_core3.setApiResponse)(
|
|
886
|
+
import_core3.HTTP.FORBIDDEN,
|
|
841
887
|
"FORBIDDEN",
|
|
842
888
|
"Personal workspaces cannot be archived"
|
|
843
889
|
);
|
|
844
890
|
}
|
|
845
891
|
await workspaces.archive(ctx.workspace.id, ctx.user.id);
|
|
846
|
-
return (0,
|
|
892
|
+
return (0, import_core3.setApiResponse)(import_core3.HTTP.OK, "WORKSPACE_ARCHIVED", "Workspace archived successfully.");
|
|
847
893
|
},
|
|
848
894
|
async restore(ctx) {
|
|
849
|
-
if (!ctx.workspace) return (0,
|
|
895
|
+
if (!ctx.workspace) return (0, import_core3.setApiResponse)(import_core3.HTTP.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
850
896
|
await workspaces.restore(ctx.workspace.id);
|
|
851
|
-
return (0,
|
|
897
|
+
return (0, import_core3.setApiResponse)(import_core3.HTTP.OK, "WORKSPACE_RESTORED", "Workspace restored successfully.");
|
|
852
898
|
},
|
|
853
899
|
async getSettings(ctx) {
|
|
854
|
-
if (!ctx.workspace) return (0,
|
|
900
|
+
if (!ctx.workspace) return (0, import_core3.setApiResponse)(import_core3.HTTP.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
855
901
|
const settings = await workspaces.getSettings(ctx.workspace.id);
|
|
856
|
-
return (0,
|
|
857
|
-
|
|
902
|
+
return (0, import_core3.setApiResponse)(
|
|
903
|
+
import_core3.HTTP.OK,
|
|
858
904
|
"SETTINGS_FETCHED",
|
|
859
905
|
"Workspace settings retrieved successfully.",
|
|
860
906
|
{
|
|
@@ -863,18 +909,18 @@ function workspaceController(store, config) {
|
|
|
863
909
|
);
|
|
864
910
|
},
|
|
865
911
|
async updateSettings(ctx) {
|
|
866
|
-
if (!ctx.workspace) return (0,
|
|
912
|
+
if (!ctx.workspace) return (0, import_core3.setApiResponse)(import_core3.HTTP.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
867
913
|
const body = ctx.meta["body"];
|
|
868
914
|
if (!body || Object.keys(body).length === 0) {
|
|
869
|
-
return (0,
|
|
915
|
+
return (0, import_core3.setApiResponse)(import_core3.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "No settings provided");
|
|
870
916
|
}
|
|
871
917
|
const patch = {};
|
|
872
918
|
for (const key of ["locale", "timezone", "currency", "dateFormat", "timeFormat"]) {
|
|
873
919
|
if (typeof body[key] === "string") patch[key] = body[key];
|
|
874
920
|
}
|
|
875
921
|
const settings = await workspaces.updateSettings(ctx.workspace.id, patch);
|
|
876
|
-
return (0,
|
|
877
|
-
|
|
922
|
+
return (0, import_core3.setApiResponse)(
|
|
923
|
+
import_core3.HTTP.OK,
|
|
878
924
|
"SETTINGS_UPDATED",
|
|
879
925
|
"Workspace settings updated successfully.",
|
|
880
926
|
{
|
|
@@ -886,22 +932,22 @@ function workspaceController(store, config) {
|
|
|
886
932
|
}
|
|
887
933
|
|
|
888
934
|
// src/controllers/member.controller.ts
|
|
889
|
-
var
|
|
935
|
+
var import_core4 = require("@fonderie/core");
|
|
890
936
|
function memberController(store) {
|
|
891
937
|
const members = new MemberModel(store);
|
|
892
938
|
return {
|
|
893
939
|
async list(ctx) {
|
|
894
|
-
if (!ctx.workspace) return (0,
|
|
940
|
+
if (!ctx.workspace) return (0, import_core4.setApiResponse)(import_core4.HTTP.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
895
941
|
const list = await members.list(ctx.workspace.id);
|
|
896
|
-
return (0,
|
|
942
|
+
return (0, import_core4.setApiResponse)(import_core4.HTTP.OK, "MEMBERS_FETCHED", "Members retrieved successfully.", {
|
|
897
943
|
members: list.map(toMemberDTO)
|
|
898
944
|
});
|
|
899
945
|
},
|
|
900
946
|
async remove(ctx) {
|
|
901
|
-
if (!ctx.workspace) return (0,
|
|
947
|
+
if (!ctx.workspace) return (0, import_core4.setApiResponse)(import_core4.HTTP.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
902
948
|
if (ctx.workspace.isPersonal) {
|
|
903
|
-
return (0,
|
|
904
|
-
|
|
949
|
+
return (0, import_core4.setApiResponse)(
|
|
950
|
+
import_core4.HTTP.FORBIDDEN,
|
|
905
951
|
"FORBIDDEN",
|
|
906
952
|
"Personal workspaces do not support member management"
|
|
907
953
|
);
|
|
@@ -909,78 +955,86 @@ function memberController(store) {
|
|
|
909
955
|
const params = ctx.meta["params"];
|
|
910
956
|
const userId = params?.["userId"];
|
|
911
957
|
if (!userId)
|
|
912
|
-
return (0,
|
|
958
|
+
return (0, import_core4.setApiResponse)(import_core4.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "userId is required");
|
|
913
959
|
if (userId === ctx.user?.id) {
|
|
914
|
-
return (0,
|
|
960
|
+
return (0, import_core4.setApiResponse)(import_core4.HTTP.BAD_REQUEST, "INVALID_OPERATION", "Cannot remove yourself");
|
|
961
|
+
}
|
|
962
|
+
const ownerId = ctx.workspace.ownerId;
|
|
963
|
+
if (ownerId && userId === ownerId) {
|
|
964
|
+
return (0, import_core4.setApiResponse)(
|
|
965
|
+
import_core4.HTTP.BAD_REQUEST,
|
|
966
|
+
"INVALID_OPERATION",
|
|
967
|
+
"Cannot remove the workspace owner"
|
|
968
|
+
);
|
|
915
969
|
}
|
|
916
970
|
await members.remove(userId, ctx.workspace.id);
|
|
917
|
-
return (0,
|
|
971
|
+
return (0, import_core4.setApiResponse)(import_core4.HTTP.OK, "MEMBER_REMOVED", "Member removed successfully.");
|
|
918
972
|
},
|
|
919
973
|
async getUserRoles(ctx) {
|
|
920
|
-
if (!ctx.workspace) return (0,
|
|
974
|
+
if (!ctx.workspace) return (0, import_core4.setApiResponse)(import_core4.HTTP.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
921
975
|
const params = ctx.meta["params"];
|
|
922
976
|
const userId = params?.["userId"];
|
|
923
977
|
if (!userId)
|
|
924
|
-
return (0,
|
|
978
|
+
return (0, import_core4.setApiResponse)(import_core4.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "userId is required");
|
|
925
979
|
const roles = await members.getUserRoles(userId, ctx.workspace.id);
|
|
926
|
-
return (0,
|
|
980
|
+
return (0, import_core4.setApiResponse)(import_core4.HTTP.OK, "ROLES_FETCHED", "Member roles retrieved successfully.", {
|
|
927
981
|
roles: roles.map(toRoleDTO)
|
|
928
982
|
});
|
|
929
983
|
},
|
|
930
984
|
async addRole(ctx) {
|
|
931
|
-
if (!ctx.workspace) return (0,
|
|
985
|
+
if (!ctx.workspace) return (0, import_core4.setApiResponse)(import_core4.HTTP.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
932
986
|
const params = ctx.meta["params"];
|
|
933
987
|
const body = ctx.meta["body"];
|
|
934
988
|
const userId = params?.["userId"];
|
|
935
989
|
const roleId = body?.["roleId"] ?? params?.["roleId"];
|
|
936
990
|
if (!userId)
|
|
937
|
-
return (0,
|
|
991
|
+
return (0, import_core4.setApiResponse)(import_core4.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "userId is required");
|
|
938
992
|
if (!roleId)
|
|
939
|
-
return (0,
|
|
993
|
+
return (0, import_core4.setApiResponse)(import_core4.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "roleId is required");
|
|
940
994
|
const assigned = await members.addRole(userId, ctx.workspace.id, roleId);
|
|
941
995
|
if (!assigned) {
|
|
942
|
-
return (0,
|
|
943
|
-
|
|
996
|
+
return (0, import_core4.setApiResponse)(
|
|
997
|
+
import_core4.HTTP.UNPROCESSABLE,
|
|
944
998
|
"INVALID_ROLE",
|
|
945
999
|
"Role is not assignable in this workspace."
|
|
946
1000
|
);
|
|
947
1001
|
}
|
|
948
|
-
return (0,
|
|
1002
|
+
return (0, import_core4.setApiResponse)(import_core4.HTTP.OK, "ROLE_ASSIGNED", "Role assigned successfully.");
|
|
949
1003
|
},
|
|
950
1004
|
async removeRole(ctx) {
|
|
951
|
-
if (!ctx.workspace) return (0,
|
|
1005
|
+
if (!ctx.workspace) return (0, import_core4.setApiResponse)(import_core4.HTTP.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
952
1006
|
const params = ctx.meta["params"];
|
|
953
1007
|
const userId = params?.["userId"];
|
|
954
1008
|
const roleId = params?.["roleId"];
|
|
955
1009
|
if (!userId)
|
|
956
|
-
return (0,
|
|
1010
|
+
return (0, import_core4.setApiResponse)(import_core4.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "userId is required");
|
|
957
1011
|
if (!roleId)
|
|
958
|
-
return (0,
|
|
1012
|
+
return (0, import_core4.setApiResponse)(import_core4.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "roleId is required");
|
|
959
1013
|
try {
|
|
960
1014
|
await members.removeRole(userId, ctx.workspace.id, roleId);
|
|
961
|
-
return (0,
|
|
1015
|
+
return (0, import_core4.setApiResponse)(import_core4.HTTP.OK, "ROLE_REMOVED", "Role removed successfully.");
|
|
962
1016
|
} catch (err) {
|
|
963
1017
|
const message = err instanceof Error ? err.message : "Failed";
|
|
964
|
-
return (0,
|
|
1018
|
+
return (0, import_core4.setApiResponse)(import_core4.HTTP.BAD_REQUEST, "OPERATION_FAILED", message);
|
|
965
1019
|
}
|
|
966
1020
|
}
|
|
967
1021
|
};
|
|
968
1022
|
}
|
|
969
1023
|
|
|
970
1024
|
// src/controllers/role.controller.ts
|
|
971
|
-
var
|
|
1025
|
+
var import_core5 = require("@fonderie/core");
|
|
972
1026
|
function roleController(store) {
|
|
973
1027
|
const roles = new RoleModel(store);
|
|
974
1028
|
return {
|
|
975
1029
|
async create(ctx) {
|
|
976
1030
|
if (!ctx.workspace) {
|
|
977
|
-
return (0,
|
|
1031
|
+
return (0, import_core5.setApiResponse)(import_core5.HTTP.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
978
1032
|
}
|
|
979
1033
|
const body = ctx.meta["body"];
|
|
980
1034
|
const name2 = body?.["name"];
|
|
981
1035
|
const description = body?.["description"];
|
|
982
1036
|
if (typeof name2 !== "string" || name2.trim().length === 0) {
|
|
983
|
-
return (0,
|
|
1037
|
+
return (0, import_core5.setApiResponse)(import_core5.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "name is required");
|
|
984
1038
|
}
|
|
985
1039
|
try {
|
|
986
1040
|
const opts = {
|
|
@@ -991,49 +1045,49 @@ function roleController(store) {
|
|
|
991
1045
|
opts.description = description;
|
|
992
1046
|
}
|
|
993
1047
|
const role = await roles.create(opts);
|
|
994
|
-
return (0,
|
|
1048
|
+
return (0, import_core5.setApiResponse)(import_core5.HTTP.CREATED, "ROLE_CREATED", "Role created successfully.", {
|
|
995
1049
|
role: toRoleDTO(role)
|
|
996
1050
|
});
|
|
997
1051
|
} catch (err) {
|
|
998
1052
|
const message = err instanceof Error ? err.message : "Failed to create role";
|
|
999
|
-
return (0,
|
|
1053
|
+
return (0, import_core5.setApiResponse)(import_core5.HTTP.BAD_REQUEST, "OPERATION_FAILED", message);
|
|
1000
1054
|
}
|
|
1001
1055
|
},
|
|
1002
1056
|
async list(ctx) {
|
|
1003
1057
|
if (!ctx.workspace) {
|
|
1004
|
-
return (0,
|
|
1058
|
+
return (0, import_core5.setApiResponse)(import_core5.HTTP.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
1005
1059
|
}
|
|
1006
1060
|
const list = await roles.list(ctx.workspace.id);
|
|
1007
|
-
return (0,
|
|
1061
|
+
return (0, import_core5.setApiResponse)(import_core5.HTTP.OK, "ROLES_FETCHED", "Roles retrieved successfully.", {
|
|
1008
1062
|
roles: list.map(toRoleDTO)
|
|
1009
1063
|
});
|
|
1010
1064
|
},
|
|
1011
1065
|
async get(ctx) {
|
|
1012
1066
|
if (!ctx.workspace) {
|
|
1013
|
-
return (0,
|
|
1067
|
+
return (0, import_core5.setApiResponse)(import_core5.HTTP.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
1014
1068
|
}
|
|
1015
1069
|
const params = ctx.meta["params"];
|
|
1016
1070
|
const roleId = params?.["roleId"];
|
|
1017
1071
|
if (!roleId) {
|
|
1018
|
-
return (0,
|
|
1072
|
+
return (0, import_core5.setApiResponse)(import_core5.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "roleId is required");
|
|
1019
1073
|
}
|
|
1020
1074
|
const role = await roles.findById(roleId, ctx.workspace.id);
|
|
1021
1075
|
if (!role) {
|
|
1022
|
-
return (0,
|
|
1076
|
+
return (0, import_core5.setApiResponse)(import_core5.HTTP.NOT_FOUND, "NOT_FOUND", "Role not found");
|
|
1023
1077
|
}
|
|
1024
|
-
return (0,
|
|
1078
|
+
return (0, import_core5.setApiResponse)(import_core5.HTTP.OK, "ROLE_FETCHED", "Role retrieved successfully.", {
|
|
1025
1079
|
role: toRoleDTO(role)
|
|
1026
1080
|
});
|
|
1027
1081
|
},
|
|
1028
1082
|
async update(ctx) {
|
|
1029
1083
|
if (!ctx.workspace) {
|
|
1030
|
-
return (0,
|
|
1084
|
+
return (0, import_core5.setApiResponse)(import_core5.HTTP.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
1031
1085
|
}
|
|
1032
1086
|
const params = ctx.meta["params"];
|
|
1033
1087
|
const body = ctx.meta["body"];
|
|
1034
1088
|
const roleId = params?.["roleId"];
|
|
1035
1089
|
if (!roleId) {
|
|
1036
|
-
return (0,
|
|
1090
|
+
return (0, import_core5.setApiResponse)(import_core5.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "roleId is required");
|
|
1037
1091
|
}
|
|
1038
1092
|
const opts = {};
|
|
1039
1093
|
if (typeof body?.["name"] === "string") {
|
|
@@ -1050,60 +1104,60 @@ function roleController(store) {
|
|
|
1050
1104
|
}
|
|
1051
1105
|
const role = await roles.update(roleId, ctx.workspace.id, opts);
|
|
1052
1106
|
if (!role) {
|
|
1053
|
-
return (0,
|
|
1107
|
+
return (0, import_core5.setApiResponse)(import_core5.HTTP.NOT_FOUND, "NOT_FOUND", "Role not found or is a system role");
|
|
1054
1108
|
}
|
|
1055
|
-
return (0,
|
|
1109
|
+
return (0, import_core5.setApiResponse)(import_core5.HTTP.OK, "ROLE_UPDATED", "Role updated successfully.", {
|
|
1056
1110
|
role: toRoleDTO(role)
|
|
1057
1111
|
});
|
|
1058
1112
|
},
|
|
1059
1113
|
async remove(ctx) {
|
|
1060
1114
|
if (!ctx.workspace) {
|
|
1061
|
-
return (0,
|
|
1115
|
+
return (0, import_core5.setApiResponse)(import_core5.HTTP.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
1062
1116
|
}
|
|
1063
1117
|
const params = ctx.meta["params"];
|
|
1064
1118
|
const roleId = params?.["roleId"];
|
|
1065
1119
|
if (!roleId) {
|
|
1066
|
-
return (0,
|
|
1120
|
+
return (0, import_core5.setApiResponse)(import_core5.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "roleId is required");
|
|
1067
1121
|
}
|
|
1068
1122
|
await roles.delete(roleId, ctx.workspace.id);
|
|
1069
|
-
return (0,
|
|
1123
|
+
return (0, import_core5.setApiResponse)(import_core5.HTTP.OK, "ROLE_DELETED", "Role deleted successfully.");
|
|
1070
1124
|
},
|
|
1071
1125
|
async getPermissions(ctx) {
|
|
1072
1126
|
if (!ctx.workspace) {
|
|
1073
|
-
return (0,
|
|
1127
|
+
return (0, import_core5.setApiResponse)(import_core5.HTTP.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
1074
1128
|
}
|
|
1075
1129
|
const params = ctx.meta["params"];
|
|
1076
1130
|
const roleId = params?.["roleId"];
|
|
1077
1131
|
if (!roleId) {
|
|
1078
|
-
return (0,
|
|
1132
|
+
return (0, import_core5.setApiResponse)(import_core5.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "roleId is required");
|
|
1079
1133
|
}
|
|
1080
1134
|
const role = await roles.findById(roleId, ctx.workspace.id);
|
|
1081
1135
|
if (!role) {
|
|
1082
|
-
return (0,
|
|
1136
|
+
return (0, import_core5.setApiResponse)(import_core5.HTTP.NOT_FOUND, "NOT_FOUND", "Role not found");
|
|
1083
1137
|
}
|
|
1084
1138
|
const permissions = await roles.getPermissions(roleId, ctx.workspace.id);
|
|
1085
|
-
return (0,
|
|
1139
|
+
return (0, import_core5.setApiResponse)(import_core5.HTTP.OK, "PERMISSIONS_FETCHED", "Role permissions retrieved successfully.", {
|
|
1086
1140
|
permissions
|
|
1087
1141
|
});
|
|
1088
1142
|
},
|
|
1089
1143
|
async setPermissions(ctx) {
|
|
1090
1144
|
if (!ctx.workspace) {
|
|
1091
|
-
return (0,
|
|
1145
|
+
return (0, import_core5.setApiResponse)(import_core5.HTTP.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
1092
1146
|
}
|
|
1093
1147
|
const params = ctx.meta["params"];
|
|
1094
1148
|
const body = ctx.meta["body"];
|
|
1095
1149
|
const roleId = params?.["roleId"];
|
|
1096
1150
|
if (!roleId) {
|
|
1097
|
-
return (0,
|
|
1151
|
+
return (0, import_core5.setApiResponse)(import_core5.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "roleId is required");
|
|
1098
1152
|
}
|
|
1099
1153
|
const target = await roles.findById(roleId, ctx.workspace.id);
|
|
1100
1154
|
if (!target || target.isSystem) {
|
|
1101
|
-
return (0,
|
|
1155
|
+
return (0, import_core5.setApiResponse)(import_core5.HTTP.NOT_FOUND, "NOT_FOUND", "Role not found");
|
|
1102
1156
|
}
|
|
1103
1157
|
const perms = body?.["permissions"];
|
|
1104
1158
|
if (!Array.isArray(perms)) {
|
|
1105
|
-
return (0,
|
|
1106
|
-
|
|
1159
|
+
return (0, import_core5.setApiResponse)(
|
|
1160
|
+
import_core5.HTTP.UNPROCESSABLE,
|
|
1107
1161
|
"INVALID_PARAMETER",
|
|
1108
1162
|
"permissions array is required"
|
|
1109
1163
|
);
|
|
@@ -1119,13 +1173,13 @@ function roleController(store) {
|
|
|
1119
1173
|
};
|
|
1120
1174
|
}).filter((p) => p.permissionKey.length > 0);
|
|
1121
1175
|
await roles.setPermissions(roleId, ctx.workspace.id, normalized);
|
|
1122
|
-
return (0,
|
|
1176
|
+
return (0, import_core5.setApiResponse)(import_core5.HTTP.OK, "PERMISSIONS_SET", "Role permissions updated successfully.");
|
|
1123
1177
|
}
|
|
1124
1178
|
};
|
|
1125
1179
|
}
|
|
1126
1180
|
|
|
1127
1181
|
// src/controllers/invitation.controller.ts
|
|
1128
|
-
var
|
|
1182
|
+
var import_core6 = require("@fonderie/core");
|
|
1129
1183
|
var import_events = require("@fonderie/events");
|
|
1130
1184
|
|
|
1131
1185
|
// src/services/invitations.ts
|
|
@@ -1134,7 +1188,7 @@ function generateToken() {
|
|
|
1134
1188
|
return (0, import_node_crypto.randomBytes)(32).toString("hex");
|
|
1135
1189
|
}
|
|
1136
1190
|
function generatePin() {
|
|
1137
|
-
return
|
|
1191
|
+
return (0, import_node_crypto.randomInt)(1e5, 1e6).toString();
|
|
1138
1192
|
}
|
|
1139
1193
|
function parseTtl(ttl) {
|
|
1140
1194
|
const units = {
|
|
@@ -1205,8 +1259,8 @@ async function acceptInvitationByPin(opts, store) {
|
|
|
1205
1259
|
const [inv] = await store.query(
|
|
1206
1260
|
`SELECT id, workspace_id AS "workspaceId", role_id AS "roleId", expires_at AS "expiresAt"
|
|
1207
1261
|
FROM fonderie_workspace_invitations
|
|
1208
|
-
WHERE pin = $1 AND status = 'PENDING'`,
|
|
1209
|
-
[opts.pin]
|
|
1262
|
+
WHERE pin = $1 AND lower(email) = lower($2) AND status = 'PENDING'`,
|
|
1263
|
+
[opts.pin, opts.email]
|
|
1210
1264
|
);
|
|
1211
1265
|
if (!inv) throw new Error("Invalid PIN");
|
|
1212
1266
|
if (/* @__PURE__ */ new Date() > new Date(inv.expiresAt)) throw new Error("Invitation expired");
|
|
@@ -1226,6 +1280,31 @@ async function acceptInvitationByPin(opts, store) {
|
|
|
1226
1280
|
});
|
|
1227
1281
|
return { workspaceId: inv.workspaceId, roleId: inv.roleId };
|
|
1228
1282
|
}
|
|
1283
|
+
async function acceptInvitationByToken(token, userId, store) {
|
|
1284
|
+
const [inv] = await store.query(
|
|
1285
|
+
`SELECT id, workspace_id AS "workspaceId", role_id AS "roleId", expires_at AS "expiresAt"
|
|
1286
|
+
FROM fonderie_workspace_invitations
|
|
1287
|
+
WHERE token = $1 AND status = 'PENDING'`,
|
|
1288
|
+
[token]
|
|
1289
|
+
);
|
|
1290
|
+
if (!inv) throw new Error("Invalid token");
|
|
1291
|
+
if (/* @__PURE__ */ new Date() > new Date(inv.expiresAt)) throw new Error("Invitation expired");
|
|
1292
|
+
await store.transaction(async (tx) => {
|
|
1293
|
+
await Promise.all([
|
|
1294
|
+
tx.query(
|
|
1295
|
+
`INSERT INTO fonderie_role_user_workspaces (user_id, workspace_id, role_id, confirmed)
|
|
1296
|
+
VALUES ($1, $2, $3, true)
|
|
1297
|
+
ON CONFLICT (user_id, workspace_id, role_id) DO UPDATE
|
|
1298
|
+
SET confirmed = true, removed = false`,
|
|
1299
|
+
[userId, inv.workspaceId, inv.roleId]
|
|
1300
|
+
),
|
|
1301
|
+
tx.query(`UPDATE fonderie_workspace_invitations SET status = 'ACCEPTED' WHERE id = $1`, [
|
|
1302
|
+
inv.id
|
|
1303
|
+
])
|
|
1304
|
+
]);
|
|
1305
|
+
});
|
|
1306
|
+
return { workspaceId: inv.workspaceId, roleId: inv.roleId };
|
|
1307
|
+
}
|
|
1229
1308
|
|
|
1230
1309
|
// src/models/invitation.model.ts
|
|
1231
1310
|
var InvitationModel = class {
|
|
@@ -1245,6 +1324,9 @@ var InvitationModel = class {
|
|
|
1245
1324
|
acceptByPin(opts) {
|
|
1246
1325
|
return acceptInvitationByPin(opts, this.store);
|
|
1247
1326
|
}
|
|
1327
|
+
acceptByToken(token, userId) {
|
|
1328
|
+
return acceptInvitationByToken(token, userId, this.store);
|
|
1329
|
+
}
|
|
1248
1330
|
};
|
|
1249
1331
|
|
|
1250
1332
|
// src/controllers/invitation.controller.ts
|
|
@@ -1258,17 +1340,17 @@ function invitationController(store, ttl, bus) {
|
|
|
1258
1340
|
const invitations = new InvitationModel(store);
|
|
1259
1341
|
return {
|
|
1260
1342
|
async list(ctx) {
|
|
1261
|
-
if (!ctx.workspace) return (0,
|
|
1343
|
+
if (!ctx.workspace) return (0, import_core6.setApiResponse)(import_core6.HTTP.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
1262
1344
|
const list = await invitations.list(ctx.workspace.id);
|
|
1263
|
-
return (0,
|
|
1345
|
+
return (0, import_core6.setApiResponse)(import_core6.HTTP.OK, "INVITATIONS_FETCHED", "Invitations retrieved successfully.", {
|
|
1264
1346
|
invitations: list.map(toInvitationDTO)
|
|
1265
1347
|
});
|
|
1266
1348
|
},
|
|
1267
1349
|
async invite(ctx) {
|
|
1268
|
-
if (!ctx.workspace) return (0,
|
|
1350
|
+
if (!ctx.workspace) return (0, import_core6.setApiResponse)(import_core6.HTTP.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
1269
1351
|
if (ctx.workspace.isPersonal) {
|
|
1270
|
-
return (0,
|
|
1271
|
-
|
|
1352
|
+
return (0, import_core6.setApiResponse)(
|
|
1353
|
+
import_core6.HTTP.FORBIDDEN,
|
|
1272
1354
|
"FORBIDDEN",
|
|
1273
1355
|
"Personal workspaces do not support invitations"
|
|
1274
1356
|
);
|
|
@@ -1276,11 +1358,11 @@ function invitationController(store, ttl, bus) {
|
|
|
1276
1358
|
const body = ctx.meta["body"];
|
|
1277
1359
|
const entries = Array.isArray(body) ? body : [body];
|
|
1278
1360
|
if (!entries.length) {
|
|
1279
|
-
return (0,
|
|
1361
|
+
return (0, import_core6.setApiResponse)(import_core6.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "at least one invite is required");
|
|
1280
1362
|
}
|
|
1281
1363
|
for (const entry of entries) {
|
|
1282
1364
|
if (typeof entry?.["email"] !== "string") {
|
|
1283
|
-
return (0,
|
|
1365
|
+
return (0, import_core6.setApiResponse)(import_core6.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "email is required for every invite");
|
|
1284
1366
|
}
|
|
1285
1367
|
}
|
|
1286
1368
|
const seatLimit = seatLimitFromMeta(ctx);
|
|
@@ -1293,8 +1375,8 @@ function invitationController(store, ttl, bus) {
|
|
|
1293
1375
|
const total = parseInt(countRow.count, 10);
|
|
1294
1376
|
const occupied = ctx.workspace.isPersonal ? total : Math.max(0, total - 1);
|
|
1295
1377
|
if (occupied + entries.length > seatLimit) {
|
|
1296
|
-
return (0,
|
|
1297
|
-
|
|
1378
|
+
return (0, import_core6.setApiResponse)(
|
|
1379
|
+
import_core6.HTTP.PAYMENT_REQUIRED,
|
|
1298
1380
|
"SEAT_LIMIT_REACHED",
|
|
1299
1381
|
`Your plan allows ${seatLimit} seat${seatLimit === 1 ? "" : "s"}. Upgrade to invite more members.`,
|
|
1300
1382
|
{ limit: seatLimit }
|
|
@@ -1311,7 +1393,7 @@ function invitationController(store, ttl, bus) {
|
|
|
1311
1393
|
);
|
|
1312
1394
|
defaultRoleId = row?.id;
|
|
1313
1395
|
if (!defaultRoleId) {
|
|
1314
|
-
return (0,
|
|
1396
|
+
return (0, import_core6.setApiResponse)(import_core6.HTTP.SERVER_ERROR, "SERVER_ERROR", "Default role not found");
|
|
1315
1397
|
}
|
|
1316
1398
|
}
|
|
1317
1399
|
const results = await Promise.all(
|
|
@@ -1333,33 +1415,48 @@ function invitationController(store, ttl, bus) {
|
|
|
1333
1415
|
return { invitationId: invitation.id, email: email2 };
|
|
1334
1416
|
})
|
|
1335
1417
|
);
|
|
1336
|
-
return (0,
|
|
1418
|
+
return (0, import_core6.setApiResponse)(import_core6.HTTP.CREATED, "INVITATIONS_SENT", "Invitations sent successfully.", {
|
|
1337
1419
|
invitations: results
|
|
1338
1420
|
});
|
|
1339
1421
|
},
|
|
1340
1422
|
async cancel(ctx) {
|
|
1341
|
-
if (!ctx.workspace) return (0,
|
|
1423
|
+
if (!ctx.workspace) return (0, import_core6.setApiResponse)(import_core6.HTTP.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
1342
1424
|
const params = ctx.meta["params"];
|
|
1343
1425
|
const invitationId = params?.["inviteId"];
|
|
1344
1426
|
if (!invitationId)
|
|
1345
|
-
return (0,
|
|
1427
|
+
return (0, import_core6.setApiResponse)(import_core6.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "inviteId is required");
|
|
1346
1428
|
await invitations.cancel(invitationId, ctx.workspace.id);
|
|
1347
|
-
return (0,
|
|
1429
|
+
return (0, import_core6.setApiResponse)(import_core6.HTTP.OK, "INVITATION_CANCELLED", "Invitation cancelled successfully.");
|
|
1348
1430
|
},
|
|
1349
1431
|
async accept(ctx) {
|
|
1350
1432
|
const body = ctx.meta["body"];
|
|
1351
1433
|
const pin = body?.["pin"];
|
|
1352
|
-
|
|
1353
|
-
return (0, import_core5.setApiResponse)(import_core5.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "pin is required");
|
|
1354
|
-
}
|
|
1434
|
+
const token = body?.["token"];
|
|
1355
1435
|
try {
|
|
1356
|
-
|
|
1357
|
-
|
|
1436
|
+
if (typeof token === "string") {
|
|
1437
|
+
const { workspaceId: workspaceId2 } = await invitations.acceptByToken(token, ctx.user.id);
|
|
1438
|
+
return (0, import_core6.setApiResponse)(import_core6.HTTP.OK, "INVITATION_ACCEPTED", "Invitation accepted successfully.", {
|
|
1439
|
+
workspaceId: workspaceId2
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
if (typeof pin !== "string") {
|
|
1443
|
+
return (0, import_core6.setApiResponse)(import_core6.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "pin or token is required");
|
|
1444
|
+
}
|
|
1445
|
+
const email2 = ctx.user.email;
|
|
1446
|
+
if (!email2) {
|
|
1447
|
+
return (0, import_core6.setApiResponse)(
|
|
1448
|
+
import_core6.HTTP.BAD_REQUEST,
|
|
1449
|
+
"INVITATION_FAILED",
|
|
1450
|
+
"This account has no email address \u2014 use the invitation link instead of the PIN"
|
|
1451
|
+
);
|
|
1452
|
+
}
|
|
1453
|
+
const { workspaceId } = await invitations.acceptByPin({ pin, userId: ctx.user.id, email: email2 });
|
|
1454
|
+
return (0, import_core6.setApiResponse)(import_core6.HTTP.OK, "INVITATION_ACCEPTED", "Invitation accepted successfully.", {
|
|
1358
1455
|
workspaceId
|
|
1359
1456
|
});
|
|
1360
1457
|
} catch (err) {
|
|
1361
1458
|
const message = err instanceof Error ? err.message : "Invalid invitation";
|
|
1362
|
-
return (0,
|
|
1459
|
+
return (0, import_core6.setApiResponse)(import_core6.HTTP.BAD_REQUEST, "INVITATION_FAILED", message);
|
|
1363
1460
|
}
|
|
1364
1461
|
}
|
|
1365
1462
|
};
|
|
@@ -1369,6 +1466,12 @@ function invitationController(store, ttl, bus) {
|
|
|
1369
1466
|
function buildWorkspaceRoutes(store, config, bus) {
|
|
1370
1467
|
const ttl = config.invitationTtl ?? "7d";
|
|
1371
1468
|
const wsCtx = withWorkspace(store);
|
|
1469
|
+
const manager = requireManager(store, config);
|
|
1470
|
+
const acceptLimit = (0, import_rate_limit.rateLimit)({
|
|
1471
|
+
store: new import_rate_limit.StoreAdapterStore(store),
|
|
1472
|
+
rule: { capacity: 10, refillPerSec: 10 / (15 * 60) },
|
|
1473
|
+
key: (0, import_rate_limit.byIp)("workspaces:invitation-accept")
|
|
1474
|
+
});
|
|
1372
1475
|
const workspace = workspaceController(store, config);
|
|
1373
1476
|
const member = memberController(store);
|
|
1374
1477
|
const role = roleController(store);
|
|
@@ -1385,34 +1488,34 @@ function buildWorkspaceRoutes(store, config, bus) {
|
|
|
1385
1488
|
R("listWorkspaces", "GET", "/workspaces", import_middlewares.requireAuth, workspace.list),
|
|
1386
1489
|
// ── Members (workspace resolved from X-Workspace-ID header)
|
|
1387
1490
|
R("listMembers", "GET", "/workspaces/members", import_middlewares.requireAuth, wsCtx, member.list),
|
|
1388
|
-
R("removeMember", "DELETE", "/workspaces/members/:userId", import_middlewares.requireAuth, wsCtx, member.remove),
|
|
1491
|
+
R("removeMember", "DELETE", "/workspaces/members/:userId", import_middlewares.requireAuth, wsCtx, manager, member.remove),
|
|
1389
1492
|
R("getMemberRoles", "GET", "/workspaces/members/:userId/roles", import_middlewares.requireAuth, wsCtx, member.getUserRoles),
|
|
1390
|
-
R("addMemberRole", "POST", "/workspaces/members/:userId/roles", import_middlewares.requireAuth, wsCtx, (0, import_middlewares.validate)(addMemberRoleSchema), member.addRole),
|
|
1391
|
-
R("removeMemberRole", "DELETE", "/workspaces/members/:userId/roles/:roleId", import_middlewares.requireAuth, wsCtx, member.removeRole),
|
|
1493
|
+
R("addMemberRole", "POST", "/workspaces/members/:userId/roles", import_middlewares.requireAuth, wsCtx, manager, (0, import_middlewares.validate)(addMemberRoleSchema), member.addRole),
|
|
1494
|
+
R("removeMemberRole", "DELETE", "/workspaces/members/:userId/roles/:roleId", import_middlewares.requireAuth, wsCtx, manager, member.removeRole),
|
|
1392
1495
|
// ── Invitations
|
|
1393
1496
|
R("listInvitations", "GET", "/workspaces/invitations", import_middlewares.requireAuth, wsCtx, invitation.list),
|
|
1394
|
-
R("invite", "POST", "/workspaces/invitations", import_middlewares.requireAuth, wsCtx, (0, import_middlewares.validate)(createInvitationsSchema), invitation.invite),
|
|
1395
|
-
R("cancelInvitation", "DELETE", "/workspaces/invitations/:inviteId", import_middlewares.requireAuth, wsCtx, invitation.cancel),
|
|
1396
|
-
R("acceptInvitation", "POST", "/workspaces/invitations/accept", import_middlewares.requireAuth, (0, import_middlewares.validate)(acceptInvitationSchema), invitation.accept),
|
|
1497
|
+
R("invite", "POST", "/workspaces/invitations", import_middlewares.requireAuth, wsCtx, manager, (0, import_middlewares.validate)(createInvitationsSchema), invitation.invite),
|
|
1498
|
+
R("cancelInvitation", "DELETE", "/workspaces/invitations/:inviteId", import_middlewares.requireAuth, wsCtx, manager, invitation.cancel),
|
|
1499
|
+
R("acceptInvitation", "POST", "/workspaces/invitations/accept", acceptLimit, import_middlewares.requireAuth, (0, import_middlewares.validate)(acceptInvitationSchema), invitation.accept),
|
|
1397
1500
|
// ── Roles
|
|
1398
|
-
R("createRole", "POST", "/workspaces/roles", import_middlewares.requireAuth, wsCtx, (0, import_middlewares.validate)(createRoleSchema), role.create),
|
|
1501
|
+
R("createRole", "POST", "/workspaces/roles", import_middlewares.requireAuth, wsCtx, manager, (0, import_middlewares.validate)(createRoleSchema), role.create),
|
|
1399
1502
|
R("listRoles", "GET", "/workspaces/roles", import_middlewares.requireAuth, wsCtx, role.list),
|
|
1400
1503
|
R("getRole", "GET", "/workspaces/roles/:roleId", import_middlewares.requireAuth, wsCtx, role.get),
|
|
1401
|
-
R("updateRole", "PUT", "/workspaces/roles/:roleId", import_middlewares.requireAuth, wsCtx, (0, import_middlewares.validate)(updateRoleSchema), role.update),
|
|
1402
|
-
R("removeRole", "DELETE", "/workspaces/roles/:roleId", import_middlewares.requireAuth, wsCtx, role.remove),
|
|
1504
|
+
R("updateRole", "PUT", "/workspaces/roles/:roleId", import_middlewares.requireAuth, wsCtx, manager, (0, import_middlewares.validate)(updateRoleSchema), role.update),
|
|
1505
|
+
R("removeRole", "DELETE", "/workspaces/roles/:roleId", import_middlewares.requireAuth, wsCtx, manager, role.remove),
|
|
1403
1506
|
R("getRolePermissions", "GET", "/workspaces/roles/:roleId/permissions", import_middlewares.requireAuth, wsCtx, role.getPermissions),
|
|
1404
|
-
R("setRolePermissions", "POST", "/workspaces/roles/:roleId/permissions", import_middlewares.requireAuth, wsCtx, (0, import_middlewares.validate)(setRolePermissionsSchema), role.setPermissions),
|
|
1507
|
+
R("setRolePermissions", "POST", "/workspaces/roles/:roleId/permissions", import_middlewares.requireAuth, wsCtx, manager, (0, import_middlewares.validate)(setRolePermissionsSchema), role.setPermissions),
|
|
1405
1508
|
// ── Workspace lifecycle
|
|
1406
|
-
R("archive", "POST", "/workspaces/archive", import_middlewares.requireAuth, wsCtx, workspace.archive),
|
|
1407
|
-
R("restore", "POST", "/workspaces/restore", import_middlewares.requireAuth, wsCtx, workspace.restore),
|
|
1509
|
+
R("archive", "POST", "/workspaces/archive", import_middlewares.requireAuth, wsCtx, manager, workspace.archive),
|
|
1510
|
+
R("restore", "POST", "/workspaces/restore", import_middlewares.requireAuth, wsCtx, manager, workspace.restore),
|
|
1408
1511
|
R("getSettings", "GET", "/workspaces/settings", import_middlewares.requireAuth, wsCtx, workspace.getSettings),
|
|
1409
|
-
R("updateSettings", "PUT", "/workspaces/settings", import_middlewares.requireAuth, wsCtx, (0, import_middlewares.validate)(updateSettingsSchema), workspace.updateSettings),
|
|
1512
|
+
R("updateSettings", "PUT", "/workspaces/settings", import_middlewares.requireAuth, wsCtx, manager, (0, import_middlewares.validate)(updateSettingsSchema), workspace.updateSettings),
|
|
1410
1513
|
// ── Path-based lookup by ID (admin / cross-workspace use)
|
|
1411
1514
|
R("getWorkspace", "GET", "/workspaces/:id", import_middlewares.requireAuth, wsCtx, workspace.get),
|
|
1412
1515
|
// ── Update current workspace — ID from :id path param (wsCtx) or the
|
|
1413
1516
|
// X-Workspace-ID header (or personal-workspace fallback when absent). Set
|
|
1414
1517
|
// routes.updateWorkspace = '/workspaces/:id' to match a path-id frontend.
|
|
1415
|
-
R("updateWorkspace", "PUT", "/workspaces", import_middlewares.requireAuth, wsCtx, (0, import_middlewares.validate)(updateWorkspaceSchema), workspace.update)
|
|
1518
|
+
R("updateWorkspace", "PUT", "/workspaces", import_middlewares.requireAuth, wsCtx, manager, (0, import_middlewares.validate)(updateWorkspaceSchema), workspace.update)
|
|
1416
1519
|
];
|
|
1417
1520
|
}
|
|
1418
1521
|
|
|
@@ -1496,10 +1599,10 @@ var SAMPLE_PAYLOADS = {
|
|
|
1496
1599
|
};
|
|
1497
1600
|
|
|
1498
1601
|
// src/middlewares/require-workspace.ts
|
|
1499
|
-
var
|
|
1602
|
+
var import_core7 = require("@fonderie/core");
|
|
1500
1603
|
var requireWorkspace = async (ctx, next) => {
|
|
1501
1604
|
if (!ctx.workspace) {
|
|
1502
|
-
return (0,
|
|
1605
|
+
return (0, import_core7.setApiResponse)(import_core7.HTTP.BAD_REQUEST, "WORKSPACE_REQUIRED", "Workspace context required");
|
|
1503
1606
|
}
|
|
1504
1607
|
return next();
|
|
1505
1608
|
};
|
|
@@ -1612,6 +1715,7 @@ function workspaceExportContributor(store) {
|
|
|
1612
1715
|
importMembership,
|
|
1613
1716
|
importRole,
|
|
1614
1717
|
importWorkspace,
|
|
1718
|
+
requireManager,
|
|
1615
1719
|
requireWorkspace,
|
|
1616
1720
|
schemas,
|
|
1617
1721
|
toInvitationDTO,
|