diff --git a/cmd/archive.go b/cmd/archive.go index 16e17aca2..173dbf402 100644 --- a/cmd/archive.go +++ b/cmd/archive.go @@ -5,6 +5,7 @@ import ( "encoding/json" "html/template" "net/http" + "net/url" "github.com/gorilla/feeds" "github.com/knadh/listmonk/internal/manager" @@ -120,10 +121,19 @@ func handleCampaignArchivesPage(c echo.Context) error { func handleCampaignArchivePage(c echo.Context) error { var ( app = c.Get("app").(*App) - uuid = c.Param("uuid") + id = c.Param("id") + uuid = "" + slug = "" ) - pubCamp, err := app.core.GetArchivedCampaign(0, uuid) + // ID can be the UUID or slug. + if reUUID.MatchString(id) { + uuid = id + } else { + slug = id + } + + pubCamp, err := app.core.GetArchivedCampaign(0, uuid, slug) if err != nil || pubCamp.Type != models.CampaignTypeRegular { notFound := false if er, ok := err.(*echo.HTTPError); ok { @@ -202,7 +212,12 @@ func getCampaignArchives(offset, limit int, renderBody bool, app *App) ([]campAr Subject: camp.Subject, CreatedAt: camp.CreatedAt, SendAt: camp.SendAt, - URL: app.constants.ArchiveURL + "/" + camp.UUID, + } + + if camp.ArchiveSlug.Valid { + archive.URL, _ = url.JoinPath(app.constants.ArchiveURL, camp.ArchiveSlug.String) + } else { + archive.URL, _ = url.JoinPath(app.constants.ArchiveURL, camp.UUID) } if renderBody { diff --git a/cmd/campaigns.go b/cmd/campaigns.go index 5f640d70e..cabf6f862 100644 --- a/cmd/campaigns.go +++ b/cmd/campaigns.go @@ -16,6 +16,7 @@ import ( "github.com/knadh/listmonk/models" "github.com/labstack/echo/v4" "github.com/lib/pq" + "gopkg.in/volatiletech/null.v6" ) // campaignReq is a wrapper over the Campaign model for receiving @@ -48,6 +49,7 @@ type campaignContentReq struct { var ( regexFromAddress = regexp.MustCompile(`((.+?)\s)?<(.+?)@(.+?)>`) + regexSlug = regexp.MustCompile(`[^\p{L}\p{M}\p{N}]`) ) // handleGetCampaigns handles retrieval of campaigns. @@ -99,7 +101,7 @@ func handleGetCampaign(c echo.Context) error { noBody, _ = strconv.ParseBool(c.QueryParam("no_body")) ) - out, err := app.core.GetCampaign(id, "") + out, err := app.core.GetCampaign(id, "", "") if err != nil { return err } @@ -244,7 +246,7 @@ func handleUpdateCampaign(c echo.Context) error { } - cm, err := app.core.GetCampaign(id, "") + cm, err := app.core.GetCampaign(id, "", "") if err != nil { return err } @@ -314,9 +316,10 @@ func handleUpdateCampaignArchive(c echo.Context) error { ) req := struct { - Archive bool `json:"archive"` - TemplateID int `json:"archive_template_id"` - Meta models.JSON `json:"archive_meta"` + Archive bool `json:"archive"` + TemplateID int `json:"archive_template_id"` + Meta models.JSON `json:"archive_meta"` + ArchiveSlug string `json:"archive_slug"` }{} // Get and validate fields. @@ -324,11 +327,19 @@ func handleUpdateCampaignArchive(c echo.Context) error { return err } - if err := app.core.UpdateCampaignArchive(id, req.Archive, req.TemplateID, req.Meta); err != nil { + if req.ArchiveSlug != "" { + // Format the slug to be alpha-numeric-dash. + s := strings.ToLower(req.ArchiveSlug) + s = strings.TrimSpace(regexSlug.ReplaceAllString(s, " ")) + s = regexpSpaces.ReplaceAllString(s, "-") + req.ArchiveSlug = s + } + + if err := app.core.UpdateCampaignArchive(id, req.Archive, req.TemplateID, req.Meta, req.ArchiveSlug); err != nil { return err } - return c.JSON(http.StatusOK, okResp{true}) + return c.JSON(http.StatusOK, okResp{req}) } // handleDeleteCampaign handles campaign deletion. @@ -572,6 +583,18 @@ func validateCampaignFields(c campaignReq, app *App) (campaignReq, error) { c.ArchiveMeta = json.RawMessage("{}") } + if c.ArchiveSlug.String != "" { + // Format the slug to be alpha-numeric-dash. + s := strings.ToLower(c.ArchiveSlug.String) + s = strings.TrimSpace(regexSlug.ReplaceAllString(s, " ")) + s = regexpSpaces.ReplaceAllString(s, "-") + + c.ArchiveSlug = null.NewString(s, true) + } else { + // If there's no slug set, set it to NULL in the DB. + c.ArchiveSlug.Valid = false + } + return c, nil } diff --git a/cmd/handlers.go b/cmd/handlers.go index 9cc4f88b5..51b7fb771 100644 --- a/cmd/handlers.go +++ b/cmd/handlers.go @@ -206,7 +206,7 @@ func initHTTPHandlers(e *echo.Echo, app *App) { if app.constants.EnablePublicArchive { e.GET("/archive", handleCampaignArchivesPage) e.GET("/archive.xml", handleGetCampaignArchivesFeed) - e.GET("/archive/:uuid", handleCampaignArchivePage) + e.GET("/archive/:id", handleCampaignArchivePage) e.GET("/archive/latest", handleCampaignArchivePageLatest) } diff --git a/cmd/install.go b/cmd/install.go index 3dce05418..a4e7ac597 100644 --- a/cmd/install.go +++ b/cmd/install.go @@ -158,6 +158,7 @@ func install(lastVer string, db *sqlx.DB, fs stuffbin.FileSystem, prompt, idempo campTplID, pq.Int64Array{1}, false, + "welcome-to-listmonk", archiveTplID, `{"name": "Subscriber"}`, nil, diff --git a/cmd/manager_store.go b/cmd/manager_store.go index c5787a28b..c9e2d7a78 100644 --- a/cmd/manager_store.go +++ b/cmd/manager_store.go @@ -50,7 +50,7 @@ func (s *store) NextSubscribers(campID, limit int) ([]models.Subscriber, error) // GetCampaign fetches a campaign from the database. func (s *store) GetCampaign(campID int) (*models.Campaign, error) { var out = &models.Campaign{} - err := s.queries.GetCampaign.Get(out, campID, nil, "default") + err := s.queries.GetCampaign.Get(out, campID, nil, nil, "default") return out, err } diff --git a/cmd/public.go b/cmd/public.go index 9ed9228ba..cb2ddd329 100644 --- a/cmd/public.go +++ b/cmd/public.go @@ -143,7 +143,7 @@ func handleViewCampaignMessage(c echo.Context) error { ) // Get the campaign. - camp, err := app.core.GetCampaign(0, campUUID) + camp, err := app.core.GetCampaign(0, campUUID, "") if err != nil { if er, ok := err.(*echo.HTTPError); ok { if er.Code == http.StatusBadRequest { diff --git a/frontend/cypress/e2e/archive.cy.js b/frontend/cypress/e2e/archive.cy.js index be5c93603..20875ee18 100644 --- a/frontend/cypress/e2e/archive.cy.js +++ b/frontend/cypress/e2e/archive.cy.js @@ -13,40 +13,68 @@ describe('Archive', () => { cy.get('.modal input').clear().type('clone').click(); cy.get('.modal button.is-primary').click(); cy.wait(250); + + cy.loginAndVisit('/campaigns'); + cy.get('[data-cy=btn-clone]').first().click(); + cy.get('.modal input').clear().type('clone2').click(); + cy.get('.modal button.is-primary').click(); + cy.wait(250); + cy.clickMenu('all-campaigns'); }); - it('Starts un-archived campaign', () => { + it('Starts campaigns', () => { cy.get('td[data-label=Status] a').eq(0).click(); cy.get('[data-cy=btn-start]').click(); cy.get('.modal button.is-primary').click(); + + cy.get('td[data-label=Status] a').eq(1).click(); + cy.get('[data-cy=btn-start]').click(); + cy.get('.modal button.is-primary').click(); cy.wait(1000); }); - it('Enables archive on one campaign', () => { + it('Enables archive on one campaign (no slug)', () => { cy.loginAndVisit('/campaigns'); cy.wait(250); - cy.get('td[data-label=Status] a').eq(1).click(); + cy.get('td[data-label=Status] a').eq(0).click(); // Switch to archive tab and enable archive. cy.get('.b-tabs nav a').eq(2).click(); cy.wait(500); cy.get('[data-cy=btn-archive] .check').click(); + cy.get('[data-cy=archive-slug]').clear(); cy.get('[data-cy=archive-meta]').clear() - .type('{"email": "archive@domain.com", "name": "Archive", "attribs": { "city": "Bengaluru"}}', { 'parseSpecialCharSequences': false }); - - // Start the campaign. + .type('{"email": "archive@domain.com", "name": "Archive", "attribs": { "city": "Bengaluru"}}', { parseSpecialCharSequences: false }); cy.get('[data-cy=btn-save]').click(); + cy.wait(250); + }); + + it('Enables archive on one campaign', () => { + cy.loginAndVisit('/campaigns'); + cy.wait(250); + cy.get('td[data-label=Status] a').eq(1).click(); + + // Switch to archive tab and enable archive. + cy.get('.b-tabs nav a').eq(2).click(); cy.wait(500); - cy.get('[data-cy=btn-start]').click(); - cy.get('.modal button.is-primary').click(); - cy.wait(1000); + cy.get('[data-cy=btn-archive] .check').click(); + cy.get('[data-cy=archive-slug]').clear().type('my-archived-campaign'); + cy.get('[data-cy=btn-save]').click(); + cy.wait(250); }); it('Opens campaign archive page', () => { - cy.loginAndVisit(`${apiUrl}/archive`); - cy.get('li a').click(); - cy.get('h3').contains('Hi Archive!'); - cy.get('p').eq(0).contains('Bengaluru'); + for (let i = 0; i < 2; i++) { + cy.loginAndVisit(`${apiUrl}/archive`); + cy.get('li a').eq(i).click(); + cy.wait(250); + if (i === 0) { + cy.get('h3').contains('Hi Archive!'); + cy.get('p').eq(0).contains('Bengaluru'); + } else { + cy.get('h3').contains('Hi Subscriber!'); + } + } }); }); diff --git a/frontend/src/views/Campaign.vue b/frontend/src/views/Campaign.vue index df681ac42..16db43680 100644 --- a/frontend/src/views/Campaign.vue +++ b/frontend/src/views/Campaign.vue @@ -59,7 +59,7 @@
+ :placeholder="$t('globals.fields.name')" required autofocus /> @@ -201,19 +201,32 @@
- -
-
- -
-
- - - -
+
+
+ +
+
+ +
+
+ + + +
+
+
- +
+ + + {{ $t('globals.buttons.saveChanges') }} + + +
+
@@ -234,18 +247,18 @@ @click.prevent="onFillArchiveMeta">{}
+ + + + + - - - - {{ $t('globals.buttons.saveChanges') }} - -
@@ -295,6 +308,7 @@ export default Vue.extend({ // Binds form input values. form: { + archiveSlug: null, name: '', subject: '', fromEmail: '', @@ -470,6 +484,7 @@ export default Vue.extend({ createCampaign() { const data = { + archiveSlug: this.form.subject, name: this.form.name, subject: this.form.subject, lists: this.form.lists.map((l) => l.id), @@ -494,6 +509,7 @@ export default Vue.extend({ async updateCampaign(typ) { const data = { + archive_slug: this.form.archiveSlug, name: this.form.name, subject: this.form.subject, lists: this.form.lists.map((l) => l.id), @@ -523,6 +539,7 @@ export default Vue.extend({ return new Promise((resolve) => { this.$api.updateCampaign(this.data.id, data).then((d) => { this.data = d; + this.form.archiveSlug = d.archiveSlug; this.$utils.toast(this.$t(typMsg, { name: d.name })); resolve(); }); @@ -538,9 +555,12 @@ export default Vue.extend({ archive: this.form.archive, archive_template_id: this.form.archiveTemplateId, archive_meta: JSON.parse(this.form.archiveMetaStr), + archive_slug: this.form.archiveSlug, }; - this.$api.updateCampaignArchive(this.data.id, data); + this.$api.updateCampaignArchive(this.data.id, data).then((d) => { + this.form.archiveSlug = d.archiveSlug; + }); }, // Starts or schedule a campaign. diff --git a/frontend/src/views/Campaigns.vue b/frontend/src/views/Campaigns.vue index 439343bb2..dd82ba0ee 100644 --- a/frontend/src/views/Campaigns.vue +++ b/frontend/src/views/Campaigns.vue @@ -145,8 +145,10 @@

- + {{ stats.rate.toFixed(0) }} / {{ $t('campaigns.rateMinuteShort') }} @@ -225,8 +227,7 @@ placeholder: $t('globals.fields.name'), value: $t('campaigns.copyOf', { name: props.row.name }), }, - (name) => cloneCampaign(name, props.row))" data-cy="btn-clone" - :aria-label="$t('globals.buttons.clone')"> + (name) => cloneCampaign(name, props.row))" data-cy="btn-clone" :aria-label="$t('globals.buttons.clone')"> @@ -405,6 +406,7 @@ export default Vue.extend({ const data = { name, + archive_slug: `${c.name}-2`, subject: c.subject, lists: c.lists.map((l) => l.id), type: c.type, diff --git a/i18n/ca.json b/i18n/ca.json index 165a0a676..67fbd1a3b 100644 --- a/i18n/ca.json +++ b/i18n/ca.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "Publica (en curs, aturada, finalitzada) el missatge de campanya a l'arxiu públic ", "campaigns.archiveMeta": "Metadades de la campanya", "campaigns.archiveMetaHelp": "Dades del subscriptor de prova per ser usat en el missatge públic que inclou nom, correu electrònic i qualsevol atribut opcional emprat en el missatge de campanya o plantilla.", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "Adjunts", "campaigns.cantUpdate": "No es pot actualitzar una campanya en curs o ja finalitzada.", "campaigns.clicks": "Clics", diff --git a/i18n/cs-cz.json b/i18n/cs-cz.json index 33b239eb4..782f98cdf 100644 --- a/i18n/cs-cz.json +++ b/i18n/cs-cz.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "Zveřejnit (bežící, pozastavenou, dokončenou) zprávu kampaně ve veřejném archivu", "campaigns.archiveMeta": "Metadata kampaně", "campaigns.archiveMetaHelp": "Použít prázdná data přihlášených ve veřejné zpráve včetně jména, emailu a jiných volitelných atributů použitých ve zprávách kampaně nebo šablonách.", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "Přílohy", "campaigns.cantUpdate": "Nelze aktualizovat spuštěnou nebo dokončenou kampaň.", "campaigns.clicks": "Klepnutí", diff --git a/i18n/cy.json b/i18n/cy.json index 402bf0f25..4261c02dc 100644 --- a/i18n/cy.json +++ b/i18n/cy.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "Cyhoeddi neges yr ymgyrch (wrthi'n rhedeg", "campaigns.archiveMeta": "Ymgyrch metaddata", "campaigns.archiveMetaHelp": "Data tanysgrifiwr ffug i'w defnyddio yn y neges gyhoeddus", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "Atodiadau", "campaigns.cantUpdate": "Does dim modd diweddaru ymgyrch fyw neu ymgyrch sydd wedi dod i ben.", "campaigns.clicks": "Cliciau", diff --git a/i18n/da.json b/i18n/da.json index 14d9b41e4..8f0061b70 100644 --- a/i18n/da.json +++ b/i18n/da.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "Udgiv (kør, hold pause, afslut) kampagnebesked til det offentlige arkiv.", "campaigns.archiveMeta": "Kampagne metadata", "campaigns.archiveMetaHelp": "Dummy abonnent-data til brug i den offebntlige besked herunder navn, e-mail og enhver valgfri egenskab, der bruges i kampagebeskeden eller skabelonen.", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "Vedhæftninger", "campaigns.cantUpdate": "Kan ike opdatere en kørende eller afsluttet kampagne.", "campaigns.clicks": "Klik", diff --git a/i18n/de.json b/i18n/de.json index e8f43250d..cf6bb5683 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "Veröffentliche die Nachricht (laufende, pausierte, beendete) der Kampagne im öffentlichen Archiv.", "campaigns.archiveMeta": "Kampagne Metadaten", "campaigns.archiveMetaHelp": "Dummy-Abonnentendaten, die in der öffentlichen Nachricht verwendet werden sollen, einschließlich Name, E-Mail und alle optionalen Attribute, die in der Kampagnennachricht oder -vorlage verwendet werden.", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "Anhänge", "campaigns.cantUpdate": "Eine laufende oder abgeschlossene Kampagne kann nicht geändert werden.", "campaigns.clicks": "Klicks", diff --git a/i18n/el.json b/i18n/el.json index 7a1e1504b..504bcc335 100644 --- a/i18n/el.json +++ b/i18n/el.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "Δημοσιεύστε το μήνυμα της (σε εξέλιξη, σε παύση, ολοκληρωμένης) εκστρατείας στο δημόσιο αρχείο.", "campaigns.archiveMeta": "Μεταδεδομένα εκστρατείας", "campaigns.archiveMetaHelp": "Εικονικά δεδομένα συνδρομητή που χρησιμοποιούνται στο δημόσιο μήνυμα, συμπεριλαμβανομένου του ονόματος, της διεύθυνσης email και οποιωνδήποτε προαιρετικών χαρακτηριστικών που χρησιμοποιούνται στο μήνυμα ή το πρότυπο της εκστρατείας.", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "Συνημμένα", "campaigns.cantUpdate": "Δεν είναι δυνατή η ενημέρωση μιας εκστρατείας που βρίσκεται σε εξέλιξη ή έχει ολοκληρωθεί.", "campaigns.clicks": "Κλικ", diff --git a/i18n/en.json b/i18n/en.json index 64177158a..4bc559497 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "Publish (running, paused, finished) the campaign message on the public archive.", "campaigns.archiveMeta": "Campaign metadata", "campaigns.archiveMetaHelp": "Dummy subscriber data to use in the public message including name, email, and any optional attributes used in the campaign message or template.", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "Attachments", "campaigns.cantUpdate": "Cannot update a running or a finished campaign.", "campaigns.clicks": "Clicks", diff --git a/i18n/es.json b/i18n/es.json index f4c7f4e88..9f9598bb2 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "Publicar los mensajes de las campañas (en marcha, pausadas y terminadas) en el archivo público.", "campaigns.archiveMeta": "Metadata de la campaña", "campaigns.archiveMetaHelp": "Información de suscripción de ejemplo (por defecto) para ser usada en el mensaje público incluido nombre, correo electrónico, o cualquier valor accesible mediante atributos `{}` opcionales tanto en el mensaje de la campaña como en la plantilla.", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "Archivos adjuntos", "campaigns.cantUpdate": "No es posible actualizar una campaña iniciada o finalizada.", "campaigns.clicks": "Clics", diff --git a/i18n/fi.json b/i18n/fi.json index 8d9cb3afc..ae4adb0d7 100644 --- a/i18n/fi.json +++ b/i18n/fi.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "Julkaise (käynnissä, pausessa, valmis) kampanjaviesti julkisessa arkistossa.", "campaigns.archiveMeta": "Kampanjan metatiedot", "campaigns.archiveMetaHelp": "Tietuekuvioita voidaan käyttää julkisessa viestissä, joissa on mukana nimi, sähköposti ja kampanjaviestissä tai mallipohjassa käytetyt valinnaiset attribuutit.", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "Liitteet", "campaigns.cantUpdate": "Käynnissä olevaa tai päättynyttä kampanjaa ei voi päivittää.", "campaigns.clicks": "Klikkaukset", diff --git a/i18n/fr.json b/i18n/fr.json index 2341d3efa..e5f4a0976 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "Publier (en cours, en pause, terminé) le message de la campagne sur l'archive publique.", "campaigns.archiveMeta": "Métadonnées de la campagne", "campaigns.archiveMetaHelp": "Données d'abonné fictives à utiliser dans le message public, notamment le nom, l'adresse électronique et tout attribut facultatif utilisé dans le message ou le modèle de la campagne.", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "Pièces jointes", "campaigns.cantUpdate": "Impossible de mettre à jour une campagne en cours ou terminée.", "campaigns.clicks": "Clics", diff --git a/i18n/he.json b/i18n/he.json index 8b420cd17..003a80235 100644 --- a/i18n/he.json +++ b/i18n/he.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "פרסם (פועל, מושהה, הושלם) את הודעת הקמפיין בארכיון הציבורי.", "campaigns.archiveMeta": "מטא-נתונים של קמפיין", "campaigns.archiveMetaHelp": "נתוני חבוי של המנויים לשימוש בהודעה ציבורית כולל שם, דואר אלקטרוני, וכל מאפיינים אופציונליים שבשימוש בהודעת הקמפיין או התבנית.", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "קבצים מצורפים", "campaigns.cantUpdate": "לא ניתן לעדכן קמפיין בריצה או שהושלם.", "campaigns.clicks": "לחיצות", diff --git a/i18n/hu.json b/i18n/hu.json index 05d5a67db..1b85da6ac 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "A kampány nyilvános archívumba mentése, közzététele.", "campaigns.archiveMeta": "Kapány metaadat", "campaigns.archiveMetaHelp": "A nyilvánosan közzétett kampányüzenetbe helyettesítendő adatok (pl. név, e-mail cím, és amiket a sablon használ).", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "Mellékletek", "campaigns.cantUpdate": "Nem lehet frissíteni futó vagy befejezett kampányt.", "campaigns.clicks": "Kattintások", diff --git a/i18n/it.json b/i18n/it.json index af3a55e12..77d46a846 100644 --- a/i18n/it.json +++ b/i18n/it.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "Pubblicare i messaggi delle campagne (avviate, pausate, finite) nel archivio pubblico.", "campaigns.archiveMeta": "Metadati della campagna", "campaigns.archiveMetaHelp": "Dati fittizi dell'iscritto da utilizzare nel messaggio pubblico, inclusi nome, e-mail ed eventuali attributi facoltativi utilizzati nel messaggio o nel modello della campagna.", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "Allegati", "campaigns.cantUpdate": "Impossibile aggiornare una campagna in corso o già effettuata.", "campaigns.clicks": "Click", diff --git a/i18n/jp.json b/i18n/jp.json index c012e4c8c..4fe1c6196 100644 --- a/i18n/jp.json +++ b/i18n/jp.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "公開アーカイブにキャンペーンメッセージを発行(実行中, 停止された, 終わりましたキャンペーン全部含めて)。", "campaigns.archiveMeta": "キャンペーンメタデータ", "campaigns.archiveMetaHelp": "キャンペーンのメッセージやテンプレートに使う偽データ(名やメールアドレスや設定)。", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "添付ファイル", "campaigns.cantUpdate": "実行中又は終了しているキャンペーンの更新はできません。", "campaigns.clicks": "クリック", diff --git a/i18n/ml.json b/i18n/ml.json index f7bdc9934..00cd19954 100644 --- a/i18n/ml.json +++ b/i18n/ml.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "പ്രചാരണ സന്ദേശം (റൺ ചെയ്യുന്ന, താൽക്കാലികമായി നിർത്തിയ, പൂർത്തിയായ) പൊതു ആർക്കൈവിൽ പ്രസിദ്ധീകരിക്കുക.", "campaigns.archiveMeta": "കാമ്പെയ്‌ൻ മെറ്റാഡാറ്റ", "campaigns.archiveMetaHelp": "പേര്, ഇമെയിൽ, പ്രചാരണ സന്ദേശത്തിലോ ടെംപ്ലേറ്റിലോ ഉപയോഗിക്കുന്ന ഏതെങ്കിലും ഓപ്ഷണൽ ആട്രിബ്യൂട്ടുകൾ എന്നിവയുൾപ്പെടെ പൊതു സന്ദേശത്തിൽ ഉപയോഗിക്കാനുള്ള ഡമ്മി സബ്സ്ക്രൈബർ ഡാറ്റ.", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "അറ്റാച്ച്മെന്റ്സ്", "campaigns.cantUpdate": "ഇപ്പോൾ നടന്നുകൊണ്ടിരിയ്ക്കുന്നതോ, അവസാനിച്ചതോ ആയ ക്യാമ്പേയ്ൻ പുതുക്കാനാകില്ല.", "campaigns.clicks": "ക്ലീക്കുകൾ", diff --git a/i18n/nl.json b/i18n/nl.json index 4c852b192..94160e84c 100644 --- a/i18n/nl.json +++ b/i18n/nl.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "Publiceer (lopende, gepauzeerde, afgeronde) het campange bericht naar het publiek archief.", "campaigns.archiveMeta": "Campagne metadata", "campaigns.archiveMetaHelp": "Dummy-abonneegegevens om te gebruiken in het openbare bericht, inclusief naam, e-mail en eventuele optionele attributen die worden gebruikt in het campagnebericht of de sjabloon.", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "Bijlagen", "campaigns.cantUpdate": "Kan een lopende of afgelopen campagne niet updaten.", "campaigns.clicks": "Kliks", diff --git a/i18n/pl.json b/i18n/pl.json index 686b0db77..e27ec69d9 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "Opublikuj (w trakcie, zatrzymane, zakończone) treść kampanii do publicznego archiwum.", "campaigns.archiveMeta": "Metadane kampanii", "campaigns.archiveMetaHelp": "Dane podstawione subskrybenta do użycia w publicznym archiwum. W tym nazwa, email, i dowolne opcjonalne atrybuty użyte w szablonie kampanii.", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "Załączniki", "campaigns.cantUpdate": "Nie można aktualizować aktywnej ani zakończonej kampanii", "campaigns.clicks": "Kliknięcia", diff --git a/i18n/pt-BR.json b/i18n/pt-BR.json index 07ee75230..3d5fca3c1 100644 --- a/i18n/pt-BR.json +++ b/i18n/pt-BR.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "Publicar (executando, pausada, finalizada) a mensagem da campanha no arquivo publico.", "campaigns.archiveMeta": "Metadados da campanha", "campaigns.archiveMetaHelp": "Dados de assinante fictício para utilizar na mensagem publica incluindo nome, email e qualquer atributo opcional usado na mensagem ou template da campanha.", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "Anexos", "campaigns.cantUpdate": "Não é possível atualizar uma campanha em execução ou finalizada.", "campaigns.clicks": "Cliques", diff --git a/i18n/pt.json b/i18n/pt.json index 020ee9bc7..55d98d407 100644 --- a/i18n/pt.json +++ b/i18n/pt.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "Publicar (em execução, em pausa e terminadas) as mensagens da campanha no arquivo público.", "campaigns.archiveMeta": "Metadados da campanha", "campaigns.archiveMetaHelp": "Dados do subscritor modelo a usar em mensagens públicas, tais como nome, email e quais quer outros atributos opcionais usados na mensagem ou template da campanha.", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "Anexos", "campaigns.cantUpdate": "Não é possível atualizar uma campanha em curso ou terminada.", "campaigns.clicks": "Cliques", diff --git a/i18n/ro.json b/i18n/ro.json index d9dcd815d..2e367f304 100644 --- a/i18n/ro.json +++ b/i18n/ro.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "Publicați (rulând, întrerupt, terminat) mesajul campaniei în arhiva publică.", "campaigns.archiveMeta": "Metadatele campaniei", "campaigns.archiveMetaHelp": "Datele abonaților inactivi de utilizat în mesajul public, inclusiv numele, e-mailul și orice atribute opționale utilizate în mesajul sau șablonul campaniei.", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "Fișiere atașate", "campaigns.cantUpdate": "Nu se poate actualiza o campanie care rulează sau s-a terminat.", "campaigns.clicks": "Click-uri", diff --git a/i18n/ru.json b/i18n/ru.json index a5102de39..3f123c11f 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "Опубликовать (запущено, на паузе, завершено) сообщение кампании в общедоступном архиве.", "campaigns.archiveMeta": "Метаданные кампании", "campaigns.archiveMetaHelp": "Данные фиктивных подписчиков для использования в публичном сообщении, включая имя, электронную почту и любые дополнительные атрибуты, используемые в сообщении или шаблоне кампании.", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "Вложения", "campaigns.cantUpdate": "Не возможно обновить запущенную или завершённую кампанию.", "campaigns.clicks": "Клики", diff --git a/i18n/se.json b/i18n/se.json index 487b00988..3b36a1421 100644 --- a/i18n/se.json +++ b/i18n/se.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "Publish (running, paused, finished) the campaign message on the public archive.", "campaigns.archiveMeta": "Campaign metadata", "campaigns.archiveMetaHelp": "Dummy subscriber data to use in the public message including name, email, and any optional attributes used in the campaign message or template.", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "Attachments", "campaigns.cantUpdate": "Cannot update a running or a finished campaign.", "campaigns.clicks": "Clicks", diff --git a/i18n/sk.json b/i18n/sk.json index 4d91654b9..0e07d2015 100644 --- a/i18n/sk.json +++ b/i18n/sk.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "Zverejniť (prebiehajúcu, pozastavenú, dokončenú) správu kampane vo verejnom archíve", "campaigns.archiveMeta": "Metadáta kampane", "campaigns.archiveMetaHelp": "Použíť prázdne dáta prihlásených vo verejnom archíve vrátane mena, emailu a iných voliteľných atribútov použitých v správach kampane aleebo šablónach.", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "Prílohy", "campaigns.cantUpdate": "Nedá sa aktualizovať spustená alebo dokončená kampaň.", "campaigns.clicks": "Kliknutia", diff --git a/i18n/tr.json b/i18n/tr.json index 1c235d3b0..58691c075 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "Kampanya mesajını genel arşivde yayınlayın (çalışıyor, duraklatıldı, bitti).", "campaigns.archiveMeta": "Kampanya meta verisi", "campaigns.archiveMetaHelp": "Ad, e-posta ve kampanya mesajında veya şablonunda kullanılan tüm isteğe bağlı öznitelikler dahil olmak üzere genel mesajda kullanılacak kukla abone verileri.", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "Ekler", "campaigns.cantUpdate": "Gönderilmekte olan veya gönderilmiş kampaynalar güncellenemez.", "campaigns.clicks": "Tıklama", diff --git a/i18n/uk.json b/i18n/uk.json index 98a6c8e3c..411dbbefe 100644 --- a/i18n/uk.json +++ b/i18n/uk.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "Розмістити лист кампанії (запущеної, призупиненої, завершеної) в загальнодоступному архіві.", "campaigns.archiveMeta": "Метадані кампанії", "campaigns.archiveMetaHelp": "Дані вигаданої підписни_ці для використання в загальнодоступному листі, зокрема ім'я (name), е-пошта (email) та будь-які необов'язкові атрибути, використані в листі чи шаблоні кампанії.", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "Вкладення", "campaigns.cantUpdate": "Неможливо оновити запущену чи завершену кампанію.", "campaigns.clicks": "Переходи", diff --git a/i18n/vi.json b/i18n/vi.json index 4131fbfc1..b04619755 100644 --- a/i18n/vi.json +++ b/i18n/vi.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "Xuất bản (đang chạy, tạm dừng, hoàn thành) tin nhắn chiến dịch vào lưu trữ công khai.", "campaigns.archiveMeta": "Dữ liệu siêu của chiến dịch", "campaigns.archiveMetaHelp": "Dữ liệu giả của người đăng ký để sử dụng trong tin nhắn công khai bao gồm tên, email và bất kỳ thuộc tính tùy chọn nào được sử dụng trong tin nhắn chiến dịch hoặc mẫu.", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "Tệp đính kèm", "campaigns.cantUpdate": "Không thể cập nhật chiến dịch đang chạy hoặc đã kết thúc.", "campaigns.clicks": "Số lần nhấp chuột", diff --git a/i18n/zh-CN.json b/i18n/zh-CN.json index 6c43a79b0..25c2caf5a 100644 --- a/i18n/zh-CN.json +++ b/i18n/zh-CN.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "在公共档案中发布(运行、暂停、完成)活动消息。", "campaigns.archiveMeta": "活动元数据", "campaigns.archiveMetaHelp": "在公共消息中使用的模拟订阅者数据,包括姓名、电子邮件以及活动消息或模板中使用的任何可选属性。", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "附件", "campaigns.cantUpdate": "无法更新正在运行或已完成的广告系列。", "campaigns.clicks": "点击次数", diff --git a/i18n/zh-TW.json b/i18n/zh-TW.json index 6711611cf..2c5d42b79 100644 --- a/i18n/zh-TW.json +++ b/i18n/zh-TW.json @@ -23,6 +23,8 @@ "campaigns.archiveHelp": "在公開歸檔中發送(運行中、暫停、已完成)的活動訊息。", "campaigns.archiveMeta": "活動中繼資料", "campaigns.archiveMetaHelp": "用於公開訊息的虛擬訂閱者資料,包括姓名、電子郵件和任何在活動訊息或範本中使用的選擇性屬性。", + "campaigns.archiveSlug": "URL Slug", + "campaigns.archiveSlugHelp": "A short name for the page to be used in the public URL. eg: my-newsletter-edition-2", "campaigns.attachments": "附件", "campaigns.cantUpdate": "無法更新正在運行或已完成的廣告系列。", "campaigns.clicks": "點擊次數", diff --git a/internal/core/campaigns.go b/internal/core/campaigns.go index 2ced81197..3e42f5744 100644 --- a/internal/core/campaigns.go +++ b/internal/core/campaigns.go @@ -65,13 +65,13 @@ func (c *Core) QueryCampaigns(searchStr string, statuses, tags []string, orderBy } // GetCampaign retrieves a campaign. -func (c *Core) GetCampaign(id int, uuid string) (models.Campaign, error) { - return c.getCampaign(id, uuid, campaignTplDefault) +func (c *Core) GetCampaign(id int, uuid, archiveSlug string) (models.Campaign, error) { + return c.getCampaign(id, uuid, archiveSlug, campaignTplDefault) } // GetArchivedCampaign retrieves a campaign with the archive template body. -func (c *Core) GetArchivedCampaign(id int, uuid string) (models.Campaign, error) { - out, err := c.getCampaign(id, uuid, campaignTplArchive) +func (c *Core) GetArchivedCampaign(id int, uuid, archiveSlug string) (models.Campaign, error) { + out, err := c.getCampaign(id, uuid, archiveSlug, campaignTplArchive) if err != nil { return out, err } @@ -87,7 +87,7 @@ func (c *Core) GetArchivedCampaign(id int, uuid string) (models.Campaign, error) // getCampaign retrieves a campaign. If typlType=default, then the campaign's // template body is returned as "template_body". If tplType="archive", // the archive template is returned. -func (c *Core) getCampaign(id int, uuid string, tplType string) (models.Campaign, error) { +func (c *Core) getCampaign(id int, uuid, archiveSlug string, tplType string) (models.Campaign, error) { // Unsafe to ignore scanning fields not present in models.Campaigns. var uu interface{} if uuid != "" { @@ -95,7 +95,7 @@ func (c *Core) getCampaign(id int, uuid string, tplType string) (models.Campaign } var out models.Campaigns - if err := c.q.GetCampaign.Select(&out, id, uu, tplType); err != nil { + if err := c.q.GetCampaign.Select(&out, id, uu, archiveSlug, tplType); err != nil { // if err := c.db.Select(&out, stmt, 0, pq.Array([]string{}), queryStr, 0, 1); err != nil { c.log.Printf("error fetching campaign: %v", err) return models.Campaign{}, echo.NewHTTPError(http.StatusInternalServerError, @@ -185,6 +185,7 @@ func (c *Core) CreateCampaign(o models.Campaign, listIDs []int, mediaIDs []int) o.TemplateID, pq.Array(listIDs), o.Archive, + o.ArchiveSlug, o.ArchiveTemplateID, o.ArchiveMeta, pq.Array(mediaIDs), @@ -198,7 +199,7 @@ func (c *Core) CreateCampaign(o models.Campaign, listIDs []int, mediaIDs []int) c.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.campaign}", "error", pqErrMsg(err))) } - out, err := c.GetCampaign(newID, "") + out, err := c.GetCampaign(newID, "", "") if err != nil { return models.Campaign{}, err } @@ -223,6 +224,7 @@ func (c *Core) UpdateCampaign(id int, o models.Campaign, listIDs []int, mediaIDs o.TemplateID, pq.Array(listIDs), o.Archive, + o.ArchiveSlug, o.ArchiveTemplateID, o.ArchiveMeta, pq.Array(mediaIDs)) @@ -232,7 +234,7 @@ func (c *Core) UpdateCampaign(id int, o models.Campaign, listIDs []int, mediaIDs c.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.campaign}", "error", pqErrMsg(err))) } - out, err := c.GetCampaign(id, "") + out, err := c.GetCampaign(id, "", "") if err != nil { return models.Campaign{}, err } @@ -242,7 +244,7 @@ func (c *Core) UpdateCampaign(id int, o models.Campaign, listIDs []int, mediaIDs // UpdateCampaignStatus updates a campaign's status, eg: draft to running. func (c *Core) UpdateCampaignStatus(id int, status string) (models.Campaign, error) { - cm, err := c.GetCampaign(id, "") + cm, err := c.GetCampaign(id, "", "") if err != nil { return models.Campaign{}, err } @@ -297,8 +299,8 @@ func (c *Core) UpdateCampaignStatus(id int, status string) (models.Campaign, err } // UpdateCampaignArchive updates a campaign's archive properties. -func (c *Core) UpdateCampaignArchive(id int, enabled bool, tplID int, meta models.JSON) error { - if _, err := c.q.UpdateCampaignArchive.Exec(id, enabled, tplID, meta); err != nil { +func (c *Core) UpdateCampaignArchive(id int, enabled bool, tplID int, meta models.JSON, archiveSlug string) error { + if _, err := c.q.UpdateCampaignArchive.Exec(id, enabled, archiveSlug, tplID, meta); err != nil { c.log.Printf("error updating campaign: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, diff --git a/internal/manager/pipe.go b/internal/manager/pipe.go index d97b9f592..0ab3b16f7 100644 --- a/internal/manager/pipe.go +++ b/internal/manager/pipe.go @@ -204,7 +204,7 @@ func (p *pipe) cleanup() { // Fetch the up-to-date campaign status from the DB. c, err := p.m.store.GetCampaign(p.camp.ID) if err != nil { - p.m.log.Printf("error fetching campaign (%s) for ending", p.camp.Name) + p.m.log.Printf("error fetching campaign (%s) for ending: %v", p.camp.Name, err) return } diff --git a/internal/migrations/v3.0.0.go b/internal/migrations/v3.0.0.go index 0f2092241..58fcf519e 100644 --- a/internal/migrations/v3.0.0.go +++ b/internal/migrations/v3.0.0.go @@ -18,5 +18,9 @@ func V3_0_0(db *sqlx.DB, fs stuffbin.FileSystem, ko *koanf.Koanf) error { return err } + if _, err := db.Exec(`ALTER TABLE campaigns ADD COLUMN IF NOT EXISTS archive_slug TEXT NULL UNIQUE`); err != nil { + return err + } + return nil } diff --git a/models/models.go b/models/models.go index 4368db28b..c051328ae 100644 --- a/models/models.go +++ b/models/models.go @@ -252,6 +252,7 @@ type Campaign struct { TemplateID int `db:"template_id" json:"template_id"` Messenger string `db:"messenger" json:"messenger"` Archive bool `db:"archive" json:"archive"` + ArchiveSlug null.String `db:"archive_slug" json:"archive_slug"` ArchiveTemplateID int `db:"archive_template_id" json:"archive_template_id"` ArchiveMeta json.RawMessage `db:"archive_meta" json:"archive_meta"` diff --git a/queries.sql b/queries.sql index a5fd1f28a..973f291b0 100644 --- a/queries.sql +++ b/queries.sql @@ -492,16 +492,16 @@ counts AS ( AND subscribers.status='enabled' ), camp AS ( - INSERT INTO campaigns (uuid, type, name, subject, from_email, body, altbody, content_type, send_at, headers, tags, messenger, template_id, to_send, max_subscriber_id, archive, archive_template_id, archive_meta) + INSERT INTO campaigns (uuid, type, name, subject, from_email, body, altbody, content_type, send_at, headers, tags, messenger, template_id, to_send, max_subscriber_id, archive, archive_slug, archive_template_id, archive_meta) SELECT $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, (SELECT id FROM tpl), (SELECT to_send FROM counts), - (SELECT max_sub_id FROM counts), $15, - (CASE WHEN $16 = 0 THEN (SELECT id FROM tpl) ELSE $16 END), $17 + (SELECT max_sub_id FROM counts), $15, $16, + (CASE WHEN $17 = 0 THEN (SELECT id FROM tpl) ELSE $17 END), $18 RETURNING id ), med AS ( INSERT INTO campaign_media (campaign_id, media_id, filename) - (SELECT (SELECT id FROM camp), id, filename FROM media WHERE id=ANY($18::INT[])) + (SELECT (SELECT id FROM camp), id, filename FROM media WHERE id=ANY($19::INT[])) ) INSERT INTO campaign_lists (campaign_id, list_id, list_name) (SELECT (SELECT id FROM camp), id, name FROM lists WHERE id=ANY($14::INT[])) @@ -517,7 +517,7 @@ INSERT INTO campaign_lists (campaign_id, list_id, list_name) SELECT c.id, c.uuid, c.name, c.subject, c.from_email, c.messenger, c.started_at, c.to_send, c.sent, c.type, c.body, c.altbody, c.send_at, c.headers, c.status, c.content_type, c.tags, - c.template_id, c.archive, c.archive_template_id, c.archive_meta, + c.template_id, c.archive, c.archive_slug, c.archive_template_id, c.archive_meta, c.created_at, c.updated_at, COUNT(*) OVER () AS total, ( @@ -539,10 +539,14 @@ SELECT campaigns.*, COALESCE(templates.body, (SELECT body FROM templates WHERE is_default = true LIMIT 1)) AS template_body FROM campaigns LEFT JOIN templates ON ( - CASE WHEN $3 = 'default' THEN templates.id = campaigns.template_id + CASE WHEN $4 = 'default' THEN templates.id = campaigns.template_id ELSE templates.id = campaigns.archive_template_id END ) - WHERE CASE WHEN $1 > 0 THEN campaigns.id = $1 ELSE uuid = $2 END; + WHERE CASE + WHEN $1 > 0 THEN campaigns.id = $1 + WHEN $3 != '' THEN campaigns.archive_slug = $3 + ELSE uuid = $2 + END; -- name: get-archived-campaigns SELECT COUNT(*) OVER () AS total, campaigns.*, @@ -808,8 +812,9 @@ WITH camp AS ( messenger=$12, template_id=$13, archive=$15, - archive_template_id=$16, - archive_meta=$17, + archive_slug=$16, + archive_template_id=$17, + archive_meta=$18, updated_at=NOW() WHERE id = $1 RETURNING id ), @@ -819,11 +824,11 @@ clists AS ( ), med AS ( DELETE FROM campaign_media WHERE campaign_id = $1 - AND media_id IS NULL or NOT(media_id = ANY($18)) RETURNING media_id + AND media_id IS NULL or NOT(media_id = ANY($19)) RETURNING media_id ), medi AS ( INSERT INTO campaign_media (campaign_id, media_id, filename) - (SELECT $1 AS campaign_id, id, filename FROM media WHERE id=ANY($18::INT[])) + (SELECT $1 AS campaign_id, id, filename FROM media WHERE id=ANY($19::INT[])) ON CONFLICT (campaign_id, media_id) DO NOTHING ) INSERT INTO campaign_lists (campaign_id, list_id, list_name) @@ -844,8 +849,9 @@ UPDATE campaigns SET status=$2, updated_at=NOW() WHERE id = $1; -- name: update-campaign-archive UPDATE campaigns SET archive=$2, - archive_template_id=(CASE WHEN $3 > 0 THEN $3 ELSE archive_template_id END), - archive_meta=(CASE WHEN $4::TEXT != '' THEN $4::JSONB ELSE archive_meta END), + archive_slug=(CASE WHEN $3::TEXT = '' THEN NULL ELSE $3 END), + archive_template_id=(CASE WHEN $4 > 0 THEN $4 ELSE archive_template_id END), + archive_meta=(CASE WHEN $5::TEXT != '' THEN $5::JSONB ELSE archive_meta END), updated_at=NOW() WHERE id=$1; diff --git a/schema.sql b/schema.sql index c4bc63828..9077e45dc 100644 --- a/schema.sql +++ b/schema.sql @@ -103,6 +103,7 @@ CREATE TABLE campaigns ( -- Publishing. archive BOOLEAN NOT NULL DEFAULT false, + archive_slug TEXT NULL UNIQUE, archive_template_id INTEGER REFERENCES templates(id) ON DELETE SET DEFAULT DEFAULT 1, archive_meta JSONB NOT NULL DEFAULT '{}',