Merge pull request #290 from authorizerdev/feat/plain-html-editor

feat: add plain html editor for email templates
This commit is contained in:
Lakhan Samani 2022-11-16 11:31:42 +05:30 committed by GitHub
commit 824f286b9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 565 additions and 1500 deletions

File diff suppressed because it is too large Load Diff

View File

@ -29,6 +29,10 @@ import {
Tbody, Tbody,
Td, Td,
Code, Code,
Radio,
RadioGroup,
Stack,
Textarea,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { FaPlus, FaAngleDown, FaAngleUp } from 'react-icons/fa'; import { FaPlus, FaAngleDown, FaAngleUp } from 'react-icons/fa';
import { useClient } from 'urql'; import { useClient } from 'urql';
@ -38,6 +42,7 @@ import {
EmailTemplateInputDataFields, EmailTemplateInputDataFields,
emailTemplateEventNames, emailTemplateEventNames,
emailTemplateVariables, emailTemplateVariables,
EmailTemplateEditors,
} from '../constants'; } from '../constants';
import { capitalizeFirstLetter } from '../utils'; import { capitalizeFirstLetter } from '../utils';
import { AddEmailTemplate, EditEmailTemplate } from '../graphql/mutation'; import { AddEmailTemplate, EditEmailTemplate } from '../graphql/mutation';
@ -66,6 +71,8 @@ interface templateVariableDataTypes {
interface emailTemplateDataType { interface emailTemplateDataType {
[EmailTemplateInputDataFields.EVENT_NAME]: string; [EmailTemplateInputDataFields.EVENT_NAME]: string;
[EmailTemplateInputDataFields.SUBJECT]: string; [EmailTemplateInputDataFields.SUBJECT]: string;
[EmailTemplateInputDataFields.TEMPLATE]: string;
[EmailTemplateInputDataFields.DESIGN]: string;
} }
interface validatorDataType { interface validatorDataType {
@ -75,6 +82,8 @@ interface validatorDataType {
const initTemplateData: emailTemplateDataType = { const initTemplateData: emailTemplateDataType = {
[EmailTemplateInputDataFields.EVENT_NAME]: emailTemplateEventNames.Signup, [EmailTemplateInputDataFields.EVENT_NAME]: emailTemplateEventNames.Signup,
[EmailTemplateInputDataFields.SUBJECT]: '', [EmailTemplateInputDataFields.SUBJECT]: '',
[EmailTemplateInputDataFields.TEMPLATE]: '',
[EmailTemplateInputDataFields.DESIGN]: '',
}; };
const initTemplateValidatorData: validatorDataType = { const initTemplateValidatorData: validatorDataType = {
@ -91,6 +100,9 @@ const UpdateEmailTemplate = ({
const emailEditorRef = useRef(null); const emailEditorRef = useRef(null);
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false);
const [editor, setEditor] = useState<string>(
EmailTemplateEditors.PLAIN_HTML_EDITOR,
);
const [templateVariables, setTemplateVariables] = useState< const [templateVariables, setTemplateVariables] = useState<
templateVariableDataTypes[] templateVariableDataTypes[]
>([]); >([]);
@ -107,9 +119,11 @@ const UpdateEmailTemplate = ({
if (selectedTemplate) { if (selectedTemplate) {
const { design } = selectedTemplate; const { design } = selectedTemplate;
try { try {
const designData = JSON.parse(design); if (design) {
// @ts-ignore const designData = JSON.parse(design);
emailEditorRef.current.editor.loadDesign(designData); // @ts-ignore
emailEditorRef.current.editor.loadDesign(designData);
}
} catch (error) { } catch (error) {
console.error(error); console.error(error);
onClose(); onClose();
@ -136,70 +150,85 @@ const UpdateEmailTemplate = ({
); );
}; };
const updateTemplate = async (params: emailTemplateDataType) => {
let res: any = {};
if (
view === UpdateModalViews.Edit &&
selectedTemplate?.[EmailTemplateInputDataFields.ID]
) {
res = await client
.mutation(EditEmailTemplate, {
params: {
...params,
id: selectedTemplate[EmailTemplateInputDataFields.ID],
},
})
.toPromise();
} else {
res = await client.mutation(AddEmailTemplate, { params }).toPromise();
}
setLoading(false);
if (res.error) {
toast({
title: capitalizeFirstLetter(res.error.message),
isClosable: true,
status: 'error',
position: 'bottom-right',
});
} else if (
res.data?._add_email_template ||
res.data?._update_email_template
) {
toast({
title: capitalizeFirstLetter(
res.data?._add_email_template?.message ||
res.data?._update_email_template?.message,
),
isClosable: true,
status: 'success',
position: 'bottom-right',
});
setTemplateData({
...initTemplateData,
});
setValidator({ ...initTemplateValidatorData });
fetchEmailTemplatesData();
}
};
const saveData = async () => { const saveData = async () => {
if (!validateData()) return; if (!validateData()) return;
setLoading(true); setLoading(true);
// @ts-ignore let params: emailTemplateDataType = {
return await emailEditorRef.current.editor.exportHtml(async (data) => { [EmailTemplateInputDataFields.EVENT_NAME]:
const { design, html } = data; templateData[EmailTemplateInputDataFields.EVENT_NAME],
if (!html || !design) { [EmailTemplateInputDataFields.SUBJECT]:
setLoading(false); templateData[EmailTemplateInputDataFields.SUBJECT],
return; [EmailTemplateInputDataFields.TEMPLATE]:
} templateData[EmailTemplateInputDataFields.TEMPLATE],
const params = { [EmailTemplateInputDataFields.DESIGN]: '',
[EmailTemplateInputDataFields.EVENT_NAME]: };
templateData[EmailTemplateInputDataFields.EVENT_NAME], if (editor === EmailTemplateEditors.UNLAYER_EDITOR) {
[EmailTemplateInputDataFields.SUBJECT]: // @ts-ignore
templateData[EmailTemplateInputDataFields.SUBJECT], await emailEditorRef.current.editor.exportHtml(async (data) => {
[EmailTemplateInputDataFields.TEMPLATE]: html.trim(), const { design, html } = data;
[EmailTemplateInputDataFields.DESIGN]: JSON.stringify(design), if (!html || !design) {
}; setLoading(false);
let res: any = {}; return;
if ( }
view === UpdateModalViews.Edit && params = {
selectedTemplate?.[EmailTemplateInputDataFields.ID] ...params,
) { [EmailTemplateInputDataFields.TEMPLATE]: html.trim(),
res = await client [EmailTemplateInputDataFields.DESIGN]: JSON.stringify(design),
.mutation(EditEmailTemplate, { };
params: { await updateTemplate(params);
...params, });
id: selectedTemplate[EmailTemplateInputDataFields.ID], } else {
}, await updateTemplate(params);
}) }
.toPromise(); view === UpdateModalViews.ADD && onClose();
} else {
res = await client.mutation(AddEmailTemplate, { params }).toPromise();
}
setLoading(false);
if (res.error) {
toast({
title: capitalizeFirstLetter(res.error.message),
isClosable: true,
status: 'error',
position: 'bottom-right',
});
} else if (
res.data?._add_email_template ||
res.data?._update_email_template
) {
toast({
title: capitalizeFirstLetter(
res.data?._add_email_template?.message ||
res.data?._update_email_template?.message,
),
isClosable: true,
status: 'success',
position: 'bottom-right',
});
setTemplateData({
...initTemplateData,
});
setValidator({ ...initTemplateValidatorData });
fetchEmailTemplatesData();
}
view === UpdateModalViews.ADD && onClose();
});
}; };
const resetData = () => { const resetData = () => {
if (selectedTemplate) { if (selectedTemplate) {
setTemplateData(selectedTemplate); setTemplateData(selectedTemplate);
@ -207,6 +236,8 @@ const UpdateEmailTemplate = ({
setTemplateData({ ...initTemplateData }); setTemplateData({ ...initTemplateData });
} }
}; };
// set template data if edit modal is open
useEffect(() => { useEffect(() => {
if ( if (
isOpen && isOpen &&
@ -214,10 +245,12 @@ const UpdateEmailTemplate = ({
selectedTemplate && selectedTemplate &&
Object.keys(selectedTemplate || {}).length Object.keys(selectedTemplate || {}).length
) { ) {
const { id, created_at, template, design, ...rest } = selectedTemplate; const { id, created_at, ...rest } = selectedTemplate;
setTemplateData(rest); setTemplateData(rest);
} }
}, [isOpen]); }, [isOpen]);
// set template variables
useEffect(() => { useEffect(() => {
const updatedTemplateVariables = Object.entries( const updatedTemplateVariables = Object.entries(
emailTemplateVariables, emailTemplateVariables,
@ -244,6 +277,51 @@ const UpdateEmailTemplate = ({
setTemplateVariables(updatedTemplateVariables); setTemplateVariables(updatedTemplateVariables);
}, [templateData[EmailTemplateInputDataFields.EVENT_NAME]]); }, [templateData[EmailTemplateInputDataFields.EVENT_NAME]]);
// change editor
useEffect(() => {
if (isOpen && selectedTemplate) {
const { design } = selectedTemplate;
if (design) {
setEditor(EmailTemplateEditors.UNLAYER_EDITOR);
} else {
setEditor(EmailTemplateEditors.PLAIN_HTML_EDITOR);
}
}
}, [isOpen, selectedTemplate]);
// reset fields when editor is changed
useEffect(() => {
if (selectedTemplate?.design) {
if (editor === EmailTemplateEditors.UNLAYER_EDITOR) {
setTemplateData({
...templateData,
[EmailTemplateInputDataFields.TEMPLATE]: selectedTemplate.template,
[EmailTemplateInputDataFields.DESIGN]: selectedTemplate.design,
});
} else {
setTemplateData({
...templateData,
[EmailTemplateInputDataFields.TEMPLATE]: '',
[EmailTemplateInputDataFields.DESIGN]: '',
});
}
} else if (selectedTemplate?.template) {
if (editor === EmailTemplateEditors.UNLAYER_EDITOR) {
setTemplateData({
...templateData,
[EmailTemplateInputDataFields.TEMPLATE]: '',
[EmailTemplateInputDataFields.DESIGN]: '',
});
} else {
setTemplateData({
...templateData,
[EmailTemplateInputDataFields.TEMPLATE]: selectedTemplate?.template,
[EmailTemplateInputDataFields.DESIGN]: '',
});
}
}
}, [editor]);
return ( return (
<> <>
{view === UpdateModalViews.ADD ? ( {view === UpdateModalViews.ADD ? (
@ -414,7 +492,22 @@ const UpdateEmailTemplate = ({
alignItems="center" alignItems="center"
marginBottom="2%" marginBottom="2%"
> >
Template Body <Flex flex="1">Template Body</Flex>
<Flex flex="3">
<RadioGroup
onChange={(value) => setEditor(value)}
value={editor}
>
<Stack direction="row" spacing="50px">
<Radio value={EmailTemplateEditors.PLAIN_HTML_EDITOR}>
Plain HTML
</Radio>
<Radio value={EmailTemplateEditors.UNLAYER_EDITOR}>
Unlayer Editor
</Radio>
</Stack>
</RadioGroup>
</Flex>
</Flex> </Flex>
<Flex <Flex
width="100%" width="100%"
@ -423,7 +516,22 @@ const UpdateEmailTemplate = ({
border="1px solid" border="1px solid"
borderColor="gray.200" borderColor="gray.200"
> >
<EmailEditor ref={emailEditorRef} onReady={onReady} /> {editor === EmailTemplateEditors.UNLAYER_EDITOR ? (
<EmailEditor ref={emailEditorRef} onReady={onReady} />
) : (
<Textarea
value={templateData.template}
onChange={(e) => {
setTemplateData({
...templateData,
[EmailTemplateInputDataFields.TEMPLATE]: e.target.value,
});
}}
placeholder="Template HTML"
border="0"
height="500px"
/>
)}
</Flex> </Flex>
</Flex> </Flex>
</ModalBody> </ModalBody>

View File

@ -337,3 +337,8 @@ export const webhookPayloadExample: string = `{
}, },
"auth_recipe":"google" "auth_recipe":"google"
}`; }`;
export enum EmailTemplateEditors {
UNLAYER_EDITOR = 'unlayer_editor',
PLAIN_HTML_EDITOR = 'plain_html_editor',
}

View File

@ -1971,501 +1971,501 @@ scalar Map
scalar Any scalar Any
type Pagination { type Pagination {
limit: Int64! limit: Int64!
page: Int64! page: Int64!
offset: Int64! offset: Int64!
total: Int64! total: Int64!
} }
type Meta { type Meta {
version: String! version: String!
client_id: String! client_id: String!
is_google_login_enabled: Boolean! is_google_login_enabled: Boolean!
is_facebook_login_enabled: Boolean! is_facebook_login_enabled: Boolean!
is_github_login_enabled: Boolean! is_github_login_enabled: Boolean!
is_linkedin_login_enabled: Boolean! is_linkedin_login_enabled: Boolean!
is_apple_login_enabled: Boolean! is_apple_login_enabled: Boolean!
is_twitter_login_enabled: Boolean! is_twitter_login_enabled: Boolean!
is_email_verification_enabled: Boolean! is_email_verification_enabled: Boolean!
is_basic_authentication_enabled: Boolean! is_basic_authentication_enabled: Boolean!
is_magic_link_login_enabled: Boolean! is_magic_link_login_enabled: Boolean!
is_sign_up_enabled: Boolean! is_sign_up_enabled: Boolean!
is_strong_password_enabled: Boolean! is_strong_password_enabled: Boolean!
is_multi_factor_auth_enabled: Boolean! is_multi_factor_auth_enabled: Boolean!
} }
type User { type User {
id: ID! id: ID!
email: String! email: String!
email_verified: Boolean! email_verified: Boolean!
signup_methods: String! signup_methods: String!
given_name: String given_name: String
family_name: String family_name: String
middle_name: String middle_name: String
nickname: String nickname: String
# defaults to email # defaults to email
preferred_username: String preferred_username: String
gender: String gender: String
birthdate: String birthdate: String
phone_number: String phone_number: String
phone_number_verified: Boolean phone_number_verified: Boolean
picture: String picture: String
roles: [String!]! roles: [String!]!
created_at: Int64 created_at: Int64
updated_at: Int64 updated_at: Int64
revoked_timestamp: Int64 revoked_timestamp: Int64
is_multi_factor_auth_enabled: Boolean is_multi_factor_auth_enabled: Boolean
} }
type Users { type Users {
pagination: Pagination! pagination: Pagination!
users: [User!]! users: [User!]!
} }
type VerificationRequest { type VerificationRequest {
id: ID! id: ID!
identifier: String identifier: String
token: String token: String
email: String email: String
expires: Int64 expires: Int64
created_at: Int64 created_at: Int64
updated_at: Int64 updated_at: Int64
nonce: String nonce: String
redirect_uri: String redirect_uri: String
} }
type VerificationRequests { type VerificationRequests {
pagination: Pagination! pagination: Pagination!
verification_requests: [VerificationRequest!]! verification_requests: [VerificationRequest!]!
} }
type Error { type Error {
message: String! message: String!
reason: String! reason: String!
} }
type AuthResponse { type AuthResponse {
message: String! message: String!
should_show_otp_screen: Boolean should_show_otp_screen: Boolean
access_token: String access_token: String
id_token: String id_token: String
refresh_token: String refresh_token: String
expires_in: Int64 expires_in: Int64
user: User user: User
} }
type Response { type Response {
message: String! message: String!
} }
type Env { type Env {
ACCESS_TOKEN_EXPIRY_TIME: String ACCESS_TOKEN_EXPIRY_TIME: String
ADMIN_SECRET: String ADMIN_SECRET: String
DATABASE_NAME: String DATABASE_NAME: String
DATABASE_URL: String DATABASE_URL: String
DATABASE_TYPE: String DATABASE_TYPE: String
DATABASE_USERNAME: String DATABASE_USERNAME: String
DATABASE_PASSWORD: String DATABASE_PASSWORD: String
DATABASE_HOST: String DATABASE_HOST: String
DATABASE_PORT: String DATABASE_PORT: String
CLIENT_ID: String! CLIENT_ID: String!
CLIENT_SECRET: String! CLIENT_SECRET: String!
CUSTOM_ACCESS_TOKEN_SCRIPT: String CUSTOM_ACCESS_TOKEN_SCRIPT: String
SMTP_HOST: String SMTP_HOST: String
SMTP_PORT: String SMTP_PORT: String
SMTP_USERNAME: String SMTP_USERNAME: String
SMTP_PASSWORD: String SMTP_PASSWORD: String
SMTP_LOCAL_NAME: String SMTP_LOCAL_NAME: String
SENDER_EMAIL: String SENDER_EMAIL: String
JWT_TYPE: String JWT_TYPE: String
JWT_SECRET: String JWT_SECRET: String
JWT_PRIVATE_KEY: String JWT_PRIVATE_KEY: String
JWT_PUBLIC_KEY: String JWT_PUBLIC_KEY: String
ALLOWED_ORIGINS: [String!] ALLOWED_ORIGINS: [String!]
APP_URL: String APP_URL: String
REDIS_URL: String REDIS_URL: String
RESET_PASSWORD_URL: String RESET_PASSWORD_URL: String
DISABLE_EMAIL_VERIFICATION: Boolean! DISABLE_EMAIL_VERIFICATION: Boolean!
DISABLE_BASIC_AUTHENTICATION: Boolean! DISABLE_BASIC_AUTHENTICATION: Boolean!
DISABLE_MAGIC_LINK_LOGIN: Boolean! DISABLE_MAGIC_LINK_LOGIN: Boolean!
DISABLE_LOGIN_PAGE: Boolean! DISABLE_LOGIN_PAGE: Boolean!
DISABLE_SIGN_UP: Boolean! DISABLE_SIGN_UP: Boolean!
DISABLE_REDIS_FOR_ENV: Boolean! DISABLE_REDIS_FOR_ENV: Boolean!
DISABLE_STRONG_PASSWORD: Boolean! DISABLE_STRONG_PASSWORD: Boolean!
DISABLE_MULTI_FACTOR_AUTHENTICATION: Boolean! DISABLE_MULTI_FACTOR_AUTHENTICATION: Boolean!
ENFORCE_MULTI_FACTOR_AUTHENTICATION: Boolean! ENFORCE_MULTI_FACTOR_AUTHENTICATION: Boolean!
ROLES: [String!] ROLES: [String!]
PROTECTED_ROLES: [String!] PROTECTED_ROLES: [String!]
DEFAULT_ROLES: [String!] DEFAULT_ROLES: [String!]
JWT_ROLE_CLAIM: String JWT_ROLE_CLAIM: String
GOOGLE_CLIENT_ID: String GOOGLE_CLIENT_ID: String
GOOGLE_CLIENT_SECRET: String GOOGLE_CLIENT_SECRET: String
GITHUB_CLIENT_ID: String GITHUB_CLIENT_ID: String
GITHUB_CLIENT_SECRET: String GITHUB_CLIENT_SECRET: String
FACEBOOK_CLIENT_ID: String FACEBOOK_CLIENT_ID: String
FACEBOOK_CLIENT_SECRET: String FACEBOOK_CLIENT_SECRET: String
LINKEDIN_CLIENT_ID: String LINKEDIN_CLIENT_ID: String
LINKEDIN_CLIENT_SECRET: String LINKEDIN_CLIENT_SECRET: String
APPLE_CLIENT_ID: String APPLE_CLIENT_ID: String
APPLE_CLIENT_SECRET: String APPLE_CLIENT_SECRET: String
TWITTER_CLIENT_ID: String TWITTER_CLIENT_ID: String
TWITTER_CLIENT_SECRET: String TWITTER_CLIENT_SECRET: String
ORGANIZATION_NAME: String ORGANIZATION_NAME: String
ORGANIZATION_LOGO: String ORGANIZATION_LOGO: String
APP_COOKIE_SECURE: Boolean! APP_COOKIE_SECURE: Boolean!
ADMIN_COOKIE_SECURE: Boolean! ADMIN_COOKIE_SECURE: Boolean!
} }
type ValidateJWTTokenResponse { type ValidateJWTTokenResponse {
is_valid: Boolean! is_valid: Boolean!
claims: Map claims: Map
} }
type GenerateJWTKeysResponse { type GenerateJWTKeysResponse {
secret: String secret: String
public_key: String public_key: String
private_key: String private_key: String
} }
type Webhook { type Webhook {
id: ID! id: ID!
event_name: String event_name: String
endpoint: String endpoint: String
enabled: Boolean enabled: Boolean
headers: Map headers: Map
created_at: Int64 created_at: Int64
updated_at: Int64 updated_at: Int64
} }
type Webhooks { type Webhooks {
pagination: Pagination! pagination: Pagination!
webhooks: [Webhook!]! webhooks: [Webhook!]!
} }
type WebhookLog { type WebhookLog {
id: ID! id: ID!
http_status: Int64 http_status: Int64
response: String response: String
request: String request: String
webhook_id: ID webhook_id: ID
created_at: Int64 created_at: Int64
updated_at: Int64 updated_at: Int64
} }
type TestEndpointResponse { type TestEndpointResponse {
http_status: Int64 http_status: Int64
response: String response: String
} }
type WebhookLogs { type WebhookLogs {
pagination: Pagination! pagination: Pagination!
webhook_logs: [WebhookLog!]! webhook_logs: [WebhookLog!]!
} }
type EmailTemplate { type EmailTemplate {
id: ID! id: ID!
event_name: String! event_name: String!
template: String! template: String!
design: String! design: String!
subject: String! subject: String!
created_at: Int64 created_at: Int64
updated_at: Int64 updated_at: Int64
} }
type EmailTemplates { type EmailTemplates {
pagination: Pagination! pagination: Pagination!
email_templates: [EmailTemplate!]! email_templates: [EmailTemplate!]!
} }
input UpdateEnvInput { input UpdateEnvInput {
ACCESS_TOKEN_EXPIRY_TIME: String ACCESS_TOKEN_EXPIRY_TIME: String
ADMIN_SECRET: String ADMIN_SECRET: String
CUSTOM_ACCESS_TOKEN_SCRIPT: String CUSTOM_ACCESS_TOKEN_SCRIPT: String
OLD_ADMIN_SECRET: String OLD_ADMIN_SECRET: String
SMTP_HOST: String SMTP_HOST: String
SMTP_PORT: String SMTP_PORT: String
SMTP_USERNAME: String SMTP_USERNAME: String
SMTP_PASSWORD: String SMTP_PASSWORD: String
SMTP_LOCAL_NAME: String SMTP_LOCAL_NAME: String
SENDER_EMAIL: String SENDER_EMAIL: String
JWT_TYPE: String JWT_TYPE: String
JWT_SECRET: String JWT_SECRET: String
JWT_PRIVATE_KEY: String JWT_PRIVATE_KEY: String
JWT_PUBLIC_KEY: String JWT_PUBLIC_KEY: String
ALLOWED_ORIGINS: [String!] ALLOWED_ORIGINS: [String!]
APP_URL: String APP_URL: String
RESET_PASSWORD_URL: String RESET_PASSWORD_URL: String
APP_COOKIE_SECURE: Boolean APP_COOKIE_SECURE: Boolean
ADMIN_COOKIE_SECURE: Boolean ADMIN_COOKIE_SECURE: Boolean
DISABLE_EMAIL_VERIFICATION: Boolean DISABLE_EMAIL_VERIFICATION: Boolean
DISABLE_BASIC_AUTHENTICATION: Boolean DISABLE_BASIC_AUTHENTICATION: Boolean
DISABLE_MAGIC_LINK_LOGIN: Boolean DISABLE_MAGIC_LINK_LOGIN: Boolean
DISABLE_LOGIN_PAGE: Boolean DISABLE_LOGIN_PAGE: Boolean
DISABLE_SIGN_UP: Boolean DISABLE_SIGN_UP: Boolean
DISABLE_REDIS_FOR_ENV: Boolean DISABLE_REDIS_FOR_ENV: Boolean
DISABLE_STRONG_PASSWORD: Boolean DISABLE_STRONG_PASSWORD: Boolean
DISABLE_MULTI_FACTOR_AUTHENTICATION: Boolean DISABLE_MULTI_FACTOR_AUTHENTICATION: Boolean
ENFORCE_MULTI_FACTOR_AUTHENTICATION: Boolean ENFORCE_MULTI_FACTOR_AUTHENTICATION: Boolean
ROLES: [String!] ROLES: [String!]
PROTECTED_ROLES: [String!] PROTECTED_ROLES: [String!]
DEFAULT_ROLES: [String!] DEFAULT_ROLES: [String!]
JWT_ROLE_CLAIM: String JWT_ROLE_CLAIM: String
GOOGLE_CLIENT_ID: String GOOGLE_CLIENT_ID: String
GOOGLE_CLIENT_SECRET: String GOOGLE_CLIENT_SECRET: String
GITHUB_CLIENT_ID: String GITHUB_CLIENT_ID: String
GITHUB_CLIENT_SECRET: String GITHUB_CLIENT_SECRET: String
FACEBOOK_CLIENT_ID: String FACEBOOK_CLIENT_ID: String
FACEBOOK_CLIENT_SECRET: String FACEBOOK_CLIENT_SECRET: String
LINKEDIN_CLIENT_ID: String LINKEDIN_CLIENT_ID: String
LINKEDIN_CLIENT_SECRET: String LINKEDIN_CLIENT_SECRET: String
APPLE_CLIENT_ID: String APPLE_CLIENT_ID: String
APPLE_CLIENT_SECRET: String APPLE_CLIENT_SECRET: String
TWITTER_CLIENT_ID: String TWITTER_CLIENT_ID: String
TWITTER_CLIENT_SECRET: String TWITTER_CLIENT_SECRET: String
ORGANIZATION_NAME: String ORGANIZATION_NAME: String
ORGANIZATION_LOGO: String ORGANIZATION_LOGO: String
} }
input AdminLoginInput { input AdminLoginInput {
admin_secret: String! admin_secret: String!
} }
input AdminSignupInput { input AdminSignupInput {
admin_secret: String! admin_secret: String!
} }
input SignUpInput { input SignUpInput {
email: String! email: String!
given_name: String given_name: String
family_name: String family_name: String
middle_name: String middle_name: String
nickname: String nickname: String
gender: String gender: String
birthdate: String birthdate: String
phone_number: String phone_number: String
picture: String picture: String
password: String! password: String!
confirm_password: String! confirm_password: String!
roles: [String!] roles: [String!]
scope: [String!] scope: [String!]
redirect_uri: String redirect_uri: String
is_multi_factor_auth_enabled: Boolean is_multi_factor_auth_enabled: Boolean
} }
input LoginInput { input LoginInput {
email: String! email: String!
password: String! password: String!
roles: [String!] roles: [String!]
scope: [String!] scope: [String!]
} }
input VerifyEmailInput { input VerifyEmailInput {
token: String! token: String!
} }
input ResendVerifyEmailInput { input ResendVerifyEmailInput {
email: String! email: String!
identifier: String! identifier: String!
} }
input UpdateProfileInput { input UpdateProfileInput {
old_password: String old_password: String
new_password: String new_password: String
confirm_new_password: String confirm_new_password: String
email: String email: String
given_name: String given_name: String
family_name: String family_name: String
middle_name: String middle_name: String
nickname: String nickname: String
gender: String gender: String
birthdate: String birthdate: String
phone_number: String phone_number: String
picture: String picture: String
is_multi_factor_auth_enabled: Boolean is_multi_factor_auth_enabled: Boolean
} }
input UpdateUserInput { input UpdateUserInput {
id: ID! id: ID!
email: String email: String
email_verified: Boolean email_verified: Boolean
given_name: String given_name: String
family_name: String family_name: String
middle_name: String middle_name: String
nickname: String nickname: String
gender: String gender: String
birthdate: String birthdate: String
phone_number: String phone_number: String
picture: String picture: String
roles: [String] roles: [String]
is_multi_factor_auth_enabled: Boolean is_multi_factor_auth_enabled: Boolean
} }
input ForgotPasswordInput { input ForgotPasswordInput {
email: String! email: String!
state: String state: String
redirect_uri: String redirect_uri: String
} }
input ResetPasswordInput { input ResetPasswordInput {
token: String! token: String!
password: String! password: String!
confirm_password: String! confirm_password: String!
} }
input DeleteUserInput { input DeleteUserInput {
email: String! email: String!
} }
input MagicLinkLoginInput { input MagicLinkLoginInput {
email: String! email: String!
roles: [String!] roles: [String!]
scope: [String!] scope: [String!]
state: String state: String
redirect_uri: String redirect_uri: String
} }
input SessionQueryInput { input SessionQueryInput {
roles: [String!] roles: [String!]
scope: [String!] scope: [String!]
} }
input PaginationInput { input PaginationInput {
limit: Int64 limit: Int64
page: Int64 page: Int64
} }
input PaginatedInput { input PaginatedInput {
pagination: PaginationInput pagination: PaginationInput
} }
input OAuthRevokeInput { input OAuthRevokeInput {
refresh_token: String! refresh_token: String!
} }
input InviteMemberInput { input InviteMemberInput {
emails: [String!]! emails: [String!]!
redirect_uri: String redirect_uri: String
} }
input UpdateAccessInput { input UpdateAccessInput {
user_id: String! user_id: String!
} }
input ValidateJWTTokenInput { input ValidateJWTTokenInput {
token_type: String! token_type: String!
token: String! token: String!
roles: [String!] roles: [String!]
} }
input GenerateJWTKeysInput { input GenerateJWTKeysInput {
type: String! type: String!
} }
input ListWebhookLogRequest { input ListWebhookLogRequest {
pagination: PaginationInput pagination: PaginationInput
webhook_id: String webhook_id: String
} }
input AddWebhookRequest { input AddWebhookRequest {
event_name: String! event_name: String!
endpoint: String! endpoint: String!
enabled: Boolean! enabled: Boolean!
headers: Map headers: Map
} }
input UpdateWebhookRequest { input UpdateWebhookRequest {
id: ID! id: ID!
event_name: String event_name: String
endpoint: String endpoint: String
enabled: Boolean enabled: Boolean
headers: Map headers: Map
} }
input WebhookRequest { input WebhookRequest {
id: ID! id: ID!
} }
input TestEndpointRequest { input TestEndpointRequest {
endpoint: String! endpoint: String!
event_name: String! event_name: String!
headers: Map headers: Map
} }
input AddEmailTemplateRequest { input AddEmailTemplateRequest {
event_name: String! event_name: String!
subject: String! subject: String!
template: String! template: String!
design: String! design: String
} }
input UpdateEmailTemplateRequest { input UpdateEmailTemplateRequest {
id: ID! id: ID!
event_name: String event_name: String
template: String template: String
subject: String subject: String
design: String design: String
} }
input DeleteEmailTemplateRequest { input DeleteEmailTemplateRequest {
id: ID! id: ID!
} }
input VerifyOTPRequest { input VerifyOTPRequest {
email: String! email: String!
otp: String! otp: String!
} }
input ResendOTPRequest { input ResendOTPRequest {
email: String! email: String!
} }
type Mutation { type Mutation {
signup(params: SignUpInput!): AuthResponse! signup(params: SignUpInput!): AuthResponse!
login(params: LoginInput!): AuthResponse! login(params: LoginInput!): AuthResponse!
magic_link_login(params: MagicLinkLoginInput!): Response! magic_link_login(params: MagicLinkLoginInput!): Response!
logout: Response! logout: Response!
update_profile(params: UpdateProfileInput!): Response! update_profile(params: UpdateProfileInput!): Response!
verify_email(params: VerifyEmailInput!): AuthResponse! verify_email(params: VerifyEmailInput!): AuthResponse!
resend_verify_email(params: ResendVerifyEmailInput!): Response! resend_verify_email(params: ResendVerifyEmailInput!): Response!
forgot_password(params: ForgotPasswordInput!): Response! forgot_password(params: ForgotPasswordInput!): Response!
reset_password(params: ResetPasswordInput!): Response! reset_password(params: ResetPasswordInput!): Response!
revoke(params: OAuthRevokeInput!): Response! revoke(params: OAuthRevokeInput!): Response!
verify_otp(params: VerifyOTPRequest!): AuthResponse! verify_otp(params: VerifyOTPRequest!): AuthResponse!
resend_otp(params: ResendOTPRequest!): Response! resend_otp(params: ResendOTPRequest!): Response!
# admin only apis # admin only apis
_delete_user(params: DeleteUserInput!): Response! _delete_user(params: DeleteUserInput!): Response!
_update_user(params: UpdateUserInput!): User! _update_user(params: UpdateUserInput!): User!
_admin_signup(params: AdminSignupInput!): Response! _admin_signup(params: AdminSignupInput!): Response!
_admin_login(params: AdminLoginInput!): Response! _admin_login(params: AdminLoginInput!): Response!
_admin_logout: Response! _admin_logout: Response!
_update_env(params: UpdateEnvInput!): Response! _update_env(params: UpdateEnvInput!): Response!
_invite_members(params: InviteMemberInput!): Response! _invite_members(params: InviteMemberInput!): Response!
_revoke_access(param: UpdateAccessInput!): Response! _revoke_access(param: UpdateAccessInput!): Response!
_enable_access(param: UpdateAccessInput!): Response! _enable_access(param: UpdateAccessInput!): Response!
_generate_jwt_keys(params: GenerateJWTKeysInput!): GenerateJWTKeysResponse! _generate_jwt_keys(params: GenerateJWTKeysInput!): GenerateJWTKeysResponse!
_add_webhook(params: AddWebhookRequest!): Response! _add_webhook(params: AddWebhookRequest!): Response!
_update_webhook(params: UpdateWebhookRequest!): Response! _update_webhook(params: UpdateWebhookRequest!): Response!
_delete_webhook(params: WebhookRequest!): Response! _delete_webhook(params: WebhookRequest!): Response!
_test_endpoint(params: TestEndpointRequest!): TestEndpointResponse! _test_endpoint(params: TestEndpointRequest!): TestEndpointResponse!
_add_email_template(params: AddEmailTemplateRequest!): Response! _add_email_template(params: AddEmailTemplateRequest!): Response!
_update_email_template(params: UpdateEmailTemplateRequest!): Response! _update_email_template(params: UpdateEmailTemplateRequest!): Response!
_delete_email_template(params: DeleteEmailTemplateRequest!): Response! _delete_email_template(params: DeleteEmailTemplateRequest!): Response!
} }
type Query { type Query {
meta: Meta! meta: Meta!
session(params: SessionQueryInput): AuthResponse! session(params: SessionQueryInput): AuthResponse!
profile: User! profile: User!
validate_jwt_token(params: ValidateJWTTokenInput!): ValidateJWTTokenResponse! validate_jwt_token(params: ValidateJWTTokenInput!): ValidateJWTTokenResponse!
# admin only apis # admin only apis
_users(params: PaginatedInput): Users! _users(params: PaginatedInput): Users!
_verification_requests(params: PaginatedInput): VerificationRequests! _verification_requests(params: PaginatedInput): VerificationRequests!
_admin_session: Response! _admin_session: Response!
_env: Env! _env: Env!
_webhook(params: WebhookRequest!): Webhook! _webhook(params: WebhookRequest!): Webhook!
_webhooks(params: PaginatedInput): Webhooks! _webhooks(params: PaginatedInput): Webhooks!
_webhook_logs(params: ListWebhookLogRequest): WebhookLogs! _webhook_logs(params: ListWebhookLogRequest): WebhookLogs!
_email_templates(params: PaginatedInput): EmailTemplates! _email_templates(params: PaginatedInput): EmailTemplates!
} }
`, BuiltIn: false}, `, BuiltIn: false},
} }
@ -14130,7 +14130,7 @@ func (ec *executionContext) unmarshalInputAddEmailTemplateRequest(ctx context.Co
var err error var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("design")) ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("design"))
it.Design, err = ec.unmarshalNString2string(ctx, v) it.Design, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil { if err != nil {
return it, err return it, err
} }

View File

@ -3,10 +3,10 @@
package model package model
type AddEmailTemplateRequest struct { type AddEmailTemplateRequest struct {
EventName string `json:"event_name"` EventName string `json:"event_name"`
Subject string `json:"subject"` Subject string `json:"subject"`
Template string `json:"template"` Template string `json:"template"`
Design string `json:"design"` Design *string `json:"design"`
} }
type AddWebhookRequest struct { type AddWebhookRequest struct {

View File

@ -430,7 +430,7 @@ input AddEmailTemplateRequest {
event_name: String! event_name: String!
subject: String! subject: String!
template: String! template: String!
design: String! design: String
} }
input UpdateEmailTemplateRequest { input UpdateEmailTemplateRequest {

View File

@ -8,6 +8,7 @@ import (
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/refs"
"github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
"github.com/authorizerdev/authorizer/server/validators" "github.com/authorizerdev/authorizer/server/validators"
@ -40,15 +41,17 @@ func AddEmailTemplateResolver(ctx context.Context, params model.AddEmailTemplate
return nil, fmt.Errorf("empty template not allowed") return nil, fmt.Errorf("empty template not allowed")
} }
if strings.TrimSpace(params.Design) == "" { var design string
return nil, fmt.Errorf("empty design not allowed")
if params.Design == nil || strings.TrimSpace(refs.StringValue(params.Design)) == "" {
design = ""
} }
_, err = db.Provider.AddEmailTemplate(ctx, models.EmailTemplate{ _, err = db.Provider.AddEmailTemplate(ctx, models.EmailTemplate{
EventName: params.EventName, EventName: params.EventName,
Template: params.Template, Template: params.Template,
Subject: params.Subject, Subject: params.Subject,
Design: params.Design, Design: design,
}) })
if err != nil { if err != nil {
log.Debug("Failed to add email template: ", err) log.Debug("Failed to add email template: ", err)

View File

@ -51,16 +51,30 @@ func addEmailTemplateTest(t *testing.T, s TestSetup) {
assert.Nil(t, emailTemplate) assert.Nil(t, emailTemplate)
}) })
t.Run("should not add email template with empty design", func(t *testing.T) { var design string
emailTemplate, err := resolvers.AddEmailTemplateResolver(ctx, model.AddEmailTemplateRequest{ design = ""
EventName: s.TestInfo.TestEmailTemplateEventTypes[0],
Template: "test", for _, eventType := range s.TestInfo.TestEmailTemplateEventTypes {
Subject: "test", t.Run("should add email template with empty design for "+eventType, func(t *testing.T) {
Design: " ", emailTemplate, err := resolvers.AddEmailTemplateResolver(ctx, model.AddEmailTemplateRequest{
EventName: eventType,
Template: "Test email",
Subject: "Test email",
Design: &design,
})
assert.NoError(t, err)
assert.NotNil(t, emailTemplate)
assert.NotEmpty(t, emailTemplate.Message)
et, err := db.Provider.GetEmailTemplateByEventName(ctx, eventType)
assert.NoError(t, err)
assert.Equal(t, et.EventName, eventType)
assert.Equal(t, "Test email", et.Subject)
assert.Equal(t, "Test design", et.Design)
}) })
assert.Error(t, err) }
assert.Nil(t, emailTemplate)
}) design = "Test design"
for _, eventType := range s.TestInfo.TestEmailTemplateEventTypes { for _, eventType := range s.TestInfo.TestEmailTemplateEventTypes {
t.Run("should add email template for "+eventType, func(t *testing.T) { t.Run("should add email template for "+eventType, func(t *testing.T) {
@ -68,7 +82,7 @@ func addEmailTemplateTest(t *testing.T, s TestSetup) {
EventName: eventType, EventName: eventType,
Template: "Test email", Template: "Test email",
Subject: "Test email", Subject: "Test email",
Design: "Test design", Design: &design,
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, emailTemplate) assert.NotNil(t, emailTemplate)