@talkpilot/core-db 1.3.83 → 1.3.84

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,6 +1,7 @@
1
1
  export * from "./muniIssues.getters";
2
2
  export * from "./muniIssues.setters";
3
3
  export * from "./muniIssues.setup";
4
+ export * from "./muniIssues.utils";
4
5
  export { MUNI_ISSUES_COLLECTION, MUNI_ISSUE_MAX_RESPONSE_IMAGES, MUNI_ISSUE_STATUSES, MUNI_ISSUE_SYNC_STATUSES, MUNI_ISSUE_TEXT_MAX_LENGTH, MUNI_ISSUE_NAME_MAX_LENGTH, } from "./muniIssues.constants";
5
6
  export { muniIssuesJsonSchema, muniIssuesIndexes } from "./muniIssues.schema";
6
7
  export type { MuniIssue, MuniIssueContent, MuniIssueJira, MuniIssueSyncStatus, MuniIssueStatus, MuniIssueResponse, CreateMuniIssueInput, SetMuniIssueResponseInput, FindMuniIssuesOptions, } from "./muniIssues.types";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/municipal/muniIssues/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,OAAO,EACL,sBAAsB,EACtB,8BAA8B,EAC9B,mBAAmB,EACnB,wBAAwB,EACxB,0BAA0B,EAC1B,0BAA0B,GAC3B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC9E,YAAY,EACV,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,mBAAmB,EACnB,eAAe,EACf,iBAAiB,EACjB,oBAAoB,EACpB,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/municipal/muniIssues/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,OAAO,EACL,sBAAsB,EACtB,8BAA8B,EAC9B,mBAAmB,EACnB,wBAAwB,EACxB,0BAA0B,EAC1B,0BAA0B,GAC3B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC9E,YAAY,EACV,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,mBAAmB,EACnB,eAAe,EACf,iBAAiB,EACjB,oBAAoB,EACpB,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,oBAAoB,CAAC"}
@@ -18,6 +18,7 @@ exports.muniIssuesIndexes = exports.muniIssuesJsonSchema = exports.MUNI_ISSUE_NA
18
18
  __exportStar(require("./muniIssues.getters"), exports);
19
19
  __exportStar(require("./muniIssues.setters"), exports);
20
20
  __exportStar(require("./muniIssues.setup"), exports);
21
+ __exportStar(require("./muniIssues.utils"), exports);
21
22
  var muniIssues_constants_1 = require("./muniIssues.constants");
22
23
  Object.defineProperty(exports, "MUNI_ISSUES_COLLECTION", { enumerable: true, get: function () { return muniIssues_constants_1.MUNI_ISSUES_COLLECTION; } });
