0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-02-17 22:04:19 -05:00

feat(core): delete email template by langaugeTag or templateType (#7018)

Add apis to delete email template by languageTag or templateType
This commit is contained in:
simeng-li 2025-02-11 16:38:14 +08:00 committed by GitHub
parent a94f3b1c83
commit 2a838d71a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 171 additions and 1 deletions

View file

@ -86,4 +86,28 @@ export default class EmailTemplatesQueries extends SchemaQueries<
`)
);
}
/**
* Delete multiple email templates by language tag and template type
*
* @param where - Where clause to filter email templates by language tag and template type
* @param where.languageTag - The language tag of the email template
* @param where.templateType - The type of the email template
*/
async deleteMany(
where: Partial<Pick<EmailTemplate, 'languageTag' | 'templateType'>>
): Promise<{ rowCount: number }> {
const { fields, table } = convertToIdentifiers(EmailTemplates);
return this.pool.query(sql`
delete from ${table}
where ${sql.join(
Object.entries(where).map(
// eslint-disable-next-line no-restricted-syntax -- Object.entries can not infer the key type properly.
([key, value]) => sql`${fields[key as keyof EmailTemplate]} = ${value}`
),
sql` and `
)}
`);
}
}

View file

@ -148,6 +148,68 @@
}
}
}
},
"/api/email-templates/language-tag/{languageTag}": {
"delete": {
"summary": "Delete email templates by language tag",
"description": "Delete all email templates by the language tag.",
"parameters": [
{
"name": "languageTag",
"in": "path",
"description": "The language tag of the email template, e.g., `en` or `fr`."
}
],
"responses": {
"200": {
"description": "The email templates were deleted successfully.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"rowCount": {
"type": "number",
"description": "The number of email templates deleted."
}
}
}
}
}
}
}
}
},
"/api/email-templates/template-type/{templateType}": {
"delete": {
"summary": "Delete email templates by template type",
"description": "Delete all email templates by the template type.",
"parameters": [
{
"name": "templateType",
"in": "path",
"description": "The type of the email template, e.g. `SignIn` or `ForgotPassword`"
}
],
"responses": {
"200": {
"description": "The email templates were deleted successfully.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"rowCount": {
"type": "number",
"description": "The number of email templates deleted."
}
}
}
}
}
}
}
}
}
}
}

View file

@ -125,4 +125,45 @@ export default function emailTemplateRoutes<T extends ManagementApiRouter>(
return next();
}
);
router.delete(
`${pathPrefix}/language-tag/:languageTag`,
koaGuard({
params: z.object({
languageTag: z.string(),
}),
status: [200],
response: z.object({
rowCount: z.number(),
}),
}),
async (ctx, next) => {
const {
params: { languageTag },
} = ctx.guard;
ctx.body = await emailTemplatesQueries.deleteMany({ languageTag });
return next();
}
);
router.delete(
`${pathPrefix}/template-type/:templateType`,
koaGuard({
params: EmailTemplates.guard.pick({
templateType: true,
}),
status: [200],
response: z.object({
rowCount: z.number(),
}),
}),
async (ctx, next) => {
const {
params: { templateType },
} = ctx.guard;
ctx.body = await emailTemplatesQueries.deleteMany({ templateType });
return next();
}
);
}

View file

@ -27,7 +27,11 @@ const methodToVerb = Object.freeze({
type RouteDictionary = Record<`${OpenAPIV3.HttpMethods} ${string}`, string>;
const devFeatureCustomRoutes: RouteDictionary = Object.freeze({});
const devFeatureCustomRoutes: RouteDictionary = Object.freeze({
// TODO: move to the bellow list once the feature is published
'delete /email-templates/language-tag/:languageTag': 'DeleteAllEmailTemplatesByLanguageTag',
'delete /email-templates/template-type/:templateType': 'DeleteAllEmailTemplatesByTemplateType',
});
export const customRoutes: Readonly<RouteDictionary> = Object.freeze({
// Authn

View file

@ -2,6 +2,7 @@ import {
type EmailTemplateDetails,
type CreateEmailTemplate,
type EmailTemplate,
type TemplateType,
} from '@logto/schemas';
import { authedAdminApi } from './index.js';
@ -33,4 +34,16 @@ export class EmailTemplatesApi {
): Promise<EmailTemplate> {
return authedAdminApi.patch(`${path}/${id}/details`, { json: details }).json<EmailTemplate>();
}
async deleteAllByLanguageTag(languageTag: string): Promise<{ rowCount: number }> {
return authedAdminApi
.delete(`${path}/language-tag/${languageTag}`)
.json<{ rowCount: number }>();
}
async deleteAllByTemplateType(templateType: TemplateType): Promise<{ rowCount: number }> {
return authedAdminApi
.delete(`${path}/template-type/${templateType}`)
.json<{ rowCount: number }>();
}
}

View file

@ -100,4 +100,30 @@ devFeatureTest.describe('email templates', () => {
status: 404,
});
});
it('should delete email templates by language tag successfully', async () => {
const templates = await emailTemplatesApi.create(mockEmailTemplates);
const { rowCount } = await emailTemplatesApi.deleteAllByLanguageTag('en');
expect(rowCount).toBe(templates.filter(({ languageTag }) => languageTag === 'en').length);
const remaining = await emailTemplatesApi.findAll({
languageTag: 'en',
});
expect(remaining).toHaveLength(0);
});
it('should delete email templates by template type successfully', async () => {
const templates = await emailTemplatesApi.create(mockEmailTemplates);
const { rowCount } = await emailTemplatesApi.deleteAllByTemplateType(TemplateType.SignIn);
expect(rowCount).toBe(
templates.filter(({ templateType }) => templateType === TemplateType.SignIn).length
);
const remaining = await emailTemplatesApi.findAll({
templateType: TemplateType.SignIn,
});
expect(remaining).toHaveLength(0);
});
});