@social-mail/social-mail-web-server 1.5.97 → 1.5.99

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.
Files changed (67) hide show
  1. package/dist/server/model/entities/Email.d.ts +1 -0
  2. package/dist/server/model/entities/Email.d.ts.map +1 -1
  3. package/dist/server/model/entities/Email.js +4 -0
  4. package/dist/server/model/entities/Email.js.map +1 -1
  5. package/dist/server/model/entities/EmailEngagement.d.ts.map +1 -1
  6. package/dist/server/model/entities/EmailEngagement.js +5 -1
  7. package/dist/server/model/entities/EmailEngagement.js.map +1 -1
  8. package/dist/server/model/entities/EmailTag.d.ts.map +1 -1
  9. package/dist/server/model/entities/EmailTag.js +5 -1
  10. package/dist/server/model/entities/EmailTag.js.map +1 -1
  11. package/dist/server/model/entities/LoginSession.d.ts +2 -2
  12. package/dist/server/model/entities/LoginSession.d.ts.map +1 -1
  13. package/dist/server/model/entities/LoginSession.js +2 -2
  14. package/dist/server/model/entities/LoginSession.js.map +1 -1
  15. package/dist/server/model/entities/MailboxEmail.d.ts +13 -0
  16. package/dist/server/model/entities/MailboxEmail.d.ts.map +1 -1
  17. package/dist/server/model/entities/MailboxEmail.js +30 -1
  18. package/dist/server/model/entities/MailboxEmail.js.map +1 -1
  19. package/dist/server/model/events/EmailEvents.d.ts.map +1 -1
  20. package/dist/server/model/events/EmailEvents.js +15 -2
  21. package/dist/server/model/events/EmailEvents.js.map +1 -1
  22. package/dist/server/model/events/LoginSessionEvents.js +7 -7
  23. package/dist/server/model/events/LoginSessionEvents.js.map +1 -1
  24. package/dist/server/model/events/MailboxEmailEvents.d.ts +1 -0
  25. package/dist/server/model/events/MailboxEmailEvents.d.ts.map +1 -1
  26. package/dist/server/model/events/MailboxEmailEvents.js +32 -0
  27. package/dist/server/model/events/MailboxEmailEvents.js.map +1 -1
  28. package/dist/server/model/events/MailboxEvents.d.ts.map +1 -1
  29. package/dist/server/model/events/MailboxEvents.js +4 -2
  30. package/dist/server/model/events/MailboxEvents.js.map +1 -1
  31. package/dist/server/model/events/UserEvents.js +1 -1
  32. package/dist/server/model/events/UserEvents.js.map +1 -1
  33. package/dist/server/services/UserInfoProvider.js +1 -1
  34. package/dist/server/services/UserInfoProvider.js.map +1 -1
  35. package/dist/server/smtp/services/EngagementService.d.ts.map +1 -1
  36. package/dist/server/smtp/services/EngagementService.js +11 -1
  37. package/dist/server/smtp/services/EngagementService.js.map +1 -1
  38. package/dist/server/workflows/SocialWorkflowContext.d.ts.map +1 -1
  39. package/dist/server/workflows/SocialWorkflowContext.js +2 -0
  40. package/dist/server/workflows/SocialWorkflowContext.js.map +1 -1
  41. package/dist/server/workflows/channel/BroadcastEmail.d.ts.map +1 -1
  42. package/dist/server/workflows/channel/BroadcastEmail.js +6 -1
  43. package/dist/server/workflows/channel/BroadcastEmail.js.map +1 -1
  44. package/dist/server/workflows/daily/DailyWorkflow.js +1 -1
  45. package/dist/server/workflows/daily/DailyWorkflow.js.map +1 -1
  46. package/dist/server/workflows/repair/channel/RepairChannelEmailCountsWorkflow.d.ts +8 -0
  47. package/dist/server/workflows/repair/channel/RepairChannelEmailCountsWorkflow.d.ts.map +1 -0
  48. package/dist/server/workflows/repair/channel/RepairChannelEmailCountsWorkflow.js +57 -0
  49. package/dist/server/workflows/repair/channel/RepairChannelEmailCountsWorkflow.js.map +1 -0
  50. package/dist/tsconfig.tsbuildinfo +1 -1
  51. package/package.json +2 -2
  52. package/src/server/model/entities/Email.ts +3 -0
  53. package/src/server/model/entities/EmailEngagement.ts +5 -1
  54. package/src/server/model/entities/EmailTag.ts +5 -1
  55. package/src/server/model/entities/LoginSession.ts +2 -2
  56. package/src/server/model/entities/MailboxEmail.ts +35 -0
  57. package/src/server/model/events/EmailEvents.ts +16 -2
  58. package/src/server/model/events/LoginSessionEvents.ts +7 -7
  59. package/src/server/model/events/MailboxEmailEvents.ts +23 -0
  60. package/src/server/model/events/MailboxEvents.ts +7 -2
  61. package/src/server/model/events/UserEvents.ts +1 -1
  62. package/src/server/services/UserInfoProvider.ts +1 -1
  63. package/src/server/smtp/services/EngagementService.ts +12 -1
  64. package/src/server/workflows/SocialWorkflowContext.ts +2 -0
  65. package/src/server/workflows/channel/BroadcastEmail.ts +7 -1
  66. package/src/server/workflows/daily/DailyWorkflow.ts +1 -1
  67. package/src/server/workflows/repair/channel/RepairChannelEmailCountsWorkflow.ts +49 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@social-mail/social-mail-web-server",
