@plusscommunities/pluss-core-aws 2.0.26-auth.0 → 2.0.26-beta.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 (94) hide show
  1. package/aws/getDefaultEmailAddress.js +21 -21
  2. package/aws/getEmailService.js +16 -16
  3. package/aws/getEmailServiceInfo.js +26 -26
  4. package/aws/sendEmail.js +31 -31
  5. package/config.js +1 -1
  6. package/db/activity/publishActivity.js +22 -22
  7. package/db/analytics/checkActivityExists.js +15 -15
  8. package/db/analytics/logAnalyticsActivity.js +69 -37
  9. package/db/analytics/scheduleOldAggregation.js +14 -14
  10. package/db/auth/getSiteSetting.js +12 -12
  11. package/db/auth/getSiteUserTypes.js +16 -16
  12. package/db/auth/getUserAuth.js +13 -13
  13. package/db/automatedactions/getActionBySiteTrigger.js +9 -9
  14. package/db/common/deleteRef.js +21 -21
  15. package/db/common/editRef.js +36 -36
  16. package/db/common/getRef.js +23 -23
  17. package/db/common/getTableCount.js +18 -18
  18. package/db/common/indexQuery.js +17 -17
  19. package/db/common/indexQueryRecursive.js +20 -20
  20. package/db/common/scanRef.js +18 -18
  21. package/db/common/scanRefRecursive.js +20 -20
  22. package/db/common/updateAttribute.js +27 -27
  23. package/db/common/updateRef.js +20 -20
  24. package/db/linkedUsers/getLinkedBy.js +21 -21
  25. package/db/linkedUsers/getLinkedTo.js +21 -21
  26. package/db/notifications/deleteNotificationsByEntity.js +21 -21
  27. package/db/notifications/getNotificationSetting.js +14 -14
  28. package/db/notifications/publishNotifications.js +39 -39
  29. package/db/postgres/getKnex.js +214 -0
  30. package/db/postgres/safeError.js +40 -0
  31. package/db/property/Property.js +128 -0
  32. package/db/property/PropertyRepository.js +86 -0
  33. package/db/scheduledActions/deleteActionQueue.js +1 -1
  34. package/db/scheduledActions/getActionQueueByEntityId.js +10 -10
  35. package/db/scheduledActions/getActionQueueByEntityKey.js +9 -9
  36. package/db/scheduledActions/getActionQueueById.js +9 -9
  37. package/db/scheduledActions/getActionQueueByTriggerAt.js +14 -14
  38. package/db/scheduledActions/updateActionQueue.js +29 -29
  39. package/db/strings/getString.js +20 -20
  40. package/db/strings/logUpdate.js +18 -18
  41. package/db/templates/getTemplateById.js +1 -1
  42. package/db/templates/getTemplatesList.js +10 -10
  43. package/db/templates/updateTemplate.js +9 -9
  44. package/db/users/getRole.js +1 -1
  45. package/db/users/getUser.js +9 -9
  46. package/db/users/getUserByEmail.js +17 -17
  47. package/helper/audience/filterByAudienceType.js +27 -27
  48. package/helper/audience/filterOnAudienceType.js +26 -26
  49. package/helper/audience/getAudience.js +187 -187
  50. package/helper/audience/getMatchingAudienceTypes.js +21 -21
  51. package/helper/audience/getMatchingAudienceTypesFromPreview.js +60 -60
  52. package/helper/audience/getMatchingTags.js +15 -15
  53. package/helper/audience/isValidAudience.js +20 -20
  54. package/helper/auth/checkTokenBlacklist.js +17 -17
  55. package/helper/auth/getApiKeyFromReq.js +2 -2
  56. package/helper/auth/getSessionUser.js +70 -85
  57. package/helper/auth/getSessionUserFromReq.js +2 -2
  58. package/helper/auth/getSessionUserFromReqAuthKey.js +11 -11
  59. package/helper/auth/validateApiKey.js +32 -32
  60. package/helper/auth/validateMasterAuth.js +174 -174
  61. package/helper/auth/validateSiteAccess.js +12 -12
  62. package/helper/auth/validateSiteSetting.js +7 -7
  63. package/helper/auth/validateUserLoggedIn.js +19 -19
  64. package/helper/createGuid.js +5 -5
  65. package/helper/generateJsonResponse.js +27 -27
  66. package/helper/getUserPreview.js +57 -57
  67. package/helper/getUserPreviewFromHeader.js +17 -17
  68. package/helper/getUserPreviewFromReq.js +17 -17
  69. package/helper/hqPublishing.js +465 -0
  70. package/helper/index.js +28 -28
  71. package/helper/notifySiteConfigs.js +67 -52
  72. package/helper/opengraph/getOpenGraph.js +12 -12
  73. package/helper/rates/checkRateLimit.js +38 -38
  74. package/helper/requestToSource.js +10 -10
  75. package/helper/sendEmail.js +120 -120
  76. package/helper/templates/replacePlaceHolders.js +29 -29
  77. package/helper/time/getLocalTimestamp.js +18 -18
  78. package/helper/time/getSiteTimezone.js +11 -11
  79. package/helper/triggerAutomatedAction.js +25 -25
  80. package/helper/userToUserPreview.js +23 -23
  81. package/helper/users/getUserTypesByPermission.js +24 -24
  82. package/helper/users/getUsersByPermission.js +20 -20
  83. package/notification/prepNotification.js +144 -144
  84. package/notification/sendNotifications.js +166 -166
  85. package/package.json +37 -40
  86. package/templates/supportTicketEmails.js +8 -8
  87. package/helper/auth/context/AuthenticationContext.js +0 -50
  88. package/helper/auth/context/AuthenticationStrategy.js +0 -20
  89. package/helper/auth/context/auth0/Strategy.js +0 -12
  90. package/helper/auth/context/auth0/functions/decodeAccessToken.js +0 -102
  91. package/helper/auth/context/auth0/functions/getSessionUser.js +0 -21
  92. package/helper/auth/context/boltonclarke/Strategy.js +0 -10
  93. package/helper/auth/context/cognito/Strategy.js +0 -12
  94. package/helper/auth/context/cognito/functions/getSessionUser.js +0 -76
