❇️ Template language ()

*  Throw specific error for duplicate template name

* 🧹 Cleanup console

*  Throw duplicate template name error from update too

*  Add language to templates

* 📝 Add changeset

*  Add missing event type

*  Add language format in lexicon and error checker in util

* 🚨 fix linter issues
This commit is contained in:
Foysal Ahamed 2024-09-04 12:42:39 +02:00 committed by GitHub
parent 8252c652e8
commit e4d41d66fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 229 additions and 30 deletions
.changeset
lexicons/tools/ozone
packages
api/src/client
ozone/src
pds/src/lexicon

@ -0,0 +1,6 @@
---
"@atproto/ozone": patch
"@atproto/api": patch
---
Add language property to communication templates

@ -23,6 +23,11 @@
"type": "string",
"description": "Subject of the message, used in emails."
},
"lang": {
"type": "string",
"format": "language",
"description": "Message language."
},
"createdBy": {
"type": "string",
"format": "did",
@ -37,7 +42,8 @@
"type": "ref",
"ref": "tools.ozone.communication.defs#templateView"
}
}
},
"errors": [{ "name": "DuplicateTemplateName" }]
}
}
}

@ -25,6 +25,11 @@
"description": "Subject of the message, used in emails."
},
"disabled": { "type": "boolean" },
"lang": {
"type": "string",
"format": "language",
"description": "Message language."
},
"lastUpdatedBy": {
"type": "string",
"format": "did",

@ -19,6 +19,11 @@
"type": "string",
"description": "Name of the template."
},
"lang": {
"type": "string",
"format": "language",
"description": "Message language."
},
"contentMarkdown": {
"type": "string",
"description": "Content of the template, markdown supported, can contain variable placeholders."
@ -44,7 +49,8 @@
"type": "ref",
"ref": "tools.ozone.communication.defs#templateView"
}
}
},
"errors": [{ "name": "DuplicateTemplateName" }]
}
}
}

@ -25,6 +25,7 @@
"tools.ozone.moderation.defs#modEventMuteReporter",
"tools.ozone.moderation.defs#modEventUnmuteReporter",
"tools.ozone.moderation.defs#modEventReverseTakedown",
"tools.ozone.moderation.defs#modEventResolveAppeal",
"tools.ozone.moderation.defs#modEventEmail",
"tools.ozone.moderation.defs#modEventTag"
]

@ -3415,12 +3415,11 @@ export class ToolsOzoneCommunicationNS {
data?: ToolsOzoneCommunicationCreateTemplate.InputSchema,
opts?: ToolsOzoneCommunicationCreateTemplate.CallOptions,
): Promise<ToolsOzoneCommunicationCreateTemplate.Response> {
return this._client.call(
'tools.ozone.communication.createTemplate',
opts?.qp,
data,
opts,
)
return this._client
.call('tools.ozone.communication.createTemplate', opts?.qp, data, opts)
.catch((e) => {
throw ToolsOzoneCommunicationCreateTemplate.toKnownErr(e)
})
}
deleteTemplate(
@ -3451,12 +3450,11 @@ export class ToolsOzoneCommunicationNS {
data?: ToolsOzoneCommunicationUpdateTemplate.InputSchema,
opts?: ToolsOzoneCommunicationUpdateTemplate.CallOptions,
): Promise<ToolsOzoneCommunicationUpdateTemplate.Response> {
return this._client.call(
'tools.ozone.communication.updateTemplate',
opts?.qp,
data,
opts,
)
return this._client
.call('tools.ozone.communication.updateTemplate', opts?.qp, data, opts)
.catch((e) => {
throw ToolsOzoneCommunicationUpdateTemplate.toKnownErr(e)
})
}
}