3
- "version": "1.5.97",
3
+ "version": "1.5.99",
4
4
  "description": "## Phase 1",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -21,7 +21,7 @@
21
21
  "dependencies": {
22
22
  "@aws-sdk/client-s3": "^3.609.0",
23
23
  "@azure/storage-blob": "^12.25.0",
24
- "@entity-access/entity-access": "^1.0.360",
24
+ "@entity-access/entity-access": "^1.0.364",
25
25
  "@entity-access/server-pages": "^1.1.292",
26
26
  "@otplib/preset-v11": "^12.0.1",
27
27
  "@social-mail/noto-emoji-svg": "^2.0.42",
@@ -133,6 +133,9 @@ export class Email {
133
133
  @Column({ dataType: "Int", default: () => 0})
134
134
  public viewCount: number;
135
135
 
136
+ @Column({ dataType: "Int", default: () => 0})
137
+ public engagementCount: number;
138
+
136
139
  @Column({ dataType: "BigInt", nullable: true })
137
140
  @RelateTo({
138
141
  type: () => EmailLog,
@@ -12,7 +12,11 @@ export default class EmailEngagement {
12
12
  @Column({ dataType: "BigInt", key: true, order: 1})
13
13
  @RelateTo(Email, {
14
14
  property: (x) => x.email,
15
- inverseProperty: (x) => x.engagements
15
+ inverseProperty: (x) => x.engagements,
16
+ foreignKeyConstraint: {
17
+ name: "FC_Email_Engagements",
18
+ cascade: "delete"
19
+ }
16
20
  })
17
21
  public emailID: number;
18
22
 
@@ -10,7 +10,11 @@ export default class EmailTag {
10
10
  @Column({key: true, dataType: "BigInt", order : 1})
11
11
  @RelateTo(Email, {
12
12
  property: (et) => et.email,
13
- inverseProperty: (e) => e.emailTags
13
+ inverseProperty: (e) => e.emailTags,
14
+ foreignKeyConstraint: {
15
+ name: "FC_Email_Tags",
16
+ cascade: "delete"
17
+ }
14
18
  })
15
19
  public emailID: number;
16
20
 
@@ -35,11 +35,11 @@ export default class LoginSession {
35
35
 
36
36
  @Column({})
37
37
  @IgnoreJsonProperty
38
- public start: Date;
38
+ public start: DateTime;
39
39
 
40
40
  @Column({})
41
41
  @IgnoreJsonProperty
42
- public expiry: Date;
42
+ public expiry: DateTime;
43
43
 
44
44
  @Column({})
45
45
  public invalid: boolean;
@@ -3,8 +3,25 @@ import { RelateTo } from "@entity-access/entity-access/dist/decorators/Relate.js
3
3
  import Table from "@entity-access/entity-access/dist/decorators/Table.js";
4
4
  import { Email } from "./Email.js";
5
5
  import Mailbox from "./Mailbox.js";
6
+ import DateTime from "@entity-access/entity-access/dist/types/DateTime.js";
7
+ import Index from "@entity-access/entity-access/dist/decorators/Index.js";
6
8
 
7
9
  @Table("MailboxEmails")
10
+ @Index({
11
+ name: "IX_Mailbox_Email_Engagement_Count",
12
+ columns: [
13
+ { name: (x) => x.dateUpdated, descending: true },
14
+ { name: (x) => x.engagementCount, descending: true },
15
+ ],
16
+ filter: (x) => x.dateUpdated !== null
17
+ })
18
+ @Index({
19
+ name: "IX_Mailbox_Email_Published",
20
+ columns: [
21
+ { name: (x) => x.datePublished, descending: true },
22
+ ],
23
+ filter: (x) => x.datePublished !== null
24
+ })
8
25
  export default class MailboxEmail {
9
26
 
10
27
  @Column({ dataType: "BigInt", key: true, order: 1 })
@@ -29,6 +46,24 @@ export default class MailboxEmail {
29
46
 
30
47
  email: Email;
31
48
 
49
+ /**
50
+ * Forwarded from email for caching, only for channels
51
+ */
52
+ @Column({ dataType: "Int", default: () => 0})
53
+ engagementCount: number;
54
+
55
+ /**
56
+ * Forwarded from email for caching, only for channels
57
+ */
58
+ @Column({ dataType: "DateTime", nullable: true})
59
+ dateUpdated: DateTime;
60
+
61
+ /**
62
+ * Forwarded from email for caching, only for channels
63
+ */
64
+ @Column({ dataType: "DateTime", nullable: true})
65
+ datePublished: DateTime;
66
+
32
67
  mailbox: Mailbox;
33
68
 
34
69
  }
@@ -180,6 +180,9 @@ export default class EmailEvents extends AuthenticatedEvents<Email> {
180
180
  await this.db.appFiles.asQuery()
181
181
  .delete(entity, (p) => (x) => x.emailID === p.emailID && x.parentID === null);
182
182
 
183
+ await this.db.emailEngagements.asQuery()
184
+ .delete(entity, (p) => (x) => x.emailID === p.emailID);
185
+
183
186
  await this.db.mailboxEmails
184
187
  .asQuery()
185
188
  .delete(entity, (p) => (x) => x.emailID === p.emailID);
@@ -205,8 +208,19 @@ export default class EmailEvents extends AuthenticatedEvents<Email> {
205
208
  return this.updateScheduleAndReferences(entity);
206
209
  }
207
210
 
208
- afterUpdate(entity: Email, entry: ChangeEntry<Email>) {
209
- return this.updateScheduleAndReferences(entity);
211
+ async afterUpdate(entity: Email, entry: ChangeEntry<Email>) {
212
+ if (entry.isUpdated("status")) {
213
+ switch(entity.status) {
214
+ case "published":
215
+ await this.db.mailboxEmails.where(entity, (p) => (x) => x.emailID === p.emailID)
216
+ .update(entity, (p) => (x) => ({ datePublished: Sql.cast.asDateTime(p.dateSent) as any }));
217
+ break;
218
+ default:
219
+ await this.db.mailboxEmails.where(entity, (p) => (x) => x.emailID === p.emailID)
220
+ .update(void 0, (p) => (x) => ({ datePublished: Sql.cast.asDateTime(null) as any }));
221
+ }
222
+ }
223
+ await this.updateScheduleAndReferences(entity);
210
224
  }
211
225
 
212
226
  includeFilter(query: IEntityQuery<Email>, type?: any, key?: string): IEntityQuery<Email> {
@@ -95,8 +95,8 @@ export default class LoginSessionEvents extends EntityEvents<LoginSession> {
95
95
  && x.expires >= p.now)
96
96
  .firstOrFail();
97
97
  entity.userID = user.userID;
98
- entity.start = now.asJSDate;
99
- entity.expiry = now.addDays(30).asJSDate;
98
+ entity.start = now;
99
+ entity.expiry = now.addDays(30);
100
100
  entity.dateUpdated = now;
101
101
  entity.invalid = false;
102
102
  entity.status = "created";
@@ -120,8 +120,8 @@ export default class LoginSessionEvents extends EntityEvents<LoginSession> {
120
120
  }
121
121
  }
122
122
  entity.userID = user.userID;
123
- entity.start = now.asJSDate;
124
- entity.expiry = now.addDays(30).asJSDate;
123
+ entity.start = now;
124
+ entity.expiry = now.addDays(30);
125
125
  entity.dateUpdated = now;
126
126
  entity.invalid = false;
127
127
  entity.status = "created";
@@ -165,7 +165,7 @@ export default class LoginSessionEvents extends EntityEvents<LoginSession> {
165
165
  await this.sessionUser.setAuthCookie({
166
166
  userID: entity.userID,
167
167
  id: entity.sessionID,
168
- expiry: entity.expiry,
168
+ expiry: entity.expiry.asJSDate,
169
169
  version: "1.1",
170
170
  active: false
171
171
  });
@@ -176,7 +176,7 @@ export default class LoginSessionEvents extends EntityEvents<LoginSession> {
176
176
  await this.sessionUser.setAuthCookie({
177
177
  userID: entity.userID,
178
178
  id: entity.sessionID,
179
- expiry: entity.expiry,
179
+ expiry: entity.expiry.asJSDate,
180
180
  version: "1.1",
181
181
  active: true
182
182
  });
@@ -304,7 +304,7 @@ export default class LoginSessionEvents extends EntityEvents<LoginSession> {
304
304
  await this.sessionUser.setAuthCookie({
305
305
  userID: entity.userID,
306
306
  id: entity.sessionID,
307
- expiry: entity.expiry,
307
+ expiry: entity.expiry.asJSDate,
308
308
  version: "1.1",
309
309
  active: true
310
310
  });
@@ -2,6 +2,12 @@ import ChangeEntry from "@entity-access/entity-access/dist/model/changes/ChangeE
2
2
  import MailboxEmail from "../entities/MailboxEmail.js";
3
3
  import AuthenticatedEvents from "./AuthenticatedEvents.js";
4
4
  import { IEntityQuery } from "@entity-access/entity-access/dist/model/IFilterWithParameter.js";
5
+ import { ServiceProvider } from "@entity-access/entity-access/dist/di/di.js";
6
+ import Sql from "@entity-access/entity-access/dist/sql/Sql.js";
7
+ import ExternalQuery from "@entity-access/server-pages/dist/decorators/ExternalQuery.js";
8
+ import WebSiteChannelService from "../../smtp/services/WebSiteChannelService.js";
9
+ import Page from "@entity-access/server-pages/dist/Page.js";
10
+ import SocialMailContext from "../SocialMailContext.js";
5
11
 
6
12
  export default class MailboxEmailEvents extends AuthenticatedEvents<MailboxEmail> {
7
13
 
@@ -19,6 +25,23 @@ export default class MailboxEmailEvents extends AuthenticatedEvents<MailboxEmail
19
25
  || x.mailbox.domain.hostedDomain.domainUsers.some((d) => d.userID === p.userID));
20
26
  }
21
27
 
28
+ @ExternalQuery
29
+ async channelEmails(channelID) {
30
+ // get current host
31
+ const page = ServiceProvider.resolve(this, Page as any) as Page;
32
+ const host = page.request.host;
33
+
34
+ const wcs = ServiceProvider.resolve(this, WebSiteChannelService);
35
+ const mailboxes = await wcs.getMailboxes(host);
36
+
37
+ const db = ServiceProvider.resolve(this, SocialMailContext);
38
+
39
+ const q = db.mailboxEmails.where({ channelID, host, mailboxes}, (p) => (x) =>
40
+ x.datePublished !== null
41
+ && Sql.in(x.mailboxID, p.mailboxes));
42
+ return q.limit(25);
43
+ }
44
+
22
45
  beforeInsert(entity: MailboxEmail, entry: ChangeEntry<MailboxEmail>) {
23
46
  if (this.verify) {
24
47
  this.sessionUser.ensureLoggedIn();
@@ -17,6 +17,7 @@ import { Prepare } from "@entity-access/server-pages/dist/decorators/Prepare.js"
17
17
  import EntityAccessError from "@entity-access/entity-access/dist/common/EntityAccessError.js";
18
18
  import SyncMailboxWorkflow from "../../workflows/sync/SyncMailboxWorkflow.js";
19
19
  import TagService from "../../services/tags/TagService.js";
20
+ import WebSiteChannelService from "../../smtp/services/WebSiteChannelService.js";
20
21
 
21
22
  export default class MailboxEvents extends AuthenticatedEvents<Mailbox> {
22
23
 
@@ -45,8 +46,12 @@ export default class MailboxEvents extends AuthenticatedEvents<Mailbox> {
45
46
  const page = ServiceProvider.resolve(this, Page as any) as Page;
46
47
  const host = page.request.host;
47
48
 
48
- let q = this.db.mailboxes.where(void 0, (p) => (x) => x.isChannel === true && x.isPrivateChannel === false);
49
- q = q.where({ host }, (p) => (x) => x.websiteChannels.some((c) => c.website.hosts.some((h) => h.host === p.host)));
49
+ const wcs = ServiceProvider.resolve(this, WebSiteChannelService);
50
+
51
+ const mailboxes = await wcs.getMailboxes(host);
52
+
53
+ let q = this.db.mailboxes.where({ mailboxes }, (p) => (x) => Sql.in(x.mailboxID, p.mailboxes) );
54
+
50
55
  if (text) {
51
56
  text = text.toLocaleLowerCase();
52
57
  if (text.startsWith("@")) {
@@ -218,7 +218,7 @@ export default class UserEvents extends AuthenticatedEvents<User> {
218
218
  async afterUpdate(entity: User, entry: ChangeEntry<User>) {
219
219
  if (entry.isUpdated("status")) {
220
220
  if (entity.status === "blocked") {
221
- const now = new Date();
221
+ const now = DateTime.now;
222
222
  const { userID } = entity;
223
223
 
224
224
  await this.db.loginSessions.where({ userID, now}, (p) => (x) => x.userID === p.userID
@@ -30,7 +30,7 @@ export default class UserSessionProvider extends USP {
30
30
  userID,
31
31
  sessionID,
32
32
  userName: session.user.userName,
33
- expiry: session.expiry,
33
+ expiry: session.expiry.asJSDate,
34
34
  roles: session.user.roles?.map((x) => x.roleName) ?? [],
35
35
  fileAccessList: session.user.fileAccessList?.map(({ type, file: { path }}) => ({ type, path })),
36
36
  invalid: false
@@ -187,8 +187,19 @@ export default class EngagementService {
187
187
 
188
188
  const ea = { emailID, engagement };
189
189
 
190
+ if (email.status === "published") {
191
+ await this.db.mailboxEmails.where(ea, (p) => (x) => x.emailID === p.emailID)
192
+ .update(void 0, (p) => (x) => ({
193
+ engagementCount: x.engagementCount + 1,
194
+ dateUpdated: Sql.date.now()
195
+ }));
196
+ }
197
+
190
198
  await this.db.emails.where(ea, (p) => (x) => x.emailID === p.emailID)
191
- .update(void 0, (p) => (x) => ({ dateUpdated: Sql.date.now()}));
199
+ .update(void 0, (p) => (x) => ({
200
+ engagementCount: x.engagementCount + 1,
201
+ dateUpdated: Sql.date.now()
202
+ }));
192
203
 
193
204
  const r = await this.db.emailEngagements.where(ea, (p) => (x) => x.emailID === p.emailID && x.engagement === p.engagement)
194
205
  .update(void 0, (p) => (x) => ({ count: x.count + 1}));
@@ -33,6 +33,7 @@ import RepairMailboxFoldersWorkflow from "./repair/mailbox-folders/RepairMailbox
33
33
  import DispatchNotificationToMX from "./smtp/DispatchNotificationToMX.js";
34
34
  import RepairCacheClearWorkflow from "./repair/cache/RepairCacheClearWorkflow.js";
35
35
  import RepairEmailNameTagsWorkflow from "./repair/tags/RepairEmailNameTagsWorkflow.js";
36
+ import RepairChannelEmailCountsWorkflow from "./repair/channel/RepairChannelEmailCountsWorkflow.js";
36
37
 
37
38
  @RegisterSingleton
38
39
  export default class SocialWorkflowContext extends WorkflowContext {
@@ -66,6 +67,7 @@ export default class SocialWorkflowContext extends WorkflowContext {
66
67
  this.register(RepairStoreTagsWorkflow);
67
68
  this.register(RepairCacheClearWorkflow);
68
69
  this.register(RepairEmailNameTagsWorkflow);
70
+ this.register(RepairChannelEmailCountsWorkflow);
69
71
  // this.register(MigrateEmailFilesWorkflow);
70
72
  this.register(CleanupDeletedFilesWorkflow);
71
73
 
@@ -85,7 +85,13 @@ export default class BroadcastEmail extends Workflow<IBroadcastEmail> {
85
85
  .update(void 0, (p) => (x) => ({ status: "published"}));
86
86
 
87
87
  await db.mailboxes.where(this.input, (p) => (x) => x.mailboxID === p.mailboxID)
88
- .update(void 0, (p) => (x) => ({ datePublished: Sql.date.now()}));
88
+ .update(void 0, (p) => (x) => ({ datePublished: Sql.cast.asDateTime(Sql.date.now()) }));
89
+
90
+ await db.mailboxEmails.where(this.input, (p) => (x) => x.emailID === p.emailID && x.mailboxID === p.mailboxID)
91
+ .update(void 0, (p) => (x) => ({
92
+ dateUpdated: Sql.cast.asDateTime(Sql.date.now()),
93
+ datePublished: Sql.cast.asDateTime(Sql.date.now())
94
+ }));
89
95
 
90
96
  // load email..
91
97
  const from = (await eas.getEmailAddress(this.input.mailboxID)).emailAddress;
@@ -72,7 +72,7 @@ export default class DailyWorkflow extends Workflow<any,any> {
72
72
 
73
73
  // deactivate all expired sessions...
74
74
  await db.loginSessions
75
- .where(void 0, (p) => (x) => x.expiry <= (Sql.date.now() as any as Date))
75
+ .where(void 0, (p) => (x) => x.expiry <= (Sql.date.now()))
76
76
  .limit(1000)
77
77
  .update(void 0, (p) => (x) => ({ invalid: true }));
78
78
 
@@ -0,0 +1,49 @@
1
+ import Inject from "@entity-access/entity-access/dist/di/di.js";
2
+ import Workflow, { UniqueActivity } from "@entity-access/entity-access/dist/workflows/Workflow.js";
3
+ import SocialMailContext from "../../../model/SocialMailContext.js";
4
+ import Sql from "@entity-access/entity-access/dist/sql/Sql.js";
5
+
6
+ export default class RepairChannelEmailCountsWorkflow extends Workflow {
7
+
8
+ static taskGroup = "batch";
9
+
10
+ async run() {
11
+ let start = 0;
12
+ do{
13
+ start = await this.fixEmailTags(start);
14
+ } while(BigInt(start));
15
+ }
16
+
17
+ @UniqueActivity
18
+ async fixEmailTags(
19
+ start: number,
20
+ @Inject db?: SocialMailContext
21
+ ) {
22
+
23
+ for(let i = 0;i< 100;i++) {
24
+ const mailboxes = await db.mailboxes.where({ start }, (p) => (x) => x.mailboxID > p.start && x.isChannel === true)
25
+ .orderBy(void 0, (p) => (x) => x.mailboxID)
26
+ .limit(10)
27
+ .include((x) => [x.folders])
28
+ .toArray();
29
+
30
+ if (!mailboxes.length) {
31
+ return 0;
32
+ }
33
+
34
+ for (const iterator of mailboxes) {
35
+ start = iterator.mailboxID;
36
+
37
+ await db.mailboxEmails.where(iterator, (p) => (x) => x.mailboxID === p.mailboxID)
38
+ .update(void 0, (p) => (x) => ({
39
+ datePublished: Sql.cast.asDateTime( x.email.status === "published" ? x.email.dateSent : null ) as any,
40
+ dateUpdated: Sql.cast.asDateTime( x.email.status === "published" ? x.email.dateUpdated : null ) as any,
41
+ engagementCount: Sql.cast.asInteger( x.email.status === "published" ? x.email.engagementCount : 0 ) as any
42
+ }));
43
+
44
+ }
45
+ db.changeSet.clear();
46
+ }
47
+ return start;
48
+ }
49
+ }