@@ -5,50 +5,50 @@ const sendNotifications = require("../../notification/sendNotifications");
5
5
  const prepNotification = require("../../notification/prepNotification");
6
6
 
7
7
  const saveNotification = (receiver, notification) => {
8
- const toSave = { ...notification };
8
+ const toSave = { ...notification };
9
9
 
10
- toSave.UserId = receiver;
11
- toSave.RowId = getMultiRowId([toSave.Site, toSave.Type, toSave.Id, receiver]);
12
- toSave.Seen = false;
13
- toSave.Clicked = false;
10
+ toSave.UserId = receiver;
11
+ toSave.RowId = getMultiRowId([toSave.Site, toSave.Type, toSave.Id, receiver]);
12
+ toSave.Seen = false;
13
+ toSave.Clicked = false;
14
14
 
15
- updateRef(`notifications`, toSave)
16
- .then(() => {
17
- //resolve();
18
- })
19
- .catch((error) => {
20
- //reject(error);
21
- });
15
+ updateRef(`notifications`, toSave)
16
+ .then(() => {
17
+ //resolve();
18
+ })
19
+ .catch((error) => {
20
+ //reject(error);
21
+ });
22
22
  };
23
23
 
24
24
  module.exports = async (
25
- receivers,
26
- type,
27
- site,
28
- id,
29
- data,
30
- sendPush,
31
- config = { type: "app", id: null, ignoreMute: false, raiseError: false }
25
+ receivers,
26
+ type,
27
+ site,
28
+ id,
29
+ data,
30
+ sendPush,
31
+ config = { type: "app", id: null, ignoreMute: false, raiseError: false },
32
32
  ) => {
33
- const notification = {};
34
- notification.Timestamp = moment.utc().valueOf();
35
- notification.Type = type;
36
- notification.Site = site;
37
- notification.Id = id;
38
- notification.Data = data;
33
+ const notification = {};
34
+ notification.Timestamp = moment.utc().valueOf();
35
+ notification.Type = type;
36
+ notification.Site = site;
37
+ notification.Id = id;
38
+ notification.Data = data;
39
39
 
40
- receivers.forEach((receiver) => {
41
- saveNotification(receiver, notification);
42
- });
43
- if (sendPush) {
44
- const notiData = prepNotification(notification);
45
- await sendNotifications(
46
- receivers,
47
- notiData.Text,
48
- type,
49
- id,
50
- notiData,
51
- config
52
- );
53
- }
40
+ receivers.forEach((receiver) => {
41
+ saveNotification(receiver, notification);
42
+ });
43
+ if (sendPush) {
44
+ const notiData = prepNotification(notification);
45
+ await sendNotifications(
46
+ receivers,
47
+ notiData.Text,
48
+ type,
49
+ id,
50
+ notiData,
51
+ config,
52
+ );
53
+ }
54
54
  };
