@carlonicora/nestjs-neo4jsonapi 8.5.0 → 8.6.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.
Files changed (112) hide show
  1. package/dist/bootstrap/app.module.factory.d.ts.map +1 -1
  2. package/dist/bootstrap/app.module.factory.js +1 -0
  3. package/dist/bootstrap/app.module.factory.js.map +1 -1
  4. package/dist/bootstrap/bootstrap.options.d.ts +14 -0
  5. package/dist/bootstrap/bootstrap.options.d.ts.map +1 -1
  6. package/dist/config/enums/queue.id.d.ts +1 -0
  7. package/dist/config/enums/queue.id.d.ts.map +1 -1
  8. package/dist/config/enums/queue.id.js +1 -0
  9. package/dist/config/enums/queue.id.js.map +1 -1
  10. package/dist/core/email/templates/en/accountDeleted.hbs +23 -0
  11. package/dist/core/email/templates/en/accountDeletionRequested.hbs +28 -0
  12. package/dist/foundations/account-deletion/account-deletion.config.d.ts +18 -0
  13. package/dist/foundations/account-deletion/account-deletion.config.d.ts.map +1 -0
  14. package/dist/foundations/account-deletion/account-deletion.config.js +6 -0
  15. package/dist/foundations/account-deletion/account-deletion.config.js.map +1 -0
  16. package/dist/foundations/account-deletion/account-deletion.constants.d.ts +36 -0
  17. package/dist/foundations/account-deletion/account-deletion.constants.d.ts.map +1 -0
  18. package/dist/foundations/account-deletion/account-deletion.constants.js +41 -0
  19. package/dist/foundations/account-deletion/account-deletion.constants.js.map +1 -0
  20. package/dist/foundations/account-deletion/account-deletion.module.d.ts +31 -0
  21. package/dist/foundations/account-deletion/account-deletion.module.d.ts.map +1 -0
  22. package/dist/foundations/account-deletion/account-deletion.module.js +98 -0
  23. package/dist/foundations/account-deletion/account-deletion.module.js.map +1 -0
  24. package/dist/foundations/account-deletion/controllers/account-deletion.controller.d.ts +28 -0
  25. package/dist/foundations/account-deletion/controllers/account-deletion.controller.d.ts.map +1 -0
  26. package/dist/foundations/account-deletion/controllers/account-deletion.controller.js +132 -0
  27. package/dist/foundations/account-deletion/controllers/account-deletion.controller.js.map +1 -0
  28. package/dist/foundations/account-deletion/cron/account-deletion.cron.d.ts +20 -0
  29. package/dist/foundations/account-deletion/cron/account-deletion.cron.d.ts.map +1 -0
  30. package/dist/foundations/account-deletion/cron/account-deletion.cron.js +78 -0
  31. package/dist/foundations/account-deletion/cron/account-deletion.cron.js.map +1 -0
  32. package/dist/foundations/account-deletion/dtos/account-deletion.keep.post.dto.d.ts +14 -0
  33. package/dist/foundations/account-deletion/dtos/account-deletion.keep.post.dto.d.ts.map +1 -0
  34. package/dist/foundations/account-deletion/dtos/account-deletion.keep.post.dto.js +51 -0
  35. package/dist/foundations/account-deletion/dtos/account-deletion.keep.post.dto.js.map +1 -0
  36. package/dist/foundations/account-deletion/dtos/account-deletion.post.dto.d.ts +14 -0
  37. package/dist/foundations/account-deletion/dtos/account-deletion.post.dto.d.ts.map +1 -0
  38. package/dist/foundations/account-deletion/dtos/account-deletion.post.dto.js +51 -0
  39. package/dist/foundations/account-deletion/dtos/account-deletion.post.dto.js.map +1 -0
  40. package/dist/foundations/account-deletion/entities/account-deletion.d.ts +20 -0
  41. package/dist/foundations/account-deletion/entities/account-deletion.d.ts.map +1 -0
  42. package/dist/foundations/account-deletion/entities/account-deletion.js +21 -0
  43. package/dist/foundations/account-deletion/entities/account-deletion.js.map +1 -0
  44. package/dist/foundations/account-deletion/entities/account-deletion.meta.d.ts +3 -0
  45. package/dist/foundations/account-deletion/entities/account-deletion.meta.d.ts.map +1 -0
  46. package/dist/foundations/account-deletion/entities/account-deletion.meta.js +10 -0
  47. package/dist/foundations/account-deletion/entities/account-deletion.meta.js.map +1 -0
  48. package/dist/foundations/account-deletion/index.d.ts +15 -0
  49. package/dist/foundations/account-deletion/index.d.ts.map +1 -0
  50. package/dist/foundations/account-deletion/index.js +31 -0
  51. package/dist/foundations/account-deletion/index.js.map +1 -0
  52. package/dist/foundations/account-deletion/interfaces/account-deletion-handler.interface.d.ts +29 -0
  53. package/dist/foundations/account-deletion/interfaces/account-deletion-handler.interface.d.ts.map +1 -0
  54. package/dist/foundations/account-deletion/interfaces/account-deletion-handler.interface.js +5 -0
  55. package/dist/foundations/account-deletion/interfaces/account-deletion-handler.interface.js.map +1 -0
  56. package/dist/foundations/account-deletion/interfaces/account-deletion-login-hook.interface.d.ts +12 -0
  57. package/dist/foundations/account-deletion/interfaces/account-deletion-login-hook.interface.d.ts.map +1 -0
  58. package/dist/foundations/account-deletion/interfaces/account-deletion-login-hook.interface.js +5 -0
  59. package/dist/foundations/account-deletion/interfaces/account-deletion-login-hook.interface.js.map +1 -0
  60. package/dist/foundations/account-deletion/processors/account-deletion.processor.d.ts +28 -0
  61. package/dist/foundations/account-deletion/processors/account-deletion.processor.d.ts.map +1 -0
  62. package/dist/foundations/account-deletion/processors/account-deletion.processor.js +97 -0
  63. package/dist/foundations/account-deletion/processors/account-deletion.processor.js.map +1 -0
  64. package/dist/foundations/account-deletion/repositories/account-deletion.repository.d.ts +85 -0
  65. package/dist/foundations/account-deletion/repositories/account-deletion.repository.d.ts.map +1 -0
  66. package/dist/foundations/account-deletion/repositories/account-deletion.repository.js +186 -0
  67. package/dist/foundations/account-deletion/repositories/account-deletion.repository.js.map +1 -0
  68. package/dist/foundations/account-deletion/repositories/account-deletion.wipe.repository.d.ts +77 -0
  69. package/dist/foundations/account-deletion/repositories/account-deletion.wipe.repository.d.ts.map +1 -0
  70. package/dist/foundations/account-deletion/repositories/account-deletion.wipe.repository.js +243 -0
  71. package/dist/foundations/account-deletion/repositories/account-deletion.wipe.repository.js.map +1 -0
  72. package/dist/foundations/account-deletion/services/account-deletion.service.d.ts +105 -0
  73. package/dist/foundations/account-deletion/services/account-deletion.service.d.ts.map +1 -0
  74. package/dist/foundations/account-deletion/services/account-deletion.service.js +300 -0
  75. package/dist/foundations/account-deletion/services/account-deletion.service.js.map +1 -0
  76. package/dist/foundations/account-deletion/services/account-wipe.service.d.ts +64 -0
  77. package/dist/foundations/account-deletion/services/account-wipe.service.d.ts.map +1 -0
  78. package/dist/foundations/account-deletion/services/account-wipe.service.js +134 -0
  79. package/dist/foundations/account-deletion/services/account-wipe.service.js.map +1 -0
  80. package/dist/foundations/account-deletion/services/full-company-deletion.service.d.ts +88 -0
  81. package/dist/foundations/account-deletion/services/full-company-deletion.service.d.ts.map +1 -0
  82. package/dist/foundations/account-deletion/services/full-company-deletion.service.js +184 -0
  83. package/dist/foundations/account-deletion/services/full-company-deletion.service.js.map +1 -0
  84. package/dist/foundations/account-deletion/utils/deliverable-email.d.ts +8 -0
  85. package/dist/foundations/account-deletion/utils/deliverable-email.d.ts.map +1 -0
  86. package/dist/foundations/account-deletion/utils/deliverable-email.js +20 -0
  87. package/dist/foundations/account-deletion/utils/deliverable-email.js.map +1 -0
  88. package/dist/foundations/auth/entities/auth.d.ts +1 -0
  89. package/dist/foundations/auth/entities/auth.d.ts.map +1 -1
  90. package/dist/foundations/auth/entities/auth.js +4 -0
  91. package/dist/foundations/auth/entities/auth.js.map +1 -1
  92. package/dist/foundations/auth/repositories/auth.repository.d.ts +2 -0
  93. package/dist/foundations/auth/repositories/auth.repository.d.ts.map +1 -1
  94. package/dist/foundations/auth/repositories/auth.repository.js +4 -0
  95. package/dist/foundations/auth/repositories/auth.repository.js.map +1 -1
  96. package/dist/foundations/auth/services/auth.service.d.ts +3 -1
  97. package/dist/foundations/auth/services/auth.service.d.ts.map +1 -1
  98. package/dist/foundations/auth/services/auth.service.js +11 -2
  99. package/dist/foundations/auth/services/auth.service.js.map +1 -1
  100. package/dist/foundations/foundations.modules.d.ts +7 -0
  101. package/dist/foundations/foundations.modules.d.ts.map +1 -1
  102. package/dist/foundations/foundations.modules.js +5 -0
  103. package/dist/foundations/foundations.modules.js.map +1 -1
  104. package/dist/foundations/index.d.ts +1 -0
  105. package/dist/foundations/index.d.ts.map +1 -1
  106. package/dist/foundations/index.js +1 -0
  107. package/dist/foundations/index.js.map +1 -1
  108. package/dist/foundations/user/entities/user.d.ts +3 -0
  109. package/dist/foundations/user/entities/user.d.ts.map +1 -1
  110. package/dist/foundations/user/entities/user.js +4 -0
  111. package/dist/foundations/user/entities/user.js.map +1 -1
  112. package/package.json +1 -1
