Skip to content

Commit fd34e38

Browse files
committed
Update sanitize-html
1 parent 857c17f commit fd34e38

8 files changed

Lines changed: 466 additions & 662 deletions

File tree

adminforth/documentation/docs/tutorial/08-Plugins/01-agent.md

Lines changed: 57 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -580,16 +580,16 @@ Please send record counts to all admin users
580580

581581
### Writing custom tools
582582

583-
To define a custom tool, register an API endpoint with `admin.express.endpoint`. The endpoint schema makes the API visible in OpenAPI, and the endpoint `handler` gives the agent a direct function it can call as a tool.
583+
To define a custom tool you should simply define an express API route in your app using `admin.express.withSchema` wrapper which makes the route available for the agent (clear and predictable schema is a crucial part of making the tool work well).
584584

585-
`admin.express.withSchema` is useful when you already have a regular Express route and want it to appear in the generated OpenAPI document. However, a route registered only with `withSchema` is not a directly callable agent tool, because it does not register a direct OpenAPI handler. For agent tools, use `admin.express.endpoint`.
586-
587-
By default, `admin.express.endpoint` applies AdminForth authorization. The endpoint handler receives `adminUser` from the user who is controlling the agent. In other words, all permissions and access rights of the agent are defined by that admin user. At the same time, actions done by the agent are automatically attributed in the audit log to the admin user who is controlling the agent.
585+
If you are using `admin.express.authorize` wrapper for authorization, adminuser will be injected atomatically from user which sits on the surface and controls the agent. In other words all permissions and access rights of the agent are defined by the admin user which is controlling this agent. At the same time all actions done by agent are automatically attributed in the audit log to the admin user which is controlling the agent.
588586

589587
This example uses the same email adapter pattern shown in the Email Invite and Email Password Reset plugins. The transport below uses Mailgun only to keep the snippet short; you can replace it with SES or any other adapter from [List of adapters](/docs/tutorial/ListOfAdapters/).
590588

591589
```ts title="./api.ts"
592-
import { Filters, type IAdminForth } from 'adminforth';
590+
import type { Express, Response } from 'express';
591+
import { Filters, type IAdminForth, type IAdminUserExpressRequest } from 'adminforth';
592+
import * as z from 'zod';
593593
import EmailAdapterMailgun from '@adminforth/email-adapter-mailgun';
594594

595595
const agentEmailAdapter = new EmailAdapterMailgun({
@@ -610,67 +610,59 @@ function renderEmailHtml(body: string) {
610610
return `<html><body><pre style="font-family: sans-serif; white-space: pre-wrap;">${escapeHtml(body)}</pre></body></html>`;
611611
}
612612

613-
type SendEmailBody = {
614-
userId: string;
615-
subject: string;
616-
body: string;
617-
};
618-
619-
export function initApi(admin: IAdminForth) {
620-
admin.express.endpoint({
621-
method: 'POST',
622-
path: '/send_email_to_user',
623-
description: 'Send an email to one AdminForth user by id. Use this after the user row is resolved.',
624-
request_schema: {
625-
type: 'object',
626-
additionalProperties: false,
627-
required: ['userId', 'subject', 'body'],
628-
properties: {
629-
userId: { type: 'string' },
630-
subject: { type: 'string', minLength: 1 },
631-
body: { type: 'string', minLength: 1 },
632-
},
633-
},
634-
response_schema: {
635-
type: 'object',
636-
additionalProperties: false,
637-
required: ['ok'],
638-
properties: {
639-
ok: { type: 'boolean' },
640-
email: { type: 'string' },
641-
error: { type: 'string' },
613+
export function initApi(app: Express, admin: IAdminForth) {
614+
app.post('/send_email_to_user',
615+
admin.express.withSchema(
616+
{
617+
description: 'Send an email to one AdminForth user by id. Use this after the user row is resolved.',
618+
request: z.object({
619+
userId: z.string(),
620+
subject: z.string().min(1),
621+
body: z.string().min(1),
622+
}),
623+
response: z.object({
624+
ok: z.boolean(),
625+
email: z.string().email().optional(),
626+
error: z.string().optional(),
627+
}),
642628
},
643-
},
644-
handler: async ({ body, response }) => {
645-
const { userId, subject, body: emailBody } = body as SendEmailBody;
646-
647-
await agentEmailAdapter.validate();
648-
649-
const user = await admin.resource('adminuser').get(
650-
Filters.EQ('id', userId),
651-
);
652-
653-
if (!user) {
654-
response.setStatus(404);
655-
return { ok: false, error: 'User not found' };
656-
}
657-
658-
const result = await agentEmailAdapter.sendEmail(
659-
agentSendFrom,
660-
user.email as string,
661-
emailBody,
662-
renderEmailHtml(emailBody),
663-
subject,
664-
);
665-
666-
if (!result.ok) {
667-
response.setStatus(500);
668-
return { ok: false, error: result.error ?? 'Failed to send email' };
669-
}
670-
671-
return { ok: true, email: user.email };
672-
},
673-
});
629+
admin.express.authorize(
630+
async (req: IAdminUserExpressRequest, res: Response) => {
631+
const { userId, subject, body } = req.body as {
632+
userId: string;
633+
subject: string;
634+
body: string;
635+
};
636+
637+
await agentEmailAdapter.validate();
638+
639+
const user = await admin.resource('adminuser').get(
640+
Filters.EQ('id', userId),
641+
);
642+
643+
if (!user || typeof user.email !== 'string' || !user.email) {
644+
res.status(404).json({ ok: false, error: 'User not found' });
645+
return;
646+
}
647+
648+
const result = await agentEmailAdapter.sendEmail(
649+
agentSendFrom,
650+
user.email,
651+
body,
652+
renderEmailHtml(body),
653+
subject,
654+
);
655+
656+
if (!result.ok) {
657+
res.status(500).json({ ok: false, error: result.error ?? 'Failed to send email' });
658+
return;
659+
}
660+
661+
res.json({ ok: true, email: user.email });
662+
}
663+
)
664+
)
665+
);
674666
}
675667
```
676668

adminforth/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,9 @@
121121
"private-ip": "^3.0.2",
122122
"rate-limiter-flexible": "^8.1.0",
123123
"recast": "^0.23.11",
124+
"sanitize-html": "2.17.4",
124125
"ws": "^8.18.0",
125-
"yaml": "^2.9.0",
126+
"yaml": "^2.8.2",
126127
"zod": "^4.3.6"
127128
},
128129
"devDependencies": {

0 commit comments

Comments
 (0)