@ -10466,6 +10466,11 @@ export const schemaDict = {
type: 'string',
description: 'Subject of the message, used in emails.',
},
lang: {
type: 'string',
format: 'language',
description: 'Message language.',
},
createdBy: {
type: 'string',
format: 'did',
@ -10481,6 +10486,11 @@ export const schemaDict = {
ref: 'lex:tools.ozone.communication.defs#templateView',
},
},
errors: [
{
name: 'DuplicateTemplateName',
},
],
},
},
},
@ -10519,6 +10529,11 @@ export const schemaDict = {
disabled: {
type: 'boolean',
},
lang: {
type: 'string',
format: 'language',
description: 'Message language.',
},
lastUpdatedBy: {
type: 'string',
format: 'did',
@ -10606,6 +10621,11 @@ export const schemaDict = {
type: 'string',
description: 'Name of the template.',
},
lang: {
type: 'string',
format: 'language',
description: 'Message language.',
},
contentMarkdown: {
type: 'string',
description:
@ -10633,6 +10653,11 @@ export const schemaDict = {
ref: 'lex:tools.ozone.communication.defs#templateView',
},
},
errors: [
{
name: 'DuplicateTemplateName',
},
],
},
},
},
@ -11432,6 +11457,7 @@ export const schemaDict = {
'lex:tools.ozone.moderation.defs#modEventMuteReporter',
'lex:tools.ozone.moderation.defs#modEventUnmuteReporter',
'lex:tools.ozone.moderation.defs#modEventReverseTakedown',
'lex:tools.ozone.moderation.defs#modEventResolveAppeal',
'lex:tools.ozone.moderation.defs#modEventEmail',
'lex:tools.ozone.moderation.defs#modEventTag',
],

