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