@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.js
CHANGED
|
@@ -14,6 +14,7 @@ var EVENT_KEYS = {
|
|
|
14
14
|
|
|
15
15
|
// src/routes.ts
|
|
16
16
|
import { requireAuth, validate } from "@fonderie/core/middlewares";
|
|
17
|
+
import { byIp, rateLimit, StoreAdapterStore } from "@fonderie/rate-limit";
|
|
17
18
|
|
|
18
19
|
// src/schemas.ts
|
|
19
20
|
var schemas_exports = {};
|
|
@@ -70,7 +71,10 @@ var createInvitationsSchema = z.union([
|
|
|
70
71
|
inviteEntry,
|
|
71
72
|
z.array(inviteEntry).min(1, "at least one invite is required")
|
|
72
73
|
]);
|
|
73
|
-
var acceptInvitationSchema = z.
|
|
74
|
+
var acceptInvitationSchema = z.union([
|
|
75
|
+
z.object({ pin: z.string().trim().min(1, "pin is required") }),
|
|
76
|
+
z.object({ token: z.string().trim().min(1, "token is required") })
|
|
77
|
+
]);
|
|
74
78
|
var createRoleSchema = z.object({
|
|
75
79
|
name,
|
|
76
80
|
description: z.string().max(1e3).optional()
|
|
@@ -423,8 +427,45 @@ function withWorkspace(store, ctx, next) {
|
|
|
423
427
|
return handler;
|
|
424
428
|
}
|
|
425
429
|
|
|
426
|
-
// src/
|
|
430
|
+
// src/middlewares/require-manager.ts
|
|
427
431
|
import { setApiResponse as setApiResponse2, HTTP as HTTP2 } from "@fonderie/core";
|
|
432
|
+
function requireManager(store, config) {
|
|
433
|
+
const managerRoles = config.managerRoles ?? ["ADMIN"];
|
|
434
|
+
return async (ctx, next) => {
|
|
435
|
+
if (config.management === "any-member") return next();
|
|
436
|
+
if (!ctx.user) {
|
|
437
|
+
return setApiResponse2(HTTP2.UNAUTHORIZED, "UNAUTHORIZED", "Unauthorized");
|
|
438
|
+
}
|
|
439
|
+
if (!ctx.workspace) return next();
|
|
440
|
+
const ownerId = ctx.workspace.ownerId;
|
|
441
|
+
if (ownerId && ownerId === ctx.user.id) return next();
|
|
442
|
+
const [row] = await store.query(
|
|
443
|
+
`SELECT 1 AS ok
|
|
444
|
+
FROM fonderie_role_user_workspaces ruw
|
|
445
|
+
JOIN fonderie_roles r ON r.id = ruw.role_id
|
|
446
|
+
WHERE ruw.user_id = $1
|
|
447
|
+
AND ruw.workspace_id = $2
|
|
448
|
+
AND ruw.removed = false
|
|
449
|
+
AND ruw.suspended = false
|
|
450
|
+
AND r.is_system = true
|
|
451
|
+
AND r.active = true
|
|
452
|
+
AND r.name = ANY($3)
|
|
453
|
+
LIMIT 1`,
|
|
454
|
+
[ctx.user.id, ctx.workspace.id, managerRoles]
|
|
455
|
+
);
|
|
456
|
+
if (!row) {
|
|
457
|
+
return setApiResponse2(
|
|
458
|
+
HTTP2.FORBIDDEN,
|
|
459
|
+
"MANAGER_REQUIRED",
|
|
460
|
+
"This action requires the workspace owner or an admin role"
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
return next();
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// src/controllers/workspace.controller.ts
|
|
468
|
+
import { setApiResponse as setApiResponse3, HTTP as HTTP3 } from "@fonderie/core";
|
|
428
469
|
|
|
429
470
|
// src/models/workspace.model.ts
|
|
430
471
|
var WorkspaceModel = class {
|
|
@@ -702,7 +743,11 @@ function toInvitationDTO(inv) {
|
|
|
702
743
|
workspaceId: stringOrEmpty(inv.workspaceId),
|
|
703
744
|
email: stringOrEmpty(inv.email),
|
|
704
745
|
roleId: stringOrEmpty(inv.roleId),
|
|
705
|
-
token
|
|
746
|
+
// The accept token is a bearer credential delivered to the INVITEE by
|
|
747
|
+
// email — never expose it through the API. Listing invitations used to
|
|
748
|
+
// leak it, letting any member accept (hijack) someone else's pending
|
|
749
|
+
// invite. The field stays for DTO-shape compatibility, always empty.
|
|
750
|
+
token: "",
|
|
706
751
|
status: stringOrEmpty(inv.status),
|
|
707
752
|
expiresAt: dateOrEmpty(inv.expiresAt),
|
|
708
753
|
createdAt: dateOrEmpty(inv.createdAt)
|
|
@@ -726,9 +771,9 @@ function workspaceController(store, config) {
|
|
|
726
771
|
return {
|
|
727
772
|
async list(ctx) {
|
|
728
773
|
if (!ctx.user)
|
|
729
|
-
return
|
|
774
|
+
return setApiResponse3(HTTP3.UNAUTHORIZED, "UNAUTHORIZED", "Authentication required");
|
|
730
775
|
const list = await workspaces.findByUserId(ctx.user.id);
|
|
731
|
-
return
|
|
776
|
+
return setApiResponse3(HTTP3.OK, "WORKSPACES_FETCHED", "Workspaces retrieved successfully.", {
|
|
732
777
|
workspaces: list.map(toWorkspaceDTO)
|
|
733
778
|
});
|
|
734
779
|
},
|
|
@@ -738,11 +783,11 @@ function workspaceController(store, config) {
|
|
|
738
783
|
const description = body?.["description"];
|
|
739
784
|
const type = body?.["type"];
|
|
740
785
|
if (typeof name2 !== "string" || name2.trim().length === 0) {
|
|
741
|
-
return
|
|
786
|
+
return setApiResponse3(HTTP3.UNPROCESSABLE, "INVALID_PARAMETER", "name is required");
|
|
742
787
|
}
|
|
743
788
|
if (type === "PERSONAL") {
|
|
744
|
-
return
|
|
745
|
-
|
|
789
|
+
return setApiResponse3(
|
|
790
|
+
HTTP3.UNPROCESSABLE,
|
|
746
791
|
"INVALID_PARAMETER",
|
|
747
792
|
"Personal workspaces are created automatically"
|
|
748
793
|
);
|
|
@@ -765,18 +810,18 @@ function workspaceController(store, config) {
|
|
|
765
810
|
await memModel.add({ userId: ctx.user.id, workspaceId: ws.id, roleId: adminRole.id });
|
|
766
811
|
return ws;
|
|
767
812
|
});
|
|
768
|
-
return
|
|
813
|
+
return setApiResponse3(HTTP3.CREATED, "WORKSPACE_CREATED", "Workspace created successfully.", {
|
|
769
814
|
workspace: toWorkspaceDTO(workspace)
|
|
770
815
|
});
|
|
771
816
|
},
|
|
772
817
|
async get(ctx) {
|
|
773
|
-
if (!ctx.workspace) return
|
|
774
|
-
return
|
|
818
|
+
if (!ctx.workspace) return setApiResponse3(HTTP3.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
819
|
+
return setApiResponse3(HTTP3.OK, "WORKSPACE_FETCHED", "Workspace retrieved successfully.", {
|
|
775
820
|
workspace: toWorkspaceDTO(ctx.workspace)
|
|
776
821
|
});
|
|
777
822
|
},
|
|
778
823
|
async update(ctx) {
|
|
779
|
-
if (!ctx.workspace) return
|
|
824
|
+
if (!ctx.workspace) return setApiResponse3(HTTP3.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
780
825
|
const body = ctx.meta["body"];
|
|
781
826
|
const opts = {};
|
|
782
827
|
if (typeof body?.["name"] === "string") opts.name = body["name"].trim();
|
|
@@ -791,33 +836,33 @@ function workspaceController(store, config) {
|
|
|
791
836
|
if (body?.["address"] !== void 0 && typeof body["address"] === "object")
|
|
792
837
|
opts.address = body["address"] ?? null;
|
|
793
838
|
const workspace = await workspaces.update(ctx.workspace.id, opts);
|
|
794
|
-
if (!workspace) return
|
|
795
|
-
return
|
|
839
|
+
if (!workspace) return setApiResponse3(HTTP3.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
840
|
+
return setApiResponse3(HTTP3.OK, "WORKSPACE_UPDATED", "Workspace updated successfully.", {
|
|
796
841
|
workspace: toWorkspaceDTO(workspace)
|
|
797
842
|
});
|
|
798
843
|
},
|
|
799
844
|
async archive(ctx) {
|
|
800
|
-
if (!ctx.workspace) return
|
|
845
|
+
if (!ctx.workspace) return setApiResponse3(HTTP3.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
801
846
|
if (ctx.workspace.isPersonal) {
|
|
802
|
-
return
|
|
803
|
-
|
|
847
|
+
return setApiResponse3(
|
|
848
|
+
HTTP3.FORBIDDEN,
|
|
804
849
|
"FORBIDDEN",
|
|
805
850
|
"Personal workspaces cannot be archived"
|
|
806
851
|
);
|
|
807
852
|
}
|
|
808
853
|
await workspaces.archive(ctx.workspace.id, ctx.user.id);
|
|
809
|
-
return
|
|
854
|
+
return setApiResponse3(HTTP3.OK, "WORKSPACE_ARCHIVED", "Workspace archived successfully.");
|
|
810
855
|
},
|
|
811
856
|
async restore(ctx) {
|
|
812
|
-
if (!ctx.workspace) return
|
|
857
|
+
if (!ctx.workspace) return setApiResponse3(HTTP3.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
813
858
|
await workspaces.restore(ctx.workspace.id);
|
|
814
|
-
return
|
|
859
|
+
return setApiResponse3(HTTP3.OK, "WORKSPACE_RESTORED", "Workspace restored successfully.");
|
|
815
860
|
},
|
|
816
861
|
async getSettings(ctx) {
|
|
817
|
-
if (!ctx.workspace) return
|
|
862
|
+
if (!ctx.workspace) return setApiResponse3(HTTP3.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
818
863
|
const settings = await workspaces.getSettings(ctx.workspace.id);
|
|
819
|
-
return
|
|
820
|
-
|
|
864
|
+
return setApiResponse3(
|
|
865
|
+
HTTP3.OK,
|
|
821
866
|
"SETTINGS_FETCHED",
|
|
822
867
|
"Workspace settings retrieved successfully.",
|
|
823
868
|
{
|
|
@@ -826,18 +871,18 @@ function workspaceController(store, config) {
|
|
|
826
871
|
);
|
|
827
872
|
},
|
|
828
873
|
async updateSettings(ctx) {
|
|
829
|
-
if (!ctx.workspace) return
|
|
874
|
+
if (!ctx.workspace) return setApiResponse3(HTTP3.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
830
875
|
const body = ctx.meta["body"];
|
|
831
876
|
if (!body || Object.keys(body).length === 0) {
|
|
832
|
-
return
|
|
877
|
+
return setApiResponse3(HTTP3.UNPROCESSABLE, "INVALID_PARAMETER", "No settings provided");
|
|
833
878
|
}
|
|
834
879
|
const patch = {};
|
|
835
880
|
for (const key of ["locale", "timezone", "currency", "dateFormat", "timeFormat"]) {
|
|
836
881
|
if (typeof body[key] === "string") patch[key] = body[key];
|
|
837
882
|
}
|
|
838
883
|
const settings = await workspaces.updateSettings(ctx.workspace.id, patch);
|
|
839
|
-
return
|
|
840
|
-
|
|
884
|
+
return setApiResponse3(
|
|
885
|
+
HTTP3.OK,
|
|
841
886
|
"SETTINGS_UPDATED",
|
|
842
887
|
"Workspace settings updated successfully.",
|
|
843
888
|
{
|
|
@@ -849,22 +894,22 @@ function workspaceController(store, config) {
|
|
|
849
894
|
}
|
|
850
895
|
|
|
851
896
|
// src/controllers/member.controller.ts
|
|
852
|
-
import { setApiResponse as
|
|
897
|
+
import { setApiResponse as setApiResponse4, HTTP as HTTP4 } from "@fonderie/core";
|
|
853
898
|
function memberController(store) {
|
|
854
899
|
const members = new MemberModel(store);
|
|
855
900
|
return {
|
|
856
901
|
async list(ctx) {
|
|
857
|
-
if (!ctx.workspace) return
|
|
902
|
+
if (!ctx.workspace) return setApiResponse4(HTTP4.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
858
903
|
const list = await members.list(ctx.workspace.id);
|
|
859
|
-
return
|
|
904
|
+
return setApiResponse4(HTTP4.OK, "MEMBERS_FETCHED", "Members retrieved successfully.", {
|
|
860
905
|
members: list.map(toMemberDTO)
|
|
861
906
|
});
|
|
862
907
|
},
|
|
863
908
|
async remove(ctx) {
|
|
864
|
-
if (!ctx.workspace) return
|
|
909
|
+
if (!ctx.workspace) return setApiResponse4(HTTP4.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
865
910
|
if (ctx.workspace.isPersonal) {
|
|
866
|
-
return
|
|
867
|
-
|
|
911
|
+
return setApiResponse4(
|
|
912
|
+
HTTP4.FORBIDDEN,
|
|
868
913
|
"FORBIDDEN",
|
|
869
914
|
"Personal workspaces do not support member management"
|
|
870
915
|
);
|
|
@@ -872,78 +917,86 @@ function memberController(store) {
|
|
|
872
917
|
const params = ctx.meta["params"];
|
|
873
918
|
const userId = params?.["userId"];
|
|
874
919
|
if (!userId)
|
|
875
|
-
return
|
|
920
|
+
return setApiResponse4(HTTP4.UNPROCESSABLE, "INVALID_PARAMETER", "userId is required");
|
|
876
921
|
if (userId === ctx.user?.id) {
|
|
877
|
-
return
|
|
922
|
+
return setApiResponse4(HTTP4.BAD_REQUEST, "INVALID_OPERATION", "Cannot remove yourself");
|
|
923
|
+
}
|
|
924
|
+
const ownerId = ctx.workspace.ownerId;
|
|
925
|
+
if (ownerId && userId === ownerId) {
|
|
926
|
+
return setApiResponse4(
|
|
927
|
+
HTTP4.BAD_REQUEST,
|
|
928
|
+
"INVALID_OPERATION",
|
|
929
|
+
"Cannot remove the workspace owner"
|
|
930
|
+
);
|
|
878
931
|
}
|
|
879
932
|
await members.remove(userId, ctx.workspace.id);
|
|
880
|
-
return
|
|
933
|
+
return setApiResponse4(HTTP4.OK, "MEMBER_REMOVED", "Member removed successfully.");
|
|
881
934
|
},
|
|
882
935
|
async getUserRoles(ctx) {
|
|
883
|
-
if (!ctx.workspace) return
|
|
936
|
+
if (!ctx.workspace) return setApiResponse4(HTTP4.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
884
937
|
const params = ctx.meta["params"];
|
|
885
938
|
const userId = params?.["userId"];
|
|
886
939
|
if (!userId)
|
|
887
|
-
return
|
|
940
|
+
return setApiResponse4(HTTP4.UNPROCESSABLE, "INVALID_PARAMETER", "userId is required");
|
|
888
941
|
const roles = await members.getUserRoles(userId, ctx.workspace.id);
|
|
889
|
-
return
|
|
942
|
+
return setApiResponse4(HTTP4.OK, "ROLES_FETCHED", "Member roles retrieved successfully.", {
|
|
890
943
|
roles: roles.map(toRoleDTO)
|
|
891
944
|
});
|
|
892
945
|
},
|
|
893
946
|
async addRole(ctx) {
|
|
894
|
-
if (!ctx.workspace) return
|
|
947
|
+
if (!ctx.workspace) return setApiResponse4(HTTP4.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
895
948
|
const params = ctx.meta["params"];
|
|
896
949
|
const body = ctx.meta["body"];
|
|
897
950
|
const userId = params?.["userId"];
|
|
898
951
|
const roleId = body?.["roleId"] ?? params?.["roleId"];
|
|
899
952
|
if (!userId)
|
|
900
|
-
return
|
|
953
|
+
return setApiResponse4(HTTP4.UNPROCESSABLE, "INVALID_PARAMETER", "userId is required");
|
|
901
954
|
if (!roleId)
|
|
902
|
-
return
|
|
955
|
+
return setApiResponse4(HTTP4.UNPROCESSABLE, "INVALID_PARAMETER", "roleId is required");
|
|
903
956
|
const assigned = await members.addRole(userId, ctx.workspace.id, roleId);
|
|
904
957
|
if (!assigned) {
|
|
905
|
-
return
|
|
906
|
-
|
|
958
|
+
return setApiResponse4(
|
|
959
|
+
HTTP4.UNPROCESSABLE,
|
|
907
960
|
"INVALID_ROLE",
|
|
908
961
|
"Role is not assignable in this workspace."
|
|
909
962
|
);
|
|
910
963
|
}
|
|
911
|
-
return
|
|
964
|
+
return setApiResponse4(HTTP4.OK, "ROLE_ASSIGNED", "Role assigned successfully.");
|
|
912
965
|
},
|
|
913
966
|
async removeRole(ctx) {
|
|
914
|
-
if (!ctx.workspace) return
|
|
967
|
+
if (!ctx.workspace) return setApiResponse4(HTTP4.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
915
968
|
const params = ctx.meta["params"];
|
|
916
969
|
const userId = params?.["userId"];
|
|
917
970
|
const roleId = params?.["roleId"];
|
|
918
971
|
if (!userId)
|
|
919
|
-
return
|
|
972
|
+
return setApiResponse4(HTTP4.UNPROCESSABLE, "INVALID_PARAMETER", "userId is required");
|
|
920
973
|
if (!roleId)
|
|
921
|
-
return
|
|
974
|
+
return setApiResponse4(HTTP4.UNPROCESSABLE, "INVALID_PARAMETER", "roleId is required");
|
|
922
975
|
try {
|
|
923
976
|
await members.removeRole(userId, ctx.workspace.id, roleId);
|
|
924
|
-
return
|
|
977
|
+
return setApiResponse4(HTTP4.OK, "ROLE_REMOVED", "Role removed successfully.");
|
|
925
978
|
} catch (err) {
|
|
926
979
|
const message = err instanceof Error ? err.message : "Failed";
|
|
927
|
-
return
|
|
980
|
+
return setApiResponse4(HTTP4.BAD_REQUEST, "OPERATION_FAILED", message);
|
|
928
981
|
}
|
|
929
982
|
}
|
|
930
983
|
};
|
|
931
984
|
}
|
|
932
985
|
|
|
933
986
|
// src/controllers/role.controller.ts
|
|
934
|
-
import { setApiResponse as
|
|
987
|
+
import { setApiResponse as setApiResponse5, HTTP as HTTP5 } from "@fonderie/core";
|
|
935
988
|
function roleController(store) {
|
|
936
989
|
const roles = new RoleModel(store);
|
|
937
990
|
return {
|
|
938
991
|
async create(ctx) {
|
|
939
992
|
if (!ctx.workspace) {
|
|
940
|
-
return
|
|
993
|
+
return setApiResponse5(HTTP5.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
941
994
|
}
|
|
942
995
|
const body = ctx.meta["body"];
|
|
943
996
|
const name2 = body?.["name"];
|
|
944
997
|
const description = body?.["description"];
|
|
945
998
|
if (typeof name2 !== "string" || name2.trim().length === 0) {
|
|
946
|
-
return
|
|
999
|
+
return setApiResponse5(HTTP5.UNPROCESSABLE, "INVALID_PARAMETER", "name is required");
|
|
947
1000
|
}
|
|
948
1001
|
try {
|
|
949
1002
|
const opts = {
|
|
@@ -954,49 +1007,49 @@ function roleController(store) {
|
|
|
954
1007
|
opts.description = description;
|
|
955
1008
|
}
|
|
956
1009
|
const role = await roles.create(opts);
|
|
957
|
-
return
|
|
1010
|
+
return setApiResponse5(HTTP5.CREATED, "ROLE_CREATED", "Role created successfully.", {
|
|
958
1011
|
role: toRoleDTO(role)
|
|
959
1012
|
});
|
|
960
1013
|
} catch (err) {
|
|
961
1014
|
const message = err instanceof Error ? err.message : "Failed to create role";
|
|
962
|
-
return
|
|
1015
|
+
return setApiResponse5(HTTP5.BAD_REQUEST, "OPERATION_FAILED", message);
|
|
963
1016
|
}
|
|
964
1017
|
},
|
|
965
1018
|
async list(ctx) {
|
|
966
1019
|
if (!ctx.workspace) {
|
|
967
|
-
return
|
|
1020
|
+
return setApiResponse5(HTTP5.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
968
1021
|
}
|
|
969
1022
|
const list = await roles.list(ctx.workspace.id);
|
|
970
|
-
return
|
|
1023
|
+
return setApiResponse5(HTTP5.OK, "ROLES_FETCHED", "Roles retrieved successfully.", {
|
|
971
1024
|
roles: list.map(toRoleDTO)
|
|
972
1025
|
});
|
|
973
1026
|
},
|
|
974
1027
|
async get(ctx) {
|
|
975
1028
|
if (!ctx.workspace) {
|
|
976
|
-
return
|
|
1029
|
+
return setApiResponse5(HTTP5.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
977
1030
|
}
|
|
978
1031
|
const params = ctx.meta["params"];
|
|
979
1032
|
const roleId = params?.["roleId"];
|
|
980
1033
|
if (!roleId) {
|
|
981
|
-
return
|
|
1034
|
+
return setApiResponse5(HTTP5.UNPROCESSABLE, "INVALID_PARAMETER", "roleId is required");
|
|
982
1035
|
}
|
|
983
1036
|
const role = await roles.findById(roleId, ctx.workspace.id);
|
|
984
1037
|
if (!role) {
|
|
985
|
-
return
|
|
1038
|
+
return setApiResponse5(HTTP5.NOT_FOUND, "NOT_FOUND", "Role not found");
|
|
986
1039
|
}
|
|
987
|
-
return
|
|
1040
|
+
return setApiResponse5(HTTP5.OK, "ROLE_FETCHED", "Role retrieved successfully.", {
|
|
988
1041
|
role: toRoleDTO(role)
|
|
989
1042
|
});
|
|
990
1043
|
},
|
|
991
1044
|
async update(ctx) {
|
|
992
1045
|
if (!ctx.workspace) {
|
|
993
|
-
return
|
|
1046
|
+
return setApiResponse5(HTTP5.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
994
1047
|
}
|
|
995
1048
|
const params = ctx.meta["params"];
|
|
996
1049
|
const body = ctx.meta["body"];
|
|
997
1050
|
const roleId = params?.["roleId"];
|
|
998
1051
|
if (!roleId) {
|
|
999
|
-
return
|
|
1052
|
+
return setApiResponse5(HTTP5.UNPROCESSABLE, "INVALID_PARAMETER", "roleId is required");
|
|
1000
1053
|
}
|
|
1001
1054
|
const opts = {};
|
|
1002
1055
|
if (typeof body?.["name"] === "string") {
|
|
@@ -1013,60 +1066,60 @@ function roleController(store) {
|
|
|
1013
1066
|
}
|
|
1014
1067
|
const role = await roles.update(roleId, ctx.workspace.id, opts);
|
|
1015
1068
|
if (!role) {
|
|
1016
|
-
return
|
|
1069
|
+
return setApiResponse5(HTTP5.NOT_FOUND, "NOT_FOUND", "Role not found or is a system role");
|
|
1017
1070
|
}
|
|
1018
|
-
return
|
|
1071
|
+
return setApiResponse5(HTTP5.OK, "ROLE_UPDATED", "Role updated successfully.", {
|
|
1019
1072
|
role: toRoleDTO(role)
|
|
1020
1073
|
});
|
|
1021
1074
|
},
|
|
1022
1075
|
async remove(ctx) {
|
|
1023
1076
|
if (!ctx.workspace) {
|
|
1024
|
-
return
|
|
1077
|
+
return setApiResponse5(HTTP5.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
1025
1078
|
}
|
|
1026
1079
|
const params = ctx.meta["params"];
|
|
1027
1080
|
const roleId = params?.["roleId"];
|
|
1028
1081
|
if (!roleId) {
|
|
1029
|
-
return
|
|
1082
|
+
return setApiResponse5(HTTP5.UNPROCESSABLE, "INVALID_PARAMETER", "roleId is required");
|
|
1030
1083
|
}
|
|
1031
1084
|
await roles.delete(roleId, ctx.workspace.id);
|
|
1032
|
-
return
|
|
1085
|
+
return setApiResponse5(HTTP5.OK, "ROLE_DELETED", "Role deleted successfully.");
|
|
1033
1086
|
},
|
|
1034
1087
|
async getPermissions(ctx) {
|
|
1035
1088
|
if (!ctx.workspace) {
|
|
1036
|
-
return
|
|
1089
|
+
return setApiResponse5(HTTP5.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
1037
1090
|
}
|
|
1038
1091
|
const params = ctx.meta["params"];
|
|
1039
1092
|
const roleId = params?.["roleId"];
|
|
1040
1093
|
if (!roleId) {
|
|
1041
|
-
return
|
|
1094
|
+
return setApiResponse5(HTTP5.UNPROCESSABLE, "INVALID_PARAMETER", "roleId is required");
|
|
1042
1095
|
}
|
|
1043
1096
|
const role = await roles.findById(roleId, ctx.workspace.id);
|
|
1044
1097
|
if (!role) {
|
|
1045
|
-
return
|
|
1098
|
+
return setApiResponse5(HTTP5.NOT_FOUND, "NOT_FOUND", "Role not found");
|
|
1046
1099
|
}
|
|
1047
1100
|
const permissions = await roles.getPermissions(roleId, ctx.workspace.id);
|
|
1048
|
-
return
|
|
1101
|
+
return setApiResponse5(HTTP5.OK, "PERMISSIONS_FETCHED", "Role permissions retrieved successfully.", {
|
|
1049
1102
|
permissions
|
|
1050
1103
|
});
|
|
1051
1104
|
},
|
|
1052
1105
|
async setPermissions(ctx) {
|
|
1053
1106
|
if (!ctx.workspace) {
|
|
1054
|
-
return
|
|
1107
|
+
return setApiResponse5(HTTP5.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
1055
1108
|
}
|
|
1056
1109
|
const params = ctx.meta["params"];
|
|
1057
1110
|
const body = ctx.meta["body"];
|
|
1058
1111
|
const roleId = params?.["roleId"];
|
|
1059
1112
|
if (!roleId) {
|
|
1060
|
-
return
|
|
1113
|
+
return setApiResponse5(HTTP5.UNPROCESSABLE, "INVALID_PARAMETER", "roleId is required");
|
|
1061
1114
|
}
|
|
1062
1115
|
const target = await roles.findById(roleId, ctx.workspace.id);
|
|
1063
1116
|
if (!target || target.isSystem) {
|
|
1064
|
-
return
|
|
1117
|
+
return setApiResponse5(HTTP5.NOT_FOUND, "NOT_FOUND", "Role not found");
|
|
1065
1118
|
}
|
|
1066
1119
|
const perms = body?.["permissions"];
|
|
1067
1120
|
if (!Array.isArray(perms)) {
|
|
1068
|
-
return
|
|
1069
|
-
|
|
1121
|
+
return setApiResponse5(
|
|
1122
|
+
HTTP5.UNPROCESSABLE,
|
|
1070
1123
|
"INVALID_PARAMETER",
|
|
1071
1124
|
"permissions array is required"
|
|
1072
1125
|
);
|
|
@@ -1082,22 +1135,22 @@ function roleController(store) {
|
|
|
1082
1135
|
};
|
|
1083
1136
|
}).filter((p) => p.permissionKey.length > 0);
|
|
1084
1137
|
await roles.setPermissions(roleId, ctx.workspace.id, normalized);
|
|
1085
|
-
return
|
|
1138
|
+
return setApiResponse5(HTTP5.OK, "PERMISSIONS_SET", "Role permissions updated successfully.");
|
|
1086
1139
|
}
|
|
1087
1140
|
};
|
|
1088
1141
|
}
|
|
1089
1142
|
|
|
1090
1143
|
// src/controllers/invitation.controller.ts
|
|
1091
|
-
import { setApiResponse as
|
|
1144
|
+
import { setApiResponse as setApiResponse6, HTTP as HTTP6 } from "@fonderie/core";
|
|
1092
1145
|
import { NOTIFICATION_EVENT } from "@fonderie/events";
|
|
1093
1146
|
|
|
1094
1147
|
// src/services/invitations.ts
|
|
1095
|
-
import { randomBytes } from "crypto";
|
|
1148
|
+
import { randomBytes, randomInt } from "crypto";
|
|
1096
1149
|
function generateToken() {
|
|
1097
1150
|
return randomBytes(32).toString("hex");
|
|
1098
1151
|
}
|
|
1099
1152
|
function generatePin() {
|
|
1100
|
-
return
|
|
1153
|
+
return randomInt(1e5, 1e6).toString();
|
|
1101
1154
|
}
|
|
1102
1155
|
function parseTtl(ttl) {
|
|
1103
1156
|
const units = {
|
|
@@ -1168,8 +1221,8 @@ async function acceptInvitationByPin(opts, store) {
|
|
|
1168
1221
|
const [inv] = await store.query(
|
|
1169
1222
|
`SELECT id, workspace_id AS "workspaceId", role_id AS "roleId", expires_at AS "expiresAt"
|
|
1170
1223
|
FROM fonderie_workspace_invitations
|
|
1171
|
-
WHERE pin = $1 AND status = 'PENDING'`,
|
|
1172
|
-
[opts.pin]
|
|
1224
|
+
WHERE pin = $1 AND lower(email) = lower($2) AND status = 'PENDING'`,
|
|
1225
|
+
[opts.pin, opts.email]
|
|
1173
1226
|
);
|
|
1174
1227
|
if (!inv) throw new Error("Invalid PIN");
|
|
1175
1228
|
if (/* @__PURE__ */ new Date() > new Date(inv.expiresAt)) throw new Error("Invitation expired");
|
|
@@ -1189,6 +1242,31 @@ async function acceptInvitationByPin(opts, store) {
|
|
|
1189
1242
|
});
|
|
1190
1243
|
return { workspaceId: inv.workspaceId, roleId: inv.roleId };
|
|
1191
1244
|
}
|
|
1245
|
+
async function acceptInvitationByToken(token, userId, store) {
|
|
1246
|
+
const [inv] = await store.query(
|
|
1247
|
+
`SELECT id, workspace_id AS "workspaceId", role_id AS "roleId", expires_at AS "expiresAt"
|
|
1248
|
+
FROM fonderie_workspace_invitations
|
|
1249
|
+
WHERE token = $1 AND status = 'PENDING'`,
|
|
1250
|
+
[token]
|
|
1251
|
+
);
|
|
1252
|
+
if (!inv) throw new Error("Invalid token");
|
|
1253
|
+
if (/* @__PURE__ */ new Date() > new Date(inv.expiresAt)) throw new Error("Invitation expired");
|
|
1254
|
+
await store.transaction(async (tx) => {
|
|
1255
|
+
await Promise.all([
|
|
1256
|
+
tx.query(
|
|
1257
|
+
`INSERT INTO fonderie_role_user_workspaces (user_id, workspace_id, role_id, confirmed)
|
|
1258
|
+
VALUES ($1, $2, $3, true)
|
|
1259
|
+
ON CONFLICT (user_id, workspace_id, role_id) DO UPDATE
|
|
1260
|
+
SET confirmed = true, removed = false`,
|
|
1261
|
+
[userId, inv.workspaceId, inv.roleId]
|
|
1262
|
+
),
|
|
1263
|
+
tx.query(`UPDATE fonderie_workspace_invitations SET status = 'ACCEPTED' WHERE id = $1`, [
|
|
1264
|
+
inv.id
|
|
1265
|
+
])
|
|
1266
|
+
]);
|
|
1267
|
+
});
|
|
1268
|
+
return { workspaceId: inv.workspaceId, roleId: inv.roleId };
|
|
1269
|
+
}
|
|
1192
1270
|
|
|
1193
1271
|
// src/models/invitation.model.ts
|
|
1194
1272
|
var InvitationModel = class {
|
|
@@ -1208,6 +1286,9 @@ var InvitationModel = class {
|
|
|
1208
1286
|
acceptByPin(opts) {
|
|
1209
1287
|
return acceptInvitationByPin(opts, this.store);
|
|
1210
1288
|
}
|
|
1289
|
+
acceptByToken(token, userId) {
|
|
1290
|
+
return acceptInvitationByToken(token, userId, this.store);
|
|
1291
|
+
}
|
|
1211
1292
|
};
|
|
1212
1293
|
|
|
1213
1294
|
// src/controllers/invitation.controller.ts
|
|
@@ -1221,17 +1302,17 @@ function invitationController(store, ttl, bus) {
|
|
|
1221
1302
|
const invitations = new InvitationModel(store);
|
|
1222
1303
|
return {
|
|
1223
1304
|
async list(ctx) {
|
|
1224
|
-
if (!ctx.workspace) return
|
|
1305
|
+
if (!ctx.workspace) return setApiResponse6(HTTP6.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
1225
1306
|
const list = await invitations.list(ctx.workspace.id);
|
|
1226
|
-
return
|
|
1307
|
+
return setApiResponse6(HTTP6.OK, "INVITATIONS_FETCHED", "Invitations retrieved successfully.", {
|
|
1227
1308
|
invitations: list.map(toInvitationDTO)
|
|
1228
1309
|
});
|
|
1229
1310
|
},
|
|
1230
1311
|
async invite(ctx) {
|
|
1231
|
-
if (!ctx.workspace) return
|
|
1312
|
+
if (!ctx.workspace) return setApiResponse6(HTTP6.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
1232
1313
|
if (ctx.workspace.isPersonal) {
|
|
1233
|
-
return
|
|
1234
|
-
|
|
1314
|
+
return setApiResponse6(
|
|
1315
|
+
HTTP6.FORBIDDEN,
|
|
1235
1316
|
"FORBIDDEN",
|
|
1236
1317
|
"Personal workspaces do not support invitations"
|
|
1237
1318
|
);
|
|
@@ -1239,11 +1320,11 @@ function invitationController(store, ttl, bus) {
|
|
|
1239
1320
|
const body = ctx.meta["body"];
|
|
1240
1321
|
const entries = Array.isArray(body) ? body : [body];
|
|
1241
1322
|
if (!entries.length) {
|
|
1242
|
-
return
|
|
1323
|
+
return setApiResponse6(HTTP6.UNPROCESSABLE, "INVALID_PARAMETER", "at least one invite is required");
|
|
1243
1324
|
}
|
|
1244
1325
|
for (const entry of entries) {
|
|
1245
1326
|
if (typeof entry?.["email"] !== "string") {
|
|
1246
|
-
return
|
|
1327
|
+
return setApiResponse6(HTTP6.UNPROCESSABLE, "INVALID_PARAMETER", "email is required for every invite");
|
|
1247
1328
|
}
|
|
1248
1329
|
}
|
|
1249
1330
|
const seatLimit = seatLimitFromMeta(ctx);
|
|
@@ -1256,8 +1337,8 @@ function invitationController(store, ttl, bus) {
|
|
|
1256
1337
|
const total = parseInt(countRow.count, 10);
|
|
1257
1338
|
const occupied = ctx.workspace.isPersonal ? total : Math.max(0, total - 1);
|
|
1258
1339
|
if (occupied + entries.length > seatLimit) {
|
|
1259
|
-
return
|
|
1260
|
-
|
|
1340
|
+
return setApiResponse6(
|
|
1341
|
+
HTTP6.PAYMENT_REQUIRED,
|
|
1261
1342
|
"SEAT_LIMIT_REACHED",
|
|
1262
1343
|
`Your plan allows ${seatLimit} seat${seatLimit === 1 ? "" : "s"}. Upgrade to invite more members.`,
|
|
1263
1344
|
{ limit: seatLimit }
|
|
@@ -1274,7 +1355,7 @@ function invitationController(store, ttl, bus) {
|
|
|
1274
1355
|
);
|
|
1275
1356
|
defaultRoleId = row?.id;
|
|
1276
1357
|
if (!defaultRoleId) {
|
|
1277
|
-
return
|
|
1358
|
+
return setApiResponse6(HTTP6.SERVER_ERROR, "SERVER_ERROR", "Default role not found");
|
|
1278
1359
|
}
|
|
1279
1360
|
}
|
|
1280
1361
|
const results = await Promise.all(
|
|
@@ -1296,33 +1377,48 @@ function invitationController(store, ttl, bus) {
|
|
|
1296
1377
|
return { invitationId: invitation.id, email: email2 };
|
|
1297
1378
|
})
|
|
1298
1379
|
);
|
|
1299
|
-
return
|
|
1380
|
+
return setApiResponse6(HTTP6.CREATED, "INVITATIONS_SENT", "Invitations sent successfully.", {
|
|
1300
1381
|
invitations: results
|
|
1301
1382
|
});
|
|
1302
1383
|
},
|
|
1303
1384
|
async cancel(ctx) {
|
|
1304
|
-
if (!ctx.workspace) return
|
|
1385
|
+
if (!ctx.workspace) return setApiResponse6(HTTP6.NOT_FOUND, "NOT_FOUND", "Workspace not found");
|
|
1305
1386
|
const params = ctx.meta["params"];
|
|
1306
1387
|
const invitationId = params?.["inviteId"];
|
|
1307
1388
|
if (!invitationId)
|
|
1308
|
-
return
|
|
1389
|
+
return setApiResponse6(HTTP6.UNPROCESSABLE, "INVALID_PARAMETER", "inviteId is required");
|
|
1309
1390
|
await invitations.cancel(invitationId, ctx.workspace.id);
|
|
1310
|
-
return
|
|
1391
|
+
return setApiResponse6(HTTP6.OK, "INVITATION_CANCELLED", "Invitation cancelled successfully.");
|
|
1311
1392
|
},
|
|
1312
1393
|
async accept(ctx) {
|
|
1313
1394
|
const body = ctx.meta["body"];
|
|
1314
1395
|
const pin = body?.["pin"];
|
|
1315
|
-
|
|
1316
|
-
return setApiResponse5(HTTP5.UNPROCESSABLE, "INVALID_PARAMETER", "pin is required");
|
|
1317
|
-
}
|
|
1396
|
+
const token = body?.["token"];
|
|
1318
1397
|
try {
|
|
1319
|
-
|
|
1320
|
-
|
|
1398
|
+
if (typeof token === "string") {
|
|
1399
|
+
const { workspaceId: workspaceId2 } = await invitations.acceptByToken(token, ctx.user.id);
|
|
1400
|
+
return setApiResponse6(HTTP6.OK, "INVITATION_ACCEPTED", "Invitation accepted successfully.", {
|
|
1401
|
+
workspaceId: workspaceId2
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
if (typeof pin !== "string") {
|
|
1405
|
+
return setApiResponse6(HTTP6.UNPROCESSABLE, "INVALID_PARAMETER", "pin or token is required");
|
|
1406
|
+
}
|
|
1407
|
+
const email2 = ctx.user.email;
|
|
1408
|
+
if (!email2) {
|
|
1409
|
+
return setApiResponse6(
|
|
1410
|
+
HTTP6.BAD_REQUEST,
|
|
1411
|
+
"INVITATION_FAILED",
|
|
1412
|
+
"This account has no email address \u2014 use the invitation link instead of the PIN"
|
|
1413
|
+
);
|
|
1414
|
+
}
|
|
1415
|
+
const { workspaceId } = await invitations.acceptByPin({ pin, userId: ctx.user.id, email: email2 });
|
|
1416
|
+
return setApiResponse6(HTTP6.OK, "INVITATION_ACCEPTED", "Invitation accepted successfully.", {
|
|
1321
1417
|
workspaceId
|
|
1322
1418
|
});
|
|
1323
1419
|
} catch (err) {
|
|
1324
1420
|
const message = err instanceof Error ? err.message : "Invalid invitation";
|
|
1325
|
-
return
|
|
1421
|
+
return setApiResponse6(HTTP6.BAD_REQUEST, "INVITATION_FAILED", message);
|
|
1326
1422
|
}
|
|
1327
1423
|
}
|
|
1328
1424
|
};
|
|
@@ -1332,6 +1428,12 @@ function invitationController(store, ttl, bus) {
|
|
|
1332
1428
|
function buildWorkspaceRoutes(store, config, bus) {
|
|
1333
1429
|
const ttl = config.invitationTtl ?? "7d";
|
|
1334
1430
|
const wsCtx = withWorkspace(store);
|
|
1431
|
+
const manager = requireManager(store, config);
|
|
1432
|
+
const acceptLimit = rateLimit({
|
|
1433
|
+
store: new StoreAdapterStore(store),
|
|
1434
|
+
rule: { capacity: 10, refillPerSec: 10 / (15 * 60) },
|
|
1435
|
+
key: byIp("workspaces:invitation-accept")
|
|
1436
|
+
});
|
|
1335
1437
|
const workspace = workspaceController(store, config);
|
|
1336
1438
|
const member = memberController(store);
|
|
1337
1439
|
const role = roleController(store);
|
|
@@ -1348,34 +1450,34 @@ function buildWorkspaceRoutes(store, config, bus) {
|
|
|
1348
1450
|
R("listWorkspaces", "GET", "/workspaces", requireAuth, workspace.list),
|
|
1349
1451
|
// ── Members (workspace resolved from X-Workspace-ID header)
|
|
1350
1452
|
R("listMembers", "GET", "/workspaces/members", requireAuth, wsCtx, member.list),
|
|
1351
|
-
R("removeMember", "DELETE", "/workspaces/members/:userId", requireAuth, wsCtx, member.remove),
|
|
1453
|
+
R("removeMember", "DELETE", "/workspaces/members/:userId", requireAuth, wsCtx, manager, member.remove),
|
|
1352
1454
|
R("getMemberRoles", "GET", "/workspaces/members/:userId/roles", requireAuth, wsCtx, member.getUserRoles),
|
|
1353
|
-
R("addMemberRole", "POST", "/workspaces/members/:userId/roles", requireAuth, wsCtx, validate(addMemberRoleSchema), member.addRole),
|
|
1354
|
-
R("removeMemberRole", "DELETE", "/workspaces/members/:userId/roles/:roleId", requireAuth, wsCtx, member.removeRole),
|
|
1455
|
+
R("addMemberRole", "POST", "/workspaces/members/:userId/roles", requireAuth, wsCtx, manager, validate(addMemberRoleSchema), member.addRole),
|
|
1456
|
+
R("removeMemberRole", "DELETE", "/workspaces/members/:userId/roles/:roleId", requireAuth, wsCtx, manager, member.removeRole),
|
|
1355
1457
|
// ── Invitations
|
|
1356
1458
|
R("listInvitations", "GET", "/workspaces/invitations", requireAuth, wsCtx, invitation.list),
|
|
1357
|
-
R("invite", "POST", "/workspaces/invitations", requireAuth, wsCtx, validate(createInvitationsSchema), invitation.invite),
|
|
1358
|
-
R("cancelInvitation", "DELETE", "/workspaces/invitations/:inviteId", requireAuth, wsCtx, invitation.cancel),
|
|
1359
|
-
R("acceptInvitation", "POST", "/workspaces/invitations/accept", requireAuth, validate(acceptInvitationSchema), invitation.accept),
|
|
1459
|
+
R("invite", "POST", "/workspaces/invitations", requireAuth, wsCtx, manager, validate(createInvitationsSchema), invitation.invite),
|
|
1460
|
+
R("cancelInvitation", "DELETE", "/workspaces/invitations/:inviteId", requireAuth, wsCtx, manager, invitation.cancel),
|
|
1461
|
+
R("acceptInvitation", "POST", "/workspaces/invitations/accept", acceptLimit, requireAuth, validate(acceptInvitationSchema), invitation.accept),
|
|
1360
1462
|
// ── Roles
|
|
1361
|
-
R("createRole", "POST", "/workspaces/roles", requireAuth, wsCtx, validate(createRoleSchema), role.create),
|
|
1463
|
+
R("createRole", "POST", "/workspaces/roles", requireAuth, wsCtx, manager, validate(createRoleSchema), role.create),
|
|
1362
1464
|
R("listRoles", "GET", "/workspaces/roles", requireAuth, wsCtx, role.list),
|
|
1363
1465
|
R("getRole", "GET", "/workspaces/roles/:roleId", requireAuth, wsCtx, role.get),
|
|
1364
|
-
R("updateRole", "PUT", "/workspaces/roles/:roleId", requireAuth, wsCtx, validate(updateRoleSchema), role.update),
|
|
1365
|
-
R("removeRole", "DELETE", "/workspaces/roles/:roleId", requireAuth, wsCtx, role.remove),
|
|
1466
|
+
R("updateRole", "PUT", "/workspaces/roles/:roleId", requireAuth, wsCtx, manager, validate(updateRoleSchema), role.update),
|
|
1467
|
+
R("removeRole", "DELETE", "/workspaces/roles/:roleId", requireAuth, wsCtx, manager, role.remove),
|
|
1366
1468
|
R("getRolePermissions", "GET", "/workspaces/roles/:roleId/permissions", requireAuth, wsCtx, role.getPermissions),
|
|
1367
|
-
R("setRolePermissions", "POST", "/workspaces/roles/:roleId/permissions", requireAuth, wsCtx, validate(setRolePermissionsSchema), role.setPermissions),
|
|
1469
|
+
R("setRolePermissions", "POST", "/workspaces/roles/:roleId/permissions", requireAuth, wsCtx, manager, validate(setRolePermissionsSchema), role.setPermissions),
|
|
1368
1470
|
// ── Workspace lifecycle
|
|
1369
|
-
R("archive", "POST", "/workspaces/archive", requireAuth, wsCtx, workspace.archive),
|
|
1370
|
-
R("restore", "POST", "/workspaces/restore", requireAuth, wsCtx, workspace.restore),
|
|
1471
|
+
R("archive", "POST", "/workspaces/archive", requireAuth, wsCtx, manager, workspace.archive),
|
|
1472
|
+
R("restore", "POST", "/workspaces/restore", requireAuth, wsCtx, manager, workspace.restore),
|
|
1371
1473
|
R("getSettings", "GET", "/workspaces/settings", requireAuth, wsCtx, workspace.getSettings),
|
|
1372
|
-
R("updateSettings", "PUT", "/workspaces/settings", requireAuth, wsCtx, validate(updateSettingsSchema), workspace.updateSettings),
|
|
1474
|
+
R("updateSettings", "PUT", "/workspaces/settings", requireAuth, wsCtx, manager, validate(updateSettingsSchema), workspace.updateSettings),
|
|
1373
1475
|
// ── Path-based lookup by ID (admin / cross-workspace use)
|
|
1374
1476
|
R("getWorkspace", "GET", "/workspaces/:id", requireAuth, wsCtx, workspace.get),
|
|
1375
1477
|
// ── Update current workspace — ID from :id path param (wsCtx) or the
|
|
1376
1478
|
// X-Workspace-ID header (or personal-workspace fallback when absent). Set
|
|
1377
1479
|
// routes.updateWorkspace = '/workspaces/:id' to match a path-id frontend.
|
|
1378
|
-
R("updateWorkspace", "PUT", "/workspaces", requireAuth, wsCtx, validate(updateWorkspaceSchema), workspace.update)
|
|
1480
|
+
R("updateWorkspace", "PUT", "/workspaces", requireAuth, wsCtx, manager, validate(updateWorkspaceSchema), workspace.update)
|
|
1379
1481
|
];
|
|
1380
1482
|
}
|
|
1381
1483
|
|
|
@@ -1459,10 +1561,10 @@ var SAMPLE_PAYLOADS = {
|
|
|
1459
1561
|
};
|
|
1460
1562
|
|
|
1461
1563
|
// src/middlewares/require-workspace.ts
|
|
1462
|
-
import { setApiResponse as
|
|
1564
|
+
import { setApiResponse as setApiResponse7, HTTP as HTTP7 } from "@fonderie/core";
|
|
1463
1565
|
var requireWorkspace = async (ctx, next) => {
|
|
1464
1566
|
if (!ctx.workspace) {
|
|
1465
|
-
return
|
|
1567
|
+
return setApiResponse7(HTTP7.BAD_REQUEST, "WORKSPACE_REQUIRED", "Workspace context required");
|
|
1466
1568
|
}
|
|
1467
1569
|
return next();
|
|
1468
1570
|
};
|
|
@@ -1574,6 +1676,7 @@ export {
|
|
|
1574
1676
|
importMembership,
|
|
1575
1677
|
importRole,
|
|
1576
1678
|
importWorkspace,
|
|
1679
|
+
requireManager,
|
|
1577
1680
|
requireWorkspace,
|
|
1578
1681
|
schemas_exports as schemas,
|
|
1579
1682
|
toInvitationDTO,
|