@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.
- package/.github/copilot-instructions.md +77 -0
- package/CHANGELOG.md +11 -0
- package/README.md +52 -0
- package/package.json +112 -0
- package/src/accounts/accounts.controller.ts +166 -0
- package/src/accounts/accounts.route.ts +107 -0
- package/src/accounts/accounts.schemas.ts +16 -0
- package/src/accounts/accounts.service.ts +85 -0
- package/src/accounts/accounts.validation.ts +118 -0
- package/src/accounts/auth0.service.ts +226 -0
- package/src/config/config.ts +49 -0
- package/src/config/logger.ts +33 -0
- package/src/config/morgan.ts +22 -0
- package/src/config/passport.cjs +30 -0
- package/src/config/roles.ts +13 -0
- package/src/config/tokens.cjs +10 -0
- package/src/devices/devices.controller.ts +276 -0
- package/src/devices/devices.model.ts +126 -0
- package/src/devices/devices.route.ts +198 -0
- package/src/devices/devices.schemas.ts +94 -0
- package/src/devices/devices.service.ts +320 -0
- package/src/devices/devices.validation.ts +221 -0
- package/src/devicesNotifications/devicesNotifications.controller.ts +72 -0
- package/src/devicesNotifications/devicesNotifications.model.ts +67 -0
- package/src/devicesNotifications/devicesNotifications.route.ts +150 -0
- package/src/devicesNotifications/devicesNotifications.schemas.ts +11 -0
- package/src/devicesNotifications/devicesNotifications.service.ts +222 -0
- package/src/devicesNotifications/devicesNotifications.validation.ts +56 -0
- package/src/email/email.service.ts +609 -0
- package/src/files/upload.service.ts +145 -0
- package/src/i18n/i18n.ts +51 -0
- package/src/i18n/saveMissingLocalJsonBackend.ts +92 -0
- package/src/index.ts +7 -0
- package/src/iotdevice/iotdevice.controller.ts +136 -0
- package/src/iotdevice/iotdevice.model.ts +32 -0
- package/src/iotdevice/iotdevice.route.ts +181 -0
- package/src/iotdevice/iotdevice.schemas.ts +79 -0
- package/src/iotdevice/iotdevice.service.ts +732 -0
- package/src/iotdevice/iotdevice.validation.ts +61 -0
- package/src/middlewares/auth.ts +110 -0
- package/src/middlewares/checkJwt.cjs +19 -0
- package/src/middlewares/error.js.legacy +44 -0
- package/src/middlewares/error.ts +41 -0
- package/src/middlewares/mongooseValidations/ensureSameOrganization.ts +15 -0
- package/src/middlewares/rateLimiter.ts +10 -0
- package/src/middlewares/validate.ts +25 -0
- package/src/middlewares/validateAction.ts +41 -0
- package/src/middlewares/validateAdmin.ts +21 -0
- package/src/middlewares/validateAi.ts +24 -0
- package/src/middlewares/validateCurrentAuthUser.ts +23 -0
- package/src/middlewares/validateCurrentUser.ts +35 -0
- package/src/middlewares/validateDevice.ts +191 -0
- package/src/middlewares/validateDeviceUserOrganization.ts +54 -0
- package/src/middlewares/validateOrganization.ts +109 -0
- package/src/middlewares/validateQuerySearchUserAndOrganization.ts +75 -0
- package/src/middlewares/validateTokens.ts +36 -0
- package/src/middlewares/validateUser.ts +75 -0
- package/src/middlewares/validateZod.ts +54 -0
- package/src/models/plugins/index.ts +7 -0
- package/src/models/plugins/paginate.plugin.ts +145 -0
- package/src/models/plugins/paginateNew.plugin.ts +206 -0
- package/src/models/plugins/simplePopulate.ts +12 -0
- package/src/models/plugins/toJSON.plugin.ts +51 -0
- package/src/organizations/organizations.controller.ts +101 -0
- package/src/organizations/organizations.model.ts +62 -0
- package/src/organizations/organizations.route.ts +119 -0
- package/src/organizations/organizations.schemas.ts +8 -0
- package/src/organizations/organizations.service.ts +85 -0
- package/src/organizations/organizations.validation.ts +76 -0
- package/src/pdf/pdf.controller.ts +18 -0
- package/src/pdf/pdf.route.ts +28 -0
- package/src/pdf/pdf.schemas.ts +7 -0
- package/src/pdf/pdf.service.ts +89 -0
- package/src/pdf/pdf.validation.ts +30 -0
- package/src/tokens/tokens.controller.ts +81 -0
- package/src/tokens/tokens.model.ts +24 -0
- package/src/tokens/tokens.route.ts +66 -0
- package/src/tokens/tokens.schemas.ts +15 -0
- package/src/tokens/tokens.service.ts +46 -0
- package/src/tokens/tokens.validation.ts +13 -0
- package/src/types/routeSpec.ts +1 -0
- package/src/users/users.controller.ts +234 -0
- package/src/users/users.model.ts +89 -0
- package/src/users/users.route.ts +171 -0
- package/src/users/users.schemas.ts +79 -0
- package/src/users/users.service.ts +393 -0
- package/src/users/users.validation.ts +166 -0
- package/src/utils/ApiError.ts +18 -0
- package/src/utils/buildRouterAndDocs.ts +85 -0
- package/src/utils/catchAsync.ts +9 -0
- package/src/utils/comparePapers.service.ts +48 -0
- package/src/utils/filterOptions.ts +37 -0
- package/src/utils/medicationName.ts +12 -0
- package/src/utils/pick.ts +16 -0
- package/src/utils/registerOpenApi.ts +32 -0
- package/src/utils/urlUtils.ts +14 -0
- package/src/utils/userName.ts +27 -0
- package/src/utils/zValidations.ts +89 -0
- package/src/validations/auth.validation.cjs +60 -0
- package/src/validations/custom.validation.ts +26 -0
- package/src/validations/index.cjs +2 -0
- 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,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
|
+
};
|