@ -17,6 +17,8 @@ export interface InputSchema {
contentMarkdown: string
/** Subject of the message, used in emails. */
subject: string
/** Message language. */
lang?: string
/** DID of the user who is creating the template. */
createdBy?: string
[k: string]: unknown
@ -37,6 +39,17 @@ export interface Response {
data: OutputSchema
}
export class DuplicateTemplateNameError extends XRPCError {
constructor(src: XRPCError) {
super(src.status, src.error, src.message, src.headers, { cause: src })
}
}
export function toKnownErr(e: any) {
if (e instanceof XRPCError) {
if (e.error === 'DuplicateTemplateName')
return new DuplicateTemplateNameError(e)
}
return e
}

@ -15,6 +15,8 @@ export interface TemplateView {
/** Subject of the message, used in emails. */
contentMarkdown: string
disabled: boolean
/** Message language. */
lang?: string
/** DID of the user who last updated the template. */
lastUpdatedBy: string
createdAt: string

@ -15,6 +15,8 @@ export interface InputSchema {
id: string
/** Name of the template. */
name?: string
/** Message language. */
lang?: string
/** Content of the template, markdown supported, can contain variable placeholders. */
contentMarkdown?: string
/** Subject of the message, used in emails. */
@ -40,6 +42,17 @@ export interface Response {
data: OutputSchema
}
export class DuplicateTemplateNameError extends XRPCError {
constructor(src: XRPCError) {
super(src.status, src.error, src.message, src.headers, { cause: src })
}
}
export function toKnownErr(e: any) {
if (e instanceof XRPCError) {
if (e.error === 'DuplicateTemplateName')
return new DuplicateTemplateNameError(e)
}
return e
}

@ -25,6 +25,7 @@ export interface InputSchema {
| ToolsOzoneModerationDefs.ModEventMuteReporter
| ToolsOzoneModerationDefs.ModEventUnmuteReporter
| ToolsOzoneModerationDefs.ModEventReverseTakedown
| ToolsOzoneModerationDefs.ModEventResolveAppeal
| ToolsOzoneModerationDefs.ModEventEmail
| ToolsOzoneModerationDefs.ModEventTag
| { $type: string; [k: string]: unknown }

@ -1,6 +1,7 @@
import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server'
import { Server } from '../../lexicon'
import AppContext from '../../context'
import { isDuplicateTemplateNameError } from '../../communication-service/util'
export default function (server: Server, ctx: AppContext) {
server.tools.ozone.communication.createTemplate({
@ -8,7 +9,7 @@ export default function (server: Server, ctx: AppContext) {
handler: async ({ input, auth }) => {
const access = auth.credentials
const db = ctx.db
const { createdBy, ...template } = input.body
const { createdBy, lang, ...template } = input.body
if (!access.isModerator) {
throw new AuthRequiredError(
@ -22,15 +23,28 @@ export default function (server: Server, ctx: AppContext) {
}
const communicationTemplate = ctx.communicationTemplateService(db)
const newTemplate = await communicationTemplate.create({
...template,
disabled: false,
lastUpdatedBy: createdBy,
})
return {
encoding: 'application/json',
body: communicationTemplate.view(newTemplate),
try {
const newTemplate = await communicationTemplate.create({
...template,
// We are not using ?? here because we want to use null instead of potentially empty string
lang: lang || null,
disabled: false,
lastUpdatedBy: createdBy,
})
return {
encoding: 'application/json',
body: communicationTemplate.view(newTemplate),
}
} catch (err) {
if (isDuplicateTemplateNameError(err)) {
throw new InvalidRequestError(
`${template.name} already exists. Please choose a different name.`,
'DuplicateTemplateName',
)
}
throw err
}
},
})

@ -1,6 +1,7 @@
import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server'
import { Server } from '../../lexicon'
import AppContext from '../../context'
import { isDuplicateTemplateNameError } from '../../communication-service/util'
export default function (server: Server, ctx: AppContext) {
server.tools.ozone.communication.updateTemplate({
@ -26,14 +27,24 @@ export default function (server: Server, ctx: AppContext) {
}
const communicationTemplate = ctx.communicationTemplateService(db)
const updatedTemplate = await communicationTemplate.update(Number(id), {
...template,
lastUpdatedBy: updatedBy,
})
try {
const updatedTemplate = await communicationTemplate.update(Number(id), {
...template,
lastUpdatedBy: updatedBy,
})
return {
encoding: 'application/json',
body: communicationTemplate.view(updatedTemplate),
return {
encoding: 'application/json',
body: communicationTemplate.view(updatedTemplate),
}
} catch (err) {
if (isDuplicateTemplateNameError(err)) {
throw new InvalidRequestError(
`${template.name} already exists. Please choose a different name.`,
'DuplicateTemplateName',
)
}
throw err
}
},
})

@ -27,6 +27,7 @@ export class CommunicationTemplateService {
name,
contentMarkdown,
subject,
lang,
disabled,
updatedAt,
createdAt,
@ -44,6 +45,7 @@ export class CommunicationTemplateService {
name,
contentMarkdown,
subject,
lang,
disabled,
lastUpdatedBy,
updatedAt: updatedAt || new Date(),
@ -62,6 +64,7 @@ export class CommunicationTemplateService {
contentMarkdown,
subject,
disabled,
lang,
updatedAt,
lastUpdatedBy,
}: Partial<Omit<Selectable<CommunicationTemplate>, 'id' | 'createdAt'>>,
@ -73,6 +76,7 @@ export class CommunicationTemplateService {
name,
contentMarkdown,
subject,
lang,
disabled,
lastUpdatedBy,
updatedAt: updatedAt || new Date(),
@ -96,6 +100,7 @@ export class CommunicationTemplateService {
name: template.name,
contentMarkdown: template.contentMarkdown,
disabled: template.disabled,
lang: template.lang || undefined,
subject: template.subject || undefined,
createdAt: template.createdAt.toISOString(),
updatedAt: template.updatedAt.toISOString(),

@ -0,0 +1,8 @@
// Postgresql will throw a specific error code with the constraint when trying to create a template with duplicate name
// see https://www.postgresql.org/docs/current/errcodes-appendix.html
export const isDuplicateTemplateNameError = (err: any) => {
return (
err?.['code'] === '23505' &&
err?.['constraint'] === 'communication_template_unique_name'
)
}

@ -0,0 +1,12 @@
import { Kysely } from 'kysely'
export async function up(db: Kysely<unknown>): Promise<void> {
await db.schema
.alterTable('communication_template')
.addColumn('lang', 'varchar')
.execute()
}
export async function down(db: Kysely<unknown>): Promise<void> {
await db.schema.alterTable('moderation_event').dropColumn('lang').execute()
}

@ -11,3 +11,4 @@ export * as _20240408T192432676Z from './20240408T192432676Z-mute-reporting'
export * as _20240506T225055595Z from './20240506T225055595Z-message-subject'
export * as _20240430T211332580Z from './20240521T211332580Z-member'
export * as _20240814T003647759Z from './20240814T003647759Z-event-created-at-index'
export * as _20240903T205730722Z from './20240903T205730722Z-add-template-lang'

@ -7,6 +7,7 @@ export interface CommunicationTemplate {
name: string
contentMarkdown: string
subject: string | null
lang: string | null
disabled: Generated<boolean>
createdAt: Date
updatedAt: Date

@ -10466,6 +10466,11 @@ export const schemaDict = {
type: 'string',
description: 'Subject of the message, used in emails.',
},
lang: {
type: 'string',
format: 'language',
description: 'Message language.',
},
createdBy: {
type: 'string',
format: 'did',
@ -10481,6 +10486,11 @@ export const schemaDict = {
ref: 'lex:tools.ozone.communication.defs#templateView',
},
},
errors: [
{
name: 'DuplicateTemplateName',
},
],
},
},
},
@ -10519,6 +10529,11 @@ export const schemaDict = {
disabled: {
type: 'boolean',
},
lang: {
type: 'string',
format: 'language',
description: 'Message language.',
},
lastUpdatedBy: {
type: 'string',
format: 'did',
@ -10606,6 +10621,11 @@ export const schemaDict = {
type: 'string',
description: 'Name of the template.',
},
lang: {
type: 'string',
format: 'language',
description: 'Message language.',
},
contentMarkdown: {
type: 'string',
description:
@ -10633,6 +10653,11 @@ export const schemaDict = {
ref: 'lex:tools.ozone.communication.defs#templateView',
},
},
errors: [
{
name: 'DuplicateTemplateName',
},
],
},
},
},
@ -11432,6 +11457,7 @@ export const schemaDict = {
'lex:tools.ozone.moderation.defs#modEventMuteReporter',
'lex:tools.ozone.moderation.defs#modEventUnmuteReporter',
'lex:tools.ozone.moderation.defs#modEventReverseTakedown',
'lex:tools.ozone.moderation.defs#modEventResolveAppeal',
'lex:tools.ozone.moderation.defs#modEventEmail',
'lex:tools.ozone.moderation.defs#modEventTag',
],

@ -18,6 +18,8 @@ export interface InputSchema {
contentMarkdown: string
/** Subject of the message, used in emails. */
subject: string
/** Message language. */
lang?: string
/** DID of the user who is creating the template. */
createdBy?: string
[k: string]: unknown
@ -39,6 +41,7 @@ export interface HandlerSuccess {
export interface HandlerError {
status: number
message?: string
error?: 'DuplicateTemplateName'
}
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough

@ -15,6 +15,8 @@ export interface TemplateView {
/** Subject of the message, used in emails. */
contentMarkdown: string
disabled: boolean
/** Message language. */
lang?: string
/** DID of the user who last updated the template. */
lastUpdatedBy: string
createdAt: string

@ -16,6 +16,8 @@ export interface InputSchema {
id: string
/** Name of the template. */
name?: string
/** Message language. */
lang?: string
/** Content of the template, markdown supported, can contain variable placeholders. */
contentMarkdown?: string
/** Subject of the message, used in emails. */
@ -42,6 +44,7 @@ export interface HandlerSuccess {
export interface HandlerError {
status: number
message?: string
error?: 'DuplicateTemplateName'
}
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough

@ -26,6 +26,7 @@ export interface InputSchema {
| ToolsOzoneModerationDefs.ModEventMuteReporter
| ToolsOzoneModerationDefs.ModEventUnmuteReporter
| ToolsOzoneModerationDefs.ModEventReverseTakedown
| ToolsOzoneModerationDefs.ModEventResolveAppeal
| ToolsOzoneModerationDefs.ModEventEmail
| ToolsOzoneModerationDefs.ModEventTag
| { $type: string; [k: string]: unknown }

@ -10466,6 +10466,11 @@ export const schemaDict = {
type: 'string',
description: 'Subject of the message, used in emails.',
},
lang: {
type: 'string',
format: 'language',
description: 'Message language.',
},
createdBy: {
type: 'string',
format: 'did',
@ -10481,6 +10486,11 @@ export const schemaDict = {
ref: 'lex:tools.ozone.communication.defs#templateView',
},
},
errors: [
{
name: 'DuplicateTemplateName',
},
],
},
},
},
@ -10519,6 +10529,11 @@ export const schemaDict = {
disabled: {
type: 'boolean',
},
lang: {
type: 'string',
format: 'language',
description: 'Message language.',
},
lastUpdatedBy: {
type: 'string',
format: 'did',
@ -10606,6 +10621,11 @@ export const schemaDict = {
type: 'string',
description: 'Name of the template.',
},
lang: {
type: 'string',
format: 'language',
description: 'Message language.',
},
contentMarkdown: {
type: 'string',
description:
@ -10633,6 +10653,11 @@ export const schemaDict = {
ref: 'lex:tools.ozone.communication.defs#templateView',
},
},
errors: [
{
name: 'DuplicateTemplateName',
},
],
},
},
},
@ -11432,6 +11457,7 @@ export const schemaDict = {
'lex:tools.ozone.moderation.defs#modEventMuteReporter',
'lex:tools.ozone.moderation.defs#modEventUnmuteReporter',
'lex:tools.ozone.moderation.defs#modEventReverseTakedown',
'lex:tools.ozone.moderation.defs#modEventResolveAppeal',
'lex:tools.ozone.moderation.defs#modEventEmail',
'lex:tools.ozone.moderation.defs#modEventTag',
],

@ -18,6 +18,8 @@ export interface InputSchema {
contentMarkdown: string
/** Subject of the message, used in emails. */
subject: string
/** Message language. */
lang?: string
/** DID of the user who is creating the template. */
createdBy?: string
[k: string]: unknown
@ -39,6 +41,7 @@ export interface HandlerSuccess {
export interface HandlerError {
status: number
message?: string
error?: 'DuplicateTemplateName'
}
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough

@ -15,6 +15,8 @@ export interface TemplateView {
/** Subject of the message, used in emails. */
contentMarkdown: string
disabled: boolean
/** Message language. */
lang?: string
/** DID of the user who last updated the template. */
lastUpdatedBy: string
createdAt: string

@ -16,6 +16,8 @@ export interface InputSchema {
id: string
/** Name of the template. */
name?: string
/** Message language. */
lang?: string
/** Content of the template, markdown supported, can contain variable placeholders. */
contentMarkdown?: string
/** Subject of the message, used in emails. */
@ -42,6 +44,7 @@ export interface HandlerSuccess {
export interface HandlerError {
status: number
message?: string
error?: 'DuplicateTemplateName'
}
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough

@ -26,6 +26,7 @@ export interface InputSchema {
| ToolsOzoneModerationDefs.ModEventMuteReporter
| ToolsOzoneModerationDefs.ModEventUnmuteReporter
| ToolsOzoneModerationDefs.ModEventReverseTakedown
| ToolsOzoneModerationDefs.ModEventResolveAppeal
| ToolsOzoneModerationDefs.ModEventEmail
| ToolsOzoneModerationDefs.ModEventTag
| { $type: string; [k: string]: unknown }