23
24
  Object.defineProperty(exports, "MUNI_ISSUE_MAX_RESPONSE_IMAGES", { enumerable: true, get: function () { return muniIssues_constants_1.MUNI_ISSUE_MAX_RESPONSE_IMAGES; } });
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/municipal/muniIssues/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,uDAAqC;AACrC,uDAAqC;AACrC,qDAAmC;AACnC,+DAOgC;AAN9B,8HAAA,sBAAsB,OAAA;AACtB,sIAAA,8BAA8B,OAAA;AAC9B,2HAAA,mBAAmB,OAAA;AACnB,gIAAA,wBAAwB,OAAA;AACxB,kIAAA,0BAA0B,OAAA;AAC1B,kIAAA,0BAA0B,OAAA;AAE5B,yDAA8E;AAArE,yHAAA,oBAAoB,OAAA;AAAE,sHAAA,iBAAiB,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/municipal/muniIssues/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,uDAAqC;AACrC,uDAAqC;AACrC,qDAAmC;AACnC,qDAAmC;AACnC,+DAOgC;AAN9B,8HAAA,sBAAsB,OAAA;AACtB,sIAAA,8BAA8B,OAAA;AAC9B,2HAAA,mBAAmB,OAAA;AACnB,gIAAA,wBAAwB,OAAA;AACxB,kIAAA,0BAA0B,OAAA;AAC1B,kIAAA,0BAA0B,OAAA;AAE5B,yDAA8E;AAArE,yHAAA,oBAAoB,OAAA;AAAE,sHAAA,iBAAiB,OAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"muniIssues.getters.d.ts","sourceRoot":"","sources":["../../../src/municipal/muniIssues/muniIssues.getters.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAU,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEvD,OAAO,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAGtE,eAAO,MAAM,uBAAuB,QAAO,UAAU,CAAC,SAAS,CAE9D,CAAC;AAEF,eAAO,MAAM,mBAAmB,QAAO,QAA0B,CAAC;AAElE,eAAO,MAAM,gBAAgB,GAC3B,IAAI,MAAM,KACT,OAAO,CAAC,SAAS,GAAG,IAAI,CAI1B,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,SAAS,MAAM,KACd,OAAO,CAAC,SAAS,GAAG,IAAI,CAE1B,CAAC;AAYF,eAAO,MAAM,sBAAsB,GACjC,UAAU,MAAM,EAChB,4BAAmC,qBAA0B,KAC5D,OAAO,CAAC,SAAS,EAAE,CAOrB,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,UAAU,MAAM,EAChB,eAAc,IAAI,CAAC,qBAAqB,EAAE,UAAU,CAAM,KACzD,OAAO,CAAC,MAAM,CAIhB,CAAC;AAEF,eAAO,MAAM,yBAAyB,GACpC,IAAI,MAAM,EACV,UAAU,MAAM,KACf,OAAO,CAAC,SAAS,GAAG,IAAI,CAM1B,CAAC"}
1
+ {"version":3,"file":"muniIssues.getters.d.ts","sourceRoot":"","sources":["../../../src/municipal/muniIssues/muniIssues.getters.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAU,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEvD,OAAO,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAGtE,eAAO,MAAM,uBAAuB,QAAO,UAAU,CAAC,SAAS,CAE9D,CAAC;AAEF,eAAO,MAAM,mBAAmB,QAAO,QAA0B,CAAC;AAElE,eAAO,MAAM,gBAAgB,GAC3B,IAAI,MAAM,KACT,OAAO,CAAC,SAAS,GAAG,IAAI,CAI1B,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,SAAS,MAAM,KACd,OAAO,CAAC,SAAS,GAAG,IAAI,CAE1B,CAAC;AAmBF,eAAO,MAAM,sBAAsB,GACjC,UAAU,MAAM,EAChB,4BAAmC,qBAA0B,KAC5D,OAAO,CAAC,SAAS,EAAE,CAOrB,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,UAAU,MAAM,EAChB,eAAc,IAAI,CAAC,qBAAqB,EAAE,UAAU,CAAM,KACzD,OAAO,CAAC,MAAM,CAIhB,CAAC;AAEF,eAAO,MAAM,yBAAyB,GACpC,IAAI,MAAM,EACV,UAAU,MAAM,KACf,OAAO,CAAC,SAAS,GAAG,IAAI,CAM1B,CAAC"}
@@ -21,11 +21,17 @@ const getMuniIssueByJiraKey = async (jiraKey) => {
21
21
  return (0, exports.getMuniIssuesCollection)().findOne({ "jira.key": jiraKey });
22
22
  };
23
23
  exports.getMuniIssueByJiraKey = getMuniIssueByJiraKey;
24
+ // Answered = the response has text or at least one image; a cleared response object stays in the document.
25
+ const responseHasContent = [
26
+ { "response.text": { $nin: ["", null] } },
27
+ { "response.imageUrls.0": { $exists: true } },
28
+ ];
24
29
  // Tracked issues of one client. Issues without a status predate complaint tracking and are excluded.
25
30
  const clientIssuesFilter = (clientId, answered) => ({
26
31
  clientId,
27
32
  status: { $exists: true },
28
- ...(answered !== undefined && { response: { $exists: answered } }),
33
+ ...(answered === true && { $or: responseHasContent }),
34
+ ...(answered === false && { $nor: responseHasContent }),
29
35
  });
30
36
  const findMuniIssuesByClient = async (clientId, { answered, skip = 0, limit = 0 } = {}) => {
31
37
  return (0, exports.getMuniIssuesCollection)()
@@ -1 +1 @@
1
- {"version":3,"file":"muniIssues.getters.js","sourceRoot":"","sources":["../../../src/municipal/muniIssues/muniIssues.getters.ts"],"names":[],"mappings":";;;AAAA,qCAAuD;AACvD,oCAAiC;AAEjC,iEAAgE;AAEzD,MAAM,uBAAuB,GAAG,GAA0B,EAAE;IACjE,OAAO,IAAA,aAAK,GAAE,CAAC,UAAU,CAAY,6CAAsB,CAAC,CAAC;AAC/D,CAAC,CAAC;AAFW,QAAA,uBAAuB,2BAElC;AAEK,MAAM,mBAAmB,GAAG,GAAa,EAAE,CAAC,IAAI,kBAAQ,EAAE,CAAC;AAArD,QAAA,mBAAmB,uBAAkC;AAE3D,MAAM,gBAAgB,GAAG,KAAK,EACnC,EAAU,EACiB,EAAE;IAC7B,iFAAiF;IACjF,IAAI,CAAC,kBAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,IAAA,+BAAuB,GAAE,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,kBAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;AACtE,CAAC,CAAC;AANW,QAAA,gBAAgB,oBAM3B;AAEK,MAAM,qBAAqB,GAAG,KAAK,EACxC,OAAe,EACY,EAAE;IAC7B,OAAO,IAAA,+BAAuB,GAAE,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC;AACpE,CAAC,CAAC;AAJW,QAAA,qBAAqB,yBAIhC;AAEF,qGAAqG;AACrG,MAAM,kBAAkB,GAAG,CACzB,QAAgB,EAChB,QAAkB,EACC,EAAE,CAAC,CAAC;IACvB,QAAQ;IACR,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;IACzB,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,CAAC;CACnE,CAAC,CAAC;AAEI,MAAM,sBAAsB,GAAG,KAAK,EACzC,QAAgB,EAChB,EAAE,QAAQ,EAAE,IAAI,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,KAA4B,EAAE,EACvC,EAAE;IACxB,OAAO,IAAA,+BAAuB,GAAE;SAC7B,IAAI,CAAC,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;SAC5C,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,CAAC;SACvB,IAAI,CAAC,IAAI,CAAC;SACV,KAAK,CAAC,KAAK,CAAC;SACZ,OAAO,EAAE,CAAC;AACf,CAAC,CAAC;AAVW,QAAA,sBAAsB,0BAUjC;AAEK,MAAM,uBAAuB,GAAG,KAAK,EAC1C,QAAgB,EAChB,EAAE,QAAQ,KAA8C,EAAE,EACzC,EAAE;IACnB,OAAO,IAAA,+BAAuB,GAAE,CAAC,cAAc,CAC7C,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CACvC,CAAC;AACJ,CAAC,CAAC;AAPW,QAAA,uBAAuB,2BAOlC;AAEK,MAAM,yBAAyB,GAAG,KAAK,EAC5C,EAAU,EACV,QAAgB,EACW,EAAE;IAC7B,IAAI,CAAC,kBAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,IAAA,+BAAuB,GAAE,CAAC,OAAO,CAAC;QACvC,GAAG,kBAAkB,CAAC,QAAQ,CAAC;QAC/B,GAAG,EAAE,IAAI,kBAAQ,CAAC,EAAE,CAAC;KACtB,CAAC,CAAC;AACL,CAAC,CAAC;AATW,QAAA,yBAAyB,6BASpC"}
1
+ {"version":3,"file":"muniIssues.getters.js","sourceRoot":"","sources":["../../../src/municipal/muniIssues/muniIssues.getters.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { MuniIssueResponse } from "./muniIssues.types";
2
+ export declare const hasMuniIssueResponseContent: (response?: MuniIssueResponse) => boolean;
3
+ //# sourceMappingURL=muniIssues.utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"muniIssues.utils.d.ts","sourceRoot":"","sources":["../../../src/municipal/muniIssues/muniIssues.utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,eAAO,MAAM,2BAA2B,GACtC,WAAW,iBAAiB,KAC3B,OACgE,CAAC"}
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hasMuniIssueResponseContent = void 0;
4
+ const hasMuniIssueResponseContent = (response) => Boolean(response?.text) || (response?.imageUrls?.length ?? 0) > 0;
5
+ exports.hasMuniIssueResponseContent = hasMuniIssueResponseContent;
6
+ //# sourceMappingURL=muniIssues.utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"muniIssues.utils.js","sourceRoot":"","sources":["../../../src/municipal/muniIssues/muniIssues.utils.ts"],"names":[],"mappings":";;;AAEO,MAAM,2BAA2B,GAAG,CACzC,QAA4B,EACnB,EAAE,CACX,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;AAHvD,QAAA,2BAA2B,+BAG4B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@talkpilot/core-db",
3
- "version": "1.3.83",
3
+ "version": "1.3.84",
4
4
  "description": "Core database package for centralized connections and ORM integration.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -52,6 +52,27 @@ describe("muniIssues getters (complaint tracking)", () => {
52
52
  ).toBe(answered);
53
53
  expect(await findMuniIssuesByClient(CLIENT, { skip: 1, limit: 1 })).toHaveLength(1);
54
54
  });
55
+
56
+ it("should treat a response with only images as answered and a cleared response as waiting", async () => {
57
+ const imagesOnly = await createIssue();
58
+ const cleared = await createIssue();
59
+ const textOnly = await createIssue();
60
+ await setMuniIssueResponse(imagesOnly, CLIENT, {
61
+ status: "resolved",
62
+ text: "",
63
+ imageUrls: ["https://example.com/a.png"],
64
+ });
65
+ await setMuniIssueResponse(cleared, CLIENT, { status: "resolved", text: "Done" });
66
+ await setMuniIssueResponse(cleared, CLIENT, { status: "resolved", text: "", imageUrls: [] });
67
+ await setMuniIssueResponse(textOnly, CLIENT, { status: "resolved", text: "Done" });
68
+
69
+ const answered = await findMuniIssuesByClient(CLIENT, { answered: true });
70
+ const waiting = await findMuniIssuesByClient(CLIENT, { answered: false });
71
+
72
+ expect(answered.map((i) => i._id.toString()).sort()).toEqual([imagesOnly, textOnly].sort());
73
+ expect(waiting.map((i) => i._id.toString())).toEqual([cleared]);
74
+ expect(await countMuniIssuesByClient(CLIENT, { answered: false })).toBe(1);
75
+ });
55
76
  });
