@stamhoofd/email 2.60.0 → 2.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  import { Model } from '@simonbackx/simple-database';
2
+ import { SQLSelect } from '@stamhoofd/sql';
2
3
  import { EmailInterfaceRecipient } from '../classes/Email';
3
4
  export declare class EmailAddress extends Model {
4
5
  static table: string;
@@ -18,6 +19,14 @@ export declare class EmailAddress extends Model {
18
19
  static getOrCreate(email: string, organizationId: string | null): Promise<EmailAddress>;
19
20
  static getByEmails(emails: string[], organizationId: string | null): Promise<EmailAddress[]>;
20
21
  static getByEmail(email: string, organizationId: string | null): Promise<EmailAddress | undefined>;
22
+ /**
23
+ * Search organization wide if this email has been marked as spam or hard bounced
24
+ */
25
+ static getWhereHardBounceOrSpam(email: string): Promise<EmailAddress | null>;
21
26
  static filterSendTo(recipients: EmailInterfaceRecipient[]): Promise<EmailInterfaceRecipient[]>;
27
+ /**
28
+ * Experimental: needs to move to library
29
+ */
30
+ static select(): SQLSelect<EmailAddress>;
22
31
  }
23
32
  //# sourceMappingURL=EmailAddress.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"EmailAddress.d.ts","sourceRoot":"","sources":["../../src/models/EmailAddress.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,EAAE,MAAM,6BAA6B,CAAC;AAItE,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAc3D,qBAAa,YAAa,SAAQ,KAAK;IACnC,MAAM,CAAC,KAAK,SAAqB;IAOjC,EAAE,EAAG,MAAM,CAAC;IAGZ,cAAc,EAAE,MAAM,GAAG,IAAI,CAAQ;IAIrC,KAAK,EAAE,MAAM,CAAC;IAGd,YAAY,UAAS;IAGrB,UAAU,UAAS;IAGnB,qBAAqB,UAAS;IAG9B,eAAe,UAAS;IAGxB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAErB;;OAEG;IAQH,SAAS,EAAE,IAAI,CAAA;IASf,SAAS,EAAE,IAAI,CAAA;WAEF,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,YAAY,CAAC;WAoBhF,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;WA6BrF,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;WAK3F,YAAY,CAAC,UAAU,EAAE,uBAAuB,EAAE,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC;CAkBvG"}
1
+ {"version":3,"file":"EmailAddress.d.ts","sourceRoot":"","sources":["../../src/models/EmailAddress.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,EAA0B,MAAM,6BAA6B,CAAC;AAE9F,OAAO,EAAO,SAAS,EAAY,MAAM,gBAAgB,CAAC;AAG1D,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAc3D,qBAAa,YAAa,SAAQ,KAAK;IACnC,MAAM,CAAC,KAAK,SAAqB;IAOjC,EAAE,EAAG,MAAM,CAAC;IAGZ,cAAc,EAAE,MAAM,GAAG,IAAI,CAAQ;IAIrC,KAAK,EAAE,MAAM,CAAC;IAGd,YAAY,UAAS;IAGrB,UAAU,UAAS;IAGnB,qBAAqB,UAAS;IAG9B,eAAe,UAAS;IAGxB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAErB;;OAEG;IAQH,SAAS,EAAE,IAAI,CAAC;IAShB,SAAS,EAAE,IAAI,CAAC;WAEH,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,YAAY,CAAC;WAoBhF,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;WA6BrF,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;IAIxG;;OAEG;WACU,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;WAUrE,YAAY,CAAC,UAAU,EAAE,uBAAuB,EAAE,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC;IAmBpG;;OAEG;IACH,MAAM,CAAC,MAAM;CAchB"}
@@ -4,6 +4,7 @@ exports.EmailAddress = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const simple_database_1 = require("@simonbackx/simple-database");
6
6
  const queues_1 = require("@stamhoofd/queues");
7
+ const sql_1 = require("@stamhoofd/sql");
7
8
  const crypto_1 = tslib_1.__importDefault(require("crypto"));
8
9
  const uuid_1 = require("uuid");
9
10
  async function randomBytes(size) {
@@ -18,7 +19,7 @@ async function randomBytes(size) {
18
19
  });
19
20
  }
20
21
  class EmailAddress extends simple_database_1.Model {
21
- static table = "email_addresses";
22
+ static table = 'email_addresses';
22
23
  id;
23
24
  organizationId = null;
24
25
  // Columns
@@ -35,7 +36,7 @@ class EmailAddress extends simple_database_1.Model {
35
36
  updatedAt;
36
37
  static async getOrCreate(email, organizationId) {
37
38
  // Prevent race conditions when checking the same email address at the same time, and creating a new one
38
- return await queues_1.QueueHandler.schedule("email-address/create-" + email + '-' + organizationId, async () => {
39
+ return await queues_1.QueueHandler.schedule('email-address/create-' + email + '-' + organizationId, async () => {
39
40
  const existing = await this.getByEmail(email, organizationId);
40
41
  if (existing) {
41
42
  return existing;
@@ -43,7 +44,7 @@ class EmailAddress extends simple_database_1.Model {
43
44
  const n = new EmailAddress();
44
45
  n.organizationId = organizationId;
45
46
  n.email = email;
46
- n.token = (await randomBytes(64)).toString("base64").toUpperCase();
47
+ n.token = (await randomBytes(64)).toString('base64').toUpperCase();
47
48
  await n.save();
48
49
  return n;
49
50
  });
@@ -69,65 +70,86 @@ class EmailAddress extends simple_database_1.Model {
69
70
  static async getByEmail(email, organizationId) {
70
71
  return (await this.where({ email, organizationId }, { limit: 1 }))[0];
71
72
  }
73
+ /**
74
+ * Search organization wide if this email has been marked as spam or hard bounced
75
+ */
76
+ static async getWhereHardBounceOrSpam(email) {
77
+ return await this.select().where('email', email).where(sql_1.SQL.where('hardBounce', 1)
78
+ .or('markedAsSpam', 1)).first(false);
79
+ }
72
80
  // Methods
73
81
  static async filterSendTo(recipients) {
74
- if (recipients.length == 0) {
82
+ if (recipients.length === 0) {
75
83
  return [];
76
84
  }
77
85
  const emails = recipients.map(r => r.email);
78
86
  const [results] = await simple_database_1.Database.select(`SELECT email FROM ${this.table} WHERE \`email\` IN (?) AND (\`hardBounce\` = 1 OR \`markedAsSpam\` = 1)`, [emails]);
79
87
  const remove = results.map(r => r[this.table]['email']);
80
- if (remove.length == 0) {
88
+ if (remove.length === 0) {
81
89
  return recipients;
82
90
  }
83
91
  return recipients.filter(r => !remove.includes(r.email));
84
92
  }
93
+ /**
94
+ * Experimental: needs to move to library
95
+ */
96
+ static select() {
97
+ const transformer = (row) => {
98
+ const d = this.fromRow(row[this.table]);
99
+ if (!d) {
100
+ throw new Error('EmailTemplate not found');
101
+ }
102
+ return d;
103
+ };
104
+ const select = new sql_1.SQLSelect(transformer, sql_1.SQL.wildcard());
105
+ return select.from(sql_1.SQL.table(this.table));
106
+ }
85
107
  }
86
108
  exports.EmailAddress = EmailAddress;
87
109
  tslib_1.__decorate([
88
110
  (0, simple_database_1.column)({
89
- primary: true, type: "string", beforeSave(value) {
111
+ primary: true, type: 'string', beforeSave(value) {
90
112
  return value ?? (0, uuid_1.v4)();
91
- }
113
+ },
92
114
  })
93
115
  ], EmailAddress.prototype, "id", void 0);
94
116
  tslib_1.__decorate([
95
- (0, simple_database_1.column)({ type: "string", nullable: true })
117
+ (0, simple_database_1.column)({ type: 'string', nullable: true })
96
118
  ], EmailAddress.prototype, "organizationId", void 0);
97
119
  tslib_1.__decorate([
98
- (0, simple_database_1.column)({ type: "string" })
120
+ (0, simple_database_1.column)({ type: 'string' })
99
121
  ], EmailAddress.prototype, "email", void 0);
100
122
  tslib_1.__decorate([
101
- (0, simple_database_1.column)({ type: "boolean" })
123
+ (0, simple_database_1.column)({ type: 'boolean' })
102
124
  ], EmailAddress.prototype, "markedAsSpam", void 0);
103
125
  tslib_1.__decorate([
104
- (0, simple_database_1.column)({ type: "boolean" })
126
+ (0, simple_database_1.column)({ type: 'boolean' })
105
127
  ], EmailAddress.prototype, "hardBounce", void 0);
106
128
  tslib_1.__decorate([
107
- (0, simple_database_1.column)({ type: "boolean" })
129
+ (0, simple_database_1.column)({ type: 'boolean' })
108
130
  ], EmailAddress.prototype, "unsubscribedMarketing", void 0);
109
131
  tslib_1.__decorate([
110
- (0, simple_database_1.column)({ type: "boolean" })
132
+ (0, simple_database_1.column)({ type: 'boolean' })
111
133
  ], EmailAddress.prototype, "unsubscribedAll", void 0);
112
134
  tslib_1.__decorate([
113
- (0, simple_database_1.column)({ type: "string", nullable: true })
135
+ (0, simple_database_1.column)({ type: 'string', nullable: true })
114
136
  ], EmailAddress.prototype, "token", void 0);
115
137
  tslib_1.__decorate([
116
138
  (0, simple_database_1.column)({
117
- type: "datetime", beforeSave() {
139
+ type: 'datetime', beforeSave() {
118
140
  const date = new Date();
119
141
  date.setMilliseconds(0);
120
142
  return date;
121
- }
143
+ },
122
144
  })
123
145
  ], EmailAddress.prototype, "createdAt", void 0);
124
146
  tslib_1.__decorate([
125
147
  (0, simple_database_1.column)({
126
- type: "datetime", beforeSave() {
148
+ type: 'datetime', beforeSave() {
127
149
  const date = new Date();
128
150
  date.setMilliseconds(0);
129
151
  return date;
130
- }
152
+ },
131
153
  })
132
154
  ], EmailAddress.prototype, "updatedAt", void 0);
133
155
  //# sourceMappingURL=EmailAddress.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"EmailAddress.js","sourceRoot":"","sources":["../../src/models/EmailAddress.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"EmailAddress.js","sourceRoot":"","sources":["../../src/models/EmailAddress.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stamhoofd/email",
3
- "version": "2.60.0",
3
+ "version": "2.61.0",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "license": "UNLICENCED",
@@ -25,5 +25,5 @@
25
25
  "publishConfig": {
26
26
  "access": "public"
27
27
  },
28
- "gitHead": "94ec449bbd82a2e3f49471e66136b04b902587bd"
28
+ "gitHead": "3df41cac9090d5e99abdbc51e4a4a02e9ea1784a"
29
29
  }
@@ -1,7 +1,8 @@
1
- import { column, Database, Model } from '@simonbackx/simple-database';
1
+ import { column, Database, Model, SQLResultNamespacedRow } from '@simonbackx/simple-database';
2
2
  import { QueueHandler } from '@stamhoofd/queues';
3
- import crypto from "crypto";
4
- import { v4 as uuidv4 } from "uuid";
3
+ import { SQL, SQLSelect, SQLWhere } from '@stamhoofd/sql';
4
+ import crypto from 'crypto';
5
+ import { v4 as uuidv4 } from 'uuid';
5
6
  import { EmailInterfaceRecipient } from '../classes/Email';
6
7
 
7
8
  async function randomBytes(size: number): Promise<Buffer> {
@@ -17,74 +18,74 @@ async function randomBytes(size: number): Promise<Buffer> {
17
18
  }
18
19
 
19
20
  export class EmailAddress extends Model {
20
- static table = "email_addresses";
21
+ static table = 'email_addresses';
21
22
 
22
- @column({
23
- primary: true, type: "string", beforeSave(value) {
23
+ @column({
24
+ primary: true, type: 'string', beforeSave(value) {
24
25
  return value ?? uuidv4();
25
- }
26
+ },
26
27
  })
27
28
  id!: string;
28
29
 
29
- @column({ type: "string", nullable: true })
30
+ @column({ type: 'string', nullable: true })
30
31
  organizationId: string | null = null;
31
32
 
32
33
  // Columns
33
- @column({ type: "string" })
34
+ @column({ type: 'string' })
34
35
  email: string;
35
36
 
36
- @column({ type: "boolean" })
37
+ @column({ type: 'boolean' })
37
38
  markedAsSpam = false;
38
39
 
39
- @column({ type: "boolean" })
40
+ @column({ type: 'boolean' })
40
41
  hardBounce = false;
41
42
 
42
- @column({ type: "boolean" })
43
+ @column({ type: 'boolean' })
43
44
  unsubscribedMarketing = false;
44
45
 
45
- @column({ type: "boolean" })
46
+ @column({ type: 'boolean' })
46
47
  unsubscribedAll = false;
47
48
 
48
- @column({ type: "string", nullable: true })
49
+ @column({ type: 'string', nullable: true })
49
50
  token: string | null;
50
51
 
51
52
  /**
52
53
  * createdAt behaves more like createdAt for Challenge. Since every save is considered to have a new challenge
53
54
  */
54
55
  @column({
55
- type: "datetime", beforeSave() {
56
- const date = new Date()
57
- date.setMilliseconds(0)
58
- return date
59
- }
56
+ type: 'datetime', beforeSave() {
57
+ const date = new Date();
58
+ date.setMilliseconds(0);
59
+ return date;
60
+ },
60
61
  })
61
- createdAt: Date
62
+ createdAt: Date;
62
63
 
63
64
  @column({
64
- type: "datetime", beforeSave() {
65
- const date = new Date()
66
- date.setMilliseconds(0)
67
- return date
68
- }
65
+ type: 'datetime', beforeSave() {
66
+ const date = new Date();
67
+ date.setMilliseconds(0);
68
+ return date;
69
+ },
69
70
  })
70
- updatedAt: Date
71
+ updatedAt: Date;
71
72
 
72
73
  static async getOrCreate(email: string, organizationId: string | null): Promise<EmailAddress> {
73
74
  // Prevent race conditions when checking the same email address at the same time, and creating a new one
74
- return await QueueHandler.schedule("email-address/create-"+email+'-'+organizationId, async () => {
75
- const existing = await this.getByEmail(email, organizationId)
75
+ return await QueueHandler.schedule('email-address/create-' + email + '-' + organizationId, async () => {
76
+ const existing = await this.getByEmail(email, organizationId);
76
77
  if (existing) {
77
- return existing
78
+ return existing;
78
79
  }
79
80
 
80
- const n = new EmailAddress()
81
- n.organizationId = organizationId
82
- n.email = email
83
- n.token = (await randomBytes(64)).toString("base64").toUpperCase();
81
+ const n = new EmailAddress();
82
+ n.organizationId = organizationId;
83
+ n.email = email;
84
+ n.token = (await randomBytes(64)).toString('base64').toUpperCase();
84
85
 
85
- await n.save()
86
+ await n.save();
86
87
 
87
- return n
88
+ return n;
88
89
  });
89
90
  }
90
91
 
@@ -92,18 +93,18 @@ export class EmailAddress extends Model {
92
93
  static async getByEmails(emails: string[], organizationId: string | null): Promise<EmailAddress[]> {
93
94
  if (emails.length > 30) {
94
95
  // Normally an organization will never have so much bounces, so we'll request all emails and filter in them
95
- const all = await this.where({ organizationId }, { limit: 1000 })
96
- return all.filter(e => emails.includes(e.email))
96
+ const all = await this.where({ organizationId }, { limit: 1000 });
97
+ return all.filter(e => emails.includes(e.email));
97
98
  }
98
99
 
99
100
  if (emails.length == 0) {
100
- return []
101
+ return [];
101
102
  }
102
103
 
103
104
  if (organizationId === null) {
104
105
  const [rows] = await Database.select(
105
106
  `SELECT ${this.getDefaultSelect()} FROM ${this.table} WHERE \`email\` IN (?) AND \`organizationId\` is NULL`,
106
- [emails]
107
+ [emails],
107
108
  );
108
109
 
109
110
  return this.fromRows(rows, this.table);
@@ -111,7 +112,7 @@ export class EmailAddress extends Model {
111
112
 
112
113
  const [rows] = await Database.select(
113
114
  `SELECT ${this.getDefaultSelect()} FROM ${this.table} WHERE \`email\` IN (?) AND \`organizationId\` = ?`,
114
- [emails, organizationId]
115
+ [emails, organizationId],
115
116
  );
116
117
 
117
118
  return this.fromRows(rows, this.table);
@@ -119,26 +120,56 @@ export class EmailAddress extends Model {
119
120
 
120
121
  // Methods
121
122
  static async getByEmail(email: string, organizationId: string | null): Promise<EmailAddress | undefined> {
122
- return (await this.where({ email, organizationId }, { limit: 1 }))[0]
123
+ return (await this.where({ email, organizationId }, { limit: 1 }))[0];
124
+ }
125
+
126
+ /**
127
+ * Search organization wide if this email has been marked as spam or hard bounced
128
+ */
129
+ static async getWhereHardBounceOrSpam(email: string): Promise<EmailAddress | null> {
130
+ return await this.select().where(
131
+ 'email', email,
132
+ ).where(
133
+ SQL.where('hardBounce', 1)
134
+ .or('markedAsSpam', 1),
135
+ ).first(false);
123
136
  }
124
137
 
125
138
  // Methods
126
139
  static async filterSendTo(recipients: EmailInterfaceRecipient[]): Promise<EmailInterfaceRecipient[]> {
127
- if (recipients.length == 0) {
128
- return []
140
+ if (recipients.length === 0) {
141
+ return [];
129
142
  }
130
143
 
131
- const emails = recipients.map(r => r.email)
144
+ const emails = recipients.map(r => r.email);
132
145
  const [results] = await Database.select(
133
146
  `SELECT email FROM ${this.table} WHERE \`email\` IN (?) AND (\`hardBounce\` = 1 OR \`markedAsSpam\` = 1)`,
134
- [emails]
147
+ [emails],
135
148
  );
136
149
 
137
- const remove = results.map(r => r[this.table]['email'])
138
- if (remove.length == 0) {
139
- return recipients
150
+ const remove = results.map(r => r[this.table]['email']);
151
+ if (remove.length === 0) {
152
+ return recipients;
140
153
  }
141
154
 
142
- return recipients.filter(r => !remove.includes(r.email))
155
+ return recipients.filter(r => !remove.includes(r.email));
156
+ }
157
+
158
+ /**
159
+ * Experimental: needs to move to library
160
+ */
161
+ static select() {
162
+ const transformer = (row: SQLResultNamespacedRow): EmailAddress => {
163
+ const d = (this as typeof EmailAddress & typeof Model).fromRow(row[this.table] as any) as EmailAddress | undefined;
164
+
165
+ if (!d) {
166
+ throw new Error('EmailTemplate not found');
167
+ }
168
+
169
+ return d;
170
+ };
171
+
172
+ const select = new SQLSelect(transformer, SQL.wildcard());
173
+ return select.from(SQL.table(this.table));
143
174
  }
144
175
  }