move mail templates into core

This commit is contained in:
2020-11-13 12:24:07 +01:00
parent 07b87cec66
commit 019aa96a0e
23 changed files with 542 additions and 3 deletions
+110
View File
@@ -0,0 +1,110 @@
using FluentValidation;
using Raven.Client.Documents;
using Raven.Client.Documents.Session;
using System.Collections.Generic;
using System.Threading.Tasks;
using zero.Core.Api;
using zero.Core.Entities;
using zero.Core.Extensions;
namespace zero.Core.Api
{
public class MailTemplatesApi : BackofficeApi, IMailTemplatesApi
{
protected IValidator<IMailTemplate> Validator { get; private set; }
public MailTemplatesApi(IBackofficeStore store, IValidator<IMailTemplate> validator) : base(store)
{
Validator = validator;
}
/// <inheritdoc />
public async Task<IMailTemplate> GetById(string id)
{
return await GetById<IMailTemplate>(id);
}
/// <inheritdoc />
public async Task<Dictionary<string, IMailTemplate>> GetByIds(params string[] ids)
{
return await GetByIds<IMailTemplate>(ids);
}
/// <summary>
/// Get all available currencies
/// </summary>
public async Task<IList<IMailTemplate>> GetAll()
{
using (IAsyncDocumentSession session = Raven.OpenAsyncSession())
{
return await session.Query<IMailTemplate>().Scope(Scope).ToListAsync();
}
}
/// <inheritdoc />
public async Task<ListResult<IMailTemplate>> GetByQuery(ListQuery<IMailTemplate> query)
{
query.SearchSelector = entity => entity.Name;
using (IAsyncDocumentSession session = Raven.OpenAsyncSession())
{
return await session.Query<IMailTemplate>()
.Scope(Scope)
.ToQueriedListAsync(query);
}
}
/// <inheritdoc />
public async Task<EntityResult<IMailTemplate>> Save(IMailTemplate model)
{
return await SaveModel(model, Validator);
}
/// <inheritdoc />
public async Task<EntityResult<IMailTemplate>> Delete(string id)
{
return await DeleteById<IMailTemplate>(id);
}
}
public interface IMailTemplatesApi
{
/// <summary>
/// Get mail template by id
/// </summary>
Task<IMailTemplate> GetById(string id);
/// <summary>
/// Get mail templates by ids
/// </summary>
Task<Dictionary<string, IMailTemplate>> GetByIds(params string[] ids);
/// <summary>
/// Get all available mail templates
/// </summary>
Task<IList<IMailTemplate>> GetAll();
/// <summary>
/// Get all available mail templates (with query)
/// </summary>
Task<ListResult<IMailTemplate>> GetByQuery(ListQuery<IMailTemplate> query);
/// <summary>
/// Creates or updates a mail template
/// </summary>
Task<EntityResult<IMailTemplate>> Save(IMailTemplate model);
/// <summary>
/// Deletes a mail template by id
/// </summary>
Task<EntityResult<IMailTemplate>> Delete(string id);
}
}
+1
View File
@@ -57,6 +57,7 @@
public const string Languages = "languages";
public const string Translations = "translations";
public const string Countries = "countries";
public const string Mails = "mailTemplates";
public const string Logging = "logs";
public const string Plugins = "plugins";
public const string CreatePlugin = "createplugin";
+77
View File
@@ -0,0 +1,77 @@
using zero.Core.Attributes;
using zero.Core.Entities;
namespace zero.Core.Entities
{
public class MailTemplate : ZeroEntity, IMailTemplate
{
/// <inheritdoc />
public string Key { get; set; }
/// <inheritdoc />
public string SenderEmail { get; set; }
/// <inheritdoc />
public string SenderName { get; set; }
/// <inheritdoc />
public string RecipientEmail { get; set; }
/// <inheritdoc />
public string Cc { get; set; }
/// <inheritdoc />
public string Bcc { get; set; }
/// <inheritdoc />
public string Subject { get; set; }
/// <inheritdoc />
public string Body { get; set; }
}
[Collection("MailTemplates")]
public interface IMailTemplate : IZeroEntity, IAppAwareEntity, IZeroDbConventions
{
/// <summary>
/// Alias which is used to get the template in code
/// </summary>
string Key { get; set; }
/// <summary>
/// Email address of the sender (overrides email from application)
/// </summary>
string SenderEmail { get; set; }
/// <summary>
/// Name of the sender (overrides name from application)
/// </summary>
string SenderName { get; set; }
/// <summary>
/// Email address of the recipient. This is only necessary for templates which do not have a dynamic recipient (e.g. reports).
/// </summary>
string RecipientEmail { get; set; }
/// <summary>
/// Additional comma-separated emails to send a copy to
/// </summary>
string Cc { get; set; }
/// <summary>
/// Additional comma-separated emails to send a hidden copy to
/// </summary>
string Bcc { get; set; }
/// <summary>
/// Email subject (can contain placeholders)
/// </summary>
string Subject { get; set; }
/// <summary>
/// Email body (can contain placeholders)
/// </summary>
string Body { get; set; }
}
}
@@ -18,6 +18,7 @@
public const string Languages = PREFIX + Constants.Settings.Languages;
public const string Translations = PREFIX + Constants.Settings.Translations;
public const string Countries = PREFIX + Constants.Settings.Countries;
public const string Mails = PREFIX + Constants.Settings.Mails;
public const string Logging = PREFIX + Constants.Settings.Logging;
public const string Plugins = PREFIX + Constants.Settings.Plugins;
public const string CreatePlugin = PREFIX + Constants.Settings.CreatePlugin;
@@ -0,0 +1,13 @@
using FluentValidation;
using zero.Core.Entities;
namespace zero.Core.Validation
{
public class MailTemplateValidator : ZeroValidator<IMailTemplate>
{
public MailTemplateValidator()
{
}
}
}
@@ -0,0 +1,62 @@
<template>
<div class="ui-mailtemplatespicker" :class="{'is-disabled': disabled }">
<ui-pick :config="pickerConfig" :value="value" @input="onChange" :disabled="disabled" />
</div>
</template>
<script>
import MailTemplatesApi from 'zero/resources/mailTemplates.js'
import { extend as _extend } from 'underscore'
export default {
name: 'uiMailtemplatespicker',
props: {
value: {
type: [String, Array],
default: null
},
limit: {
type: Number,
default: 1
},
disabled: {
type: Boolean,
default: false
},
options: {
type: Object,
default: () => {}
}
},
data: () => ({
previews: [],
pickerConfig: {}
}),
created()
{
this.pickerConfig = _extend({
scope: 'mailtemplate',
items: MailTemplatesApi.getForPicker,
previews: MailTemplatesApi.getPreviews,
limit: this.limit,
multiple: this.limit > 1
//onChange: function (id, item)
//{
// vm.manufacturer = item ? { Id: item.id, Name: item.name } : null;
//}
}, this.options);
},
methods: {
onChange(value)
{
this.$emit('input', value);
}
}
}
</script>
@@ -0,0 +1,17 @@
<template>
<ui-mailtemplatepicker :value="value" @input="$emit('input', $event)" :limit="limit" :disabled="disabled" />
</template>
<script>
export default {
props: {
value: [String, Array],
disabled: Boolean,
limit: {
type: Number,
default: 1
}
}
}
</script>
+51
View File
@@ -0,0 +1,51 @@
<template>
<ui-form ref="form" class="mails" v-slot="form" @submit="onSubmit" @load="onLoad" :route="route">
<ui-form-header v-model="model" title="@mailTemplate.name" :disabled="disabled" :is-create="!id" :state="form.state" :can-delete="meta.canDelete" @delete="onDelete" />
<ui-editor config="mailTemplate" v-model="model" :meta="meta" :active-toggle="false" :disabled="disabled" />
</ui-form>
</template>
<script>
import MailTemplatesApi from 'zero/resources/mailTemplates.js';
import UiEditor from 'zero/editor/editor.vue';
export default {
props: ['id'],
components: { UiEditor },
data: () => ({
meta: {},
model: { name: null },
route: 'settings-mails-edit',
disabled: false
}),
methods: {
onLoad(form)
{
form.load(!this.id ? MailTemplatesApi.getEmpty() : MailTemplatesApi.getById(this.id)).then(response =>
{
this.disabled = !response.meta.canEdit;
this.meta = response.meta;
this.model = response.entity;
});
},
onSubmit(form)
{
form.handle(MailTemplatesApi.save(this.model));
},
onDelete(item, opts)
{
opts.hide();
this.$refs.form.onDelete(MailTemplatesApi.delete.bind(this, this.id));
}
}
}
</script>
+21
View File
@@ -0,0 +1,21 @@
<template>
<div class="languages">
<ui-header-bar title="@mailTemplate.list" :count="count" :back-button="true">
<ui-table-filter :attach="$refs.table" />
<ui-add-button :route="createRoute" />
</ui-header-bar>
<div class="ui-blank-box">
<ui-table ref="table" config="mailTemplates" @count="count = $event" />
</div>
</div>
</template>
<script>
export default {
data: () => ({
count: 0,
createRoute: zero.alias.settings.mails + '-create'
})
}
</script>
+4
View File
@@ -9,6 +9,8 @@ import Language from './language.vue';
import Users from './users.vue';
import User from './user.vue';
import UserRole from './role.vue';
import MailTemplates from './mails.vue';
import MailTemplate from './mail.vue';
import Translations from './translations.vue';
const alias = __zero.alias.sections.settings;
@@ -96,6 +98,8 @@ if (section)
addArea(__zero.alias.settings.translations, Translations, Translations, true);
addArea(__zero.alias.settings.mails, MailTemplates, MailTemplate, true);
addArea(__zero.alias.settings.users, Users, User, true, area =>
{
routes.push({
+3 -1
View File
@@ -6,6 +6,7 @@ import media from './media.js';
//import translation from './translation.js';
import user from './user.js';
import userRole from './userRole.js';
import mailTemplate from './mailTemplate.js';
export default {
application,
@@ -14,5 +15,6 @@ export default {
media,
//translation,
user,
userRole
userRole,
mailTemplate
};
@@ -0,0 +1,20 @@
import Editor from 'zero/core/editor.ts';
const editor = new Editor('mailTemplate', '@mailTemplate.fields.');
const general = editor.tab('general', '@ui.tab_general');
const sender = editor.tab('sender', '@mailTemplate.tabs.sender');
const recipient = editor.tab('recipient', '@mailTemplate.tabs.recipient');
general.field('name', { label: '@ui.name' }).text(60).required();
general.field('key').text(60).required();
general.field('subject').text(80).required();
general.field('body').rte();
sender.field('senderEmail').text();
sender.field('senderName').text();
recipient.field('recipientEmail').text();
recipient.field('cc').text();
recipient.field('bcc').text();
export default editor;
+3 -1
View File
@@ -3,10 +3,12 @@ import countries from './countries.js';
import languages from './languages.js';
import translations from './translations.js';
import users from './users.js';
import mails from './mails.js';
export default {
countries,
languages,
translations,
users
users,
mails
};
+16
View File
@@ -0,0 +1,16 @@
import List from 'zero/core/list.ts';
import MailTemplatesApi from 'zero/resources/mailTemplates.js';
const list = new List('mailTemplates');
list.templateLabel = x => '@mailTemplate.fields.' + x;
list.link = zero.alias.settings.mails + '-edit';
list.onFetch(filter => MailTemplatesApi.getAll(filter));
list.column('name').name();
list.column('subject').text();
list.column('key').text();
export default list;
@@ -0,0 +1,41 @@
import Axios from 'axios';
const base = 'mailTemplates/';
export default {
getById(id)
{
return Axios.get(base + 'getById', { params: { id } }).then(res => Promise.resolve(res.data));
},
getEmpty(id)
{
return Axios.get(base + 'getEmpty').then(res => Promise.resolve(res.data));
},
getAll(query)
{
return Axios.get(base + 'getAll', query ? { params: { query } } : undefined).then(res => Promise.resolve(res.data));
},
getPreviews(ids)
{
return Axios.get(base + 'getPreviews', { params: { ids } }).then(res => Promise.resolve(res.data));
},
getForPicker()
{
return Axios.get(base + 'getForPicker').then(res => Promise.resolve(res.data));
},
save(model)
{
return Axios.post(base + 'save', model).then(res => Promise.resolve(res.data));
},
delete(id)
{
return Axios.delete(base + 'delete', { params: { id } }).then(res => Promise.resolve(res.data));
}
};
+2
View File
@@ -36,6 +36,7 @@ import uiIconpicker from './pickers/iconPicker/iconpicker.vue';
import uiMediapicker from './pickers/mediaPicker/mediapicker.vue';
import uiPagepicker from './pickers/pagePicker/pagepicker.vue';
import uiUserpicker from './pickers/userPicker/userpicker.vue';
import uiMailtemplatepicker from './pickers/mailPicker/mailpicker.vue';
import uiPick from './pickers/pick.vue';
import uiTable from './tables/table.vue';
@@ -96,6 +97,7 @@ export default {
uiMediapicker,
uiPagepicker,
uiUserpicker,
uiMailtemplatepicker,
uiPick,
uiTable,
+13 -1
View File
@@ -11,6 +11,7 @@ import Output from '../editor/fields/output.vue';
import Checklist from '../editor/fields/checklist.vue';
import ColorPicker from '../editor/fields/colorpicker.vue';
import CountryPicker from '../editor/fields/countrypicker.vue';
import MailTemplatePicker from '../editor/fields/mailtemplatepicker.vue';
import CulturePicker from '../editor/fields/culturepicker.vue';
import DatePicker from '../editor/fields/datepicker.vue';
import DateRangePicker from '../editor/fields/daterangepicker.vue';
@@ -99,7 +100,7 @@ class EditorField
/**
*
*/
_setComponent(component, options)
_setComponent(component, options?)
{
this.#component = component;
this.#componentOptions = options || {};
@@ -306,6 +307,17 @@ class EditorField
}
/**
* Renders a mail template picker
* @param {number} [limit=1] - Maximum items to be selected
* @returns {EditorField}
*/
mailTemplatePicker(limit)
{
return this._setComponent(MailTemplatePicker, { limit });
}
/**
* Renders a date picker
* @param {object} [options] - Custom options
@@ -0,0 +1,51 @@
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using zero.Core.Api;
using zero.Core;
using zero.Core.Entities;
using zero.Web.Controllers;
using zero.Web.Models;
namespace zero.Commerce.Backoffice
{
public class MailTemplatesController : BackofficeController
{
IMailTemplatesApi Api;
public MailTemplatesController(IMailTemplatesApi api)
{
Api = api;
}
public EditModel<IMailTemplate> GetEmpty([FromServices] IMailTemplate blueprint) => Edit(blueprint);
public async Task<EditModel<IMailTemplate>> GetById([FromQuery] string id) => Edit(await Api.GetById(id));
public async Task<ListResult<IMailTemplate>> GetAll([FromQuery] ListQuery<IMailTemplate> query) => await Api.GetByQuery(query);
public async Task<IEnumerable<SelectModel>> GetForPicker() => (await Api.GetAll()).Select(x => new SelectModel()
{
Id = x.Id,
Name = x.Name,
IsActive = x.IsActive,
IsShared = x.AppId == Constants.Database.SharedAppId
});
public async Task<IList<PreviewModel>> GetPreviews([FromQuery] List<string> ids) => Previews(await Api.GetByIds(ids.ToArray()), item => new PreviewModel()
{
Id = item.Id,
Icon = "fth-mail",
Name = item.Name
});
public async Task<EntityResult<IMailTemplate>> Save([FromBody] IMailTemplate model) => await Api.Save(model);
public async Task<EntityResult<IMailTemplate>> Delete([FromQuery] string id) => await Api.Delete(id);
}
}
+1
View File
@@ -17,6 +17,7 @@ namespace zero.Web.Defaults
Items.Add(new Permission(Permissions.Settings.Languages, "@settings.system.languages.name", "@settings.system.languages.text", PermissionValueType.CRUD));
Items.Add(new Permission(Permissions.Settings.Countries, "@settings.system.countries.name", "@settings.system.countries.text", PermissionValueType.CRUD));
Items.Add(new Permission(Permissions.Settings.Translations, "@settings.system.translations.name", "@settings.system.translations.text", PermissionValueType.CRUD));
Items.Add(new Permission(Permissions.Settings.Mails, "@settings.system.mails.name", "@settings.system.mails.text", PermissionValueType.CRUD));
Items.Add(new Permission(Permissions.Settings.Logging, "@settings.system.logs.name", "@settings.system.logs.text", PermissionValueType.CRUD));
Items.Add(new Permission(Permissions.Settings.Plugins, "@settings.plugins.installed.name", "@settings.plugins.installed.text_default", PermissionValueType.CRUD));
Items.Add(new Permission(Permissions.Settings.CreatePlugin, "@settings.plugins.create.name", "@settings.plugins.create.text", PermissionValueType.CRUD));
+1
View File
@@ -15,6 +15,7 @@ namespace zero.Web.Defaults
AddInternal(Constants.Settings.Languages, "@settings.system.languages.name", "@settings.system.languages.text", "fth-globe");
AddInternal(Constants.Settings.Countries, "@settings.system.countries.name", "@settings.system.countries.text", "fth-map-pin");
AddInternal(Constants.Settings.Translations, "@settings.system.translations.name", "@settings.system.translations.text", "fth-type");
AddInternal(Constants.Settings.Mails, "@settings.system.mails.name", "@settings.system.mails.text", "fth-mail");
//AddInternal(Constants.Settings.Logging, "@settings.system.logs.name", "@settings.system.logs.text", "fth-file-text");
}
}
@@ -60,6 +60,7 @@ namespace zero.Web.Defaults
services.AddTransient<IValidator<IApplication>, ApplicationValidator>();
services.AddTransient<IValidator<ICountry>, CountryValidator>();
services.AddTransient<IValidator<IMailTemplate>, MailTemplateValidator>();
services.AddTransient<IValidator<ILanguage>, LanguageValidator>();
services.AddTransient<IValidator<ITranslation>, TranslationValidator>();
services.AddTransient<IValidator<IPage>, PageValidator>();
@@ -76,6 +77,7 @@ namespace zero.Web.Defaults
services.AddTransient<IPagesApi, PagesApi>();
services.AddTransient<IPageTreeApi, PageTreeApi>();
services.AddTransient<IPreviewApi, PreviewApi>();
services.AddTransient<IMailTemplatesApi, MailTemplatesApi>();
services.AddTransient<ISetupApi, SetupApi>();
services.AddTransient<ISectionsApi, SectionsApi>();
@@ -249,6 +249,10 @@
"name": "Countries",
"text": "Manage list of countries"
},
"mails": {
"name": "Mails",
"text": "Edit mail templates"
},
"logs": {
"name": "Logging",
"text": "Debug und view logs"
@@ -543,6 +547,33 @@
"child_count_x": "{count} items"
},
"mailTemplate": {
"name": "Mail template",
"list": "Mail templates",
"emailBox": "Email settings",
"tabs": {
"sender": "Sender",
"recipient": "Recipient"
},
"fields": {
"key": "Key",
"key_text": "Used to query for the template in code",
"subject": "Subject",
"body": "Body",
"body_text": "Additional body which is rendered into the mail template",
"senderEmail": "Sender email",
"senderEmail_text": "Leave empty to use application email",
"senderName": "Sender name",
"senderName_text": "Leave empty to use application name",
"recipientEmail": "Recipient email",
"recipientEmail_text": "Only used for mails which do not have a dynamic recipient (e.g. reports)",
"cc": "Send copy to (CC)",
"cc_text": "Comma-separated list of e-mails",
"bcc": "Send invisible copy to (BCC)",
"bcc_text": "Comma-separated list of e-mails"
}
},
"space": {
"list": "Spaces",
"name": "Space"
+1
View File
@@ -174,6 +174,7 @@ namespace zero.Web
settings.Add("logging", Constants.Settings.Logging);
settings.Add("languages", Constants.Settings.Languages);
settings.Add("translations", Constants.Settings.Translations);
settings.Add("mails", Constants.Settings.Mails);
settings.Add("updates", Constants.Settings.Updates);
settings.Add("users", Constants.Settings.Users);