56
77
 
57
78
  describe("getMuniIssueByIdForClient", () => {
@@ -173,6 +173,26 @@ describe("muniIssues setters", () => {
173
173
  expect(issue?.response?.updatedAt).toBeInstanceOf(Date);
174
174
  });
175
175
 
176
+ it("should save images without text, and keep a cleared response with its first-sent date", async () => {
177
+ const input = createMuniIssueInput();
178
+ const id = (await createMuniIssue(input, generateMuniIssueId())).toString();
179
+
180
+ await setMuniIssueResponse(id, input.clientId, {
181
+ status: "resolved",
182
+ text: "",
183
+ imageUrls: ["https://example.com/a.png"],
184
+ });
185
+ const issueWithImage = await getMuniIssueById(id);
186
+ await setMuniIssueResponse(id, input.clientId, { status: "resolved", text: "", imageUrls: [] });
187
+
188
+ const response = (await getMuniIssueById(id))?.response;
189
+ expect(issueWithImage?.response?.imageUrls).toEqual(["https://example.com/a.png"]);
190
+ expect(response?.text).toBe("");
191
+ expect(response?.imageUrls).toEqual([]);
192
+ expect(response?.createdAt).toEqual(issueWithImage?.response?.createdAt);
193
+ expect(response?.updatedAt).toBeInstanceOf(Date);
194
+ });
195
+
176
196
  it("should store text starting with $ as plain text", async () => {
177
197
  const input = createMuniIssueInput();
178
198
  const id = (await createMuniIssue(input, generateMuniIssueId())).toString();
@@ -0,0 +1,17 @@
1
+ import { hasMuniIssueResponseContent } from "../muniIssues.utils";
2
+
3
+ describe("hasMuniIssueResponseContent", () => {
4
+ const base = { createdAt: new Date() };
5
+
6
+ it("should be true for text, images, or both", () => {
7
+ expect(hasMuniIssueResponseContent({ ...base, text: "Done" })).toBe(true);
8
+ expect(hasMuniIssueResponseContent({ ...base, text: "", imageUrls: ["a"] })).toBe(true);
9
+ expect(hasMuniIssueResponseContent({ ...base, text: "Done", imageUrls: ["a"] })).toBe(true);
10
+ });
11
+
12
+ it("should be false for no response or an empty one", () => {
13
+ expect(hasMuniIssueResponseContent(undefined)).toBe(false);
14
+ expect(hasMuniIssueResponseContent({ ...base, text: "" })).toBe(false);
15
+ expect(hasMuniIssueResponseContent({ ...base, text: "", imageUrls: [] })).toBe(false);
16
+ });
17
+ });
@@ -1,6 +1,7 @@
1
1
  export * from "./muniIssues.getters";
2
2
  export * from "./muniIssues.setters";
3
3
  export * from "./muniIssues.setup";
4
+ export * from "./muniIssues.utils";
4
5
  export {
5
6
  MUNI_ISSUES_COLLECTION,
6
7
  MUNI_ISSUE_MAX_RESPONSE_IMAGES,
@@ -23,6 +23,12 @@ export const getMuniIssueByJiraKey = async (
23
23
  return getMuniIssuesCollection().findOne({ "jira.key": jiraKey });
24
24
  };
25
25
 
26
+ // Answered = the response has text or at least one image; a cleared response object stays in the document.
27
+ const responseHasContent: Filter<MuniIssue>[] = [
28
+ { "response.text": { $nin: ["", null] } },
29
+ { "response.imageUrls.0": { $exists: true } },
30
+ ];
31
+
26
32
  // Tracked issues of one client. Issues without a status predate complaint tracking and are excluded.
27
33
  const clientIssuesFilter = (
28
34
  clientId: string,
@@ -30,7 +36,8 @@ const clientIssuesFilter = (
30
36
  ): Filter<MuniIssue> => ({
31
37
  clientId,
32
38
  status: { $exists: true },
33
- ...(answered !== undefined && { response: { $exists: answered } }),
39
+ ...(answered === true && { $or: responseHasContent }),
40
+ ...(answered === false && { $nor: responseHasContent }),
34
41
  });
35
42
 
36
43
  export const findMuniIssuesByClient = async (
@@ -0,0 +1,6 @@
1
+ import { MuniIssueResponse } from "./muniIssues.types";
2
+
3
+ export const hasMuniIssueResponseContent = (
4
+ response?: MuniIssueResponse,
5
+ ): boolean =>
6
+ Boolean(response?.text) || (response?.imageUrls?.length ?? 0) > 0;