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