diff --git a/cmd/handlers.go b/cmd/handlers.go index 33c6ca19..d9a8e794 100644 --- a/cmd/handlers.go +++ b/cmd/handlers.go @@ -139,6 +139,10 @@ func initHTTPHandlers(e *echo.Echo, app *App) { g.PUT("/api/templates/:id/default", handleTemplateSetDefault) g.DELETE("/api/templates/:id", handleDeleteTemplate) + g.DELETE("/api/maintenance/subscribers/:type", handleGCSubscribers) + g.DELETE("/api/maintenance/analytics/:type", handleGCCampaignAnalytics) + g.DELETE("/api/maintenance/subscriptions/unconfirmed", handleGCSubscriptions) + g.POST("/api/tx", handleSendTxMessage) if app.constants.BounceWebhooksEnabled { diff --git a/cmd/maintenance.go b/cmd/maintenance.go new file mode 100644 index 00000000..c11e3a85 --- /dev/null +++ b/cmd/maintenance.go @@ -0,0 +1,92 @@ +package main + +import ( + "net/http" + "time" + + "github.com/labstack/echo/v4" +) + +// handleGCSubscribers garbage collects (deletes) orphaned or blocklisted subscribers. +func handleGCSubscribers(c echo.Context) error { + var ( + app = c.Get("app").(*App) + typ = c.Param("type") + ) + + var ( + n int + err error + ) + + switch typ { + case "blocklisted": + n, err = app.core.DeleteBlocklistedSubscribers() + case "orphan": + n, err = app.core.DeleteOrphanSubscribers() + default: + err = echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidData")) + } + + if err != nil { + return err + } + + return c.JSON(http.StatusOK, okResp{struct { + Count int `json:"count"` + }{n}}) +} + +// handleGCSubscriptions garbage collects (deletes) orphaned or blocklisted subscribers. +func handleGCSubscriptions(c echo.Context) error { + var ( + app = c.Get("app").(*App) + ) + + t, err := time.Parse(time.RFC3339, c.FormValue("before_date")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidData")) + } + + n, err := app.core.DeleteUnconfirmedSubscriptions(t) + if err != nil { + return err + } + + return c.JSON(http.StatusOK, okResp{struct { + Count int `json:"count"` + }{n}}) +} + +// handleGCCampaignAnalytics garbage collects (deletes) campaign analytics. +func handleGCCampaignAnalytics(c echo.Context) error { + var ( + app = c.Get("app").(*App) + typ = c.Param("type") + ) + + t, err := time.Parse(time.RFC3339, c.FormValue("before_date")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidData")) + } + + switch typ { + case "all": + if err := app.core.DeleteCampaignViews(t); err != nil { + return err + } + err = app.core.DeleteCampaignLinkClicks(t) + case "views": + err = app.core.DeleteCampaignViews(t) + case "clicks": + err = app.core.DeleteCampaignLinkClicks(t) + default: + err = echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidData")) + } + + if err != nil { + return err + } + + return c.JSON(http.StatusOK, okResp{true}) +} diff --git a/frontend/fontello/config.json b/frontend/fontello/config.json index d3ea0a2c..48f004e8 100755 --- a/frontend/fontello/config.json +++ b/frontend/fontello/config.json @@ -566,6 +566,20 @@ "logout-variant" ] }, + { + "uid": "10098901a143c53df6eeaeb317ae3da6", + "css": "wrench-outline", + "code": 986080, + "src": "custom_icons", + "selected": true, + "svg": { + "path": "M941.4 791L564.5 412.1Q593.8 337.9 578.1 258.8T503.9 121.1Q431.6 50.8 332 43T152.3 93.8L312.5 253.9 253.9 312.5 93.8 154.3Q35.2 234.4 42 334T121.1 503.9Q177.7 562.5 255.9 578.1T408.2 566.4L787.1 945.3Q798.8 959 816.4 959T845.7 945.3L941.4 849.6Q955.1 837.9 955.1 821.3T941.4 791ZM816.4 857.4L423.8 462.9Q384.8 492.2 340.8 498T253.9 491.2 180.7 447.3 136.7 378.9 125 302.7L253.9 431.6 429.7 253.9 300.8 125Q384.8 121.1 445.3 179.7 478.5 212.9 491.2 256.8T496.1 344.7 460.9 427.7L853.5 820.3Z", + "width": 1000 + }, + "search": [ + "wrench-outline" + ] + }, { "uid": "f4ad3f6d071a0bfb3a8452b514ed0892", "css": "vector-square", @@ -42748,20 +42762,6 @@ "wrap-disabled" ] }, - { - "uid": "10098901a143c53df6eeaeb317ae3da6", - "css": "wrench-outline", - "code": 986080, - "src": "custom_icons", - "selected": false, - "svg": { - "path": "M941.4 791L564.5 412.1Q593.8 337.9 578.1 258.8T503.9 121.1Q431.6 50.8 332 43T152.3 93.8L312.5 253.9 253.9 312.5 93.8 154.3Q35.2 234.4 42 334T121.1 503.9Q177.7 562.5 255.9 578.1T408.2 566.4L787.1 945.3Q798.8 959 816.4 959T845.7 945.3L941.4 849.6Q955.1 837.9 955.1 821.3T941.4 791ZM816.4 857.4L423.8 462.9Q384.8 492.2 340.8 498T253.9 491.2 180.7 447.3 136.7 378.9 125 302.7L253.9 431.6 429.7 253.9 300.8 125Q384.8 121.1 445.3 179.7 478.5 212.9 491.2 256.8T496.1 344.7 460.9 427.7L853.5 820.3Z", - "width": 1000 - }, - "search": [ - "wrench-outline" - ] - }, { "uid": "d670b0f395ba3a61b975a6387f8a2471", "css": "access-point-network-off", diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js index 99a20471..112df6ec 100644 --- a/frontend/src/api/index.js +++ b/frontend/src/api/index.js @@ -289,3 +289,12 @@ export const getLang = async (lang) => http.get(`/api/lang/${lang}`, export const logout = async () => http.get('/api/logout', { auth: { username: 'wrong', password: 'wrong' }, }); + +export const deleteGCCampaignAnalytics = async (typ, beforeDate) => http.delete(`/api/maintenance/analytics/${typ}`, + { loading: models.maintenance, params: { before_date: beforeDate } }); + +export const deleteGCSubscribers = async (typ) => http.delete(`/api/maintenance/subscribers/${typ}`, + { loading: models.maintenance }); + +export const deleteGCSubscriptions = async (beforeDate) => http.delete('/api/maintenance/subscriptions/unconfirmed', + { loading: models.maintenance, params: { before_date: beforeDate } }); diff --git a/frontend/src/assets/icons/fontello.css b/frontend/src/assets/icons/fontello.css index 9fb2be4b..491fa8b5 100644 --- a/frontend/src/assets/icons/fontello.css +++ b/frontend/src/assets/icons/fontello.css @@ -40,6 +40,7 @@ /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ } + .mdi-view-dashboard-variant-outline:before { content: '\e800'; } /* '' */ .mdi-format-list-bulleted-square:before { content: '\e801'; } /* '' */ .mdi-newspaper-variant-outline:before { content: '\e802'; } /* '' */ @@ -80,3 +81,4 @@ .mdi-email-bounce:before { content: '\e825'; } /* '' */ .mdi-speedometer:before { content: '\e826'; } /* '' */ .mdi-logout-variant:before { content: '󰗽'; } /* '\f05fd' */ +.mdi-wrench-outline:before { content: '󰯠'; } /* '\f0be0' */ diff --git a/frontend/src/assets/icons/fontello.woff2 b/frontend/src/assets/icons/fontello.woff2 index b2e48a99..986cb8bb 100755 Binary files a/frontend/src/assets/icons/fontello.woff2 and b/frontend/src/assets/icons/fontello.woff2 differ diff --git a/frontend/src/components/Navigation.vue b/frontend/src/components/Navigation.vue index 2ec4872e..c23f347a 100644 --- a/frontend/src/components/Navigation.vue +++ b/frontend/src/components/Navigation.vue @@ -69,6 +69,10 @@ data-cy="all-settings" icon="cog-outline" :label="$t('menu.settings')"> + + + diff --git a/frontend/src/constants.js b/frontend/src/constants.js index 51ebf27b..9bccac29 100644 --- a/frontend/src/constants.js +++ b/frontend/src/constants.js @@ -10,6 +10,7 @@ export const models = Object.freeze({ bounces: 'bounces', settings: 'settings', logs: 'logs', + maintenance: 'maintenance', }); // Ad-hoc URIs that are used outside of vuex requests. diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 52d38af9..b6219d8e 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -107,6 +107,12 @@ const routes = [ meta: { title: 'logs.title', group: 'settings' }, component: () => import(/* webpackChunkName: "main" */ '../views/Logs.vue'), }, + { + path: '/settings/maintenance', + name: 'maintenance', + meta: { title: 'logs.title', group: 'settings' }, + component: () => import(/* webpackChunkName: "main" */ '../views/Maintenance.vue'), + }, ]; const router = new VueRouter({ diff --git a/frontend/src/views/Maintenance.vue b/frontend/src/views/Maintenance.vue new file mode 100644 index 00000000..2799caee --- /dev/null +++ b/frontend/src/views/Maintenance.vue @@ -0,0 +1,163 @@ + + + diff --git a/i18n/cs-cz.json b/i18n/cs-cz.json index f41a1002..f34ad844 100644 --- a/i18n/cs-cz.json +++ b/i18n/cs-cz.json @@ -155,6 +155,7 @@ "globals.messages.created": "\"{name}\" vytvořen", "globals.messages.deleted": "\"{name}\" odstraněn", "globals.messages.deletedCount": "{name} ({num}) odstraněn", + "globals.messages.done": "Done", "globals.messages.emptyState": "Nic zde není", "globals.messages.errorCreating": "Chyba při vytváření {name}: {error}", "globals.messages.errorDeleting": "Chyba při odstraňování {name}: {error}", @@ -183,6 +184,7 @@ "globals.months.8": "Srp", "globals.months.9": "Zář", "globals.states.off": "Vypnout", + "globals.terms.all": "All", "globals.terms.analytics": "Analytika", "globals.terms.bounce": "Nedoručitelnost | Případy nedoručitelnosti", "globals.terms.bounces": "Případy nedoručitelnosti", @@ -202,6 +204,7 @@ "globals.terms.settings": "Nastavení", "globals.terms.subscriber": "Odběratel | Odběratelé", "globals.terms.subscribers": "Odběratelé", + "globals.terms.subscriptions": "Subscription | Subscriptions", "globals.terms.tag": "Značka | Značky", "globals.terms.tags": "Značky", "globals.terms.template": "Šablona | Šablony", @@ -252,6 +255,11 @@ "lists.types.private": "Soukromý", "lists.types.public": "Veřejný", "logs.title": "Protokoly", + "maintenance.help": "Some actions may take a while to complete depending on the amount of data.", + "maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions", + "maintenance.olderThan": "Older than", + "maintenance.title": "Maintenance", + "maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.", "media.errorReadingFile": "Chyba při čtení souboru: {error}", "media.errorResizing": "Chyba při změně velikosti obrázku: {error}", "media.errorSavingThumbnail": "Chyba při ukládání miniatury: {error}", @@ -269,6 +277,7 @@ "menu.forms": "Formuláře", "menu.import": "Import", "menu.logs": "Protokoly", + "menu.maintenance": "Maintenance", "menu.media": "Médium", "menu.newCampaign": "Vytvořit nový", "menu.settings": "Nastavení", diff --git a/i18n/de.json b/i18n/de.json index 868df67f..54712bbc 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -155,6 +155,7 @@ "globals.messages.created": "\"{name}\" erstellt", "globals.messages.deleted": "\"{name}\" gelöscht", "globals.messages.deletedCount": "{name} ({num}) gelöscht", + "globals.messages.done": "Done", "globals.messages.emptyState": "Hier ist nichts", "globals.messages.errorCreating": "Fehler beim Erstellen von {name}: {error}", "globals.messages.errorDeleting": "Fehler beim Löschen von {name}: {error}", @@ -183,6 +184,7 @@ "globals.months.8": "Aug", "globals.months.9": "Sep", "globals.states.off": "Off", + "globals.terms.all": "All", "globals.terms.analytics": "Statistiken", "globals.terms.bounce": "Bounce | Bounces", "globals.terms.bounces": "Bounces", @@ -202,6 +204,7 @@ "globals.terms.settings": "Einstellungen", "globals.terms.subscriber": "Abonnent | Abonnenten", "globals.terms.subscribers": "Abonnenten", + "globals.terms.subscriptions": "Subscription | Subscriptions", "globals.terms.tag": "Tag | Tags", "globals.terms.tags": "Tags", "globals.terms.template": "Vorlage | Vorlagen", @@ -252,6 +255,11 @@ "lists.types.private": "Privat", "lists.types.public": "Öffentlich", "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", + "maintenance.olderThan": "Older than", + "maintenance.title": "Maintenance", + "maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.", "media.errorReadingFile": "Fehler beim Lesen der Datei: {error}", "media.errorResizing": "Fehler beim Anpassen der Größe des Bildes: {error}", "media.errorSavingThumbnail": "Fehler beim Speichern des Thumbnails: {error}", @@ -269,6 +277,7 @@ "menu.forms": "Formulare", "menu.import": "Importieren", "menu.logs": "Logs", + "menu.maintenance": "Maintenance", "menu.media": "Medien", "menu.newCampaign": "Neu Anlegen", "menu.settings": "Einstellungen", diff --git a/i18n/en.json b/i18n/en.json index 7237397d..050d8298 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -154,6 +154,7 @@ "globals.messages.created": "\"{name}\" created", "globals.messages.deleted": "\"{name}\" deleted", "globals.messages.deletedCount": "{name} ({num}) deleted", + "globals.messages.done": "Done", "globals.messages.emptyState": "Nothing here", "globals.messages.errorCreating": "Error creating {name}: {error}", "globals.messages.errorDeleting": "Error deleting {name}: {error}", @@ -182,6 +183,7 @@ "globals.months.8": "Aug", "globals.months.9": "Sep", "globals.states.off": "Off", + "globals.terms.all": "All", "globals.terms.analytics": "Analytics", "globals.terms.bounce": "Bounce | Bounces", "globals.terms.bounces": "Bounces", @@ -201,6 +203,7 @@ "globals.terms.settings": "Settings", "globals.terms.subscriber": "Subscriber | Subscribers", "globals.terms.subscribers": "Subscribers", + "globals.terms.subscriptions": "Subscription | Subscriptions", "globals.terms.tag": "Tag | Tags", "globals.terms.tags": "Tags", "globals.terms.template": "Template | Templates", @@ -251,6 +254,11 @@ "lists.types.private": "Private", "lists.types.public": "Public", "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", + "maintenance.olderThan": "Older than", + "maintenance.title": "Maintenance", + "maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.", "media.errorReadingFile": "Error reading file: {error}", "media.errorResizing": "Error resizing image: {error}", "media.errorSavingThumbnail": "Error saving thumbnail: {error}", @@ -268,6 +276,7 @@ "menu.forms": "Forms", "menu.import": "Import", "menu.logs": "Logs", + "menu.maintenance": "Maintenance", "menu.media": "Media", "menu.newCampaign": "Create new", "menu.settings": "Settings", diff --git a/i18n/es.json b/i18n/es.json index dd8ef010..e09a4fef 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -155,6 +155,7 @@ "globals.messages.created": "\"{name}\" creado", "globals.messages.deleted": "\"{name}\" eliminado", "globals.messages.deletedCount": "{name} ({num}) eliminado(s)", + "globals.messages.done": "Done", "globals.messages.emptyState": "Vacío", "globals.messages.errorCreating": "Error creando {name}: {error}", "globals.messages.errorDeleting": "Error eliminando {name}: {error}", @@ -183,6 +184,7 @@ "globals.months.8": "Agosto", "globals.months.9": "Setiembre", "globals.states.off": "Apagado", + "globals.terms.all": "All", "globals.terms.analytics": "Analitica", "globals.terms.bounce": "Rebote | Rebotes", "globals.terms.bounces": "Rebotes", @@ -202,6 +204,7 @@ "globals.terms.settings": "Configuraciones", "globals.terms.subscriber": "Subscriptor | Subscriptores", "globals.terms.subscribers": "Subscriptores", + "globals.terms.subscriptions": "Subscription | Subscriptions", "globals.terms.tag": "Etiqueta | Etiquetas", "globals.terms.tags": "Etiqueta", "globals.terms.template": "Plantilla | Plantillas", @@ -252,6 +255,11 @@ "lists.types.private": "Privada", "lists.types.public": "Pública", "logs.title": "Registros", + "maintenance.help": "Some actions may take a while to complete depending on the amount of data.", + "maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions", + "maintenance.olderThan": "Older than", + "maintenance.title": "Maintenance", + "maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.", "media.errorReadingFile": "Error leyendo archivo: {error}", "media.errorResizing": "Error cambiando tamaño de imagen: {error}", "media.errorSavingThumbnail": "Error guardando miniatura: {error}", @@ -269,6 +277,7 @@ "menu.forms": "Formularios", "menu.import": "Importar", "menu.logs": "Registros (logs)", + "menu.maintenance": "Maintenance", "menu.media": "Multimedia", "menu.newCampaign": "Crear nueva", "menu.settings": "Configuraciones", diff --git a/i18n/fi.json b/i18n/fi.json index 015d4c1f..8db37d9f 100644 --- a/i18n/fi.json +++ b/i18n/fi.json @@ -155,6 +155,7 @@ "globals.messages.created": "\"{name}\" created", "globals.messages.deleted": "\"{name}\" deleted", "globals.messages.deletedCount": "{name} ({num}) deleted", + "globals.messages.done": "Done", "globals.messages.emptyState": "Nothing here", "globals.messages.errorCreating": "Error creating {name}: {error}", "globals.messages.errorDeleting": "Error deleting {name}: {error}", @@ -183,6 +184,7 @@ "globals.months.8": "Aug", "globals.months.9": "Sep", "globals.states.off": "Off", + "globals.terms.all": "All", "globals.terms.analytics": "Analytics", "globals.terms.bounce": "Bounce | Bounces", "globals.terms.bounces": "Bounces", @@ -202,6 +204,7 @@ "globals.terms.settings": "Settings", "globals.terms.subscriber": "Subscriber | Subscribers", "globals.terms.subscribers": "Subscribers", + "globals.terms.subscriptions": "Subscription | Subscriptions", "globals.terms.tag": "Tag | Tags", "globals.terms.tags": "Tags", "globals.terms.template": "Template | Templates", @@ -252,6 +255,11 @@ "lists.types.private": "Private", "lists.types.public": "Public", "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", + "maintenance.olderThan": "Older than", + "maintenance.title": "Maintenance", + "maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.", "media.errorReadingFile": "Error reading file: {error}", "media.errorResizing": "Error resizing image: {error}", "media.errorSavingThumbnail": "Error saving thumbnail: {error}", @@ -269,6 +277,7 @@ "menu.forms": "Forms", "menu.import": "Import", "menu.logs": "Logs", + "menu.maintenance": "Maintenance", "menu.media": "Media", "menu.newCampaign": "Create new", "menu.settings": "Settings", diff --git a/i18n/fr.json b/i18n/fr.json index 81849788..55b57aa4 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -155,6 +155,7 @@ "globals.messages.created": "Création de « {name} »", "globals.messages.deleted": "Suppression de « {name} »", "globals.messages.deletedCount": "{name} ({num}) effacé(s)", + "globals.messages.done": "Done", "globals.messages.emptyState": "Rien", "globals.messages.errorCreating": "Erreur lors de la création de {name} : {error}", "globals.messages.errorDeleting": "Erreur lors de la suppression de {name} : {error}", @@ -183,6 +184,7 @@ "globals.months.8": "août", "globals.months.9": "sept.", "globals.states.off": "Off", + "globals.terms.all": "All", "globals.terms.analytics": "Analyses", "globals.terms.bounce": "Rebond | Rebonds", "globals.terms.bounces": "Rebonds", @@ -202,6 +204,7 @@ "globals.terms.settings": "Paramètres", "globals.terms.subscriber": "Abonné·e | Abonné·es", "globals.terms.subscribers": "Abonné·es", + "globals.terms.subscriptions": "Subscription | Subscriptions", "globals.terms.tag": "Étiquette | Étiquettes", "globals.terms.tags": "Étiquettes", "globals.terms.template": "Modèle | Modèles", @@ -252,6 +255,11 @@ "lists.types.private": "Privée", "lists.types.public": "Publique", "logs.title": "Journalisations", + "maintenance.help": "Some actions may take a while to complete depending on the amount of data.", + "maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions", + "maintenance.olderThan": "Older than", + "maintenance.title": "Maintenance", + "maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.", "media.errorReadingFile": "Erreur de lecture du fichier : {error}", "media.errorResizing": "Erreur lors du redimensionnement de l'image : {error}", "media.errorSavingThumbnail": "Erreur lors de l'enregistrement de la miniature : {error}", @@ -269,6 +277,7 @@ "menu.forms": "Formulaires", "menu.import": "Importer", "menu.logs": "Journalisations", + "menu.maintenance": "Maintenance", "menu.media": "Fichiers", "menu.newCampaign": "Nouvelle campagne", "menu.settings": "Paramètres", diff --git a/i18n/hu.json b/i18n/hu.json index 1c75d319..9c1d55d2 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -155,6 +155,7 @@ "globals.messages.created": "\"{name}\" készítette", "globals.messages.deleted": "\"{name}\" törölte", "globals.messages.deletedCount": "{name} ({num}) törölve", + "globals.messages.done": "Done", "globals.messages.emptyState": "Semmi sincs itt", "globals.messages.errorCreating": "Hiba a létrehozásnál {name}: {error}", "globals.messages.errorDeleting": "Hiba a törléskor {name}: {error}", @@ -183,6 +184,7 @@ "globals.months.8": "Aug", "globals.months.9": "Sep", "globals.states.off": "Off", + "globals.terms.all": "All", "globals.terms.analytics": "Analitika", "globals.terms.bounce": "Visszapattanó | Visszapattanók", "globals.terms.bounces": "Visszapattanók", @@ -202,6 +204,7 @@ "globals.terms.settings": "Beállítások", "globals.terms.subscriber": "Feliratkozó | Feliratkozók", "globals.terms.subscribers": "Feliratkozók", + "globals.terms.subscriptions": "Subscription | Subscriptions", "globals.terms.tag": "Címke | Címkék", "globals.terms.tags": "Címkék", "globals.terms.template": "Sablon | Sablonok", @@ -252,6 +255,11 @@ "lists.types.private": "Privát", "lists.types.public": "Nyilvános", "logs.title": "Logok", + "maintenance.help": "Some actions may take a while to complete depending on the amount of data.", + "maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions", + "maintenance.olderThan": "Older than", + "maintenance.title": "Maintenance", + "maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.", "media.errorReadingFile": "Hiba a fájl olvasásakor : {error}", "media.errorResizing": "Hiba a kép átméretezésekor : {error}", "media.errorSavingThumbnail": "Hiba az indexkép mentésekor : {error}", @@ -269,6 +277,7 @@ "menu.forms": "Űrlapok", "menu.import": "Importálás", "menu.logs": "Logok", + "menu.maintenance": "Maintenance", "menu.media": "Média", "menu.newCampaign": "Új készítése", "menu.settings": "Beállítások", diff --git a/i18n/it.json b/i18n/it.json index 65cd2d7c..d9d2de14 100644 --- a/i18n/it.json +++ b/i18n/it.json @@ -155,6 +155,7 @@ "globals.messages.created": "\"{name}\" creato", "globals.messages.deleted": "\"{name}\" cancellato", "globals.messages.deletedCount": "{name} ({num}) deleted", + "globals.messages.done": "Done", "globals.messages.emptyState": "Niente da visualizzare", "globals.messages.errorCreating": "Errore durante la creazione di {name}: {error}", "globals.messages.errorDeleting": "Errore durante la cancellazione di {name}: {error}", @@ -183,6 +184,7 @@ "globals.months.8": "Ago", "globals.months.9": "Set", "globals.states.off": "Off", + "globals.terms.all": "All", "globals.terms.analytics": "Analytics", "globals.terms.bounce": "Bounce | Bounces", "globals.terms.bounces": "Bounces", @@ -202,6 +204,7 @@ "globals.terms.settings": "Impostazioni", "globals.terms.subscriber": "Iscritto | Iscritti", "globals.terms.subscribers": "Iscritti", + "globals.terms.subscriptions": "Subscription | Subscriptions", "globals.terms.tag": "Etichetta | Etichette", "globals.terms.tags": "Etichette", "globals.terms.template": "Modello | Modelli", @@ -252,6 +255,11 @@ "lists.types.private": "Privata", "lists.types.public": "Pubblico", "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", + "maintenance.olderThan": "Older than", + "maintenance.title": "Maintenance", + "maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.", "media.errorReadingFile": "Errore di lettura del file: {error}", "media.errorResizing": "Errore di ridimensionamento dell'immagine: {error}", "media.errorSavingThumbnail": "Errore durante il salvataggio dell'immagine: {error}", @@ -269,6 +277,7 @@ "menu.forms": "Formulari", "menu.import": "Importare", "menu.logs": "Logs", + "menu.maintenance": "Maintenance", "menu.media": "Media", "menu.newCampaign": "Creare nuovo", "menu.settings": "Impostazioni", diff --git a/i18n/jp.json b/i18n/jp.json index 2ca3aa4f..543b5b33 100644 --- a/i18n/jp.json +++ b/i18n/jp.json @@ -155,6 +155,7 @@ "globals.messages.created": "\"{name}\" が作成されました", "globals.messages.deleted": "\"{name}\" が削除されました", "globals.messages.deletedCount": "{name} ({num}) が削除されました。", + "globals.messages.done": "Done", "globals.messages.emptyState": "ここには何もありません", "globals.messages.errorCreating": "{name}作成エラー: {error}", "globals.messages.errorDeleting": "{name}削除エラー: {error}", @@ -183,6 +184,7 @@ "globals.months.8": "8月", "globals.months.9": "9月", "globals.states.off": "オフ", + "globals.terms.all": "All", "globals.terms.analytics": "分析", "globals.terms.bounce": "バウンス | バウンス", "globals.terms.bounces": "バウンス", @@ -202,6 +204,7 @@ "globals.terms.settings": "設定", "globals.terms.subscriber": "加入者 | 加入者", "globals.terms.subscribers": "加入者", + "globals.terms.subscriptions": "Subscription | Subscriptions", "globals.terms.tag": "タグ | タグ", "globals.terms.tags": "タグ", "globals.terms.template": "テンプレート | テンプレート", @@ -252,6 +255,11 @@ "lists.types.private": "プライベート", "lists.types.public": "パブリック", "logs.title": "ログ", + "maintenance.help": "Some actions may take a while to complete depending on the amount of data.", + "maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions", + "maintenance.olderThan": "Older than", + "maintenance.title": "Maintenance", + "maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.", "media.errorReadingFile": "ファイル読み込みエラー: {error}", "media.errorResizing": "画像のリサイズエラー: {error}", "media.errorSavingThumbnail": "サムネイル保存エラー: {error}", @@ -269,6 +277,7 @@ "menu.forms": "フォーム", "menu.import": "インポート", "menu.logs": "ログ", + "menu.maintenance": "Maintenance", "menu.media": "メディア", "menu.newCampaign": "新規作成", "menu.settings": "設定", diff --git a/i18n/ml.json b/i18n/ml.json index 2828c0f6..53339d8f 100644 --- a/i18n/ml.json +++ b/i18n/ml.json @@ -155,6 +155,7 @@ "globals.messages.created": "\"{name}\" നിർമ്മിച്ചു", "globals.messages.deleted": "\"{name}\" നീക്കം ചെയ്തു", "globals.messages.deletedCount": "{name} ({num}) deleted", + "globals.messages.done": "Done", "globals.messages.emptyState": "ഇവിടൊന്നുമില്ല", "globals.messages.errorCreating": "{name} നിർമ്മിക്കുന്നതിൽ പിശകുണ്ടായി: {error}", "globals.messages.errorDeleting": "{name} നീക്കം ചെയ്യുന്നതിൽ പിശകുണ്ടായി: {error}", @@ -183,6 +184,7 @@ "globals.months.8": "ഓഗസ്റ്റ്", "globals.months.9": "സെപ്റ്റംബർ", "globals.states.off": "Off", + "globals.terms.all": "All", "globals.terms.analytics": "Analytics", "globals.terms.bounce": "Bounce | Bounces", "globals.terms.bounces": "Bounces", @@ -202,6 +204,7 @@ "globals.terms.settings": "ക്രമീകരണങ്ങൾ", "globals.terms.subscriber": "വരിക്കാരൻ | വരിക്കാർ", "globals.terms.subscribers": "വരിക്കാർ", + "globals.terms.subscriptions": "Subscription | Subscriptions", "globals.terms.tag": "ടാഗ് | ടാഗുകൾ", "globals.terms.tags": "ടാഗുകൾ", "globals.terms.template": "ടെംപ്ലേറ്റ് | ടെംപ്ലേറ്റുകൾ", @@ -252,6 +255,11 @@ "lists.types.private": "സ്വകാര്യം", "lists.types.public": "പൊതു", "logs.title": "ലോഗുകൾ", + "maintenance.help": "Some actions may take a while to complete depending on the amount of data.", + "maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions", + "maintenance.olderThan": "Older than", + "maintenance.title": "Maintenance", + "maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.", "media.errorReadingFile": "ഫയൽ വായിക്കാനായില്ല: {error}", "media.errorResizing": "ചിത്രത്തിന്റ വലിപ്പം മാറ്റാനായില്ല: {error}", "media.errorSavingThumbnail": "തമ്പ്നെയിൽ സേവ് ചെയ്യാനായില്ല: {error}", @@ -269,6 +277,7 @@ "menu.forms": "ഫോമുകൾ", "menu.import": "ഇമ്പോർട്ട്", "menu.logs": "ലോഗുകൾ", + "menu.maintenance": "Maintenance", "menu.media": "മീഡിയ", "menu.newCampaign": "പുതിയത് തുടങ്ങുക", "menu.settings": "ക്രമീകരണങ്ങൾ", diff --git a/i18n/nl.json b/i18n/nl.json index 99524991..bda3b89d 100644 --- a/i18n/nl.json +++ b/i18n/nl.json @@ -155,6 +155,7 @@ "globals.messages.created": "\"{name}\" aangemaakt", "globals.messages.deleted": "\"{name}\" verwijderd", "globals.messages.deletedCount": "{name} ({num}) verwijderd", + "globals.messages.done": "Done", "globals.messages.emptyState": "Niks te zien hier", "globals.messages.errorCreating": "Fout bij aanmaken {name}: {error}", "globals.messages.errorDeleting": "Fout bij verwijderen {name}: {error}", @@ -183,6 +184,7 @@ "globals.months.8": "Aug", "globals.months.9": "Sep", "globals.states.off": "Uit", + "globals.terms.all": "All", "globals.terms.analytics": "Analyse", "globals.terms.bounce": "Bounce | Bounces", "globals.terms.bounces": "Bounces", @@ -202,6 +204,7 @@ "globals.terms.settings": "Instellingen", "globals.terms.subscriber": "Abonnee | Abonnees", "globals.terms.subscribers": "Abonnees", + "globals.terms.subscriptions": "Subscription | Subscriptions", "globals.terms.tag": "Label | Labels", "globals.terms.tags": "Labels", "globals.terms.template": "Sjabloon | Sjablonen", @@ -252,6 +255,11 @@ "lists.types.private": "Privé", "lists.types.public": "Publiek", "logs.title": "Logboeken", + "maintenance.help": "Some actions may take a while to complete depending on the amount of data.", + "maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions", + "maintenance.olderThan": "Older than", + "maintenance.title": "Maintenance", + "maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.", "media.errorReadingFile": "Fout bij lezen bestand: {error}", "media.errorResizing": "Fout bij wijzigen formaat afbeelding: {error}", "media.errorSavingThumbnail": "Fout bij opslaan thumbnail: {error}", @@ -269,6 +277,7 @@ "menu.forms": "Formulieren", "menu.import": "Importeer", "menu.logs": "Logboeken", + "menu.maintenance": "Maintenance", "menu.media": "Media", "menu.newCampaign": "Nieuwe aanmaken", "menu.settings": "Instellingen", diff --git a/i18n/pl.json b/i18n/pl.json index b71a89c6..d9506900 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -155,6 +155,7 @@ "globals.messages.created": "\"{name}\" utworzono", "globals.messages.deleted": "\"{name}\" usunięto", "globals.messages.deletedCount": "{name} ({num}) deleted", + "globals.messages.done": "Done", "globals.messages.emptyState": "Nic tutaj nie ma", "globals.messages.errorCreating": "Błąd podczas tworzenia {name}: {error}", "globals.messages.errorDeleting": "Błąd podczas usuwania {name}: {error}", @@ -183,6 +184,7 @@ "globals.months.8": "Sie", "globals.months.9": "Wrz", "globals.states.off": "Wyłączone", + "globals.terms.all": "All", "globals.terms.analytics": "Analityka", "globals.terms.bounce": "Odbicie | Obicia", "globals.terms.bounces": "Odbicia", @@ -202,6 +204,7 @@ "globals.terms.settings": "Ustawienia", "globals.terms.subscriber": "Subskrypcja | Subskrypcje", "globals.terms.subscribers": "Subskrypcje", + "globals.terms.subscriptions": "Subscription | Subscriptions", "globals.terms.tag": "Tag | Tagi", "globals.terms.tags": "Tagi", "globals.terms.template": "Szablon | Szablony", @@ -252,6 +255,11 @@ "lists.types.private": "Prywatna", "lists.types.public": "Publiczna", "logs.title": "Logi", + "maintenance.help": "Some actions may take a while to complete depending on the amount of data.", + "maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions", + "maintenance.olderThan": "Older than", + "maintenance.title": "Maintenance", + "maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.", "media.errorReadingFile": "Błąd odczytu pliku: {error}", "media.errorResizing": "Błąd zmiany rozmiaru obrazu: {error}", "media.errorSavingThumbnail": "Błąd zapisywania miniaturki: {error}", @@ -269,6 +277,7 @@ "menu.forms": "Formularze", "menu.import": "Import", "menu.logs": "Logi", + "menu.maintenance": "Maintenance", "menu.media": "Media", "menu.newCampaign": "Utwórz nową", "menu.settings": "Ustawienia", diff --git a/i18n/pt-BR.json b/i18n/pt-BR.json index e0a15e60..c9e57ae5 100644 --- a/i18n/pt-BR.json +++ b/i18n/pt-BR.json @@ -155,6 +155,7 @@ "globals.messages.created": "\"{name}\" criado", "globals.messages.deleted": "\"{name}\" excluído", "globals.messages.deletedCount": "{name} ({num}) deletado", + "globals.messages.done": "Done", "globals.messages.emptyState": "Nada por aqui", "globals.messages.errorCreating": "Erro ao criar {name}: {error}", "globals.messages.errorDeleting": "Erro ao excluir {name}: {error}", @@ -183,6 +184,7 @@ "globals.months.8": "Ago", "globals.months.9": "Set", "globals.states.off": "Off", + "globals.terms.all": "All", "globals.terms.analytics": "Analytics", "globals.terms.bounce": "Bounce | Bounces", "globals.terms.bounces": "Bounces", @@ -202,6 +204,7 @@ "globals.terms.settings": "Configurações", "globals.terms.subscriber": "Assinante | Assinantes", "globals.terms.subscribers": "Assinantes", + "globals.terms.subscriptions": "Subscription | Subscriptions", "globals.terms.tag": "Tag | Tags", "globals.terms.tags": "Tags", "globals.terms.template": "Modelo | Modelos", @@ -252,6 +255,11 @@ "lists.types.private": "Privada", "lists.types.public": "Pública", "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", + "maintenance.olderThan": "Older than", + "maintenance.title": "Maintenance", + "maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.", "media.errorReadingFile": "Erro ao ler arquivo: {error}", "media.errorResizing": "Erro ao redimensionar imagem: {error}", "media.errorSavingThumbnail": "Erro ao salvar miniatura: {error}", @@ -269,6 +277,7 @@ "menu.forms": "Formulários", "menu.import": "Importação", "menu.logs": "Logs", + "menu.maintenance": "Maintenance", "menu.media": "Mídia", "menu.newCampaign": "Criar nova", "menu.settings": "Configurações", diff --git a/i18n/pt.json b/i18n/pt.json index 2b4ab162..b437de26 100644 --- a/i18n/pt.json +++ b/i18n/pt.json @@ -155,6 +155,7 @@ "globals.messages.created": "\"{name}\" criado", "globals.messages.deleted": "\"{name}\" eliminado", "globals.messages.deletedCount": "{name} ({num}) deleted", + "globals.messages.done": "Done", "globals.messages.emptyState": "Não há nada aqui", "globals.messages.errorCreating": "Erro ao criar {name}: {error}", "globals.messages.errorDeleting": "Erro ao eliminar {name}: {error}", @@ -183,6 +184,7 @@ "globals.months.8": "Ago", "globals.months.9": "Set", "globals.states.off": "Off", + "globals.terms.all": "All", "globals.terms.analytics": "Analytics", "globals.terms.bounce": "Bounce | Bounces", "globals.terms.bounces": "Bounces", @@ -202,6 +204,7 @@ "globals.terms.settings": "Definições", "globals.terms.subscriber": "Subscritor | Subcritores", "globals.terms.subscribers": "Subscritores", + "globals.terms.subscriptions": "Subscription | Subscriptions", "globals.terms.tag": "Etiqueta | Etiquetas", "globals.terms.tags": "Etiquetas", "globals.terms.template": "Modelo | Modelos", @@ -252,6 +255,11 @@ "lists.types.private": "Privado", "lists.types.public": "Público", "logs.title": "Logs (Histórico)", + "maintenance.help": "Some actions may take a while to complete depending on the amount of data.", + "maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions", + "maintenance.olderThan": "Older than", + "maintenance.title": "Maintenance", + "maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.", "media.errorReadingFile": "Erro ao ler ficheiro: {error}", "media.errorResizing": "Erro ao alterar tamanho da imagem: {error}", "media.errorSavingThumbnail": "Erro ao guardar miniatura: {error}", @@ -269,6 +277,7 @@ "menu.forms": "Formulários", "menu.import": "Importar", "menu.logs": "Logs", + "menu.maintenance": "Maintenance", "menu.media": "Mídia", "menu.newCampaign": "Criar nova", "menu.settings": "Definições", diff --git a/i18n/ro.json b/i18n/ro.json index 77472011..38086d82 100644 --- a/i18n/ro.json +++ b/i18n/ro.json @@ -155,6 +155,7 @@ "globals.messages.created": "\"{nume}\" creat", "globals.messages.deleted": "\"{nume}\" șters", "globals.messages.deletedCount": "{nume} ({număr}) șters", + "globals.messages.done": "Done", "globals.messages.emptyState": "Nimic aici", "globals.messages.errorCreating": "Eroare creare {nume}: {eroare}", "globals.messages.errorDeleting": "Eroare ștergere {nume}: {eroare}", @@ -183,6 +184,7 @@ "globals.months.8": "Aug", "globals.months.9": "Sep", "globals.states.off": "Off", + "globals.terms.all": "All", "globals.terms.analytics": "Analitice", "globals.terms.bounce": "Respins | Respinse", "globals.terms.bounces": "Respinse", @@ -202,6 +204,7 @@ "globals.terms.settings": "Setări", "globals.terms.subscriber": "Abonat | Abonați", "globals.terms.subscribers": "Abonați", + "globals.terms.subscriptions": "Subscription | Subscriptions", "globals.terms.tag": "Etichetă | Etichete", "globals.terms.tags": "Etichete", "globals.terms.template": "Șablon | Șabloane", @@ -252,6 +255,11 @@ "lists.types.private": "Privat", "lists.types.public": "Public", "logs.title": "Jurnale", + "maintenance.help": "Some actions may take a while to complete depending on the amount of data.", + "maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions", + "maintenance.olderThan": "Older than", + "maintenance.title": "Maintenance", + "maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.", "media.errorReadingFile": "Eroare citire fisier: {eroare}", "media.errorResizing": "Eroare redimensionare imagine: {eroare}", "media.errorSavingThumbnail": "Eroare la salvarea miniaturii: {eroare}", @@ -269,6 +277,7 @@ "menu.forms": "Formulare", "menu.import": "Import", "menu.logs": "Jurnale", + "menu.maintenance": "Maintenance", "menu.media": "Media", "menu.newCampaign": "Creaza nou", "menu.settings": "Setări", diff --git a/i18n/ru.json b/i18n/ru.json index 03dafbe3..7089082b 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -155,6 +155,7 @@ "globals.messages.created": "\"{name}\" создано", "globals.messages.deleted": "\"{name}\" удалено", "globals.messages.deletedCount": "{name} ({num}) удалено", + "globals.messages.done": "Done", "globals.messages.emptyState": "Ничего нет", "globals.messages.errorCreating": "Ошибка создания {name}: {error}", "globals.messages.errorDeleting": "Ошибка удаления {name}: {error}", @@ -183,6 +184,7 @@ "globals.months.8": "Авг", "globals.months.9": "Сен", "globals.states.off": "Off", + "globals.terms.all": "All", "globals.terms.analytics": "Аналитика", "globals.terms.bounce": "Отскок | Отскоки", "globals.terms.bounces": "Отскоки", @@ -202,6 +204,7 @@ "globals.terms.settings": "Параметры", "globals.terms.subscriber": "Подписчик | Подписчики", "globals.terms.subscribers": "Подписчики", + "globals.terms.subscriptions": "Subscription | Subscriptions", "globals.terms.tag": "Тег | Теги", "globals.terms.tags": "Теги", "globals.terms.template": "Шаблон | Шаблоны", @@ -252,6 +255,11 @@ "lists.types.private": "Приватный", "lists.types.public": "Публичный", "logs.title": "Логи", + "maintenance.help": "Some actions may take a while to complete depending on the amount of data.", + "maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions", + "maintenance.olderThan": "Older than", + "maintenance.title": "Maintenance", + "maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.", "media.errorReadingFile": "Ошибка чтения файла: {error}", "media.errorResizing": "Ошибка изменения размера изображения: {error}", "media.errorSavingThumbnail": "Ошибка сохранения миниатюры: {error}", @@ -269,6 +277,7 @@ "menu.forms": "Формы", "menu.import": "Импорт", "menu.logs": "Логи", + "menu.maintenance": "Maintenance", "menu.media": "Медиа", "menu.newCampaign": "Создать новую", "menu.settings": "Параметры", diff --git a/i18n/tr.json b/i18n/tr.json index ab2af004..c35cb77e 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -155,6 +155,7 @@ "globals.messages.created": "\"{name}\" oluşturma", "globals.messages.deleted": "\"{name}\" silme", "globals.messages.deletedCount": "{name} ({num}) deleted", + "globals.messages.done": "Done", "globals.messages.emptyState": "Burası Boş", "globals.messages.errorCreating": "Hata oluşturma {name}: {error}", "globals.messages.errorDeleting": "Hata silme {name}: {error}", @@ -183,6 +184,7 @@ "globals.months.8": "Aug", "globals.months.9": "Eyl", "globals.states.off": "Off", + "globals.terms.all": "All", "globals.terms.analytics": "Analytics", "globals.terms.bounce": "Bounce | Bounces", "globals.terms.bounces": "Bounces", @@ -202,6 +204,7 @@ "globals.terms.settings": "Ayarlar", "globals.terms.subscriber": "Üye | Üyeler", "globals.terms.subscribers": "Üyeler", + "globals.terms.subscriptions": "Subscription | Subscriptions", "globals.terms.tag": "Tag | Tag(lar)", "globals.terms.tags": "Tag(lar)", "globals.terms.template": "Taslak | Taslaklar", @@ -252,6 +255,11 @@ "lists.types.private": "Kişisel", "lists.types.public": "Erişime açık", "logs.title": "Loglar", + "maintenance.help": "Some actions may take a while to complete depending on the amount of data.", + "maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions", + "maintenance.olderThan": "Older than", + "maintenance.title": "Maintenance", + "maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.", "media.errorReadingFile": "Hata, dosya okurken: {error}", "media.errorResizing": "Hata, resim büyüklüğü değişirken: {error}", "media.errorSavingThumbnail": "Hata, önizleme oluşurken: {error}", @@ -269,6 +277,7 @@ "menu.forms": "Formlar", "menu.import": "İçeri aktar", "menu.logs": "Loglar", + "menu.maintenance": "Maintenance", "menu.media": "Medya", "menu.newCampaign": "Yeni oluştur", "menu.settings": "Ayarlar", diff --git a/i18n/vi.json b/i18n/vi.json index bd0a77f8..d5536ef0 100644 --- a/i18n/vi.json +++ b/i18n/vi.json @@ -155,6 +155,7 @@ "globals.messages.created": "\"{name}\" đã tạo", "globals.messages.deleted": "\"{name}\" đã xóa", "globals.messages.deletedCount": "{name} ({num}) đã xóa", + "globals.messages.done": "Done", "globals.messages.emptyState": "Không có gì ở đây", "globals.messages.errorCreating": "Lỗi khi tạo {name}: {error}", "globals.messages.errorDeleting": "Lỗi khi xóa {name}: {error}", @@ -183,6 +184,7 @@ "globals.months.8": "Tháng 8", "globals.months.9": "Tháng 9", "globals.states.off": "Tắt", + "globals.terms.all": "All", "globals.terms.analytics": "phân tích", "globals.terms.bounce": "Bounces | Bounces", "globals.terms.bounces": "Bị trả lại", @@ -202,6 +204,7 @@ "globals.terms.settings": "Cài đặt", "globals.terms.subscriber": "Subscriber | Subscribers", "globals.terms.subscribers": "Người đăng ký", + "globals.terms.subscriptions": "Subscription | Subscriptions", "globals.terms.tag": "Tag | Tags", "globals.terms.tags": "Thẻ", "globals.terms.template": "Template | Templates", @@ -252,6 +255,11 @@ "lists.types.private": "Riêng tư", "lists.types.public": "Công cộng", "logs.title": "Nhật ký", + "maintenance.help": "Some actions may take a while to complete depending on the amount of data.", + "maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions", + "maintenance.olderThan": "Older than", + "maintenance.title": "Maintenance", + "maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.", "media.errorReadingFile": "Lỗi khi đọc tệp: {error}", "media.errorResizing": "Lỗi khi thay đổi kích thước hình ảnh: {error}", "media.errorSavingThumbnail": "Lỗi khi lưu hình thu nhỏ: {error}", @@ -269,6 +277,7 @@ "menu.forms": "Biểu mẫu", "menu.import": "Nhập", "menu.logs": "Nhật kí", + "menu.maintenance": "Maintenance", "menu.media": "Dữ liệu truyền thông", "menu.newCampaign": "Tạo mới", "menu.settings": "Cài đặt", diff --git a/i18n/zh-CN.json b/i18n/zh-CN.json index 248e4586..db408755 100644 --- a/i18n/zh-CN.json +++ b/i18n/zh-CN.json @@ -155,6 +155,7 @@ "globals.messages.created": "“{name}”已创建", "globals.messages.deleted": "“{name}”已删除", "globals.messages.deletedCount": "{name} ({num}) 个已删除", + "globals.messages.done": "Done", "globals.messages.emptyState": "这里没有什么", "globals.messages.errorCreating": "创建 {name} 时出错:{error}", "globals.messages.errorDeleting": "删除 {name} 时出错:{error}", @@ -183,6 +184,7 @@ "globals.months.8": "八月", "globals.months.9": "九月", "globals.states.off": "关闭", + "globals.terms.all": "All", "globals.terms.analytics": "统计", "globals.terms.bounce": "反弹 | 多个反弹", "globals.terms.bounces": "反弹", @@ -202,6 +204,7 @@ "globals.terms.settings": "设置", "globals.terms.subscriber": "订阅者 | 多个订阅者", "globals.terms.subscribers": "订阅者", + "globals.terms.subscriptions": "Subscription | Subscriptions", "globals.terms.tag": "标签 | 多个标签", "globals.terms.tags": "标签", "globals.terms.template": "模板 | 多个模板", @@ -252,6 +255,11 @@ "lists.types.private": "私人的", "lists.types.public": "公开", "logs.title": "日志", + "maintenance.help": "Some actions may take a while to complete depending on the amount of data.", + "maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions", + "maintenance.olderThan": "Older than", + "maintenance.title": "Maintenance", + "maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.", "media.errorReadingFile": "读取文件时出错:{error}", "media.errorResizing": "调整图像大小时出错:{error}", "media.errorSavingThumbnail": "保存缩略图时出错:{error}", @@ -269,6 +277,7 @@ "menu.forms": "表格", "menu.import": "导入", "menu.logs": "日志", + "menu.maintenance": "Maintenance", "menu.media": "媒体", "menu.newCampaign": "创建新的", "menu.settings": "设置", diff --git a/internal/core/campaigns.go b/internal/core/campaigns.go index de744af7..9254e818 100644 --- a/internal/core/campaigns.go +++ b/internal/core/campaigns.go @@ -3,6 +3,7 @@ package core import ( "database/sql" "net/http" + "time" "github.com/gofrs/uuid" "github.com/jmoiron/sqlx" @@ -342,3 +343,23 @@ func (c *Core) RegisterCampaignLinkClick(linkUUID, campUUID, subUUID string) (st return url, nil } + +// DeleteCampaignViews deletes campaign views older than a given date. +func (c *Core) DeleteCampaignViews(before time.Time) error { + if _, err := c.q.DeleteCampaignViews.Exec(before); err != nil { + c.log.Printf("error deleting campaign views: %s", err) + return echo.NewHTTPError(http.StatusInternalServerError, c.i18n.Ts("public.errorProcessingRequest")) + } + + return nil +} + +// DeleteCampaignLinkClicks deletes campaign views older than a given date. +func (c *Core) DeleteCampaignLinkClicks(before time.Time) error { + if _, err := c.q.DeleteCampaignLinkClicks.Exec(before); err != nil { + c.log.Printf("error deleting campaign link clicks: %s", err) + return echo.NewHTTPError(http.StatusInternalServerError, c.i18n.Ts("public.errorProcessingRequest")) + } + + return nil +} diff --git a/internal/core/subscribers.go b/internal/core/subscribers.go index bb4eea8e..55138727 100644 --- a/internal/core/subscribers.go +++ b/internal/core/subscribers.go @@ -432,3 +432,29 @@ func (c *Core) DeleteSubscriberBounces(id int, uuid string) error { return nil } + +// DeleteOrphanSubscribers deletes orphan subscriber records (subscribers without lists). +func (c *Core) DeleteOrphanSubscribers() (int, error) { + res, err := c.q.DeleteOrphanSubscribers.Exec() + if err != nil { + c.log.Printf("error deleting orphan subscribers: %v", err) + return 0, echo.NewHTTPError(http.StatusInternalServerError, + c.i18n.Ts("globals.messages.errorDeleting", "name", "{globals.terms.subscribers}", "error", pqErrMsg(err))) + } + + n, _ := res.RowsAffected() + return int(n), nil +} + +// DeleteBlocklistedSubscribers deletes blocklisted subscribers. +func (c *Core) DeleteBlocklistedSubscribers() (int, error) { + res, err := c.q.DeleteBlocklistedSubscribers.Exec() + if err != nil { + c.log.Printf("error deleting blocklisted subscribers: %v", err) + return 0, echo.NewHTTPError(http.StatusInternalServerError, + c.i18n.Ts("globals.messages.errorDeleting", "name", "{globals.terms.subscribers}", "error", pqErrMsg(err))) + } + + n, _ := res.RowsAffected() + return int(n), nil +} diff --git a/internal/core/subscriptions.go b/internal/core/subscriptions.go index db2e4ced..35cbb22e 100644 --- a/internal/core/subscriptions.go +++ b/internal/core/subscriptions.go @@ -2,6 +2,7 @@ package core import ( "net/http" + "time" "github.com/labstack/echo/v4" "github.com/lib/pq" @@ -91,3 +92,17 @@ func (c *Core) UnsubscribeListsByQuery(query string, sourceListIDs, targetListID return nil } + +// DeleteUnconfirmedSubscriptions sets list subscriptions to 'ubsubscribed' by a given arbitrary query expression. +// sourceListIDs is the list of list IDs to filter the subscriber query with. +func (c *Core) DeleteUnconfirmedSubscriptions(beforeDate time.Time) (int, error) { + res, err := c.q.DeleteUnconfirmedSubscriptions.Exec(beforeDate) + if err != nil { + c.log.Printf("error deleting unconfirmed subscribers: %v", err) + return 0, echo.NewHTTPError(http.StatusInternalServerError, + c.i18n.Ts("globals.messages.errorDeleting", "name", "{globals.terms.subscribers}", "error", pqErrMsg(err))) + } + + n, _ := res.RowsAffected() + return int(n), nil +} diff --git a/models/queries.go b/models/queries.go index aeece0dc..bf88db4e 100644 --- a/models/queries.go +++ b/models/queries.go @@ -25,9 +25,12 @@ type Queries struct { BlocklistSubscribers *sqlx.Stmt `query:"blocklist-subscribers"` AddSubscribersToLists *sqlx.Stmt `query:"add-subscribers-to-lists"` DeleteSubscriptions *sqlx.Stmt `query:"delete-subscriptions"` + DeleteUnconfirmedSubscriptions *sqlx.Stmt `query:"delete-unconfirmed-subscriptions"` ConfirmSubscriptionOptin *sqlx.Stmt `query:"confirm-subscription-optin"` UnsubscribeSubscribersFromLists *sqlx.Stmt `query:"unsubscribe-subscribers-from-lists"` DeleteSubscribers *sqlx.Stmt `query:"delete-subscribers"` + DeleteBlocklistedSubscribers *sqlx.Stmt `query:"delete-blocklisted-subscribers"` + DeleteOrphanSubscribers *sqlx.Stmt `query:"delete-orphan-subscribers"` UnsubscribeByCampaign *sqlx.Stmt `query:"unsubscribe-by-campaign"` ExportSubscriberData *sqlx.Stmt `query:"export-subscriber-data"` @@ -65,6 +68,8 @@ type Queries struct { GetCampaignClickCounts *sqlx.Stmt `query:"get-campaign-click-counts"` GetCampaignLinkCounts *sqlx.Stmt `query:"get-campaign-link-counts"` GetCampaignBounceCounts *sqlx.Stmt `query:"get-campaign-bounce-counts"` + DeleteCampaignViews *sqlx.Stmt `query:"delete-campaign-views"` + DeleteCampaignLinkClicks *sqlx.Stmt `query:"delete-campaign-link-clicks"` NextCampaigns *sqlx.Stmt `query:"next-campaigns"` NextCampaignSubscribers *sqlx.Stmt `query:"next-campaign-subscribers"` diff --git a/queries.sql b/queries.sql index f780a31e..ef186a97 100644 --- a/queries.sql +++ b/queries.sql @@ -147,6 +147,13 @@ INSERT INTO subscriber_lists (subscriber_id, list_id, status) -- Delete one or more subscribers by ID or UUID. DELETE FROM subscribers WHERE CASE WHEN ARRAY_LENGTH($1::INT[], 1) > 0 THEN id = ANY($1) ELSE uuid = ANY($2::UUID[]) END; +-- name: delete-blocklisted-subscribers +DELETE FROM subscribers WHERE status = 'blocklisted'; + +-- name: delete-orphan-subscribers +DELETE FROM subscribers a WHERE NOT EXISTS + (SELECT 1 FROM subscriber_lists b WHERE b.subscriber_id = a.id); + -- name: blocklist-subscribers WITH b AS ( UPDATE subscribers SET status='blocklisted', updated_at=NOW() @@ -196,6 +203,13 @@ UPDATE subscriber_lists SET status = 'unsubscribed' WHERE -- If $3 is false, unsubscribe from the campaign's lists, otherwise all lists. CASE WHEN $3 IS FALSE THEN list_id = ANY(SELECT list_id FROM lists) ELSE list_id != 0 END; +-- name: delete-unconfirmed-subscriptions +WITH optins AS ( + SELECT id FROM lists WHERE optin = 'double' +) +DELETE FROM subscriber_lists + WHERE status = 'unconfirmed' AND list_id IN (SELECT id FROM optins) AND created_at < $1; + -- privacy -- name: export-subscriber-data WITH prof AS ( @@ -670,6 +684,12 @@ u AS ( ) SELECT * FROM subs; +-- name: delete-campaign-views +DELETE FROM campaign_views WHERE created_at < $1; + +-- name: delete-campaign-link-clicks +DELETE FROM link_clicks WHERE created_at < $1; + -- name: get-one-campaign-subscriber SELECT * FROM subscribers LEFT JOIN subscriber_lists ON (subscribers.id = subscriber_lists.subscriber_id AND subscriber_lists.status != 'unsubscribed')