diff --git a/cmd/subscribers.go b/cmd/subscribers.go index 7be8dc1b8..d7bb4ec30 100644 --- a/cmd/subscribers.go +++ b/cmd/subscribers.go @@ -116,7 +116,7 @@ func handleQuerySubscribers(c echo.Context) error { ) // Limit the subscribers to sepcific lists? - listIDs, err := getQueryListIDs(c.QueryParams()) + listIDs, err := getQueryInts("list_id", c.QueryParams()) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidID")) } @@ -199,7 +199,13 @@ func handleExportSubscribers(c echo.Context) error { ) // Limit the subscribers to sepcific lists? - listIDs, err := getQueryListIDs(c.QueryParams()) + listIDs, err := getQueryInts("list_id", c.QueryParams()) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidID")) + } + + // Export only specific subscriber IDs? + subIDs, err := getQueryInts("id", c.QueryParams()) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidID")) } @@ -222,7 +228,7 @@ func handleExportSubscribers(c echo.Context) error { } defer tx.Rollback() - if _, err := tx.Query(stmt, nil, 0, 1); err != nil { + if _, err := tx.Query(stmt, nil, 0, nil, 1); err != nil { return echo.NewHTTPError(http.StatusBadRequest, app.i18n.Ts("subscribers.errorPreparingQuery", "error", pqErrMsg(err))) } @@ -253,7 +259,7 @@ func handleExportSubscribers(c echo.Context) error { loop: for { var out []models.SubscriberExport - if err := tx.Select(&out, listIDs, id, app.constants.DBBatchSize); err != nil { + if err := tx.Select(&out, listIDs, id, subIDs, app.constants.DBBatchSize); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, app.i18n.Ts("globals.messages.errorFetching", "name", "{globals.terms.subscribers}", "error", pqErrMsg(err))) @@ -858,9 +864,9 @@ func sanitizeSQLExp(q string) string { return q } -func getQueryListIDs(qp url.Values) (pq.Int64Array, error) { +func getQueryInts(param string, qp url.Values) (pq.Int64Array, error) { out := pq.Int64Array{} - if vals, ok := qp["list_id"]; ok { + if vals, ok := qp[param]; ok { for _, v := range vals { if v == "" { continue diff --git a/frontend/cypress/integration/subscribers.js b/frontend/cypress/integration/subscribers.js index 10f94094a..63c6bd548 100644 --- a/frontend/cypress/integration/subscribers.js +++ b/frontend/cypress/integration/subscribers.js @@ -28,6 +28,27 @@ describe('Subscribers', () => { }); }); + it('Exports subscribers', () => { + const cases = [ + { + listIDs: [], ids: [], query: '', length: 3, + }, + { + listIDs: [], ids: [], query: "name ILIKE '%anon%'", length: 2, + }, + { + listIDs: [], ids: [], query: "name like 'nope'", length: 1, + }, + ]; + + // listIDs[] and ids[] are unused for now as Cypress doesn't support encoding of arrays in `qs`. + cases.forEach((c) => { + cy.request({ url: `${apiUrl}/api/subscribers/export`, qs: { query: c.query, list_id: c.listIDs, id: c.ids } }).then((resp) => { + cy.expect(resp.body.trim().split('\n')).to.have.lengthOf(c.length); + }); + }); + }); + it('Advanced searches subscribers', () => { cy.get('[data-cy=btn-advanced-search]').click(); @@ -253,24 +274,36 @@ describe('Domain blocklist', () => { // Add non-banned domain. cy.request({ - method: 'POST', url: `${apiUrl}/api/subscribers`, failOnStatusCode: true, - body: { email: 'test1@noban.net', 'name': 'test', 'lists': [1], 'status': 'enabled' } + method: 'POST', + url: `${apiUrl}/api/subscribers`, + failOnStatusCode: true, + body: { + email: 'test1@noban.net', name: 'test', lists: [1], status: 'enabled', + }, }).should((response) => { expect(response.status).to.equal(200); }); // Add banned domain. cy.request({ - method: 'POST', url: `${apiUrl}/api/subscribers`, failOnStatusCode: false, - body: { email: 'test1@ban.com', 'name': 'test', 'lists': [1], 'status': 'enabled' } + method: 'POST', + url: `${apiUrl}/api/subscribers`, + failOnStatusCode: false, + body: { + email: 'test1@ban.com', name: 'test', lists: [1], status: 'enabled', + }, }).should((response) => { expect(response.status).to.equal(400); }); // Modify an existinb subscriber to a banned domain. cy.request({ - method: 'PUT', url: `${apiUrl}/api/subscribers/1`, failOnStatusCode: false, - body: { email: 'test3@ban.org', 'name': 'test', 'lists': [1], 'status': 'enabled' } + method: 'PUT', + url: `${apiUrl}/api/subscribers/1`, + failOnStatusCode: false, + body: { + email: 'test3@ban.org', name: 'test', lists: [1], status: 'enabled', + }, }).should((response) => { expect(response.status).to.equal(400); }); @@ -305,16 +338,24 @@ describe('Domain blocklist', () => { // Add banned domain. cy.request({ - method: 'POST', url: `${apiUrl}/api/subscribers`, failOnStatusCode: true, - body: { email: 'test4@BAN.com', 'name': 'test', 'lists': [1], 'status': 'enabled' } + method: 'POST', + url: `${apiUrl}/api/subscribers`, + failOnStatusCode: true, + body: { + email: 'test4@BAN.com', name: 'test', lists: [1], status: 'enabled', + }, }).should((response) => { expect(response.status).to.equal(200); }); // Modify an existinb subscriber to a banned domain. cy.request({ - method: 'PUT', url: `${apiUrl}/api/subscribers/1`, failOnStatusCode: true, - body: { email: 'test4@BAN.org', 'name': 'test', 'lists': [1], 'status': 'enabled' } + method: 'PUT', + url: `${apiUrl}/api/subscribers/1`, + failOnStatusCode: true, + body: { + email: 'test4@BAN.org', name: 'test', lists: [1], status: 'enabled', + }, }).should((response) => { expect(response.status).to.equal(200); }); diff --git a/frontend/src/views/Subscribers.vue b/frontend/src/views/Subscribers.vue index e3f67dc76..dfb6bf1d0 100644 --- a/frontend/src/views/Subscribers.vue +++ b/frontend/src/views/Subscribers.vue @@ -82,7 +82,8 @@