@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,76 @@
|
|
|
1
|
+
import { extendZodWithOpenApi } from "@asteasolutions/zod-to-openapi";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { objectId } from "../validations/custom.validation.js";
|
|
4
|
+
import {
|
|
5
|
+
zPagination,
|
|
6
|
+
zGet,
|
|
7
|
+
zObjectId,
|
|
8
|
+
zPatchBody,
|
|
9
|
+
zUpdate,
|
|
10
|
+
zDelete,
|
|
11
|
+
} from "../utils/zValidations.js";
|
|
12
|
+
|
|
13
|
+
extendZodWithOpenApi(z);
|
|
14
|
+
|
|
15
|
+
export const createOrganizationSchema = {
|
|
16
|
+
body: z.object({
|
|
17
|
+
/* name: z.string().openapi({
|
|
18
|
+
example: 'Acme Inc.',
|
|
19
|
+
description: 'The name of the organization',
|
|
20
|
+
}),
|
|
21
|
+
email: z.string().email().openapi({
|
|
22
|
+
example: 'contact@acme.com',
|
|
23
|
+
description: 'Contact email for the organization',
|
|
24
|
+
}), */
|
|
25
|
+
kind: z.string().openapi({
|
|
26
|
+
example: "private",
|
|
27
|
+
description: "The type or category of the organization",
|
|
28
|
+
}),
|
|
29
|
+
}),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const queryOrganizationsSchema = zPagination;
|
|
33
|
+
|
|
34
|
+
export const getOrganizationByIdSchema = zGet("organizationId");
|
|
35
|
+
|
|
36
|
+
export const updateOrganizationSchema = {
|
|
37
|
+
...zUpdate("organizationId"),
|
|
38
|
+
body: z.object({
|
|
39
|
+
name: z
|
|
40
|
+
.string()
|
|
41
|
+
.openapi({
|
|
42
|
+
example: "Acme Inc.",
|
|
43
|
+
description: "The name of the organization",
|
|
44
|
+
})
|
|
45
|
+
.optional(),
|
|
46
|
+
organization: zObjectId, // Legacy field, to be removed later
|
|
47
|
+
kind: z
|
|
48
|
+
.string()
|
|
49
|
+
.openapi({
|
|
50
|
+
example: "private",
|
|
51
|
+
description: "The type or category of the organization",
|
|
52
|
+
})
|
|
53
|
+
.optional(),
|
|
54
|
+
meta: z
|
|
55
|
+
.record(z.any())
|
|
56
|
+
.openapi({
|
|
57
|
+
example: { key: "value" },
|
|
58
|
+
description: "Additional metadata for the entry",
|
|
59
|
+
})
|
|
60
|
+
.optional(),
|
|
61
|
+
}),
|
|
62
|
+
//...zUpdate('organizationId'),
|
|
63
|
+
/* body: z.object({
|
|
64
|
+
meta: z.any,
|
|
65
|
+
/* organization: zObjectId,
|
|
66
|
+
kind: z
|
|
67
|
+
.string()
|
|
68
|
+
.openapi({
|
|
69
|
+
example: 'private',
|
|
70
|
+
description: 'The type or category of the organization',
|
|
71
|
+
})
|
|
72
|
+
.optional(),
|
|
73
|
+
}),*/
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const deleteOrganizationSchema = zDelete("organizationId");
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import httpStatus from "http-status";
|
|
2
|
+
import catchAsync from "../utils/catchAsync.js";
|
|
3
|
+
import pdfService from "./pdf.service.js";
|
|
4
|
+
|
|
5
|
+
export const generatePdfFromUrl = catchAsync(async (req, res) => {
|
|
6
|
+
const fileName = "memo-print";
|
|
7
|
+
|
|
8
|
+
const authHeader = req.headers["authorization"];
|
|
9
|
+
const token = authHeader.split(" ")[1];
|
|
10
|
+
|
|
11
|
+
const result = await pdfService.generatePdfFromUrl({ ...req.query, token });
|
|
12
|
+
|
|
13
|
+
res.status(httpStatus.CREATED).send({ signed: result });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export default {
|
|
17
|
+
generatePdfFromUrl,
|
|
18
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import buildRouterAndDocs from "../utils/buildRouterAndDocs.js";
|
|
3
|
+
import { generatePdfSchema } from "./pdf.validation.js";
|
|
4
|
+
import { pdfResponseSchema } from "./pdf.schemas.js";
|
|
5
|
+
import { generatePdfFromUrl } from "./pdf.controller.js";
|
|
6
|
+
import type { RouteSpec } from "../types/routeSpec";
|
|
7
|
+
import auth from "../middlewares/auth.js";
|
|
8
|
+
|
|
9
|
+
export const pdfRouteSpecs: RouteSpec[] = [
|
|
10
|
+
{
|
|
11
|
+
method: "get",
|
|
12
|
+
path: "/",
|
|
13
|
+
validate: [auth("manageUsers")],
|
|
14
|
+
requestSchema: generatePdfSchema,
|
|
15
|
+
responseSchema: pdfResponseSchema,
|
|
16
|
+
handler: generatePdfFromUrl,
|
|
17
|
+
summary: "Generate a PDF from a provided URL",
|
|
18
|
+
description:
|
|
19
|
+
"This endpoint allows users to generate a PDF document from a specified URL.",
|
|
20
|
+
memoOnly: true,
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const router: Router = Router();
|
|
25
|
+
|
|
26
|
+
buildRouterAndDocs(router, pdfRouteSpecs, "/pdf", ["PDF"]);
|
|
27
|
+
|
|
28
|
+
export default router;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import puppeteer from 'puppeteer';
|
|
2
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
3
|
+
import AWS from 'aws-sdk';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
// Configure AWS SDK
|
|
7
|
+
const s3 = new AWS.S3({
|
|
8
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
9
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
10
|
+
region: process.env.AWS_REGION,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// Function to upload a file to S3
|
|
14
|
+
const uploadBuffer = async (buffer: Buffer, fileName: string): Promise<string> => {
|
|
15
|
+
const params = {
|
|
16
|
+
Bucket: process.env.AWS_S3_BUCKET_NAME!,
|
|
17
|
+
Key: fileName,
|
|
18
|
+
Body: buffer,
|
|
19
|
+
ContentType: 'application/pdf',
|
|
20
|
+
ACL: 'private',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const data = await s3.upload(params).promise();
|
|
24
|
+
return data.Location;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Generate a signed URL
|
|
28
|
+
const generateSignedUrl = (fileName: string): string => {
|
|
29
|
+
const params = {
|
|
30
|
+
Bucket: process.env.AWS_S3_BUCKET_NAME!,
|
|
31
|
+
Key: fileName,
|
|
32
|
+
Expires: 60 * 60, // URL expiration time in seconds
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return s3.getSignedUrl('getObject', params);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
interface GeneratePdfOptions {
|
|
39
|
+
urlPath: string;
|
|
40
|
+
token: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const generatePdfFromUrl = async ({ urlPath, token }: GeneratePdfOptions): Promise<string> => {
|
|
44
|
+
const domain = process.env.FRONTEND_URL!;
|
|
45
|
+
const browser = await puppeteer.launch({
|
|
46
|
+
defaultViewport: {
|
|
47
|
+
width: 1300,
|
|
48
|
+
height: 1200,
|
|
49
|
+
deviceScaleFactor: 1,
|
|
50
|
+
},
|
|
51
|
+
executablePath: process.env.CHROME_BIN,
|
|
52
|
+
args: ['--no-sandbox'],
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const page = await browser.newPage();
|
|
56
|
+
|
|
57
|
+
await page.goto(domain);
|
|
58
|
+
|
|
59
|
+
// Set the token in local storage
|
|
60
|
+
await page.evaluate((token) => {
|
|
61
|
+
localStorage.setItem('print-token', token);
|
|
62
|
+
}, token);
|
|
63
|
+
|
|
64
|
+
await page.goto(domain + urlPath, { waitUntil: 'networkidle2' });
|
|
65
|
+
|
|
66
|
+
await page.waitForSelector('.pdf-render-complete');
|
|
67
|
+
|
|
68
|
+
const pdf = await page.pdf({ format: 'A4', printBackground: true });
|
|
69
|
+
|
|
70
|
+
await page.evaluate(() => {
|
|
71
|
+
localStorage.setItem('print-token', '');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
await browser.close();
|
|
75
|
+
|
|
76
|
+
const fileUrl = await uploadBuffer(pdf, `download-${uuidv4()}.pdf`);
|
|
77
|
+
|
|
78
|
+
console.log(`File uploaded successfully. File URL: ${fileUrl}`);
|
|
79
|
+
|
|
80
|
+
const fileName = path.basename(fileUrl);
|
|
81
|
+
const signedUrl = generateSignedUrl(fileName);
|
|
82
|
+
console.log(`Signed URL: ${signedUrl}`);
|
|
83
|
+
|
|
84
|
+
return signedUrl;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export default {
|
|
88
|
+
generatePdfFromUrl,
|
|
89
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
extendZodWithOpenApi(z);
|
|
5
|
+
|
|
6
|
+
export const generatePdfSchema = {
|
|
7
|
+
query: z.object({
|
|
8
|
+
urlPath: z
|
|
9
|
+
.string()
|
|
10
|
+
/* .url()
|
|
11
|
+
.refine(
|
|
12
|
+
(value) =>
|
|
13
|
+
value.startsWith('https://memo.wirewire.de') ||
|
|
14
|
+
value.startsWith('https://web.wirewire.de') ||
|
|
15
|
+
(process.env.NODE_ENV !== 'production' && value.startsWith('http://localhost:3200')),
|
|
16
|
+
{
|
|
17
|
+
message:
|
|
18
|
+
process.env.NODE_ENV !== 'production'
|
|
19
|
+
? 'urlPath must start with https://memo.wirewire.de, https://web.wirewire.de, or http://localhost:3200'
|
|
20
|
+
: 'urlPath must start with https://memo.wirewire.de or https://web.wirewire.de',
|
|
21
|
+
}
|
|
22
|
+
) */
|
|
23
|
+
.openapi({
|
|
24
|
+
example: 'https://memo.wirewire.de/example.pdf',
|
|
25
|
+
description:
|
|
26
|
+
'URL path to the PDF generation endpoint. Allowed domains: https://memo.wirewire.de, https://web.wirewire.de' +
|
|
27
|
+
(process.env.NODE_ENV !== 'production' ? ', or http://localhost:3200 (dev)' : ''),
|
|
28
|
+
}),
|
|
29
|
+
}),
|
|
30
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import httpStatus from "http-status";
|
|
2
|
+
import type { Request, Response, NextFunction } from "express";
|
|
3
|
+
import * as tokensService from "./tokens.service.js";
|
|
4
|
+
import pick from "../utils/pick.js";
|
|
5
|
+
|
|
6
|
+
export const getTokens = async (
|
|
7
|
+
req: Request,
|
|
8
|
+
res: Response,
|
|
9
|
+
next: NextFunction,
|
|
10
|
+
): Promise<void> => {
|
|
11
|
+
const filter = pick({ ...req.query, owner: res.req.auth.sub }, [
|
|
12
|
+
"name",
|
|
13
|
+
"role",
|
|
14
|
+
"owner",
|
|
15
|
+
]);
|
|
16
|
+
const options = pick(req.query, ["sortBy", "limit", "page"]);
|
|
17
|
+
const result = await tokensService.queryTokens(filter, options);
|
|
18
|
+
res.send(result);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Create a new token
|
|
22
|
+
export const createToken = async (
|
|
23
|
+
req: Request,
|
|
24
|
+
res: Response,
|
|
25
|
+
next: NextFunction,
|
|
26
|
+
): Promise<void> => {
|
|
27
|
+
try {
|
|
28
|
+
const tokenData = {
|
|
29
|
+
...req.body,
|
|
30
|
+
owner: res.req.auth.sub, // Always set owner from authenticated user
|
|
31
|
+
};
|
|
32
|
+
const token = await tokensService.createToken(tokenData);
|
|
33
|
+
res.status(httpStatus.CREATED).send(token);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
next(error);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Get a token by ID
|
|
40
|
+
export const getToken = async (
|
|
41
|
+
req: Request,
|
|
42
|
+
res: Response,
|
|
43
|
+
next: NextFunction,
|
|
44
|
+
): Promise<void> => {
|
|
45
|
+
try {
|
|
46
|
+
const token = await tokensService.getTokenById(req.params.tokenId);
|
|
47
|
+
if (!token) {
|
|
48
|
+
return res
|
|
49
|
+
.status(httpStatus.NOT_FOUND)
|
|
50
|
+
.send({ message: "Token not found" });
|
|
51
|
+
}
|
|
52
|
+
res.send(token);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
next(error);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Delete a token by ID
|
|
59
|
+
export const deleteToken = async (
|
|
60
|
+
req: Request,
|
|
61
|
+
res: Response,
|
|
62
|
+
next: NextFunction,
|
|
63
|
+
): Promise<void> => {
|
|
64
|
+
try {
|
|
65
|
+
const token = await tokensService.deleteTokenById(req.params.tokenId);
|
|
66
|
+
if (!token) {
|
|
67
|
+
return res
|
|
68
|
+
.status(httpStatus.NOT_FOUND)
|
|
69
|
+
.send({ message: "Token not found" });
|
|
70
|
+
}
|
|
71
|
+
res.status(httpStatus.NO_CONTENT).send();
|
|
72
|
+
} catch (error) {
|
|
73
|
+
next(error);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export default {
|
|
78
|
+
createToken,
|
|
79
|
+
getToken,
|
|
80
|
+
deleteToken,
|
|
81
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import mongoose, { Schema, Model } from "mongoose";
|
|
2
|
+
import crypto from "crypto";
|
|
3
|
+
import { toJSON, paginate } from "../models/plugins/index.js";
|
|
4
|
+
|
|
5
|
+
const tokenSchema: Schema = new mongoose.Schema(
|
|
6
|
+
{
|
|
7
|
+
name: { type: String, required: true, trim: true },
|
|
8
|
+
value: { type: String, required: true, select: false }, // Store hash only
|
|
9
|
+
owner: { type: String, required: true, index: true, trim: true },
|
|
10
|
+
expiresAt: { type: Date, required: false, index: true },
|
|
11
|
+
usedAt: { type: Date, required: false, index: true },
|
|
12
|
+
meta: { type: Schema.Types.Mixed, required: false },
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
timestamps: true,
|
|
16
|
+
},
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
tokenSchema.plugin(toJSON, true);
|
|
20
|
+
tokenSchema.plugin(paginate);
|
|
21
|
+
|
|
22
|
+
const Token: Model<any> = mongoose.model("Token", tokenSchema);
|
|
23
|
+
|
|
24
|
+
export default Token;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import buildRouterAndDocs from "../utils/buildRouterAndDocs.js";
|
|
3
|
+
import {
|
|
4
|
+
createTokenSchema,
|
|
5
|
+
getTokenSchema,
|
|
6
|
+
deleteTokenSchema,
|
|
7
|
+
} from "./tokens.validation.js";
|
|
8
|
+
import { tokenResponseSchema } from "./tokens.schemas.js";
|
|
9
|
+
import {
|
|
10
|
+
createToken,
|
|
11
|
+
getToken,
|
|
12
|
+
deleteToken,
|
|
13
|
+
getTokens,
|
|
14
|
+
} from "./tokens.controller.js";
|
|
15
|
+
import auth from "../middlewares/auth.js";
|
|
16
|
+
import type { RouteSpec } from "../types/routeSpec";
|
|
17
|
+
import { validateParamsToken } from "../middlewares/validateTokens.js";
|
|
18
|
+
|
|
19
|
+
export const tokensRouteSpecs: RouteSpec[] = [
|
|
20
|
+
{
|
|
21
|
+
method: "get",
|
|
22
|
+
path: "/",
|
|
23
|
+
validate: [auth("manageTokens")],
|
|
24
|
+
requestSchema: {},
|
|
25
|
+
responseSchema: tokenResponseSchema,
|
|
26
|
+
handler: getTokens,
|
|
27
|
+
privateDocs: true,
|
|
28
|
+
summary: "List all tokens of the current user",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
method: "post",
|
|
32
|
+
path: "/",
|
|
33
|
+
validate: [auth("manageTokens")],
|
|
34
|
+
requestSchema: createTokenSchema,
|
|
35
|
+
responseSchema: tokenResponseSchema,
|
|
36
|
+
handler: createToken,
|
|
37
|
+
privateDocs: true,
|
|
38
|
+
summary: "Create a new token",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
method: "get",
|
|
42
|
+
path: "/:tokenId",
|
|
43
|
+
validate: [auth("getTokens"), validateParamsToken],
|
|
44
|
+
requestSchema: getTokenSchema,
|
|
45
|
+
responseSchema: tokenResponseSchema,
|
|
46
|
+
handler: getToken,
|
|
47
|
+
privateDocs: true,
|
|
48
|
+
summary: "Get a token by ID",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
method: "delete",
|
|
52
|
+
path: "/:tokenId",
|
|
53
|
+
validate: [auth("manageTokens"), validateParamsToken],
|
|
54
|
+
requestSchema: deleteTokenSchema,
|
|
55
|
+
responseSchema: tokenResponseSchema,
|
|
56
|
+
handler: deleteToken,
|
|
57
|
+
privateDocs: true,
|
|
58
|
+
summary: "Delete a token by ID",
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
const router: Router = Router();
|
|
63
|
+
|
|
64
|
+
buildRouterAndDocs(router, tokensRouteSpecs, "/tokens", ["Tokens"]);
|
|
65
|
+
|
|
66
|
+
export default router;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
export const tokenResponseSchema = z.object({
|
|
4
|
+
// unique identifier of the token
|
|
5
|
+
tokenId: z.string().uuid(),
|
|
6
|
+
// the owning user
|
|
7
|
+
userId: z.string().uuid(),
|
|
8
|
+
// optional name or description
|
|
9
|
+
name: z.string().optional(),
|
|
10
|
+
// the token value (never returned after creation in real-world apps)
|
|
11
|
+
token: z.string(),
|
|
12
|
+
// timestamps
|
|
13
|
+
createdAt: z.string().datetime(),
|
|
14
|
+
updatedAt: z.string().datetime(),
|
|
15
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import type { Document } from "mongoose";
|
|
3
|
+
import Token from "./tokens.model.js";
|
|
4
|
+
|
|
5
|
+
export const queryTokens = async (
|
|
6
|
+
filter: Record<string, any>,
|
|
7
|
+
options: { sortBy?: string; limit?: number; page?: number },
|
|
8
|
+
): Promise<QueryResult> => {
|
|
9
|
+
return Token.paginate(filter, options);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const createToken = async (
|
|
13
|
+
tokenData: Record<string, any>,
|
|
14
|
+
): Promise<{ raw: string } & Document> => {
|
|
15
|
+
// 1. Generate raw token
|
|
16
|
+
const rawToken = crypto.randomBytes(32).toString("hex");
|
|
17
|
+
// 2. Hash the token (e.g., using SHA-256)
|
|
18
|
+
const hashedToken = crypto
|
|
19
|
+
.createHash("sha256")
|
|
20
|
+
.update(rawToken)
|
|
21
|
+
.digest("hex");
|
|
22
|
+
|
|
23
|
+
const tokenDataHased = {
|
|
24
|
+
...tokenData,
|
|
25
|
+
value: hashedToken,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const newToken = await Token.create(tokenDataHased);
|
|
29
|
+
return {
|
|
30
|
+
...newToken.toObject(),
|
|
31
|
+
raw: rawToken, // Return the raw token to the user
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const getTokenById = async (
|
|
36
|
+
tokenId: string,
|
|
37
|
+
): Promise<Document | null> => {
|
|
38
|
+
return Token.findById(tokenId);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Delete a token by ID
|
|
42
|
+
export const deleteTokenById = async (
|
|
43
|
+
tokenId: string,
|
|
44
|
+
): Promise<Document | null> => {
|
|
45
|
+
return Token.findByIdAndDelete(tokenId);
|
|
46
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { extendZodWithOpenApi } from "@asteasolutions/zod-to-openapi";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { zGet, zDelete } from "../utils/zValidations.js";
|
|
4
|
+
|
|
5
|
+
export const createTokenSchema = {
|
|
6
|
+
body: z.object({
|
|
7
|
+
name: z.string().openapi({ example: "my sample token" }),
|
|
8
|
+
}),
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const getTokenSchema = zGet("tokenId");
|
|
12
|
+
|
|
13
|
+
export const deleteTokenSchema = zDelete("tokenId");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { RouteSpec } from "../utils/buildRouterAndDocs.js";
|