mirror of
https://github.com/requarks/wiki.git
synced 2026-08-26 13:17:10 -05:00
feat: user welcome email
This commit is contained in:
@@ -36,6 +36,7 @@ extend type Mutation {
|
|||||||
groups: [UUID]!
|
groups: [UUID]!
|
||||||
mustChangePassword: Boolean!
|
mustChangePassword: Boolean!
|
||||||
sendWelcomeEmail: Boolean!
|
sendWelcomeEmail: Boolean!
|
||||||
|
sendWelcomeEmailFromSiteId: UUID
|
||||||
): UserResponse
|
): UserResponse
|
||||||
|
|
||||||
updateUser(
|
updateUser(
|
||||||
|
|||||||
@@ -919,6 +919,7 @@
|
|||||||
"admin.users.create": "Create User",
|
"admin.users.create": "Create User",
|
||||||
"admin.users.createInvalidData": "Cannot create user as some fields are invalid or missing.",
|
"admin.users.createInvalidData": "Cannot create user as some fields are invalid or missing.",
|
||||||
"admin.users.createKeepOpened": "Keep dialog opened after create",
|
"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.createSuccess": "User created successfully!",
|
||||||
"admin.users.createdAt": "Created on {date}",
|
"admin.users.createdAt": "Created on {date}",
|
||||||
"admin.users.darkMode": "Dark Mode",
|
"admin.users.darkMode": "Dark Mode",
|
||||||
@@ -1001,6 +1002,7 @@
|
|||||||
"admin.users.selectGroup": "Select Group...",
|
"admin.users.selectGroup": "Select Group...",
|
||||||
"admin.users.sendWelcomeEmail": "Send Welcome Email",
|
"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.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.sendWelcomeEmailHint": "An email will be sent to the user with his login details.",
|
||||||
"admin.users.subtitle": "Manage Users",
|
"admin.users.subtitle": "Manage Users",
|
||||||
"admin.users.tfa": "Two Factor Authentication (2FA)",
|
"admin.users.tfa": "Two Factor Authentication (2FA)",
|
||||||
|
|||||||
+36
-18
@@ -570,7 +570,7 @@ export class User extends Model {
|
|||||||
*
|
*
|
||||||
* @param {Object} param0 User Fields
|
* @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')
|
const localAuth = await WIKI.db.authentication.getStrategy('local')
|
||||||
|
|
||||||
// Check if self-registration is enabled
|
// Check if self-registration is enabled
|
||||||
@@ -630,6 +630,11 @@ export class User extends Model {
|
|||||||
throw new Error('ERR_DUPLICATE_ACCOUNT_EMAIL')
|
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}...`)
|
WIKI.logger.debug(`Creating new user account for ${email}...`)
|
||||||
|
|
||||||
// Create the account
|
// Create the account
|
||||||
@@ -681,35 +686,48 @@ export class User extends Model {
|
|||||||
userId: newUsr.id
|
userId: newUsr.id
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// TODO: Handle multilingual text
|
||||||
// Send verification email
|
// Send verification email
|
||||||
await WIKI.mail.send({
|
await WIKI.mail.send({
|
||||||
template: 'accountVerify',
|
template: 'UserVerify',
|
||||||
to: email,
|
to: email,
|
||||||
subject: 'Verify your account',
|
subject: 'Verify your account',
|
||||||
data: {
|
data: {
|
||||||
preheadertext: 'Verify your account in order to gain access to the wiki.',
|
preheadertext: 'Verify your account in order to gain access to the wiki.',
|
||||||
title: 'Verify your account',
|
title: 'Verify your account',
|
||||||
content: 'Click the button below in order to verify your account and gain access to the wiki.',
|
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'
|
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) {
|
} else if (sendWelcomeEmail) {
|
||||||
// Send welcome email
|
// TODO: Handle multilingual text
|
||||||
await WIKI.mail.send({
|
const site = WIKI.sites[sendWelcomeEmailFromSiteId]
|
||||||
template: 'accountWelcome',
|
if (site) {
|
||||||
to: email,
|
const siteUrl = site.hostname === '*' ? WIKI.config.mail.defaultBaseURL : `https://${site.hostname}`
|
||||||
subject: `Welcome to the wiki ${WIKI.config.title}`,
|
// Send welcome email
|
||||||
data: {
|
await WIKI.mail.send({
|
||||||
preheadertext: `You've been invited to the wiki ${WIKI.config.title}`,
|
template: 'UserWelcome',
|
||||||
title: `You've been invited to the wiki ${WIKI.config.title}`,
|
to: email,
|
||||||
content: `Click the button below to access the wiki.`,
|
subject: `Welcome to the wiki ${site.config.title}`,
|
||||||
buttonLink: `${WIKI.config.host}/login`,
|
data: {
|
||||||
buttonText: 'Login'
|
siteTitle: site.config.title,
|
||||||
},
|
preview: `You've been invited to the wiki ${site.config.title}`,
|
||||||
text: `You've been invited to the wiki ${WIKI.config.title}: ${WIKI.config.host}/login`
|
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.`)
|
WIKI.logger.debug(`Created new user account for ${email} successfully.`)
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" width="80px" height="80px"><path fill="#fff" d="M2.5 3.5H37.5V36.5H2.5z"/><path fill="#4788c7" d="M37,4v32H3V4H37 M38,3H2v34h36V3L38,3z"/><path fill="#98ccfd" d="M3 4H37V12H3z"/><path fill="#fff" d="M6 7H8V9H6zM10 7H12V9H10zM14 7H16V9H14z"/><path fill="#98ccfd" d="M6 16H19V33H6z"/><path fill="#4788c7" d="M33.5 17h-11c-.275 0-.5-.225-.5-.5l0 0c0-.275.225-.5.5-.5h11c.275 0 .5.225.5.5l0 0C34 16.775 33.775 17 33.5 17zM33.5 21h-11c-.275 0-.5-.225-.5-.5l0 0c0-.275.225-.5.5-.5h11c.275 0 .5.225.5.5l0 0C34 20.775 33.775 21 33.5 21zM33.5 25h-11c-.275 0-.5-.225-.5-.5l0 0c0-.275.225-.5.5-.5h11c.275 0 .5.225.5.5l0 0C34 24.775 33.775 25 33.5 25zM33.5 29h-11c-.275 0-.5-.225-.5-.5l0 0c0-.275.225-.5.5-.5h11c.275 0 .5.225.5.5l0 0C34 28.775 33.775 29 33.5 29zM33.5 33h-11c-.275 0-.5-.225-.5-.5l0 0c0-.275.225-.5.5-.5h11c.275 0 .5.225.5.5l0 0C34 32.775 33.775 33 33.5 33z"/></svg>
|
||||||
|
After Width: | Height: | Size: 931 B |
@@ -134,6 +134,24 @@ q-dialog(ref='dialogRef', @hide='onDialogHide')
|
|||||||
unchecked-icon='las la-times'
|
unchecked-icon='las la-times'
|
||||||
:aria-label='t(`admin.users.sendWelcomeEmail`)'
|
: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-card-actions.card-actions
|
||||||
q-checkbox(
|
q-checkbox(
|
||||||
v-model='state.keepOpened'
|
v-model='state.keepOpened'
|
||||||
@@ -167,6 +185,8 @@ import { useI18n } from 'vue-i18n'
|
|||||||
import { useDialogPluginComponent, useQuasar } from 'quasar'
|
import { useDialogPluginComponent, useQuasar } from 'quasar'
|
||||||
import { computed, onMounted, reactive, ref } from 'vue'
|
import { computed, onMounted, reactive, ref } from 'vue'
|
||||||
|
|
||||||
|
import { useAdminStore } from 'src/stores/admin'
|
||||||
|
|
||||||
// EMITS
|
// EMITS
|
||||||
|
|
||||||
defineEmits([
|
defineEmits([
|
||||||
@@ -178,6 +198,10 @@ defineEmits([
|
|||||||
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
|
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
|
||||||
const $q = useQuasar()
|
const $q = useQuasar()
|
||||||
|
|
||||||
|
// STORES
|
||||||
|
|
||||||
|
const adminStore = useAdminStore()
|
||||||
|
|
||||||
// I18N
|
// I18N
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
@@ -191,6 +215,7 @@ const state = reactive({
|
|||||||
userGroups: [],
|
userGroups: [],
|
||||||
userMustChangePassword: false,
|
userMustChangePassword: false,
|
||||||
userSendWelcomeEmail: false,
|
userSendWelcomeEmail: false,
|
||||||
|
userSendWelcomeEmailFromSiteId: null,
|
||||||
keepOpened: false,
|
keepOpened: false,
|
||||||
groups: [],
|
groups: [],
|
||||||
loadingGroups: false,
|
loadingGroups: false,
|
||||||
@@ -299,6 +324,9 @@ async function create () {
|
|||||||
if (!isFormValid) {
|
if (!isFormValid) {
|
||||||
throw new Error(t('admin.users.createInvalidData'))
|
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({
|
const resp = await APOLLO_CLIENT.mutate({
|
||||||
mutation: gql`
|
mutation: gql`
|
||||||
mutation createUser (
|
mutation createUser (
|
||||||
@@ -308,6 +336,7 @@ async function create () {
|
|||||||
$groups: [UUID]!
|
$groups: [UUID]!
|
||||||
$mustChangePassword: Boolean!
|
$mustChangePassword: Boolean!
|
||||||
$sendWelcomeEmail: Boolean!
|
$sendWelcomeEmail: Boolean!
|
||||||
|
$sendWelcomeEmailFromSiteId: UUID
|
||||||
) {
|
) {
|
||||||
createUser (
|
createUser (
|
||||||
name: $name
|
name: $name
|
||||||
@@ -316,6 +345,7 @@ async function create () {
|
|||||||
groups: $groups
|
groups: $groups
|
||||||
mustChangePassword: $mustChangePassword
|
mustChangePassword: $mustChangePassword
|
||||||
sendWelcomeEmail: $sendWelcomeEmail
|
sendWelcomeEmail: $sendWelcomeEmail
|
||||||
|
sendWelcomeEmailFromSiteId: $sendWelcomeEmailFromSiteId
|
||||||
) {
|
) {
|
||||||
operation {
|
operation {
|
||||||
succeeded
|
succeeded
|
||||||
@@ -330,7 +360,8 @@ async function create () {
|
|||||||
password: state.userPassword,
|
password: state.userPassword,
|
||||||
groups: state.userGroups,
|
groups: state.userGroups,
|
||||||
mustChangePassword: state.userMustChangePassword,
|
mustChangePassword: state.userMustChangePassword,
|
||||||
sendWelcomeEmail: state.userSendWelcomeEmail
|
sendWelcomeEmail: state.userSendWelcomeEmail,
|
||||||
|
sendWelcomeEmailFromSiteId: state.userSendWelcomeEmailFromSiteId
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (resp?.data?.createUser?.operation?.succeeded) {
|
if (resp?.data?.createUser?.operation?.succeeded) {
|
||||||
@@ -360,5 +391,8 @@ async function create () {
|
|||||||
|
|
||||||
// MOUNTED
|
// MOUNTED
|
||||||
|
|
||||||
onMounted(loadGroups)
|
onMounted(() => {
|
||||||
|
state.userSendWelcomeEmailFromSiteId = adminStore.currentSiteId
|
||||||
|
loadGroups()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user