@internetderdinge/api 1.229.28 → 1.229.32
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/dist/src/admin/adminSearch.controller.js +54 -0
- package/dist/src/admin/adminSearch.route.js +52 -0
- package/dist/src/admin/adminSearch.schemas.js +68 -0
- package/dist/src/admin/adminSearch.service.js +763 -0
- package/dist/src/admin/adminSearch.validation.js +24 -0
- package/dist/src/index.js +1 -0
- package/dist/src/utils/registerOpenApi.js +0 -21
- package/package.json +1 -1
- package/scripts/release-and-sync-paperless.mjs +39 -26
- package/src/admin/adminSearch.controller.ts +101 -0
- package/src/admin/adminSearch.route.ts +73 -0
- package/src/admin/adminSearch.schemas.ts +75 -0
- package/src/admin/adminSearch.service.ts +1096 -0
- package/src/admin/adminSearch.validation.ts +28 -0
- package/src/index.ts +1 -0
- package/src/utils/registerOpenApi.ts +0 -21
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const adminSearchSchema = {
|
|
3
|
+
query: z.object({
|
|
4
|
+
search: z.string().max(120).optional(),
|
|
5
|
+
limit: z.coerce.number().min(1).max(50).optional(),
|
|
6
|
+
}),
|
|
7
|
+
};
|
|
8
|
+
export const adminStatsSchema = {
|
|
9
|
+
query: z.object({}),
|
|
10
|
+
};
|
|
11
|
+
export const adminIotDevicesSchema = {
|
|
12
|
+
query: z.object({
|
|
13
|
+
page: z.coerce.number().int().min(1).optional(),
|
|
14
|
+
perPage: z.coerce.number().int().min(1).max(500).optional(),
|
|
15
|
+
updatedSince: z.string().datetime().optional(),
|
|
16
|
+
}),
|
|
17
|
+
};
|
|
18
|
+
export const adminDevicesSchema = {
|
|
19
|
+
query: z.object({
|
|
20
|
+
page: z.coerce.number().int().min(1).optional(),
|
|
21
|
+
perPage: z.coerce.number().int().min(1).max(500).optional(),
|
|
22
|
+
updatedSince: z.string().datetime().optional(),
|
|
23
|
+
}),
|
|
24
|
+
};
|
package/dist/src/index.js
CHANGED
|
@@ -10,6 +10,7 @@ export { default as i18n } from "../src/i18n/i18n";
|
|
|
10
10
|
export { default as usersRoute } from "../src/users/users.route";
|
|
11
11
|
export { default as usersService } from "../src/users/users.service";
|
|
12
12
|
export { default as accountsRoute } from "../src/accounts/accounts.route";
|
|
13
|
+
export { default as adminSearchRoute } from "../src/admin/adminSearch.route";
|
|
13
14
|
export { default as accountsService } from "../src/accounts/accounts.service";
|
|
14
15
|
export { auth0 } from "../src/accounts/auth0.service";
|
|
15
16
|
export { default as organizationsRoute } from "../src/organizations/organizations.route";
|
|
@@ -24,27 +24,6 @@ const UserSchema = z
|
|
|
24
24
|
})
|
|
25
25
|
.openapi("User");
|
|
26
26
|
/*
|
|
27
|
-
registry.registerPath({
|
|
28
|
-
method: "get",
|
|
29
|
-
path: "/usersnnn/{id}",
|
|
30
|
-
summary: "Get a single user",
|
|
31
|
-
request: {
|
|
32
|
-
params: z.object({ id: z.string() }),
|
|
33
|
-
},
|
|
34
|
-
|
|
35
|
-
responses: {
|
|
36
|
-
200: {
|
|
37
|
-
description: "Object with user data.",
|
|
38
|
-
content: {
|
|
39
|
-
"application/json": {
|
|
40
|
-
schema: UserSchema,
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
|
|
48
27
|
registry.registerPath({
|
|
49
28
|
method: "get",
|
|
50
29
|
path: "/users/{id}",
|
package/package.json
CHANGED
|
@@ -2,10 +2,12 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { execSync } from "node:child_process";
|
|
5
|
+
import dotenv from "dotenv";
|
|
5
6
|
import semver from "semver";
|
|
6
7
|
|
|
7
8
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
9
|
const repoRoot = path.resolve(__dirname, "..");
|
|
10
|
+
dotenv.config({ path: path.join(repoRoot, ".env") });
|
|
9
11
|
|
|
10
12
|
const args = process.argv.slice(2);
|
|
11
13
|
const shouldPublish = !args.includes("--no-publish");
|
|
@@ -44,12 +46,23 @@ const resolveNextVersion = (current, input) => {
|
|
|
44
46
|
);
|
|
45
47
|
};
|
|
46
48
|
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
const resolveUpdatePackagePaths = () => {
|
|
50
|
+
const updatePaths = process.env.UPDATE_PATHS?.split(",")
|
|
51
|
+
.map((value) => value.trim())
|
|
52
|
+
.filter(Boolean);
|
|
53
|
+
|
|
54
|
+
if (!updatePaths?.length) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
"UPDATE_PATHS must be set in .env to a comma-separated list of package.json paths.",
|
|
57
|
+
);
|
|
50
58
|
}
|
|
51
59
|
|
|
52
|
-
return
|
|
60
|
+
return updatePaths.map((updatePath) => {
|
|
61
|
+
const resolvedPath = path.resolve(repoRoot, updatePath);
|
|
62
|
+
return path.basename(resolvedPath) === "package.json"
|
|
63
|
+
? resolvedPath
|
|
64
|
+
: path.join(resolvedPath, "package.json");
|
|
65
|
+
});
|
|
53
66
|
};
|
|
54
67
|
|
|
55
68
|
const updateDependencyVersion = (
|
|
@@ -88,8 +101,9 @@ const updateDependencyVersion = (
|
|
|
88
101
|
currentDependencyVersion &&
|
|
89
102
|
currentDependencyVersion !== expectedVersion
|
|
90
103
|
) {
|
|
104
|
+
const packageName = path.basename(path.dirname(packageJsonPath));
|
|
91
105
|
throw new Error(
|
|
92
|
-
|
|
106
|
+
`${packageName} dependency is ${currentRange} (expected ${expectedVersion}). Update it before bumping.`,
|
|
93
107
|
);
|
|
94
108
|
}
|
|
95
109
|
|
|
@@ -117,34 +131,33 @@ if (!cleanedCurrent) {
|
|
|
117
131
|
}
|
|
118
132
|
|
|
119
133
|
const nextVersion = resolveNextVersion(currentVersion, versionInput);
|
|
134
|
+
const updatePackagePaths = resolveUpdatePackagePaths();
|
|
135
|
+
|
|
136
|
+
for (const updatePackagePath of updatePackagePaths) {
|
|
137
|
+
if (!fs.existsSync(updatePackagePath)) {
|
|
138
|
+
throw new Error(`Could not find package.json: ${updatePackagePath}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
120
141
|
|
|
121
142
|
apiPackage.version = nextVersion;
|
|
122
143
|
writeJson(apiPackagePath, apiPackage);
|
|
123
144
|
|
|
124
145
|
if (shouldPublish) {
|
|
146
|
+
// Always publish through npm, regardless of the package manager used to run this script.
|
|
125
147
|
execSync("npm publish", { cwd: repoRoot, stdio: "inherit" });
|
|
126
148
|
}
|
|
127
149
|
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const paperlessUpdate = updateDependencyVersion(
|
|
141
|
-
paperlessApiPath,
|
|
142
|
-
"@internetderdinge/api",
|
|
143
|
-
nextVersion,
|
|
144
|
-
cleanedCurrent,
|
|
145
|
-
);
|
|
150
|
+
const updates = updatePackagePaths.map((updatePackagePath) => ({
|
|
151
|
+
packageName: readJson(updatePackagePath).name,
|
|
152
|
+
...updateDependencyVersion(
|
|
153
|
+
updatePackagePath,
|
|
154
|
+
"@internetderdinge/api",
|
|
155
|
+
nextVersion,
|
|
156
|
+
cleanedCurrent,
|
|
157
|
+
),
|
|
158
|
+
}));
|
|
146
159
|
|
|
147
160
|
console.log(`Updated @internetderdinge/api to ${nextVersion}`);
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
161
|
+
for (const update of updates) {
|
|
162
|
+
console.log(`${update.packageName}: ${update.previous} -> ${update.next}`);
|
|
163
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { Request, Response } from "express";
|
|
2
|
+
import httpStatus from "http-status";
|
|
3
|
+
import { ApiError } from "../utils/ApiError.js";
|
|
4
|
+
import catchAsync from "../utils/catchAsync.js";
|
|
5
|
+
import {
|
|
6
|
+
getAdminDevices,
|
|
7
|
+
getAdminIotDevices,
|
|
8
|
+
getAdminStats,
|
|
9
|
+
searchAdminCollections,
|
|
10
|
+
} from "./adminSearch.service.js";
|
|
11
|
+
|
|
12
|
+
const ADMIN_ROLE_CLAIM = "https://memo.wirewire.de/roles";
|
|
13
|
+
|
|
14
|
+
type AuthRequest = Request & {
|
|
15
|
+
auth?: Record<string, unknown>;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const hasAdminRole = (auth: Record<string, unknown> | undefined): boolean => {
|
|
19
|
+
const roles = auth?.[ADMIN_ROLE_CLAIM];
|
|
20
|
+
return Array.isArray(roles) && roles.includes("admin");
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const readListQuery = (
|
|
24
|
+
req: Request,
|
|
25
|
+
): {
|
|
26
|
+
page: number;
|
|
27
|
+
perPage: number;
|
|
28
|
+
updatedSince: string | null;
|
|
29
|
+
} => {
|
|
30
|
+
const rawPage = Number(req.query.page);
|
|
31
|
+
const rawPerPage = Number(req.query.perPage);
|
|
32
|
+
const updatedSince = String(req.query.updatedSince ?? "").trim() || null;
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
page: Number.isFinite(rawPage) ? Math.max(1, Math.floor(rawPage)) : 1,
|
|
36
|
+
perPage: Number.isFinite(rawPerPage)
|
|
37
|
+
? Math.max(1, Math.min(500, Math.floor(rawPerPage)))
|
|
38
|
+
: 100,
|
|
39
|
+
updatedSince,
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const searchAdmin = catchAsync(
|
|
44
|
+
async (req: AuthRequest, res: Response) => {
|
|
45
|
+
if (!hasAdminRole(req.auth)) {
|
|
46
|
+
throw new ApiError(
|
|
47
|
+
httpStatus.FORBIDDEN,
|
|
48
|
+
"User is not part of the admin group",
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const search = String(req.query.search || "").trim();
|
|
53
|
+
const rawLimit = Number(req.query.limit);
|
|
54
|
+
const limit = Number.isFinite(rawLimit)
|
|
55
|
+
? Math.max(1, Math.min(50, Math.floor(rawLimit)))
|
|
56
|
+
: 12;
|
|
57
|
+
|
|
58
|
+
const result = await searchAdminCollections({ search, limit });
|
|
59
|
+
res.send(result);
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
export const getStats = catchAsync(async (req: AuthRequest, res: Response) => {
|
|
64
|
+
if (!hasAdminRole(req.auth)) {
|
|
65
|
+
throw new ApiError(
|
|
66
|
+
httpStatus.FORBIDDEN,
|
|
67
|
+
"User is not part of the admin group",
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const result = await getAdminStats();
|
|
72
|
+
res.send(result);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export const getIotDevices = catchAsync(
|
|
76
|
+
async (req: AuthRequest, res: Response) => {
|
|
77
|
+
if (!hasAdminRole(req.auth)) {
|
|
78
|
+
throw new ApiError(
|
|
79
|
+
httpStatus.FORBIDDEN,
|
|
80
|
+
"User is not part of the admin group",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const result = await getAdminIotDevices(readListQuery(req));
|
|
85
|
+
res.send(result);
|
|
86
|
+
},
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
export const getDevices = catchAsync(
|
|
90
|
+
async (req: AuthRequest, res: Response) => {
|
|
91
|
+
if (!hasAdminRole(req.auth)) {
|
|
92
|
+
throw new ApiError(
|
|
93
|
+
httpStatus.FORBIDDEN,
|
|
94
|
+
"User is not part of the admin group",
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const result = await getAdminDevices(readListQuery(req));
|
|
99
|
+
res.send(result);
|
|
100
|
+
},
|
|
101
|
+
);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import buildRouterAndDocs, { type RouteSpec } from "../utils/buildRouterAndDocs.js";
|
|
3
|
+
import auth from "../middlewares/auth.js";
|
|
4
|
+
import { validateAdmin } from "../middlewares/validateAdmin.js";
|
|
5
|
+
import {
|
|
6
|
+
getDevices,
|
|
7
|
+
getIotDevices,
|
|
8
|
+
getStats,
|
|
9
|
+
searchAdmin,
|
|
10
|
+
} from "./adminSearch.controller.js";
|
|
11
|
+
import {
|
|
12
|
+
adminDevicesSchema,
|
|
13
|
+
adminIotDevicesSchema,
|
|
14
|
+
adminSearchSchema,
|
|
15
|
+
adminStatsSchema,
|
|
16
|
+
} from "./adminSearch.validation.js";
|
|
17
|
+
import {
|
|
18
|
+
adminDevicesResponseSchema,
|
|
19
|
+
adminIotDevicesResponseSchema,
|
|
20
|
+
adminSearchResponseSchema,
|
|
21
|
+
adminStatsResponseSchema,
|
|
22
|
+
} from "./adminSearch.schemas.js";
|
|
23
|
+
|
|
24
|
+
export const adminSearchRouteSpecs: RouteSpec[] = [
|
|
25
|
+
{
|
|
26
|
+
method: "get",
|
|
27
|
+
path: "/stats",
|
|
28
|
+
validate: [auth("getUsers"), validateAdmin],
|
|
29
|
+
requestSchema: adminStatsSchema,
|
|
30
|
+
responseSchema: adminStatsResponseSchema,
|
|
31
|
+
handler: getStats,
|
|
32
|
+
summary: "Get admin stats",
|
|
33
|
+
description: "Returns total counts for organizations, users, and devices.",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
method: "get",
|
|
37
|
+
path: "/search",
|
|
38
|
+
validate: [auth("getUsers"), validateAdmin],
|
|
39
|
+
requestSchema: adminSearchSchema,
|
|
40
|
+
responseSchema: adminSearchResponseSchema,
|
|
41
|
+
handler: searchAdmin,
|
|
42
|
+
summary: "Search organizations, users, and devices",
|
|
43
|
+
description:
|
|
44
|
+
"Performs an admin-only global search over organizations, users, and devices.",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
method: "get",
|
|
48
|
+
path: "/iotDevices",
|
|
49
|
+
validate: [auth("getUsers"), validateAdmin],
|
|
50
|
+
requestSchema: adminIotDevicesSchema,
|
|
51
|
+
responseSchema: adminIotDevicesResponseSchema,
|
|
52
|
+
handler: getIotDevices,
|
|
53
|
+
summary: "List IoT devices",
|
|
54
|
+
description:
|
|
55
|
+
"Returns the IoT device status list used for admin device/order sync workflows.",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
method: "get",
|
|
59
|
+
path: "/devices",
|
|
60
|
+
validate: [auth("getUsers"), validateAdmin],
|
|
61
|
+
requestSchema: adminDevicesSchema,
|
|
62
|
+
responseSchema: adminDevicesResponseSchema,
|
|
63
|
+
handler: getDevices,
|
|
64
|
+
summary: "List MongoDB devices",
|
|
65
|
+
description:
|
|
66
|
+
"Returns all device documents from MongoDB for admin sync workflows.",
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const router: Router = Router();
|
|
71
|
+
buildRouterAndDocs(router, adminSearchRouteSpecs, "/admin", ["Admin"]);
|
|
72
|
+
|
|
73
|
+
export default router;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
const organizationSearchEntrySchema = z.object({
|
|
4
|
+
id: z.string(),
|
|
5
|
+
name: z.string().optional(),
|
|
6
|
+
kind: z.string().optional(),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const userSearchEntrySchema = z.object({
|
|
10
|
+
id: z.string(),
|
|
11
|
+
name: z.string().optional(),
|
|
12
|
+
email: z.string().optional(),
|
|
13
|
+
role: z.string().optional(),
|
|
14
|
+
organization: organizationSearchEntrySchema.optional(),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const deviceSearchEntrySchema = z.object({
|
|
18
|
+
id: z.string(),
|
|
19
|
+
name: z.string().optional(),
|
|
20
|
+
deviceId: z.string().optional(),
|
|
21
|
+
kind: z.string().optional(),
|
|
22
|
+
timezone: z.string().optional(),
|
|
23
|
+
eventDate: z.string().optional(),
|
|
24
|
+
createdAt: z.string().optional(),
|
|
25
|
+
updatedAt: z.string().optional(),
|
|
26
|
+
serialNumber: z.string().optional(),
|
|
27
|
+
paymentId: z.string().optional(),
|
|
28
|
+
batteryStatus: z.string().optional(),
|
|
29
|
+
batteryLevel: z.number().optional(),
|
|
30
|
+
signalStrength: z.number().optional(),
|
|
31
|
+
lastReachableAgo: z.string().optional(),
|
|
32
|
+
organization: organizationSearchEntrySchema.optional(),
|
|
33
|
+
patient: z
|
|
34
|
+
.object({
|
|
35
|
+
id: z.string(),
|
|
36
|
+
name: z.string().optional(),
|
|
37
|
+
})
|
|
38
|
+
.optional(),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export const adminSearchResponseSchema = z.object({
|
|
42
|
+
query: z.string(),
|
|
43
|
+
organizations: z.array(organizationSearchEntrySchema),
|
|
44
|
+
users: z.array(userSearchEntrySchema),
|
|
45
|
+
devices: z.array(deviceSearchEntrySchema),
|
|
46
|
+
total: z.number(),
|
|
47
|
+
tookMs: z.number(),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
export const adminStatsResponseSchema = z.object({
|
|
51
|
+
users: z.number(),
|
|
52
|
+
auth0Users: z.number(),
|
|
53
|
+
devices: z.number(),
|
|
54
|
+
organizations: z.number(),
|
|
55
|
+
total: z.number(),
|
|
56
|
+
tookMs: z.number(),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export const adminIotDevicesResponseSchema = z.object({
|
|
60
|
+
results: z.array(z.record(z.string(), z.unknown())),
|
|
61
|
+
page: z.number(),
|
|
62
|
+
perPage: z.number(),
|
|
63
|
+
totalPages: z.number(),
|
|
64
|
+
total: z.number(),
|
|
65
|
+
tookMs: z.number(),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export const adminDevicesResponseSchema = z.object({
|
|
69
|
+
results: z.array(z.record(z.string(), z.unknown())),
|
|
70
|
+
page: z.number(),
|
|
71
|
+
perPage: z.number(),
|
|
72
|
+
totalPages: z.number(),
|
|
73
|
+
total: z.number(),
|
|
74
|
+
tookMs: z.number(),
|
|
75
|
+
});
|