@@ -0,0 +1,85 @@
1
+ import { ClsService } from "nestjs-cls";
2
+ import { Neo4jService } from "../../../core/neo4j/services/neo4j.service";
3
+ import { AbstractRepository } from "../../../core/neo4j/abstracts/abstract.repository";
4
+ import { SecurityService } from "../../../core/security/services/security.service";
5
+ import { User, UserDescriptor } from "../../user/entities/user";
6
+ /**
7
+ * Reads and writes of the account-deletion request on the User node.
8
+ *
9
+ * Owner decision OD-1 (Carlo, 2026-10-25): this repository reads a User and its
10
+ * Memberships by explicit id with no company predicate, for exactly six callers
11
+ * (self preview/request, admin preview/request on another user, the public keep
12
+ * link, the daily cron, the login hook and the worker processor), and runs three
13
+ * hand-written writes by user id (setRequest, clearRequest, deleteSessions).
14
+ * A user deleting their account is not scoped to one company: a placeholder
15
+ * player belongs to the GM's company, an administrator acts on any user, and the
16
+ * keep link and the cron run with no session at all. Every query therefore runs
17
+ * in a fresh CLS context (`ifNested: "override"`), so `initQuery` binds no
18
+ * caller company or user. Every id is a query parameter. User nodes come back
19
+ * through readOne/readMany with the User serialiser; only findMembershipSummary
20
+ * returns a scalar row.
21
+ */
22
+ export declare class AccountDeletionRepository extends AbstractRepository<User, typeof UserDescriptor.relationships> {
23
+ protected readonly descriptor: import("../../..").EntityDescriptor<User, {
24
+ role: {
25
+ model: import("../../..").DataMeta;
26
+ direction: "out";
27
+ relationship: string;
28
+ cardinality: "many";
29
+ dtoKey: string;
30
+ readOnly: true;
31
+ };
32
+ company: {
33
+ model: import("../../..").DataModelInterface<import("../..").Company>;
34
+ direction: "out";
35
+ relationship: string;
36
+ cardinality: "one";
37
+ };
38
+ module: {
39
+ model: import("../../..").DataMeta;
40
+ direction: "out";
41
+ relationship: string;
42
+ cardinality: "many";
43
+ dtoKey: string;
44
+ };
45
+ }>;
46
+ constructor(neo4j: Neo4jService, securityService: SecurityService, clsService: ClsService);
47
+ /** Runs the callback in an empty CLS context: no caller company or user is bound (OD-1). */
48
+ private _unscoped;
49
+ private _userReturn;
50
+ findTargetUser(params: {
51
+ userId: string;
52
+ }): Promise<User | null>;
53
+ findByUndoTokenHash(params: {
54
+ undoTokenHash: string;
55
+ }): Promise<User | null>;
56
+ /** Users whose grace period is over. Worker cron only. */
57
+ findDue(params: {
58
+ now: string;
59
+ }): Promise<User[]>;
60
+ /**
61
+ * The company of the user's Membership (preferring the company the user also
62
+ * BELONGS_TO) and how many Memberships that company has. A placeholder user
63
+ * (no Membership) gets `{ companyId: null, memberCount: 0 }`.
64
+ */
65
+ findMembershipSummary(params: {
66
+ userId: string;
67
+ }): Promise<{
68
+ companyId: string | null;
69
+ memberCount: number;
70
+ }>;
71
+ setRequest(params: {
72
+ userId: string;
73
+ requestedAt: string;
74
+ scheduledAt: string;
75
+ undoTokenHash: string | null;
76
+ }): Promise<void>;
77
+ clearRequest(params: {
78
+ userId: string;
79
+ }): Promise<void>;
80
+ /** Logs the user out everywhere: every Auth and AuthCode, and every OAuth token revoked. */
81
+ deleteSessions(params: {
82
+ userId: string;
83
+ }): Promise<void>;
84
+ }
85
+ //# sourceMappingURL=account-deletion.repository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-deletion.repository.d.ts","sourceRoot":"","sources":["../../../../src/foundations/account-deletion/repositories/account-deletion.repository.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,4CAA4C,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,mDAAmD,CAAC;AACvF,OAAO,EAAE,eAAe,EAAE,MAAM,kDAAkD,CAAC;AAGnF,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAGhE;;;;;;;;;;;;;;;GAeG;AACH,qBACa,yBAA0B,SAAQ,kBAAkB,CAAC,IAAI,EAAE,OAAO,cAAc,CAAC,aAAa,CAAC;IAC1G,SAAS,CAAC,QAAQ,CAAC,UAAU;;;;;;;;;;;;;;;;;;;;;;OAAkB;gBAEnC,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,UAAU,EAAE,UAAU;IAIzF,4FAA4F;IAC5F,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,WAAW;IAOb,cAAc,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAahE,mBAAmB,CAAC,MAAM,EAAE;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAalF,0DAA0D;IACpD,OAAO,CAAC,MAAM,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAevD;;;;OAIG;IACG,qBAAqB,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IA6B7G,UAAU,CAAC,MAAM,EAAE;QACvB,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;KAC9B,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBX,YAAY,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAc7D,4FAA4F;IACtF,cAAc,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAwBhE"}
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.AccountDeletionRepository = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const nestjs_cls_1 = require("nestjs-cls");
15
+ const neo4j_service_1 = require("../../../core/neo4j/services/neo4j.service");
16
+ const abstract_repository_1 = require("../../../core/neo4j/abstracts/abstract.repository");
17
+ const security_service_1 = require("../../../core/security/services/security.service");
18
+ const company_meta_1 = require("../../company/entities/company.meta");
19
+ const revoke_user_oauth_tokens_query_1 = require("../../oauth/queries/revoke-user-oauth-tokens.query");
20
+ const user_1 = require("../../user/entities/user");
21
+ const user_meta_1 = require("../../user/entities/user.meta");
22
+ /**
23
+ * Reads and writes of the account-deletion request on the User node.
24
+ *
25
+ * Owner decision OD-1 (Carlo, 2026-10-25): this repository reads a User and its
26
+ * Memberships by explicit id with no company predicate, for exactly six callers
27
+ * (self preview/request, admin preview/request on another user, the public keep
28
+ * link, the daily cron, the login hook and the worker processor), and runs three
29
+ * hand-written writes by user id (setRequest, clearRequest, deleteSessions).
30
+ * A user deleting their account is not scoped to one company: a placeholder
31
+ * player belongs to the GM's company, an administrator acts on any user, and the
32
+ * keep link and the cron run with no session at all. Every query therefore runs
33
+ * in a fresh CLS context (`ifNested: "override"`), so `initQuery` binds no
34
+ * caller company or user. Every id is a query parameter. User nodes come back
35
+ * through readOne/readMany with the User serialiser; only findMembershipSummary
36
+ * returns a scalar row.
37
+ */
38
+ let AccountDeletionRepository = class AccountDeletionRepository extends abstract_repository_1.AbstractRepository {
39
+ constructor(neo4j, securityService, clsService) {
40
+ super(neo4j, securityService, clsService);
41
+ this.descriptor = user_1.UserDescriptor;
42
+ }
43
+ /** Runs the callback in an empty CLS context: no caller company or user is bound (OD-1). */
44
+ _unscoped(callback) {
45
+ return this.clsService.run({ ifNested: "override" }, callback);
46
+ }
47
+ _userReturn() {
48
+ return `
49
+ OPTIONAL MATCH (${user_meta_1.userMeta.nodeName})-[:BELONGS_TO]->(${user_meta_1.userMeta.nodeName}_${company_meta_1.companyMeta.nodeName}:${company_meta_1.companyMeta.labelName})
50
+ RETURN ${user_meta_1.userMeta.nodeName}, ${user_meta_1.userMeta.nodeName}_${company_meta_1.companyMeta.nodeName}
51
+ `;
52
+ }
53
+ async findTargetUser(params) {
54
+ return this._unscoped(async () => {
55
+ const query = this.neo4j.initQuery({ serialiser: user_1.UserDescriptor.model });
56
+ query.queryParams = { ...query.queryParams, userId: params.userId };
57
+ // nja-lint-ignore OD-1
58
+ query.query += `
59
+ MATCH (${user_meta_1.userMeta.nodeName}:${user_meta_1.userMeta.labelName} {id: $userId})
60
+ ${this._userReturn()}
61
+ `;
62
+ return this.neo4j.readOne(query);
63
+ });
64
+ }
65
+ async findByUndoTokenHash(params) {
66
+ return this._unscoped(async () => {
67
+ const query = this.neo4j.initQuery({ serialiser: user_1.UserDescriptor.model });
68
+ query.queryParams = { ...query.queryParams, undoTokenHash: params.undoTokenHash };
69
+ // nja-lint-ignore OD-1
70
+ query.query += `
71
+ MATCH (${user_meta_1.userMeta.nodeName}:${user_meta_1.userMeta.labelName} {deletionUndoTokenHash: $undoTokenHash})
72
+ ${this._userReturn()}
73
+ `;
74
+ return this.neo4j.readOne(query);
75
+ });
76
+ }
77
+ /** Users whose grace period is over. Worker cron only. */
78
+ async findDue(params) {
79
+ return this._unscoped(async () => {
80
+ const query = this.neo4j.initQuery({ serialiser: user_1.UserDescriptor.model, fetchAll: true });
81
+ query.queryParams = { ...query.queryParams, now: params.now };
82
+ // nja-lint-ignore OD-1
83
+ query.query += `
84
+ MATCH (${user_meta_1.userMeta.nodeName}:${user_meta_1.userMeta.labelName})
85
+ WHERE ${user_meta_1.userMeta.nodeName}.deletionScheduledAt IS NOT NULL
86
+ AND ${user_meta_1.userMeta.nodeName}.deletionScheduledAt <= datetime($now)
87
+ ${this._userReturn()}
88
+ `;
89
+ return this.neo4j.readMany(query);
90
+ });
91
+ }
92
+ /**
93
+ * The company of the user's Membership (preferring the company the user also
94
+ * BELONGS_TO) and how many Memberships that company has. A placeholder user
95
+ * (no Membership) gets `{ companyId: null, memberCount: 0 }`.
96
+ */
97
+ async findMembershipSummary(params) {
98
+ return this._unscoped(async () => {
99
+ // nja-lint-ignore OD-1
100
+ const result = await this.neo4j.read(`
101
+ MATCH (user:User {id: $userId})
102
+ OPTIONAL MATCH (user)-[:HAS_MEMBERSHIP]->(:Membership)-[:IN_COMPANY]->(company:Company)
103
+ WITH user, company
104
+ ORDER BY CASE WHEN company IS NOT NULL AND EXISTS { (user)-[:BELONGS_TO]->(company) } THEN 0 ELSE 1 END
105
+ WITH collect(company)[0] AS company
106
+ OPTIONAL MATCH (member:Membership)-[:IN_COMPANY]->(company)
107
+ RETURN company.id AS companyId, count(DISTINCT member) AS memberCount
108
+ `, { userId: params.userId });
109
+ // nja-lint-ignore OD-1 (scalar row: company id and a count, no entity node)
110
+ const record = result.records?.[0];
111
+ if (!record)
112
+ return { companyId: null, memberCount: 0 };
113
+ const companyId = record.get("companyId");
114
+ const memberCount = record.get("memberCount");
115
+ return {
116
+ companyId: companyId ?? null,
117
+ memberCount: typeof memberCount?.toNumber === "function" ? memberCount.toNumber() : Number(memberCount ?? 0),
118
+ };
119
+ });
120
+ }
121
+ async setRequest(params) {
122
+ await this._unscoped(async () => {
123
+ const query = this.neo4j.initQuery();
124
+ query.queryParams = {
125
+ ...query.queryParams,
126
+ userId: params.userId,
127
+ requestedAt: params.requestedAt,
128
+ scheduledAt: params.scheduledAt,
129
+ undoTokenHash: params.undoTokenHash,
130
+ };
131
+ // nja-lint-ignore OD-1
132
+ query.query += `
133
+ MATCH (user:User {id: $userId})
134
+ SET user.deletionRequestedAt = datetime($requestedAt),
135
+ user.deletionScheduledAt = datetime($scheduledAt),
136
+ user.deletionUndoTokenHash = $undoTokenHash,
137
+ user.updatedAt = datetime()
138
+ `;
139
+ await this.neo4j.writeOne(query);
140
+ });
141
+ }
142
+ async clearRequest(params) {
143
+ await this._unscoped(async () => {
144
+ const query = this.neo4j.initQuery();
145
+ query.queryParams = { ...query.queryParams, userId: params.userId };
146
+ // nja-lint-ignore OD-1
147
+ query.query += `
148
+ MATCH (user:User {id: $userId})
149
+ REMOVE user.deletionRequestedAt, user.deletionScheduledAt, user.deletionUndoTokenHash
150
+ SET user.updatedAt = datetime()
151
+ `;
152
+ await this.neo4j.writeOne(query);
153
+ });
154
+ }
155
+ /** Logs the user out everywhere: every Auth and AuthCode, and every OAuth token revoked. */
156
+ async deleteSessions(params) {
157
+ await this._unscoped(async () => {
158
+ const query = this.neo4j.initQuery();
159
+ query.queryParams = { ...query.queryParams, userId: params.userId };
160
+ // nja-lint-ignore OD-1
161
+ query.query += `
162
+ MATCH (user:User {id: $userId})
163
+ CALL {
164
+ WITH user
165
+ MATCH (user)-[:HAS_AUTH]->(:Auth)-[:HAS_AUTH_CODE]->(code:AuthCode)
166
+ DETACH DELETE code
167
+ }
168
+ CALL {
169
+ WITH user
170
+ MATCH (user)-[:HAS_AUTH]->(auth:Auth)
171
+ DETACH DELETE auth
172
+ }
173
+ WITH user
174
+ ${(0, revoke_user_oauth_tokens_query_1.revokeUserOAuthTokensClause)("user")}
175
+ RETURN user.id AS userId
176
+ `;
177
+ await this.neo4j.writeOne(query);
178
+ });
179
+ }
180
+ };
181
+ exports.AccountDeletionRepository = AccountDeletionRepository;
182
+ exports.AccountDeletionRepository = AccountDeletionRepository = __decorate([
183
+ (0, common_1.Injectable)(),
184
+ __metadata("design:paramtypes", [neo4j_service_1.Neo4jService, security_service_1.SecurityService, nestjs_cls_1.ClsService])
185
+ ], AccountDeletionRepository);
186
+ //# sourceMappingURL=account-deletion.repository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-deletion.repository.js","sourceRoot":"","sources":["../../../../src/foundations/account-deletion/repositories/account-deletion.repository.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ import { ClsService } from "nestjs-cls";
2
+ import { AbstractRepository } from "../../../core/neo4j/abstracts/abstract.repository";
3
+ import { Neo4jService } from "../../../core/neo4j/services/neo4j.service";
4
+ import { SecurityService } from "../../../core/security/services/security.service";
5
+ import { User, UserDescriptor } from "../../user/entities/user";
6
+ export declare class AccountDeletionWipeRepository extends AbstractRepository<User, typeof UserDescriptor.relationships> {
7
+ protected readonly descriptor: import("../../..").EntityDescriptor<User, {
8
+ role: {
9
+ model: import("../../..").DataMeta;
10
+ direction: "out";
11
+ relationship: string;
12
+ cardinality: "many";
13
+ dtoKey: string;
14
+ readOnly: true;
15
+ };
16
+ company: {
17
+ model: import("../../..").DataModelInterface<import("../..").Company>;
18
+ direction: "out";
19
+ relationship: string;
20
+ cardinality: "one";
21
+ };
22
+ module: {
23
+ model: import("../../..").DataMeta;
24
+ direction: "out";
25
+ relationship: string;
26
+ cardinality: "many";
27
+ dtoKey: string;
28
+ };
29
+ }>;
30
+ constructor(neo4j: Neo4jService, securityService: SecurityService, clsService: ClsService);
31
+ deleteUserLinkedNodes(params: {
32
+ userId: string;
33
+ batchSize: number;
34
+ }): Promise<void>;
35
+ detachUserFromForeignNodes(params: {
36
+ userId: string;
37
+ batchSize: number;
38
+ }): Promise<void>;
39
+ deleteEmailKeyed(params: {
40
+ email: string;
41
+ userId: string;
42
+ batchSize: number;
43
+ }): Promise<void>;
44
+ deleteStripeNodes(params: {
45
+ companyId: string;
46
+ batchSize: number;
47
+ }): Promise<void>;
48
+ deleteCompanyNodes(params: {
49
+ companyId: string;
50
+ batchSize: number;
51
+ }): Promise<void>;
52
+ deleteDisconnectedKnowledge(params: {
53
+ batchSize: number;
54
+ }): Promise<void>;
55
+ /**
56
+ * Deletes the Company node and its Memberships in one transaction (a company has a handful
57
+ * of memberships; an orphaned Membership would read as a platform membership). Remaining
58
+ * edges, referrals included, go with the Company.
59
+ */
60
+ deleteCompanyShell(params: {
61
+ companyId: string;
62
+ }): Promise<void>;
63
+ /** Hard-deletes the User node. Last step of an account wipe. */
64
+ deleteUserNode(params: {
65
+ userId: string;
66
+ }): Promise<void>;
67
+ /** Ids of the users of a company (legacy BELONGS_TO edge or a Membership), members first. */
68
+ findCompanyUserIds(params: {
69
+ companyId: string;
70
+ }): Promise<string[]>;
71
+ private _companyDelete;
72
+ /** Seeds a query with only the parameters it needs (never the CLS company / user). */
73
+ private _wipeQuery;
74
+ /** Runs one batch per transaction until a batch deletes nothing. */
75
+ private _deleteInBatches;
76
+ }
77
+ //# sourceMappingURL=account-deletion.wipe.repository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-deletion.wipe.repository.d.ts","sourceRoot":"","sources":["../../../../src/foundations/account-deletion/repositories/account-deletion.wipe.repository.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,mDAAmD,CAAC;AACvF,OAAO,EAAE,YAAY,EAAE,MAAM,4CAA4C,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,kDAAkD,CAAC;AACnF,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AA8EhE,qBACa,6BAA8B,SAAQ,kBAAkB,CAAC,IAAI,EAAE,OAAO,cAAc,CAAC,aAAa,CAAC;IAC9G,SAAS,CAAC,QAAQ,CAAC,UAAU;;;;;;;;;;;;;;;;;;;;;;OAAkB;gBAEnC,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,UAAU,EAAE,UAAU;IAInF,qBAAqB,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBnF,0BAA0B,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAcxF,gBAAgB,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB7F,iBAAiB,CAAC,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAMlF,kBAAkB,CAAC,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAMnF,2BAA2B,CAAC,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB/E;;;;OAIG;IACG,kBAAkB,CAAC,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IActE,gEAAgE;IAC1D,cAAc,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAW/D,6FAA6F;IACvF,kBAAkB,CAAC,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAyB1E,OAAO,CAAC,cAAc;IAatB,sFAAsF;IACtF,OAAO,CAAC,UAAU;IAYlB,oEAAoE;YACtD,gBAAgB;CAa/B"}
@@ -0,0 +1,243 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.AccountDeletionWipeRepository = void 0;
13
+ // Only caller: AccountDeletionProcessor (through AccountWipeService and FullCompanyDeletionService), never an HTTP request.
14
+ //
15
+ // Owner-approved exception (Carlo, 2026-10-25, recorded in the account-deletion plan):
16
+ // - The wipe deletes nodes of every label that hang off one user or one company, across
17
+ // companies, so it matches by an explicit user / company id instead of buildDefaultMatch().
18
+ // - initQuery() is not used: in the processor the CLS carries companyId and userId, and
19
+ // initQuery() would prepend a currentUser-BELONGS_TO-company match that stops matching as
20
+ // soon as the company or the user is gone, turning every later step into a silent no-op.
21
+ // - Bulk deletes read their own scalar "deleted" count, which readOne()/readMany() cannot
22
+ // map, so they run through executeInTransaction (writeOne returns null without a serialiser).
23
+ // - Every id is a query parameter. Every bulk delete runs LIMIT $batchSize (a neo4j Integer,
24
+ // WIPE_BATCH_SIZE = 1000) per transaction and loops until a batch deletes 0. The patterns
25
+ // interpolated into the query text are constants of this file, never request input.
26
+ const common_1 = require("@nestjs/common");
27
+ const neo4j_driver_1 = require("neo4j-driver");
28
+ const nestjs_cls_1 = require("nestjs-cls");
29
+ const abstract_repository_1 = require("../../../core/neo4j/abstracts/abstract.repository");
30
+ const neo4j_service_1 = require("../../../core/neo4j/services/neo4j.service");
31
+ const security_service_1 = require("../../../core/security/services/security.service");
32
+ const user_1 = require("../../user/entities/user");
33
+ /**
34
+ * Nodes owned by the user, deleted with the account. Each pattern binds the node to delete
35
+ * as `n`; `u` is the user. Order matters: children before the node they hang off.
36
+ */
37
+ const USER_LINKED_PATTERNS = [
38
+ "(u)-[:HAS_AUTH]->(:Auth)-[:HAS_AUTH_CODE]->(n)",
39
+ "(u)-[:HAS_AUTH]->(n)",
40
+ "(u)-[:HAS_TOTP_AUTHENTICATOR]->(n)",
41
+ "(u)-[:HAS_BACKUP_CODE]->(n)",
42
+ "(u)-[:HAS_TWO_FACTOR_CONFIG]->(n)",
43
+ "(u)-[:HAS_PENDING_TWO_FACTOR]->(n)",
44
+ "(u)-[:HAS_PASSKEY]->(n)",
45
+ "(u)-[:OWNS_CLIENT]->(:OAuthClient)-[:ISSUED_ACCESS_TOKEN]->(:OAuthAccessToken)-[:LINKED_REFRESH]->(n)",
46
+ "(u)-[:OWNS_CLIENT]->(:OAuthClient)-[:ISSUED_ACCESS_TOKEN|ISSUED_CODE]->(n)",
47
+ "(u)-[:OWNS_CLIENT]->(n)",
48
+ "(u)-[:AUTHORIZED_CODE]->(n)",
49
+ "(u)-[:HAS_ACCESS_TOKEN]->(:OAuthAccessToken)-[:LINKED_REFRESH]->(n)",
50
+ "(u)-[:HAS_ACCESS_TOKEN]->(n)",
51
+ "(n:OAuthRefreshToken {userId: u.id})",
52
+ "(n:OAuthAccessToken {userId: u.id})",
53
+ "(u)-[:HAS_DISCORD]->(n)",
54
+ "(u)-[:HAS_GOOGLE]->(n)",
55
+ "(u)-[:HAS_PUSH]->(n)",
56
+ "(n:Notification)-[:TRIGGERED_FOR]->(u)",
57
+ "(u)-[:PERFORMED]->(n)",
58
+ "(n:AnalyticsPageView)-[:IN_SESSION]->(:AnalyticsSession)-[:BY_USER]->(u)",
59
+ "(n:AnalyticsSession)-[:BY_USER]->(u)",
60
+ "(u)-[:HAS_MEMBERSHIP]->(n:Membership)",
61
+ ];
62
+ /** Edges from the user to nodes that belong to someone else: the edge goes, the node stays. */
63
+ const FOREIGN_EDGE_TYPES = "TRIGGERED_BY|CREATED_BY|CREATED|PUBLISHED|AUTHORED";
64
+ /** Email-keyed nodes. `$email` and `$userId` are parameters. */
65
+ const EMAIL_KEYED_MATCHES = [
66
+ "MATCH (n:Waitlist) WHERE ($email <> '' AND toLower(n.email) = toLower($email)) OR n.userId = $userId",
67
+ "MATCH (n:EmailAutomationEnrollment) WHERE $email <> '' AND toLower(n.email) = toLower($email)",
68
+ "MATCH (n:EmailOptOut) WHERE $email <> '' AND toLower(n.email) = toLower($email)",
69
+ ];
70
+ /**
71
+ * Stripe nodes of the company's customer, children first. StripeInvoice has no Company edge
72
+ * (it hangs off the customer and the subscription), so the company sweep alone would orphan it.
73
+ */
74
+ const STRIPE_PATTERNS = [
75
+ "(company)<-[:BELONGS_TO]-(:StripeCustomer)<-[:BELONGS_TO]-(n:StripeInvoice)",
76
+ "(company)<-[:BELONGS_TO]-(:StripeCustomer)<-[:BELONGS_TO]-(:StripeSubscription)<-[:FOR_SUBSCRIPTION]-(n:StripeInvoice)",
77
+ "(company)<-[:BELONGS_TO]-(:StripeCustomer)<-[:BELONGS_TO]-(:StripeSubscription)<-[:BELONGS_TO]-(n:StripeUsageRecord)",
78
+ "(company)<-[:BELONGS_TO]-(:StripeCustomer)--(n:StripePaymentMethod)",
79
+ "(company)<-[:BELONGS_TO]-(:StripeCustomer)<-[:BELONGS_TO]-(n:StripeSubscription)",
80
+ "(company)<-[:BELONGS_TO]-(n:StripeCustomer)",
81
+ ];
82
+ /**
83
+ * Company nodes. Users are never swept here: placeholders are deleted one by one with their
84
+ * user-linked nodes, and members keep their node (the account-deletion flow handles them).
85
+ * Memberships are not swept here either: they go with the Company in deleteCompanyShell, so a
86
+ * crash half way leaves the member still recognised as the company's only member on re-run.
87
+ */
88
+ const COMPANY_PATTERNS = [
89
+ "(company)<-[:BELONGS_TO]-(owner)-[:HAS_CHUNK]->(n:Chunk) WHERE NOT owner:User",
90
+ "(company)<-[:BELONGS_TO]-(n) WHERE NOT n:User AND NOT n:Company",
91
+ ];
92
+ /**
93
+ * Shared knowledge left with nothing pointing at it. AtomicFacts are shared across companies
94
+ * (md5 id), so only facts no chunk links any more go; KeyConcepts go only when nothing at all
95
+ * (fact, community, key-concept relationship) is linked to them.
96
+ */
97
+ const DISCONNECTED_KNOWLEDGE_MATCHES = [
98
+ "MATCH (n:AtomicFact) WHERE NOT (n)<-[:HAS_ATOMIC_FACT]-() // nja-lint-ignore wipe-exception",
99
+ "MATCH (n:KeyConcept) WHERE NOT (n)--() // nja-lint-ignore wipe-exception",
100
+ ];
101
+ let AccountDeletionWipeRepository = class AccountDeletionWipeRepository extends abstract_repository_1.AbstractRepository {
102
+ constructor(neo4j, securityService, clsService) {
103
+ super(neo4j, securityService, clsService);
104
+ this.descriptor = user_1.UserDescriptor;
105
+ }
106
+ async deleteUserLinkedNodes(params) {
107
+ for (const pattern of USER_LINKED_PATTERNS) {
108
+ await this._deleteInBatches(this._wipeQuery({ userId: params.userId, batchSize: params.batchSize }, `
109
+ MATCH (u:User {id: $userId}) // nja-lint-ignore wipe-exception
110
+ MATCH ${pattern}
111
+ WITH DISTINCT n LIMIT $batchSize
112
+ DETACH DELETE n
113
+ RETURN count(*) AS deleted
114
+ `));
115
+ }
116
+ }
117
+ async detachUserFromForeignNodes(params) {
118
+ await this._deleteInBatches(this._wipeQuery({ userId: params.userId, batchSize: params.batchSize }, `
119
+ MATCH (u:User {id: $userId})-[r:${FOREIGN_EDGE_TYPES}]-() // nja-lint-ignore wipe-exception
120
+ WITH r LIMIT $batchSize
121
+ DELETE r
122
+ RETURN count(*) AS deleted
123
+ `));
124
+ }
125
+ async deleteEmailKeyed(params) {
126
+ for (const match of EMAIL_KEYED_MATCHES) {
127
+ await this._deleteInBatches(this._wipeQuery({ email: params.email ?? "", userId: params.userId, batchSize: params.batchSize }, `
128
+ ${match} // nja-lint-ignore wipe-exception
129
+ WITH n LIMIT $batchSize
130
+ DETACH DELETE n
131
+ RETURN count(*) AS deleted
132
+ `));
133
+ }
134
+ }
135
+ async deleteStripeNodes(params) {
136
+ for (const pattern of STRIPE_PATTERNS) {
137
+ await this._deleteInBatches(this._companyDelete(params, pattern));
138
+ }
139
+ }
140
+ async deleteCompanyNodes(params) {
141
+ for (const pattern of COMPANY_PATTERNS) {
142
+ await this._deleteInBatches(this._companyDelete(params, pattern));
143
+ }
144
+ }
145
+ async deleteDisconnectedKnowledge(params) {
146
+ for (const match of DISCONNECTED_KNOWLEDGE_MATCHES) {
147
+ await this._deleteInBatches(this._wipeQuery({ batchSize: params.batchSize }, `
148
+ ${match}
149
+ WITH n LIMIT $batchSize
150
+ DETACH DELETE n
151
+ RETURN count(*) AS deleted
152
+ `));
153
+ }
154
+ }
155
+ /**
156
+ * Deletes the Company node and its Memberships in one transaction (a company has a handful
157
+ * of memberships; an orphaned Membership would read as a platform membership). Remaining
158
+ * edges, referrals included, go with the Company.
159
+ */
160
+ async deleteCompanyShell(params) {
161
+ const { query, queryParams } = this._wipeQuery({ companyId: params.companyId }, `
162
+ MATCH (company:Company {id: $companyId}) // nja-lint-ignore wipe-exception
163
+ OPTIONAL MATCH (company)<-[:IN_COMPANY]-(membership:Membership)
164
+ DETACH DELETE membership
165
+ WITH DISTINCT company
166
+ DETACH DELETE company
167
+ `);
168
+ await this.neo4j.executeInTransaction([{ query, params: queryParams }]);
169
+ }
170
+ /** Hard-deletes the User node. Last step of an account wipe. */
171
+ async deleteUserNode(params) {
172
+ const { query, queryParams } = this._wipeQuery({ userId: params.userId }, `
173
+ MATCH (u:User {id: $userId}) // nja-lint-ignore wipe-exception
174
+ DETACH DELETE u
175
+ `);
176
+ await this.neo4j.executeInTransaction([{ query, params: queryParams }]);
177
+ }
178
+ /** Ids of the users of a company (legacy BELONGS_TO edge or a Membership), members first. */
179
+ async findCompanyUserIds(params) {
180
+ const { query, queryParams } = this._wipeQuery({ companyId: params.companyId }, `
181
+ MATCH (company:Company {id: $companyId}) // nja-lint-ignore wipe-exception
182
+ CALL {
183
+ WITH company
184
+ MATCH (u:User)-[:BELONGS_TO]->(company)
185
+ RETURN u
186
+ UNION
187
+ WITH company
188
+ MATCH (u:User)-[:HAS_MEMBERSHIP]->(:Membership)-[:IN_COMPANY]->(company)
189
+ RETURN u
190
+ }
191
+ WITH DISTINCT company, u
192
+ RETURN u.id AS id,
193
+ EXISTS { (u)-[:HAS_MEMBERSHIP]->(:Membership)-[:IN_COMPANY]->(company) } AS isMember
194
+ ORDER BY isMember DESC, id ASC
195
+ `);
196
+ // nja-lint-ignore wipe-exception
197
+ const result = await this.neo4j.read(query, queryParams);
198
+ return result.records.map((record) => String(record.get("id")));
199
+ }
200
+ _companyDelete(params, pattern) {
201
+ return this._wipeQuery({ companyId: params.companyId, batchSize: params.batchSize }, `
202
+ MATCH (company:Company {id: $companyId}) // nja-lint-ignore wipe-exception
203
+ MATCH ${pattern}
204
+ WITH DISTINCT n LIMIT $batchSize
205
+ DETACH DELETE n
206
+ RETURN count(*) AS deleted
207
+ `);
208
+ }
209
+ /** Seeds a query with only the parameters it needs (never the CLS company / user). */
210
+ _wipeQuery(params, query) {
211
+ const queryParams = {};
212
+ if (params.userId !== undefined)
213
+ queryParams.userId = params.userId;
214
+ if (params.companyId !== undefined)
215
+ queryParams.companyId = params.companyId;
216
+ if (params.email !== undefined)
217
+ queryParams.email = params.email;
218
+ if (params.batchSize !== undefined)
219
+ queryParams.batchSize = (0, neo4j_driver_1.int)(params.batchSize);
220
+ return { query, queryParams };
221
+ }
222
+ /** Runs one batch per transaction until a batch deletes nothing. */
223
+ async _deleteInBatches(wipeQuery) {
224
+ let total = 0;
225
+ for (;;) {
226
+ // nja-lint-ignore wipe-exception
227
+ const [result] = await this.neo4j.executeInTransaction([
228
+ { query: wipeQuery.query, params: wipeQuery.queryParams },
229
+ ]);
230
+ const raw = result?.records?.[0]?.get("deleted");
231
+ const deleted = (0, neo4j_driver_1.isInt)(raw) ? raw.toNumber() : Number(raw ?? 0);
232
+ if (!deleted)
233
+ return total;
234
+ total += deleted;
235
+ }
236
+ }
237
+ };
238
+ exports.AccountDeletionWipeRepository = AccountDeletionWipeRepository;
239
+ exports.AccountDeletionWipeRepository = AccountDeletionWipeRepository = __decorate([
240
+ (0, common_1.Injectable)(),
241
+ __metadata("design:paramtypes", [neo4j_service_1.Neo4jService, security_service_1.SecurityService, nestjs_cls_1.ClsService])
242
+ ], AccountDeletionWipeRepository);
243
+ //# sourceMappingURL=account-deletion.wipe.repository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-deletion.wipe.repository.js","sourceRoot":"","sources":["../../../../src/foundations/account-deletion/repositories/account-deletion.wipe.repository.ts"],"names":[],"mappings":"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"}