Skip to content

Commit

Permalink
feat: user welcome email
Browse files Browse the repository at this point in the history
  • Loading branch information
NGPixel committed Oct 30, 2023
1 parent 057ef82 commit b7d2547
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 20 deletions.
1 change: 1 addition & 0 deletions server/graph/schemas/user.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ extend type Mutation {
groups: [UUID]!
mustChangePassword: Boolean!
sendWelcomeEmail: Boolean!
sendWelcomeEmailFromSiteId: UUID
): UserResponse

updateUser(
Expand Down
2 changes: 2 additions & 0 deletions server/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,7 @@
"admin.users.create": "Create User",
"admin.users.createInvalidData": "Cannot create user as some fields are invalid or missing.",
"admin.users.createKeepOpened": "Keep dialog opened after create",
"admin.users.createSendEmailMissingSiteId": "You must specify the wiki site to reference for the welcome email.",
"admin.users.createSuccess": "User created successfully!",
"admin.users.createdAt": "Created on {date}",
"admin.users.darkMode": "Dark Mode",
Expand Down Expand Up @@ -1001,6 +1002,7 @@
"admin.users.selectGroup": "Select Group...",
"admin.users.sendWelcomeEmail": "Send Welcome Email",
"admin.users.sendWelcomeEmailAltHint": "An email will be sent to the user with link(s) to the wiki(s) the user has read access to.",
"admin.users.sendWelcomeEmailFromSiteId": "Site to use for the Welcome Email",
"admin.users.sendWelcomeEmailHint": "An email will be sent to the user with his login details.",
"admin.users.subtitle": "Manage Users",
"admin.users.tfa": "Two Factor Authentication (2FA)",
Expand Down
54 changes: 36 additions & 18 deletions server/models/users.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,7 @@ export class User extends Model {
*
* @param {Object} param0 User Fields
*/
static async createNewUser ({ email, password, name, groups, userInitiated = false, mustChangePassword = false, sendWelcomeEmail = false }) {
static async createNewUser ({ email, password, name, groups, userInitiated = false, mustChangePassword = false, sendWelcomeEmail = false, sendWelcomeEmailFromSiteId }) {
const localAuth = await WIKI.db.authentication.getStrategy('local')

// Check if self-registration is enabled
Expand Down Expand Up @@ -630,6 +630,11 @@ export class User extends Model {
throw new Error('ERR_DUPLICATE_ACCOUNT_EMAIL')
}

// Check if site ID is provided when send welcome email is enabled
if (sendWelcomeEmail && !sendWelcomeEmailFromSiteId) {
throw new Error('ERR_INVALID_SITE')
}

WIKI.logger.debug(`Creating new user account for ${email}...`)

// Create the account
Expand Down Expand Up @@ -681,35 +686,48 @@ export class User extends Model {
userId: newUsr.id
})

// TODO: Handle multilingual text
// Send verification email
await WIKI.mail.send({
template: 'accountVerify',
template: 'UserVerify',
to: email,
subject: 'Verify your account',
data: {
preheadertext: 'Verify your account in order to gain access to the wiki.',
title: 'Verify your account',
content: 'Click the button below in order to verify your account and gain access to the wiki.',
buttonLink: `${WIKI.config.host}/verify/${verificationToken}`,
buttonLink: `${WIKI.config.mail.defaultBaseURL}/verify/${verificationToken}`,
buttonText: 'Verify'
},
text: `You must open the following link in your browser to verify your account and gain access to the wiki: ${WIKI.config.host}/verify/${verificationToken}`
text: `You must open the following link in your browser to verify your account and gain access to the wiki: ${WIKI.config.mail.defaultBaseURL}/verify/${verificationToken}`
})
} else if (sendWelcomeEmail) {
// Send welcome email
await WIKI.mail.send({
template: 'accountWelcome',
to: email,
subject: `Welcome to the wiki ${WIKI.config.title}`,
data: {
preheadertext: `You've been invited to the wiki ${WIKI.config.title}`,
title: `You've been invited to the wiki ${WIKI.config.title}`,
content: `Click the button below to access the wiki.`,
buttonLink: `${WIKI.config.host}/login`,
buttonText: 'Login'
},
text: `You've been invited to the wiki ${WIKI.config.title}: ${WIKI.config.host}/login`
})
// TODO: Handle multilingual text
const site = WIKI.sites[sendWelcomeEmailFromSiteId]
if (site) {
const siteUrl = site.hostname === '*' ? WIKI.config.mail.defaultBaseURL : `https://${site.hostname}`
// Send welcome email
await WIKI.mail.send({
template: 'UserWelcome',
to: email,
subject: `Welcome to the wiki ${site.config.title}`,
data: {
siteTitle: site.config.title,
preview: `You've been invited to the wiki ${site.config.title}`,
title: `You've been invited to the wiki ${site.config.title}`,
content: `Click the button below to access the wiki.`,
email,
password,
buttonLink: `${siteUrl}/login`,
buttonText: 'Login',
logo: `${siteUrl}/_site/logo`
},
text: `You've been invited to the wiki ${site.config.title}: ${siteUrl}/login`
})
} else {
WIKI.logger.warn('An invalid site ID was provided when creating user. No welcome email was sent.')
throw new Error('ERR_INVALID_SITE')
}
}

WIKI.logger.debug(`Created new user account for ${email} successfully.`)
Expand Down
109 changes: 109 additions & 0 deletions server/templates/mail/UserWelcome.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<template>
<EHtml lang="en">
<EHead />
<EPreview>{{ preview }}</EPreview>
<EBody :style="main">
<EContainer :style="container">
<ESection :style="box">
<img :src="logo" height="50" :alt="siteTitle" />
<EHr :style="hr" />
<EText :style="paragraph"> {{ title }} </EText>
<EText :style="paragraph"> <b>Email Address:</b> {{ email }} </EText>
<EText :style="paragraph"> <b>Password:</b> {{ password }} </EText>
<EText :style="paragraph"> {{ content }} </EText>
<EButton px="10" py="10" :style="button" :href="buttonLink"> {{ buttonText }} </EButton>
<EHr :style="hr" />
<EText :style="footer"> <b>{{ siteTitle }}</b> </EText>
<EText :style="footer"> Wiki.js, an open source project. </EText>
</ESection>
</EContainer>
</EBody>
</EHtml>
</template>

<script setup>
const props = defineProps({
preview: {
type: String,
default: '',
},
siteTitle: {
type: String,
default: ''
},
title: {
type: String,
default: '',
},
content: {
type: String,
default: '',
},
email: {
type: String,
default: ''
},
password: {
type: String,
default: ''
},
buttonLink: {
type: String,
default: '',
},
buttonText: {
type: String,
default: '',
},
logo: {
type: String,
default: ''
}
})
const main = {
backgroundColor: '#f6f9fc',
fontFamily: '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
}
const container = {
backgroundColor: '#ffffff',
margin: '0 auto',
padding: '20px 0 48px',
marginBottom: '64px',
}
const box = {
padding: '0 48px',
}
const hr = {
borderColor: '#e6ebf1',
margin: '20px 0',
}
const paragraph = {
color: '#525f7f',
fontSize: '16px',
lineHeight: '24px',
textAlign: 'left',
}
const button = {
backgroundColor: '#656ee8',
borderRadius: '5px',
color: '#fff',
fontSize: '16px',
fontWeight: 'bold',
textDecoration: 'none',
textAlign: 'center',
display: 'block',
width: '100%',
}
const footer = {
color: '#8898aa',
fontSize: '12px',
lineHeight: '16px',
}
</script>
1 change: 1 addition & 0 deletions ux/public/_assets/icons/ultraviolet-web-design.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 36 additions & 2 deletions ux/src/components/UserCreateDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,24 @@ q-dialog(ref='dialogRef', @hide='onDialogHide')
unchecked-icon='las la-times'
:aria-label='t(`admin.users.sendWelcomeEmail`)'
)
q-item(v-if='state.userSendWelcomeEmail')
blueprint-icon(icon='web-design')
q-item-section
q-select(
outlined
:options='adminStore.sites'
v-model='state.userSendWelcomeEmailFromSiteId'
multiple
map-options
emit-value
option-value='id'
option-label='title'
options-dense
dense
hide-bottom-space
:label='t(`admin.users.sendWelcomeEmailFromSiteId`)'
:aria-label='t(`admin.users.sendWelcomeEmailFromSiteId`)'
)
q-card-actions.card-actions
q-checkbox(
v-model='state.keepOpened'
Expand Down Expand Up @@ -167,6 +185,8 @@ import { useI18n } from 'vue-i18n'
import { useDialogPluginComponent, useQuasar } from 'quasar'
import { computed, onMounted, reactive, ref } from 'vue'
import { useAdminStore } from 'src/stores/admin'
// EMITS
defineEmits([
Expand All @@ -178,6 +198,10 @@ defineEmits([
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
const $q = useQuasar()
// STORES
const adminStore = useAdminStore()
// I18N
const { t } = useI18n()
Expand All @@ -191,6 +215,7 @@ const state = reactive({
userGroups: [],
userMustChangePassword: false,
userSendWelcomeEmail: false,
userSendWelcomeEmailFromSiteId: null,
keepOpened: false,
groups: [],
loadingGroups: false,
Expand Down Expand Up @@ -299,6 +324,9 @@ async function create () {
if (!isFormValid) {
throw new Error(t('admin.users.createInvalidData'))
}
if (state.userSendWelcomeEmail && !state.userSendWelcomeEmailFromSiteId) {
throw new Error(t('admin.users.createSendEmailMissingSiteId'))
}
const resp = await APOLLO_CLIENT.mutate({
mutation: gql`
mutation createUser (
Expand All @@ -308,6 +336,7 @@ async function create () {
$groups: [UUID]!
$mustChangePassword: Boolean!
$sendWelcomeEmail: Boolean!
$sendWelcomeEmailFromSiteId: UUID
) {
createUser (
name: $name
Expand All @@ -316,6 +345,7 @@ async function create () {
groups: $groups
mustChangePassword: $mustChangePassword
sendWelcomeEmail: $sendWelcomeEmail
sendWelcomeEmailFromSiteId: $sendWelcomeEmailFromSiteId
) {
operation {
succeeded
Expand All @@ -330,7 +360,8 @@ async function create () {
password: state.userPassword,
groups: state.userGroups,
mustChangePassword: state.userMustChangePassword,
sendWelcomeEmail: state.userSendWelcomeEmail
sendWelcomeEmail: state.userSendWelcomeEmail,
sendWelcomeEmailFromSiteId: state.userSendWelcomeEmailFromSiteId
}
})
if (resp?.data?.createUser?.operation?.succeeded) {
Expand Down Expand Up @@ -360,5 +391,8 @@ async function create () {
// MOUNTED
onMounted(loadGroups)
onMounted(() => {
state.userSendWelcomeEmailFromSiteId = adminStore.currentSiteId
loadGroups()
})
</script>

0 comments on commit b7d2547

Please sign in to comment.