@internetderdinge/api 1.224.2

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.
Files changed (102) hide show
  1. package/.github/copilot-instructions.md +77 -0
  2. package/CHANGELOG.md +11 -0
  3. package/README.md +52 -0
  4. package/package.json +112 -0
  5. package/src/accounts/accounts.controller.ts +166 -0
  6. package/src/accounts/accounts.route.ts +107 -0
  7. package/src/accounts/accounts.schemas.ts +16 -0
  8. package/src/accounts/accounts.service.ts +85 -0
  9. package/src/accounts/accounts.validation.ts +118 -0
  10. package/src/accounts/auth0.service.ts +226 -0
  11. package/src/config/config.ts +49 -0
  12. package/src/config/logger.ts +33 -0
  13. package/src/config/morgan.ts +22 -0
  14. package/src/config/passport.cjs +30 -0
  15. package/src/config/roles.ts +13 -0
  16. package/src/config/tokens.cjs +10 -0
  17. package/src/devices/devices.controller.ts +276 -0
  18. package/src/devices/devices.model.ts +126 -0
  19. package/src/devices/devices.route.ts +198 -0
  20. package/src/devices/devices.schemas.ts +94 -0
  21. package/src/devices/devices.service.ts +320 -0
  22. package/src/devices/devices.validation.ts +221 -0
  23. package/src/devicesNotifications/devicesNotifications.controller.ts +72 -0
  24. package/src/devicesNotifications/devicesNotifications.model.ts +67 -0
  25. package/src/devicesNotifications/devicesNotifications.route.ts +150 -0
  26. package/src/devicesNotifications/devicesNotifications.schemas.ts +11 -0
  27. package/src/devicesNotifications/devicesNotifications.service.ts +222 -0
  28. package/src/devicesNotifications/devicesNotifications.validation.ts +56 -0
  29. package/src/email/email.service.ts +609 -0
  30. package/src/files/upload.service.ts +145 -0
  31. package/src/i18n/i18n.ts +51 -0
  32. package/src/i18n/saveMissingLocalJsonBackend.ts +92 -0
  33. package/src/index.ts +7 -0
  34. package/src/iotdevice/iotdevice.controller.ts +136 -0
  35. package/src/iotdevice/iotdevice.model.ts +32 -0
  36. package/src/iotdevice/iotdevice.route.ts +181 -0
  37. package/src/iotdevice/iotdevice.schemas.ts +79 -0
  38. package/src/iotdevice/iotdevice.service.ts +732 -0
  39. package/src/iotdevice/iotdevice.validation.ts +61 -0
  40. package/src/middlewares/auth.ts +110 -0
  41. package/src/middlewares/checkJwt.cjs +19 -0
  42. package/src/middlewares/error.js.legacy +44 -0
  43. package/src/middlewares/error.ts +41 -0
  44. package/src/middlewares/mongooseValidations/ensureSameOrganization.ts +15 -0
  45. package/src/middlewares/rateLimiter.ts +10 -0
  46. package/src/middlewares/validate.ts +25 -0
  47. package/src/middlewares/validateAction.ts +41 -0
  48. package/src/middlewares/validateAdmin.ts +21 -0
  49. package/src/middlewares/validateAi.ts +24 -0
  50. package/src/middlewares/validateCurrentAuthUser.ts +23 -0
  51. package/src/middlewares/validateCurrentUser.ts +35 -0
  52. package/src/middlewares/validateDevice.ts +191 -0
  53. package/src/middlewares/validateDeviceUserOrganization.ts +54 -0
  54. package/src/middlewares/validateOrganization.ts +109 -0
  55. package/src/middlewares/validateQuerySearchUserAndOrganization.ts +75 -0
  56. package/src/middlewares/validateTokens.ts +36 -0
  57. package/src/middlewares/validateUser.ts +75 -0
  58. package/src/middlewares/validateZod.ts +54 -0
  59. package/src/models/plugins/index.ts +7 -0
  60. package/src/models/plugins/paginate.plugin.ts +145 -0
  61. package/src/models/plugins/paginateNew.plugin.ts +206 -0
  62. package/src/models/plugins/simplePopulate.ts +12 -0
  63. package/src/models/plugins/toJSON.plugin.ts +51 -0
  64. package/src/organizations/organizations.controller.ts +101 -0
  65. package/src/organizations/organizations.model.ts +62 -0
  66. package/src/organizations/organizations.route.ts +119 -0
  67. package/src/organizations/organizations.schemas.ts +8 -0
  68. package/src/organizations/organizations.service.ts +85 -0
  69. package/src/organizations/organizations.validation.ts +76 -0
  70. package/src/pdf/pdf.controller.ts +18 -0
  71. package/src/pdf/pdf.route.ts +28 -0
  72. package/src/pdf/pdf.schemas.ts +7 -0
  73. package/src/pdf/pdf.service.ts +89 -0
  74. package/src/pdf/pdf.validation.ts +30 -0
  75. package/src/tokens/tokens.controller.ts +81 -0
  76. package/src/tokens/tokens.model.ts +24 -0
  77. package/src/tokens/tokens.route.ts +66 -0
  78. package/src/tokens/tokens.schemas.ts +15 -0
  79. package/src/tokens/tokens.service.ts +46 -0
  80. package/src/tokens/tokens.validation.ts +13 -0
  81. package/src/types/routeSpec.ts +1 -0
  82. package/src/users/users.controller.ts +234 -0
  83. package/src/users/users.model.ts +89 -0
  84. package/src/users/users.route.ts +171 -0
  85. package/src/users/users.schemas.ts +79 -0
  86. package/src/users/users.service.ts +393 -0
  87. package/src/users/users.validation.ts +166 -0
  88. package/src/utils/ApiError.ts +18 -0
  89. package/src/utils/buildRouterAndDocs.ts +85 -0
  90. package/src/utils/catchAsync.ts +9 -0
  91. package/src/utils/comparePapers.service.ts +48 -0
  92. package/src/utils/filterOptions.ts +37 -0
  93. package/src/utils/medicationName.ts +12 -0
  94. package/src/utils/pick.ts +16 -0
  95. package/src/utils/registerOpenApi.ts +32 -0
  96. package/src/utils/urlUtils.ts +14 -0
  97. package/src/utils/userName.ts +27 -0
  98. package/src/utils/zValidations.ts +89 -0
  99. package/src/validations/auth.validation.cjs +60 -0
  100. package/src/validations/custom.validation.ts +26 -0
  101. package/src/validations/index.cjs +2 -0
  102. package/tsconfig.json +22 -0