@@ -0,0 +1,214 @@
1
+ const AWS = require("aws-sdk");
2
+ const knex = require("knex");
3
+ const safeError = require("./safeError");
4
+ const { log, generateLogId } = require("../../helper");
5
+
6
+ const secrets = new AWS.SecretsManager();
7
+
8
+ let db;
9
+ let pending;
10
+ let generation = 0;
11
+
12
+ const SUPERSEDED_INITIALISATION = "POSTGRES_CLIENT_SUPERSEDED";
13
+ const STALE_CLIENT_DESTROY_FAILED = "POSTGRES_STALE_CLIENT_DESTROY_FAILED";
14
+
15
+ function poolMax() {
16
+ return Number(process.env.POSTGRES_POOL_MAX || 1);
17
+ }
18
+
19
+ function secretArn() {
20
+ return process.env.POSTGRES_SECRET_ARN;
21
+ }
22
+
23
+ let sslWarningEmitted = false;
24
+
25
+ function ssl() {
26
+ if (process.env.POSTGRES_SSL === "disable") {
27
+ return false;
28
+ }
29
+
30
+ const rejectUnauthorized =
31
+ process.env.POSTGRES_SSL_REJECT_UNAUTHORIZED !== "false";
32
+
33
+ // Certificate verification is on by default. If a local/dev environment needs
34
+ // to opt out, log that once so the downgrade is visible in runtime logs.
35
+ if (!rejectUnauthorized && !sslWarningEmitted) {
36
+ sslWarningEmitted = true;
37
+ log("getKnex", "SslVerificationDisabled", {
38
+ reason:
39
+ "POSTGRES_SSL_REJECT_UNAUTHORIZED=false disables server certificate verification",
40
+ });
41
+ }
42
+
43
+ return { rejectUnauthorized };
44
+ }
45
+
46
+ const SECRET_FIELDS = {
47
+ host: "host",
48
+ username: "username",
49
+ password: "password",
50
+ dbname: "dbname",
51
+ };
52
+
53
+ async function secretConnection(arn) {
54
+ let result;
55
+ try {
56
+ result = await secrets.getSecretValue({ SecretId: arn }).promise();
57
+ } catch (error) {
58
+ // AWS SDK fetch errors describe the API call, not the secret body. Keep the
59
+ // message so the ARN points operators at the failed secret.
60
+ throw new Error(`Failed to load secret ${arn}: ${error.message}`);
61
+ }
62
+
63
+ let secret;
64
+ try {
65
+ secret = JSON.parse(result.SecretString);
66
+ } catch (error) {
67
+ // V8 SyntaxError messages include a slice of the parsed input. If SecretString
68
+ // is a bare connection string, error.message would leak credentials.
69
+ throw new Error(`Failed to parse secret ${arn} as JSON`);
70
+ }
71
+
72
+ if (!secret || typeof secret !== "object") {
73
+ // JSON.parse("null") or a bare primitive would otherwise become a TypeError
74
+ // without ARN context. Treat it as a bad secret body.
75
+ throw new Error(`Failed to parse secret ${arn} as JSON`);
76
+ }
77
+
78
+ const missing = Object.keys(SECRET_FIELDS).filter((field) => !secret[field]);
79
+ if (missing.length) {
80
+ // A secret missing host makes node-postgres silently dial localhost; a
81
+ // missing username/dbname falls back to the OS user / default db. Fail
82
+ // loudly with the field names (never their values).
83
+ throw new Error(`Secret ${arn} is missing fields: ${missing.join(", ")}`);
84
+ }
85
+
86
+ return {
87
+ host: secret.host,
88
+ port: secret.port ? Number(secret.port) : 5432,
89
+ database: secret.dbname,
90
+ user: secret.username,
91
+ password: secret.password,
92
+ };
93
+ }
94
+
95
+ function configSourceVar() {
96
+ if (process.env.POSTGRES_SECRET_ARN) return "POSTGRES_SECRET_ARN";
97
+ return undefined;
98
+ }
99
+
100
+ async function connection() {
101
+ const arn = secretArn();
102
+ if (!arn) {
103
+ throw new Error("PostgreSQL connection requires POSTGRES_SECRET_ARN");
104
+ }
105
+ return secretConnection(arn);
106
+ }
107
+
108
+ async function createKnex(overrides = {}) {
109
+ const database = await connection();
110
+ const max = overrides.poolMax || poolMax();
111
+
112
+ // This log is not gated by enableLog. Log the source and host only, never the
113
+ // secret ARN or credentials.
114
+ log("getKnex", "CreatingClient", {
115
+ source: configSourceVar(),
116
+ host: database.host || "url",
117
+ max,
118
+ });
119
+
120
+ const instance = knex({
121
+ client: "pg",
122
+ // Knex otherwise mutates error.message to inline bound values in the
123
+ // compiled SQL. Keep thrown errors parameterized; log paths use safeError().
124
+ compileSqlOnError: false,
125
+ connection: {
126
+ ...database,
127
+ ssl: ssl(),
128
+ connectionTimeoutMillis: 10000,
129
+ },
130
+ pool: {
131
+ min: 0,
132
+ max,
133
+ idleTimeoutMillis: 60000,
134
+ acquireTimeoutMillis: 10000,
135
+ },
136
+ });
137
+
138
+ instance.on("query-error", (error) => {
139
+ // Use a fresh id per event. Reusing the pool-creation id would put every
140
+ // query error from this instance under one trace id.
141
+ log("getKnex", "QueryError", safeError(error), generateLogId());
142
+ });
143
+
144
+ return instance;
145
+ }
146
+
147
+ async function getKnex() {
148
+ if (db) return db;
149
+ if (!pending) {
150
+ const pendingGeneration = generation;
151
+ pending = createKnex()
152
+ .then(async (instance) => {
153
+ if (generation !== pendingGeneration) {
154
+ try {
155
+ await instance.destroy();
156
+ } catch (destroyError) {
157
+ log("getKnex", "DestroyStaleError", safeError(destroyError));
158
+ const error = new Error(
159
+ `Failed to destroy superseded PostgreSQL client: ${destroyError.message}`,
160
+ );
161
+ error.code = STALE_CLIENT_DESTROY_FAILED;
162
+ error.cause = destroyError;
163
+ throw error;
164
+ }
165
+ const error = new Error(
166
+ "PostgreSQL client initialisation was superseded by destroy()",
167
+ );
168
+ error.code = SUPERSEDED_INITIALISATION;
169
+ throw error;
170
+ }
171
+ db = instance;
172
+ return instance;
173
+ })
174
+ .catch((error) => {
175
+ if (generation === pendingGeneration) pending = undefined;
176
+ throw error;
177
+ });
178
+ }
179
+ return pending;
180
+ }
181
+
182
+ getKnex.destroy = async () => {
183
+ const instance = db;
184
+ const initializer = pending;
185
+ generation += 1;
186
+ db = undefined;
187
+ pending = undefined;
188
+ if (instance) {
189
+ await instance.destroy();
190
+ return;
191
+ }
192
+
193
+ if (initializer) {
194
+ try {
195
+ await initializer;
196
+ } catch (error) {
197
+ // A pending initializer can reject because destroy() intentionally
198
+ // superseded it. Surface stale-pool cleanup failures, but keep benign
199
+ // cancellation and unrelated initializer failures out of teardown.
200
+ if (error?.code === STALE_CLIENT_DESTROY_FAILED) throw error;
201
+ }
202
+ }
203
+ };
204
+
205
+ // Create a non-singleton client. The migration runner sets poolMax: 2 because
206
+ // the advisory-lock connection stays checked out while migration queries use a
207
+ // second slot. The caller owns destroy().
208
+ getKnex.create = (overrides) => createKnex(overrides);
209
+
210
+ // Shared so the bootstrap path (provisionDatabaseUsers) honours the same
211
+ // POSTGRES_SSL / POSTGRES_SSL_REJECT_UNAUTHORIZED overrides as the pool.
212
+ getKnex.sslConfig = ssl;
213
+
214
+ module.exports = getKnex;
@@ -0,0 +1,40 @@
1
+ const PG_FIELDS = [
2
+ "code",
3
+ "severity",
4
+ "schema",
5
+ "table",
6
+ "column",
7
+ "constraint",
8
+ "routine",
9
+ ];
10
+
11
+ // knex prefixes driver errors with compiled SQL and inlined bindings:
12
+ // "<compiled sql> - <original message>". Strip through the last " - " so
13
+ // bindings stay out of logs while the useful driver message survives.
14
+ function stripCompiledSql(message) {
15
+ const separator = message.lastIndexOf(" - ");
16
+ return separator === -1 ? message : message.slice(separator + 3);
17
+ }
18
+
19
+ function safeError(error) {
20
+ if (!error || typeof error !== "object") return { message: String(error) };
21
+
22
+ const output = {
23
+ name: error.name,
24
+ };
25
+
26
+ for (const field of PG_FIELDS) {
27
+ if (error[field]) output[field] = error[field];
28
+ }
29
+
30
+ // Keep a message. Structured PG fields cover constraint-class errors, but
31
+ // codes like 42P01 would otherwise lose the missing relation name. detail,
32
+ // hint, and where can carry raw values, so leave them out.
33
+ output.message = stripCompiledSql(String(error.message || error)).slice(
34
+ 0,
35
+ 512,
36
+ );
37
+ return output;
38
+ }
39
+
40
+ module.exports = safeError;
@@ -0,0 +1,128 @@
1
+ /**
2
+ * A property row from pc_property.properties, validated on construction.
3
+ *
4
+ * Build with Property.fromRow(row); the constructor rejects rows that break a
5
+ * domain invariant so a repository never hands a handler an unvalidated record.
6
+ */
7
+ class Property {
8
+ #row;
9
+
10
+ constructor(row) {
11
+ if (!row) {
12
+ throw new Error("Property requires a row");
13
+ }
14
+ Property.assertValid(row);
15
+ this.#row = { ...row };
16
+ }
17
+
18
+ static fromRow(row) {
19
+ return new Property(row);
20
+ }
21
+
22
+ static assertValid(row) {
23
+ if (!row.id) throw new Error("Property requires an id");
24
+ if (!row.site_id) throw new Error("Property requires a site_id");
25
+ if (!row.name) throw new Error("Property requires a name");
26
+ if (row.status !== "active" && row.status !== "archived") {
27
+ throw new Error(`Property has an invalid status: ${row.status}`);
28
+ }
29
+ }
30
+
31
+ get id() {
32
+ return this.#row.id;
33
+ }
34
+
35
+ get site() {
36
+ return this.#row.site_id;
37
+ }
38
+
39
+ get name() {
40
+ return this.#row.name;
41
+ }
42
+
43
+ get propertyType() {
44
+ return this.#row.property_type ?? null;
45
+ }
46
+
47
+ get location() {
48
+ return this.#row.location ?? null;
49
+ }
50
+
51
+ get status() {
52
+ return this.#row.status;
53
+ }
54
+
55
+ get externalId() {
56
+ return this.#row.external_id ?? null;
57
+ }
58
+
59
+ get metadata() {
60
+ return this.#row.metadata ?? {};
61
+ }
62
+
63
+ get createdBy() {
64
+ return this.#row.created_by ?? null;
65
+ }
66
+
67
+ get residentCount() {
68
+ return this.#row.resident_count !== undefined
69
+ ? Number(this.#row.resident_count)
70
+ : undefined;
71
+ }
72
+
73
+ get timeCreated() {
74
+ return this.#row.time_created ?? null;
75
+ }
76
+
77
+ get timeUpdated() {
78
+ return this.#row.time_updated ?? null;
79
+ }
80
+
81
+ get timeDeleted() {
82
+ return this.#row.time_deleted ?? null;
83
+ }
84
+
85
+ get isActive() {
86
+ return this.#row.status === "active";
87
+ }
88
+
89
+ get isArchived() {
90
+ return this.#row.status === "archived";
91
+ }
92
+
93
+ get isDeleted() {
94
+ return this.#row.time_deleted != null;
95
+ }
96
+
97
+ get canLinkResidents() {
98
+ return this.isActive && !this.isDeleted;
99
+ }
100
+
101
+ toRow() {
102
+ return { ...this.#row };
103
+ }
104
+
105
+ toApi() {
106
+ return {
107
+ id: this.#row.id,
108
+ site: this.#row.site_id,
109
+ name: this.#row.name,
110
+ propertyType: this.#row.property_type ?? null,
111
+ location: this.#row.location ?? null,
112
+ status: this.#row.status,
113
+ externalId: this.#row.external_id ?? null,
114
+ metadata: this.#row.metadata ?? {},
115
+ createdBy: this.#row.created_by ?? null,
116
+ // undefined (not null) so the key drops out of the JSON when there is no count.
117
+ residentCount:
118
+ this.#row.resident_count !== undefined
119
+ ? Number(this.#row.resident_count)
120
+ : undefined,
121
+ timeCreated: this.#row.time_created ?? null,
122
+ timeUpdated: this.#row.time_updated ?? null,
123
+ timeDeleted: this.#row.time_deleted ?? null,
124
+ };
125
+ }
126
+ }
127
+
128
+ module.exports = { Property };
@@ -0,0 +1,86 @@
1
+ const getKnex = require("../postgres/getKnex");
2
+ const { log } = require("../../helper");
3
+ const { Property } = require("./Property");
4
+
5
+ // PostgreSQL schema owned by the property service.
6
+ const SCHEMA = "pc_property";
7
+ const PROPERTIES = "properties";
8
+ const PROPERTY_USERS = "property_users";
9
+
10
+ const UUID_PATTERN =
11
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
12
+
13
+ /**
14
+ * Read-only property lookups against the pc_property schema.
15
+ *
16
+ * Writes stay with the property service; extensions only resolve. Methods
17
+ * return Property entities or null — never raw rows.
18
+ */
19
+ class PropertyRepository {
20
+ /**
21
+ * Resolve the property a user is linked to at a site.
22
+ *
23
+ * @param {object} input
24
+ * @param {string} input.site
25
+ * @param {string} input.userId
26
+ * @param {string} [input.logId] - optional trace id
27
+ * @returns {Promise<Property|null>} the linked property, or null when the user has no live link
28
+ * @throws {Error} on query failure, or when a live link references a missing property
29
+ */
30
+ async forUser({ site, userId, logId }) {
31
+ if (!site || !userId) {
32
+ throw new Error("PropertyRepository.forUser requires site and userId");
33
+ }
34
+ log("PropertyRepository_forUser", "Input", { site, userId }, logId);
35
+
36
+ const knex = await getKnex();
37
+ // A user has at most one live property link.
38
+ const link = await knex
39
+ .withSchema(SCHEMA)
40
+ .table(PROPERTY_USERS)
41
+ .where({ user_id: userId, site_id: site, time_deleted: null })
42
+ .first();
43
+ if (!link) return null;
44
+
45
+ const row = await knex
46
+ .withSchema(SCHEMA)
47
+ .table(PROPERTIES)
48
+ .where({ id: link.property_id, site_id: site, time_deleted: null })
49
+ .first();
50
+ if (!row) {
51
+ throw new Error(
52
+ `Property link integrity violation: link ${link.id} references missing property ${link.property_id} for user ${userId}`,
53
+ );
54
+ }
55
+ return Property.fromRow(row);
56
+ }
57
+
58
+ /**
59
+ * Resolve a property by id, scoped to a site.
60
+ *
61
+ * @param {object} input
62
+ * @param {string} input.site
63
+ * @param {string} input.propertyId
64
+ * @param {string} [input.logId] - optional trace id
65
+ * @returns {Promise<Property|null>} the property, or null when none matches at the site
66
+ * @throws {Error} on query failure
67
+ */
68
+ async byId({ site, propertyId, logId }) {
69
+ if (!site || !propertyId) {
70
+ throw new Error("PropertyRepository.byId requires site and propertyId");
71
+ }
72
+ // A malformed id can't match anything; don't let it become a Postgres cast error.
73
+ if (!UUID_PATTERN.test(propertyId)) return null;
74
+ log("PropertyRepository_byId", "Input", { site, propertyId }, logId);
75
+
76
+ const knex = await getKnex();
77
+ const row = await knex
78
+ .withSchema(SCHEMA)
79
+ .table(PROPERTIES)
80
+ .where({ id: propertyId, site_id: site, time_deleted: null })
81
+ .first();
82
+ return row ? Property.fromRow(row) : null;
83
+ }
84
+ }
85
+
86
+ module.exports = { PropertyRepository };
@@ -1,5 +1,5 @@
1
1
  const deleteRef = require("../common/deleteRef");
2
2
 
3
3
  module.exports = (id) => {
4
- return deleteRef("actionqueue", "RowId", id);
4
+ return deleteRef("actionqueue", "RowId", id);
5
5
  };
@@ -1,15 +1,15 @@
1
1
  const indexQuery = require("../common/indexQuery");
2
2
 
3
3
  module.exports = async (entityKey, entityId) => {
4
- const query = {
5
- IndexName: "EntityKeyEntityIdIndex",
6
- KeyConditionExpression: "EntityKey = :entityKey and EntityId = :entityId",
7
- ExpressionAttributeValues: {
8
- ":entityKey": entityKey,
9
- ":entityId": entityId,
10
- },
11
- };
4
+ const query = {
5
+ IndexName: "EntityKeyEntityIdIndex",
6
+ KeyConditionExpression: "EntityKey = :entityKey and EntityId = :entityId",
7
+ ExpressionAttributeValues: {
8
+ ":entityKey": entityKey,
9
+ ":entityId": entityId,
10
+ },
11
+ };
12
12
 
13
- const { Items } = await indexQuery("actionqueue", query);
14
- return Items;
13
+ const { Items } = await indexQuery("actionqueue", query);
14
+ return Items;
15
15
  };
@@ -1,14 +1,14 @@
1
1
  const indexQueryRecursive = require("../common/indexQueryRecursive");
2
2
 
3
3
  module.exports = async (site, entityKey) => {
4
- const query = {
5
- IndexName: "SiteEntityKeyIndex",
6
- KeyConditionExpression: "Site = :site and EntityKey = :entityKey",
7
- ExpressionAttributeValues: {
8
- ":site": site,
9
- ":entityKey": entityKey,
10
- },
11
- };
4
+ const query = {
5
+ IndexName: "SiteEntityKeyIndex",
6
+ KeyConditionExpression: "Site = :site and EntityKey = :entityKey",
7
+ ExpressionAttributeValues: {
8
+ ":site": site,
9
+ ":entityKey": entityKey,
10
+ },
11
+ };
12
12
 
13
- return await indexQueryRecursive("actionqueue", query);
13
+ return await indexQueryRecursive("actionqueue", query);
14
14
  };
@@ -1,13 +1,13 @@
1
1
  const getRef = require("../common/getRef");
2
2
 
3
3
  module.exports = (rowId) => {
4
- return new Promise((resolve, reject) => {
5
- getRef("actionqueue", "RowId", rowId)
6
- .then((ev) => {
7
- resolve(ev);
8
- })
9
- .catch((error) => {
10
- reject(error);
11
- });
12
- });
4
+ return new Promise((resolve, reject) => {
5
+ getRef("actionqueue", "RowId", rowId)
6
+ .then((ev) => {
7
+ resolve(ev);
8
+ })
9
+ .catch((error) => {
10
+ reject(error);
11
+ });
12
+ });
13
13
  };
@@ -1,20 +1,20 @@
1
1
  const indexQueryRecursive = require("../common/indexQueryRecursive");
2
2
 
3
3
  module.exports = async (
4
- site,
5
- minTime = 0,
6
- maxTime = Number.MAX_SAFE_INTEGER
4
+ site,
5
+ minTime = 0,
6
+ maxTime = Number.MAX_SAFE_INTEGER,
7
7
  ) => {
8
- const query = {
9
- IndexName: "SiteTriggerAtIndex",
10
- KeyConditionExpression:
11
- "Site = :site AND TriggerAt BETWEEN :minTime and :maxTime",
12
- ExpressionAttributeValues: {
13
- ":site": site,
14
- ":minTime": minTime,
15
- ":maxTime": maxTime,
16
- },
17
- };
8
+ const query = {
9
+ IndexName: "SiteTriggerAtIndex",
10
+ KeyConditionExpression:
11
+ "Site = :site AND TriggerAt BETWEEN :minTime and :maxTime",
12
+ ExpressionAttributeValues: {
13
+ ":site": site,
14
+ ":minTime": minTime,
15
+ ":maxTime": maxTime,
16
+ },
17
+ };
18
18
 
19
- return await indexQueryRecursive("actionqueue", query);
19
+ return await indexQueryRecursive("actionqueue", query);
20
20
  };