Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,14 @@ func initI18n(lang string, fs stuffbin.FileSystem) *i18n.I18n {
}

// initCore initializes the CRUD DB core .
func initCore(fnNotify func(sub models.Subscriber, listIDs []int) (int, error), queries *models.Queries, db *sqlx.DB, i *i18n.I18n, ko *koanf.Koanf) *core.Core {
func initCore(
fnNotify func(sub models.Subscriber, listIDs []int) (int, error),
fnSendTx func(models.TxMessage) error,
queries *models.Queries,
db *sqlx.DB,
i *i18n.I18n,
ko *koanf.Koanf,
) *core.Core {
opt := &core.Opt{
Constants: core.Constants{
SendOptinConfirmation: ko.Bool("app.send_optin_confirmation"),
Expand All @@ -549,6 +556,7 @@ func initCore(fnNotify func(sub models.Subscriber, listIDs []int) (int, error),
// Initialize the CRUD core.
return core.New(opt, &core.Hooks{
SendOptinConfirmation: fnNotify,
SendTxMessage: fnSendTx,
})
}

Expand Down
2 changes: 2 additions & 0 deletions cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ func installLists(q *models.Queries) (int, int) {
models.ListStatusActive,
pq.StringArray{"test"},
"",
nil,
); err != nil {
lo.Fatalf("error creating list: %v", err)
}
Expand All @@ -156,6 +157,7 @@ func installLists(q *models.Queries) (int, int) {
models.ListStatusActive,
pq.StringArray{"test"},
"",
nil,
); err != nil {
lo.Fatalf("error creating list: %v", err)
}
Expand Down
18 changes: 16 additions & 2 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,18 @@ func init() {
queries = prepareQueries(qMap, db, ko)
}

type appRef struct {
App *App
}

func (s *appRef) sendTxMessage(tx models.TxMessage) error {
return s.App.sendTxMessage(tx)
}

func main() {
var (
app *App

// Initialize static global config.
cfg = initConstConfig(ko)

Expand All @@ -183,8 +193,10 @@ func main() {

fbOptinNotify = makeOptinNotifyHook(ko.Bool("privacy.unsubscribe_header"), urlCfg, queries, i18n)

appRef = &appRef{}

// Crud core.
core = initCore(fbOptinNotify, queries, db, i18n, ko)
core = initCore(fbOptinNotify, appRef.sendTxMessage, queries, db, i18n, ko)

// Initialize all messengers, SMTP and postback.
msgrs = append(initSMTPMessengers(), initPostbackMessengers(ko)...)
Expand Down Expand Up @@ -242,7 +254,7 @@ func main() {

// =========================================================================
// Initialize the App{} with all the global shared components, controllers and fields.
app := &App{
app = &App{
cfg: cfg,
urlCfg: urlCfg,
fs: fs,
Expand Down Expand Up @@ -279,6 +291,8 @@ func main() {
needsUserSetup: !hasUsers,
}

appRef.App = app

// Star the update checker.
if ko.Bool("app.check_updates") {
go app.checkUpdates(versionString, time.Hour*24)
Expand Down
15 changes: 13 additions & 2 deletions cmd/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@ func (a *App) SendTxMessage(c echo.Context) error {
return err
}

if err := a.sendTxMessage(m); err != nil {
return err
}

return c.JSON(http.StatusOK, okResp{true})
}

func (a *App) sendTxMessage(m models.TxMessage) error {
if a == nil {
return fmt.Errorf("app is not initialized")
}

// Validate fields.
if r, err := a.validateTxMessage(m); err != nil {
return err
Expand Down Expand Up @@ -108,7 +120,6 @@ func (a *App) SendTxMessage(c echo.Context) error {
subEmail = m.SubscriberEmails[n]
}

var err error
sub, err = a.core.GetSubscriber(subID, "", subEmail)
if err != nil {
if er, ok := err.(*echo.HTTPError); ok && er.Code == http.StatusBadRequest {
Expand Down Expand Up @@ -171,7 +182,7 @@ func (a *App) SendTxMessage(c echo.Context) error {
return echo.NewHTTPError(http.StatusBadRequest, strings.Join(notFound, "; "))
}

return c.JSON(http.StatusOK, okResp{true})
return nil
}

// validateTxMessage validates the tx message fields.
Expand Down
1 change: 1 addition & 0 deletions cmd/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ var migList = []migFunc{
{"v5.0.0", migrations.V5_0_0},
{"v5.1.0", migrations.V5_1_0},
{"v5.2.0", migrations.V5_2_0},
{"v5.2.1", migrations.V5_2_1},
}

// upgrade upgrades the database to the current version by running SQL migration files
Expand Down
32 changes: 32 additions & 0 deletions frontend/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,3 +249,35 @@ export default class Utils {
localStorage.setItem(prefKey, JSON.stringify(p));
};
}

export function snakeString(str) {
return str.replace(/[A-Z]/g, (match, offset) => (offset ? '_' : '') + match.toLowerCase());
}

export function snakeKeys(obj, testFunc, keys) {
if (obj === null) {
return obj;
}

if (Array.isArray(obj)) {
return obj.map((o) => snakeKeys(o, testFunc, `${keys || ''}.*`));
}

if (obj.constructor === Object) {
return Object.keys(obj).reduce((result, key) => {
const keyPath = `${keys || ''}.${key}`;
let k = key;

// If there's no testfunc or if a function is defined and it returns true, convert.
if (testFunc === undefined || testFunc(keyPath)) {
k = snakeString(key);
}

return {
...result,
[k]: snakeKeys(obj[key], testFunc, keyPath),
};
}, {});
}
return obj;
}
23 changes: 20 additions & 3 deletions frontend/src/views/ListForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@
</b-select>
</b-field>

<b-field :label="$t('lists.welcomeTemplate')" label-position="on-border" :message="$t('lists.welcomeTemplateHelp')">
<b-select v-model="form.welcomeTemplateId" name="welcomeTemplate">
<option :value="null">{{ $tc('globals.terms.none') }}</option>
<template v-for="t in templates">
<option v-if="t.type === 'tx'" :value="t.id" :key="t.id">
{{ t.name }}
</option>
</template>
</b-select>
</b-field>

<b-field :label="$t('globals.terms.tags')" label-position="on-border">
<b-taginput v-model="form.tags" name="tags" ellipsis icon="tag-outline"
:placeholder="$t('globals.terms.tags')" />
Expand Down Expand Up @@ -75,6 +86,7 @@
import Vue from 'vue';
import { mapState } from 'vuex';
import CopyText from '../components/CopyText.vue';
import { snakeKeys } from '../utils';

export default Vue.extend({
name: 'ListForm',
Expand All @@ -97,6 +109,7 @@ export default Vue.extend({
optin: 'single',
status: 'active',
tags: [],
welcomeTemplateId: null,
},
};
},
Expand All @@ -112,15 +125,16 @@ export default Vue.extend({
},

createList() {
this.$api.createList(this.form).then((data) => {
this.$api.createList(snakeKeys(this.form)).then((data) => {
this.$emit('finished');
this.$parent.close();
this.$utils.toast(this.$t('globals.messages.created', { name: data.name }));
});
},

updateList() {
this.$api.updateList({ id: this.data.id, ...this.form }).then((data) => {
const form = snakeKeys(this.form);
this.$api.updateList({ id: this.data.id, ...form }).then((data) => {
this.$emit('finished');
this.$parent.close();
this.$utils.toast(this.$t('globals.messages.updated', { name: data.name }));
Expand All @@ -129,7 +143,7 @@ export default Vue.extend({
},

computed: {
...mapState(['loading', 'profile']),
...mapState(['loading', 'profile', 'templates']),

isArchived: {
get() {
Expand All @@ -144,6 +158,9 @@ export default Vue.extend({
mounted() {
this.form = { ...this.form, ...this.$props.data };

// Get the templates list.
this.$api.getTemplates();

this.$nextTick(() => {
this.$refs.focus.focus();
});
Expand Down
2 changes: 2 additions & 0 deletions i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,8 @@
"lists.typeHelp": "Public lists are open to the world to subscribe and their names may appear on public pages such as the subscription management page.",
"lists.types.private": "Private",
"lists.types.public": "Public",
"lists.welcomeTemplate": "Welcome Template",
"lists.welcomeTemplateHelp": "If enabled, sends an e-mail to new confirmed subscribers using the selected template.",
"logs.title": "Logs",
"maintenance.help": "Some actions may take a while to complete depending on the amount of data.",
"maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions",
Expand Down
1 change: 1 addition & 0 deletions internal/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type Constants struct {
// Hooks contains external function hooks that are required by the core package.
type Hooks struct {
SendOptinConfirmation func(models.Subscriber, []int) (int, error)
SendTxMessage func(models.TxMessage) error
}

// Opt contains the controllers required to start the core.
Expand Down
4 changes: 2 additions & 2 deletions internal/core/lists.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ func (c *Core) CreateList(l models.List) (models.List, error) {
// Insert and read ID.
var newID int
l.UUID = uu.String()
if err := c.q.CreateList.Get(&newID, l.UUID, l.Name, l.Type, l.Optin, l.Status, pq.StringArray(normalizeTags(l.Tags)), l.Description); err != nil {
if err := c.q.CreateList.Get(&newID, l.UUID, l.Name, l.Type, l.Optin, l.Status, pq.StringArray(normalizeTags(l.Tags)), l.Description, l.WelcomeTemplateID); err != nil {
c.log.Printf("error creating list: %v", err)
return models.List{}, echo.NewHTTPError(http.StatusInternalServerError,
c.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.list}", "error", pqErrMsg(err)))
Expand All @@ -178,7 +178,7 @@ func (c *Core) CreateList(l models.List) (models.List, error) {

// UpdateList updates a given list.
func (c *Core) UpdateList(id int, l models.List) (models.List, error) {
res, err := c.q.UpdateList.Exec(id, l.Name, l.Type, l.Optin, l.Status, pq.StringArray(normalizeTags(l.Tags)), l.Description)
res, err := c.q.UpdateList.Exec(id, l.Name, l.Type, l.Optin, l.Status, pq.StringArray(normalizeTags(l.Tags)), l.Description, l.WelcomeTemplateID)
if err != nil {
c.log.Printf("error updating list: %v", err)
return models.List{}, echo.NewHTTPError(http.StatusInternalServerError,
Expand Down
Loading