@@ -0,0 +1,206 @@
1
+ /* eslint-disable no-param-reassign */
2
+ import mongoose from 'mongoose';
3
+ import type { Schema, Document, Model, PipelineStage } from 'mongoose';
4
+ import type { PaginateOptions, QueryResult } from './types';
5
+
6
+ const paginate = (schema: Schema): void => {
7
+ /**
8
+ * Query for documents with pagination
9
+ * @param {Object} [filter] - Mongo filter
10
+ * @param {PaginateOptions} [options] - Query options
11
+ * @returns {Promise<QueryResult>}
12
+ */
13
+ schema.statics.paginate = async function (
14
+ filter: Record<string, any> = {},
15
+ options: PaginateOptions = {},
16
+ plugin?: any,
17
+ ): Promise<QueryResult> {
18
+ // Parse sorting options
19
+ const sort = options.sortBy
20
+ ? options.sortBy.split(',').reduce((acc: Record<string, number>, sortOption: string) => {
21
+ const [key, order] = sortOption.split(':');
22
+ acc[key] = order === 'desc' ? -1 : 1;
23
+ return acc;
24
+ }, {})
25
+ : { createdAt: -1 };
26
+
27
+ const limit =
28
+ options.limit && parseInt(options.limit.toString(), 10) > 0 ? parseInt(options.limit.toString(), 10) : 10000;
29
+ const page = options.page && parseInt(options.page.toString(), 10) > 0 ? parseInt(options.page.toString(), 10) : 1;
30
+ const skip = (page - 1) * limit;
31
+
32
+ // Build aggregation pipeline
33
+ const pipeline: PipelineStage[] = [];
34
+
35
+ let mainMatch: Record<string, any> = {};
36
+ let virtualMatch: Record<string, any> = {};
37
+ let hasVirtualFields = false;
38
+
39
+ // Helper function to determine if a path is a virtual field
40
+ const isVirtualField = (path: string): boolean => {
41
+ const rootPath = path.split('.')[0];
42
+ return !!schema.virtuals[rootPath];
43
+ };
44
+
45
+ // Separate filter into main collection fields and virtual fields
46
+ if (filter) {
47
+ const separateFilter = (filterObj: Record<string, any>) => {
48
+ const main: Record<string, any> = {};
49
+ const virtual: Record<string, any> = {};
50
+
51
+ for (const key in filterObj) {
52
+ if (filterObj.hasOwnProperty(key)) {
53
+ if (key === '$or' || key === '$and') {
54
+ const mainArray: Record<string, any>[] = [];
55
+ const virtualArray: Record<string, any>[] = [];
56
+
57
+ filterObj[key].forEach((item: Record<string, any>) => {
58
+ const { main: itemMain, virtual: itemVirtual } = separateFilter(item);
59
+ if (Object.keys(itemMain).length > 0) mainArray.push(itemMain);
60
+ if (Object.keys(itemVirtual).length > 0) virtualArray.push(itemVirtual);
61
+ });
62
+
63
+ if (mainArray.length > 0) main[key] = mainArray;
64
+ if (virtualArray.length > 0) virtual[key] = virtualArray;
65
+ } else {
66
+ if (isVirtualField(key)) {
67
+ virtual[key] = filterObj[key];
68
+ hasVirtualFields = true;
69
+ } else {
70
+ main[key] = filterObj[key];
71
+ }
72
+ }
73
+ }
74
+ }
75
+ return { main, virtual };
76
+ };
77
+
78
+ const { main, virtual } = separateFilter(filter);
79
+ mainMatch = main;
80
+ virtualMatch = virtual;
81
+ }
82
+
83
+ // Add main collection $match stage
84
+ if (Object.keys(mainMatch).length > 0) {
85
+ pipeline.push({ $match: mainMatch });
86
+ }
87
+
88
+ // Handle virtual fields population
89
+ if (options.populate) {
90
+ options.populate.split(',').forEach((populateOption: string) => {
91
+ const paths = populateOption.split('.');
92
+ const localField = paths[0];
93
+ const virtual = schema.virtuals[localField];
94
+ if (!virtual) {
95
+ throw new Error(`Cannot populate unknown field: ${localField}`);
96
+ }
97
+ const refModel = virtual.options.ref;
98
+ const localFieldOption = virtual.options.localField;
99
+ const foreignFieldOption = virtual.options.foreignField;
100
+ const asField = localField;
101
+
102
+ const lookupStage: PipelineStage.Lookup = {
103
+ $lookup: {
104
+ from: mongoose.model(refModel).collection.name,
105
+ localField: localFieldOption,
106
+ foreignField: foreignFieldOption,
107
+ as: asField,
108
+ },
109
+ };
110
+
111
+ pipeline.push(lookupStage);
112
+
113
+ if (virtual.options.justOne) {
114
+ pipeline.push({
115
+ $unwind: {
116
+ path: `$${asField}`,
117
+ preserveNullAndEmptyArrays: true,
118
+ },
119
+ });
120
+ }
121
+
122
+ // Add $addFields stage to rename nested _id to id in populated documents
123
+ pipeline.push({
124
+ $addFields: {
125
+ [`${asField}.id`]: `$${asField}._id`,
126
+ },
127
+ });
128
+ pipeline.push({
129
+ $project: {
130
+ [`${asField}._id`]: 0,
131
+ },
132
+ });
133
+ });
134
+ }
135
+
136
+ // Add virtual fields $match stage
137
+ if (hasVirtualFields && Object.keys(virtualMatch).length > 0) {
138
+ pipeline.push({ $match: virtualMatch });
139
+ }
140
+
141
+ // Handle fuzzy search (if applicable)
142
+ if (this.fuzzySearch && options.fuzzySearch) {
143
+ throw new Error('Fuzzy search is not supported with aggregation in this paginate function.');
144
+ }
145
+
146
+ // Add sorting, skipping, and limiting stages
147
+ pipeline.push({ $sort: sort });
148
+ pipeline.push({ $skip: skip });
149
+ pipeline.push({ $limit: limit });
150
+
151
+ // Rename root _id to id
152
+ pipeline.push({
153
+ $addFields: {
154
+ id: '$_id',
155
+ },
156
+ });
157
+ pipeline.push({
158
+ $project: {
159
+ _id: 0,
160
+ },
161
+ });
162
+
163
+ // Use $facet to get both the results and the total count
164
+ const facetPipeline: PipelineStage[] = [
165
+ {
166
+ $facet: {
167
+ metadata: [
168
+ {
169
+ $count: 'totalResults',
170
+ },
171
+ ],
172
+ data: pipeline,
173
+ },
174
+ },
175
+ {
176
+ $addFields: {
177
+ totalResults: { $arrayElemAt: ['$metadata.totalResults', 0] },
178
+ },
179
+ },
180
+ ];
181
+
182
+ // Execute the aggregation pipeline
183
+ const aggResult = await this.aggregate(facetPipeline).exec();
184
+
185
+ // Extract results and total count
186
+ let totalResults = 0;
187
+ let results: any[] = [];
188
+
189
+ if (aggResult && aggResult.length > 0) {
190
+ totalResults = aggResult[0].totalResults || 0;
191
+ results = aggResult[0].data || [];
192
+ }
193
+
194
+ const totalPages = Math.ceil(totalResults / limit) || 1;
195
+
196
+ return {
197
+ results,
198
+ page,
199
+ limit,
200
+ totalPages,
201
+ totalResults,
202
+ };
203
+ };
204
+ };
205
+
206
+ export default paginate;
@@ -0,0 +1,12 @@
1
+ function simplePopulate(populate: string): Record<string, any> {
2
+ let docsPromise: Record<string, any> = {};
3
+ populate.split(',').forEach((populateOption) => {
4
+ docsPromise = populateOption
5
+ .split('.')
6
+ .reverse()
7
+ .reduce((a, b) => ({ path: b, populate: a }));
8
+ });
9
+ return docsPromise;
10
+ }
11
+
12
+ export default simplePopulate;
@@ -0,0 +1,51 @@
1
+ /* eslint-disable no-param-reassign */
2
+
3
+ /**
4
+ * A mongoose schema plugin which applies the following in the toJSON transform call:
5
+ * - removes __v, createdAt, updatedAt, and any path that has private: true
6
+ * - replaces _id with id
7
+ */
8
+
9
+ import type { Schema, Document } from 'mongoose';
10
+
11
+ const deleteAtPath = (obj: Record<string, any>, path: string[], index: number): void => {
12
+ if (index === path.length - 1) {
13
+ delete obj[path[index]];
14
+ return;
15
+ }
16
+ deleteAtPath(obj[path[index]], path, index + 1);
17
+ };
18
+
19
+ const toJSON = (schema: Schema, timestamps: boolean = false): void => {
20
+ let transform: ((doc: Document, ret: Record<string, any>, options: any) => any) | undefined;
21
+
22
+ if (schema.options.toJSON && schema.options.toJSON.transform) {
23
+ transform = schema.options.toJSON.transform;
24
+ }
25
+
26
+ schema.options.toJSON = {
27
+ ...schema.options.toJSON,
28
+ transform(doc: Document, ret: Record<string, any>, options: any): any {
29
+ Object.keys(schema.paths).forEach((path) => {
30
+ if (schema.paths[path].options && schema.paths[path].options.private) {
31
+ deleteAtPath(ret, path.split('.'), 0);
32
+ }
33
+ });
34
+
35
+ ret.id = ret._id.toString();
36
+ delete ret._id;
37
+ delete ret.__v;
38
+
39
+ if (!timestamps) {
40
+ delete ret.createdAt;
41
+ delete ret.updatedAt;
42
+ }
43
+
44
+ if (transform) {
45
+ return transform(doc, ret, options);
46
+ }
47
+ },
48
+ };
49
+ };
50
+
51
+ export default toJSON;
@@ -0,0 +1,101 @@
1
+ import express from "express";
2
+ const { Request, Response } = express;
3
+ import httpStatus from "http-status";
4
+ import pick from "../utils/pick.js";
5
+ import ApiError from "../utils/ApiError.js";
6
+ import catchAsync from "../utils/catchAsync.js";
7
+ import usersService from "../users/users.service";
8
+ import organizationsService, {
9
+ deleteOrganizationById,
10
+ } from "./organizations.service.js";
11
+ import mongoose from "mongoose";
12
+ import { filterOptions } from "../utils/filterOptions.js";
13
+
14
+ const ObjectId = mongoose.Types.ObjectId;
15
+
16
+ export const createOrganization = catchAsync(
17
+ async (req: Request, res: Response): Promise<void> => {
18
+ const organization = await organizationsService.createOrganization(
19
+ req.body,
20
+ );
21
+ const user = await usersService.createUser({
22
+ organization: organization._id,
23
+ owner: res.req.auth.sub,
24
+ role: "admin",
25
+ category: "relative",
26
+ status: "accept",
27
+ });
28
+ res.status(httpStatus.CREATED).send(organization);
29
+ },
30
+ );
31
+
32
+ export const getOrganizations = catchAsync(
33
+ async (req: Request, res: Response): Promise<void> => {
34
+ const filter = pick(req.query, ["name", "kind"]);
35
+ const options = pick(req.query, ["sortBy", "limit", "page"]);
36
+ console.log("getOrganizations", req.query, filter, options);
37
+
38
+ const filteredOptions = filterOptions(req.query, filter, {
39
+ objectIds: ["_id", "patient"],
40
+ search: ["name", "kind"],
41
+ });
42
+
43
+ const optionsPopulate = {
44
+ ...options,
45
+ // fuzzySearch: req.query.search ? { search: req.query.search, fields: ['name', 'kind'] } : undefined,
46
+ populate: "usersData,devicesData",
47
+ };
48
+
49
+ const result = await organizationsService.queryOrganizations(
50
+ filteredOptions,
51
+ optionsPopulate,
52
+ );
53
+ res.send(result);
54
+ },
55
+ );
56
+
57
+ export const queryOrganizationsByUser = catchAsync(
58
+ async (req: Request, res: Response): Promise<void> => {
59
+ const users = await usersService.getUsersByOwner(res.req.auth.sub);
60
+ const result = await organizationsService.queryOrganizationsByUser(users);
61
+ res.send(result);
62
+ },
63
+ );
64
+
65
+ export const getOrganizationById = catchAsync(
66
+ async (req: Request, res: Response): Promise<void> => {
67
+ const organization = await organizationsService.getOrganizationById(
68
+ req.params.organizationId,
69
+ );
70
+ if (!organization) {
71
+ throw new ApiError(httpStatus.NOT_FOUND, "Organization not found");
72
+ }
73
+ res.send(organization);
74
+ },
75
+ );
76
+
77
+ export const updateOrganization = catchAsync(
78
+ async (req: Request, res: Response): Promise<void> => {
79
+ const user = await organizationsService.updateOrganizationById(
80
+ req.params.organizationId,
81
+ req.body,
82
+ );
83
+ res.send(user);
84
+ },
85
+ );
86
+
87
+ export const deleteOrganization = catchAsync(
88
+ async (req: Request, res: Response): Promise<void> => {
89
+ const entry = await deleteOrganizationById(req.params.organizationId);
90
+ res.send(entry);
91
+ },
92
+ );
93
+
94
+ export default {
95
+ createOrganization,
96
+ getOrganizations,
97
+ queryOrganizationsByUser,
98
+ getOrganizationById,
99
+ updateOrganization,
100
+ deleteOrganization,
101
+ };
@@ -0,0 +1,62 @@
1
+ import mongoose, { Schema, Document, Model } from "mongoose";
2
+ import { toJSON, paginate } from "../models/plugins/index.js";
3
+
4
+ // Define the interface for the Organization document
5
+ export interface IOrganization extends Document {
6
+ name: string;
7
+ meta?: Record<string, any>;
8
+ kind?: string;
9
+ createdAt?: Date;
10
+ updatedAt?: Date;
11
+ usersData?: mongoose.Types.ObjectId[];
12
+ devicesData?: mongoose.Types.ObjectId[];
13
+ }
14
+
15
+ // Define the schema
16
+ const organizationSchema = new Schema(
17
+ {
18
+ name: {
19
+ type: String,
20
+ trim: true,
21
+ // required: true,
22
+ },
23
+ meta: { type: Object },
24
+ kind: { type: String },
25
+ },
26
+ {
27
+ timestamps: true,
28
+ toObject: {
29
+ virtuals: true,
30
+ },
31
+ toJSON: {
32
+ virtuals: true,
33
+ },
34
+ },
35
+ );
36
+
37
+ // Virtuals
38
+ organizationSchema.virtual("usersData", {
39
+ ref: "User",
40
+ localField: "_id",
41
+ foreignField: "organization",
42
+ justOne: false,
43
+ });
44
+
45
+ organizationSchema.virtual("devicesData", {
46
+ ref: "Device",
47
+ localField: "_id",
48
+ foreignField: "organization",
49
+ justOne: false,
50
+ });
51
+
52
+ // Add plugins
53
+ organizationSchema.plugin(toJSON);
54
+ organizationSchema.plugin(paginate);
55
+
56
+ // Define the model
57
+ const Organization: Model<IOrganization> = mongoose.model<IOrganization>(
58
+ "Organization",
59
+ organizationSchema,
60
+ );
61
+
62
+ export default Organization;
@@ -0,0 +1,119 @@
1
+ import { Router } from "express";
2
+ import buildRouterAndDocs from "../utils/buildRouterAndDocs.js";
3
+ import {
4
+ createOrganizationSchema,
5
+ updateOrganizationSchema,
6
+ getOrganizationByIdSchema,
7
+ queryOrganizationsSchema,
8
+ } from "./organizations.validation.js";
9
+ import { organizationResponseSchema } from "./organizations.schemas.js";
10
+ import {
11
+ createOrganization,
12
+ getOrganizations,
13
+ queryOrganizationsByUser,
14
+ getOrganizationById,
15
+ updateOrganization,
16
+ deleteOrganization,
17
+ } from "./organizations.controller.js";
18
+ import auth from "../middlewares/auth.js";
19
+ import type { RouteSpec } from "../types/routeSpec";
20
+ import { validateAdmin } from "../middlewares/validateAdmin.js";
21
+ import { validateOrganization } from "../middlewares/validateOrganization.js";
22
+ import {
23
+ validateOrganizationDelete,
24
+ validateOrganizationUpdate,
25
+ } from "../middlewares/validateAction.js";
26
+ import { request } from "http";
27
+
28
+ export const organizationsRouteSpecs: RouteSpec[] = [
29
+ {
30
+ method: "post",
31
+ path: "/",
32
+ validate: [auth("manageUsers")],
33
+ requestSchema: createOrganizationSchema,
34
+ responseSchema: {},
35
+ handler: createOrganization,
36
+ summary: "Create a new organization",
37
+ description: "Creates a new organization with the provided details.",
38
+ },
39
+ {
40
+ method: "get",
41
+ path: "/",
42
+ validate: [auth("getUsers")],
43
+ requestSchema: {},
44
+ responseSchema: organizationResponseSchema.array(),
45
+ handler: queryOrganizationsByUser,
46
+ summary: "Get all organizations",
47
+ description: "Retrieves all organizations accessible to the current user.",
48
+ },
49
+ {
50
+ method: "get",
51
+ path: "/all",
52
+ validate: [auth("getUsers"), validateAdmin],
53
+ requestSchema: queryOrganizationsSchema,
54
+ responseSchema: organizationResponseSchema.array(),
55
+ handler: getOrganizations,
56
+ summary: "Get all organizations",
57
+ description: "Retrieves all organizations in the system.",
58
+ },
59
+ {
60
+ method: "get",
61
+ path: "/:organizationId",
62
+ validate: [auth("getUsers"), validateOrganization],
63
+ requestSchema: getOrganizationByIdSchema,
64
+ responseSchema: organizationResponseSchema,
65
+ handler: getOrganizationById,
66
+ summary: "Get an organization by ID",
67
+ description: "Retrieves the details of a specific organization by its ID.",
68
+ },
69
+ {
70
+ method: "patch",
71
+ path: "/:organizationId",
72
+ validate: [
73
+ auth("manageUsers"),
74
+ validateOrganization,
75
+ validateOrganizationUpdate,
76
+ ],
77
+ requestSchema: updateOrganizationSchema,
78
+ responseSchema: organizationResponseSchema,
79
+ handler: updateOrganization,
80
+ summary: "Update an organization by ID",
81
+ description: "Updates the details of a specific organization by its ID.",
82
+ },
83
+ {
84
+ method: "post",
85
+ path: "/:organizationId",
86
+ validate: [
87
+ auth("manageUsers"),
88
+ validateOrganization,
89
+ validateOrganizationUpdate,
90
+ ],
91
+ requestSchema: updateOrganizationSchema,
92
+ responseSchema: organizationResponseSchema,
93
+ handler: updateOrganization,
94
+ summary: "Update an organization by ID",
95
+ description: "Updates the details of a specific organization by its ID.",
96
+ },
97
+ {
98
+ method: "delete",
99
+ path: "/:organizationId",
100
+ validate: [
101
+ auth("manageUsers"),
102
+ validateOrganization,
103
+ validateOrganizationDelete,
104
+ ],
105
+ requestSchema: getOrganizationByIdSchema,
106
+ responseSchema: organizationResponseSchema,
107
+ handler: deleteOrganization,
108
+ summary: "Delete an organization by ID",
109
+ description: "Deletes a specific organization by its ID.",
110
+ },
111
+ ];
112
+
113
+ const router: Router = Router();
114
+
115
+ buildRouterAndDocs(router, organizationsRouteSpecs, "/organizations", [
116
+ "Organizations",
117
+ ]);
118
+
119
+ export default router;
@@ -0,0 +1,8 @@
1
+ import { z } from 'zod';
2
+
3
+ export const organizationResponseSchema = z.object({
4
+ id: z.string(),
5
+ name: z.string(),
6
+ email: z.string().email(),
7
+ kind: z.string().optional(),
8
+ });
@@ -0,0 +1,85 @@
1
+ import httpStatus from "http-status";
2
+ import { ObjectId } from "mongoose";
3
+ import Organization from "./organizations.model.js";
4
+ import type { IOrganization, QueryResult } from "./organizations.model.js";
5
+ import ApiError from "../utils/ApiError.js";
6
+
7
+ const createOrganization = async (
8
+ organizationBody: Partial<IOrganization>,
9
+ ): Promise<IOrganization> => {
10
+ const organization = await Organization.create(organizationBody);
11
+ return organization;
12
+ };
13
+
14
+ const queryOrganizations = async (
15
+ filter: Record<string, any>,
16
+ options: { sortBy?: string; limit?: number; page?: number },
17
+ ): Promise<QueryResult> => {
18
+ const organizations = await Organization.paginate(filter, options);
19
+ return organizations;
20
+ };
21
+
22
+ const queryOrganizationsByUser = async (
23
+ organizationsList: Array<{ organization: ObjectId }>,
24
+ ): Promise<QueryResult | false> => {
25
+ if (!organizationsList) return false;
26
+ const organizationIds = organizationsList.map((e) => e.organization);
27
+ const organizations = await Organization.paginate(
28
+ {
29
+ _id: { $in: organizationIds },
30
+ },
31
+ {},
32
+ );
33
+ return organizations;
34
+ };
35
+
36
+ export const getOrganizationById = async (
37
+ id: ObjectId,
38
+ ): Promise<IOrganization | null> => {
39
+ return Organization.findById(id);
40
+ };
41
+
42
+ export const getOrganizationByEmail = async (
43
+ email: string,
44
+ ): Promise<IOrganization | null> => {
45
+ return Organization.findOne({ email });
46
+ };
47
+
48
+ const updateOrganizationById = async (
49
+ organizationId: ObjectId,
50
+ updateBody: Partial<IOrganization>,
51
+ ): Promise<IOrganization> => {
52
+ const organization = await getOrganizationById(organizationId);
53
+ if (!organization) {
54
+ throw new ApiError(httpStatus.NOT_FOUND, "Organization not found");
55
+ }
56
+
57
+ // Legacy: Remove organization field if present in updateBody
58
+ if ("organization" in updateBody) {
59
+ delete updateBody.organization;
60
+ }
61
+ Object.assign(organization, updateBody);
62
+ await organization.save();
63
+ return organization;
64
+ };
65
+
66
+ export const deleteOrganizationById = async (
67
+ organizationId: ObjectId,
68
+ ): Promise<IOrganization> => {
69
+ const organization = await getOrganizationById(organizationId);
70
+ if (!organization) {
71
+ throw new ApiError(httpStatus.NOT_FOUND, "Organization not found");
72
+ }
73
+ await organization.deleteOne();
74
+ return organization;
75
+ };
76
+
77
+ export default {
78
+ createOrganization,
79
+ queryOrganizations,
80
+ getOrganizationById,
81
+ queryOrganizationsByUser,
82
+ getOrganizationByEmail,
83
+ updateOrganizationById,
84
+ deleteOrganizationById,
85
+ };