@solidxai/core 0.1.10-beta.10 → 0.1.10-beta.11
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.
- package/.claude/settings.local.json +15 -0
- package/CLAUDE.md +18 -0
- package/CURRENT_PROMPT +6 -0
- package/dist/commands/refresh-model.command.d.ts +0 -4
- package/dist/commands/refresh-model.command.d.ts.map +1 -1
- package/dist/commands/refresh-model.command.js +0 -32
- package/dist/commands/refresh-model.command.js.map +1 -1
- package/dist/controllers/mcp-audit-log.controller.d.ts +35 -0
- package/dist/controllers/mcp-audit-log.controller.d.ts.map +1 -0
- package/dist/controllers/mcp-audit-log.controller.js +147 -0
- package/dist/controllers/mcp-audit-log.controller.js.map +1 -0
- package/dist/controllers/model-metadata.controller.d.ts +1 -1
- package/dist/controllers/model-metadata.controller.js +2 -2
- package/dist/controllers/model-metadata.controller.js.map +1 -1
- package/dist/controllers/module-metadata.controller.js +1 -1
- package/dist/controllers/module-metadata.controller.js.map +1 -1
- package/dist/dtos/create-mcp-audit-log.dto.d.ts +19 -0
- package/dist/dtos/create-mcp-audit-log.dto.d.ts.map +1 -0
- package/dist/dtos/create-mcp-audit-log.dto.js +118 -0
- package/dist/dtos/create-mcp-audit-log.dto.js.map +1 -0
- package/dist/dtos/update-mcp-audit-log.dto.d.ts +19 -0
- package/dist/dtos/update-mcp-audit-log.dto.d.ts.map +1 -0
- package/dist/dtos/update-mcp-audit-log.dto.js +117 -0
- package/dist/dtos/update-mcp-audit-log.dto.js.map +1 -0
- package/dist/entities/mcp-audit-log.entity.d.ts +19 -0
- package/dist/entities/mcp-audit-log.entity.d.ts.map +1 -0
- package/dist/entities/mcp-audit-log.entity.js +90 -0
- package/dist/entities/mcp-audit-log.entity.js.map +1 -0
- package/dist/helpers/command.service.d.ts +1 -0
- package/dist/helpers/command.service.d.ts.map +1 -1
- package/dist/helpers/command.service.js +1 -0
- package/dist/helpers/command.service.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/interfaces.d.ts +0 -2
- package/dist/interfaces.d.ts.map +1 -1
- package/dist/interfaces.js.map +1 -1
- package/dist/repository/mcp-audit-log.repository.d.ts +12 -0
- package/dist/repository/mcp-audit-log.repository.d.ts.map +1 -0
- package/dist/repository/mcp-audit-log.repository.js +34 -0
- package/dist/repository/mcp-audit-log.repository.js.map +1 -0
- package/dist/seeders/seed-data/solid-core-metadata.json +346 -0
- package/dist/services/mcp-audit-log.service.d.ts +12 -0
- package/dist/services/mcp-audit-log.service.d.ts.map +1 -0
- package/dist/services/mcp-audit-log.service.js +38 -0
- package/dist/services/mcp-audit-log.service.js.map +1 -0
- package/dist/services/model-metadata.service.d.ts +4 -1
- package/dist/services/model-metadata.service.d.ts.map +1 -1
- package/dist/services/model-metadata.service.js +20 -11
- package/dist/services/model-metadata.service.js.map +1 -1
- package/dist/services/module-metadata.service.d.ts +4 -1
- package/dist/services/module-metadata.service.d.ts.map +1 -1
- package/dist/services/module-metadata.service.js +18 -2
- package/dist/services/module-metadata.service.js.map +1 -1
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +8 -0
- package/dist/solid-core.module.js.map +1 -1
- package/package.json +4 -8
- package/src/commands/refresh-model.command.ts +1 -32
- package/src/controllers/mcp-audit-log.controller.ts +70 -0
- package/src/controllers/model-metadata.controller.ts +1 -1
- package/src/controllers/module-metadata.controller.ts +1 -1
- package/src/dtos/create-mcp-audit-log.dto.ts +84 -0
- package/src/dtos/update-mcp-audit-log.dto.ts +83 -0
- package/src/entities/mcp-audit-log.entity.ts +55 -0
- package/src/helpers/command.service.ts +2 -0
- package/src/index.ts +4 -0
- package/src/interfaces.ts +0 -2
- package/src/repository/mcp-audit-log.repository.ts +17 -0
- package/src/seeders/seed-data/solid-core-metadata.json +346 -0
- package/src/services/1.js +6 -0
- package/src/services/mcp-audit-log.service.ts +19 -0
- package/src/services/model-metadata.service.ts +13 -13
- package/src/services/module-metadata.service.ts +11 -0
- package/src/solid-core.module.ts +8 -0
- package/dist-tests/api/authenticate.spec.js +0 -119
- package/dist-tests/api/authenticate.spec.js.map +0 -1
- package/dist-tests/api/crud-service.findOne.cityMaster.spec.js +0 -97
- package/dist-tests/api/crud-service.findOne.cityMaster.spec.js.map +0 -1
- package/dist-tests/api/ping.spec.js +0 -21
- package/dist-tests/api/ping.spec.js.map +0 -1
- package/dist-tests/helpers/auth.js +0 -41
- package/dist-tests/helpers/auth.js.map +0 -1
- package/dist-tests/helpers/env.js +0 -11
- package/dist-tests/helpers/env.js.map +0 -1
- package/docs/grouping-enhancements.md +0 -89
- package/docs/java-spring/README.md +0 -3
- package/docs/java-spring/solid-core-module-deep-dive-report.md +0 -1317
- package/docs/seed-changes.md +0 -65
- package/docs/test-data-workflow.md +0 -200
- package/docs/type-declaration-import-issue.md +0 -24
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"authenticate.spec.js","sourceRoot":"","sources":["../../tests/api/authenticate.spec.ts"],"names":[],"mappings":";;AAAA,2CAAyD;AACzD,wCAAgD;AAOhD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,uBAAuB,CAAC;AACpE,MAAM,eAAe,GAAG,IAAA,oBAAc,EAAC,iBAAiB,CAAC,CAAC;AAC1D,MAAM,kBAAkB,GAAG,IAAA,oBAAc,EAAC,oBAAoB,CAAC,CAAC;AAEhE,SAAS,eAAe,CAAC,KAAa;IACpC,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC/D,MAAM,MAAM,GACV,UAAU,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC;QACzB,CAAC,CAAC,UAAU;QACZ,CAAC,CAAC,UAAU,CAAC,MAAM,CACjB,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EACjD,GAAG,CACJ,CAAC;IACN,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;AACzD,CAAC;AAED,SAAS,WAAW,CAAC,KAAa;IAChC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAC9D,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACzD,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAE1D,IAAI,CAAC,UAAU,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACvD,CAAC;IAED,IAAI,CAAC,WAAW,IAAI,OAAO,WAAW,KAAK,QAAQ,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;IAED,IAAI,OAAO,WAAW,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACvD,CAAC;IAED,OAAO,WAAyB,CAAC;AACnC,CAAC;AAED,IAAA,WAAI,EAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;IACnE,MAAM,GAAG,GAAG,MAAM,cAAO,CAAC,UAAU,CAAC;QACnC,OAAO;QACP,gBAAgB,EAAE;YAChB,MAAM,EAAE,KAAK;YACb,cAAc,EAAE,kBAAkB;SACnC;KACF,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,uBAAuB,EAAE;YAClD,IAAI,EAAE;gBACJ,KAAK,EAAE,eAAe;gBACtB,QAAQ,EAAE,EAAE;gBACZ,QAAQ,EAAE,kBAAkB;aAC7B;SACF,CAAC,CAAC;QAEH,IAAA,aAAM,EAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/B,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAE9B,IAAA,aAAM,EAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClC,IAAA,aAAM,EAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/C,IAAA,aAAM,EAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpC,IAAA,aAAM,EAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAE5B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,IAA2C,CAAC;QACpE,IAAA,aAAM,EAAC,IAAI,EAAE,qCAAqC,CAAC,CAAC,UAAU,EAAE,CAAC;QACjE,IAAA,aAAM,EAAC,OAAO,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAEnC,MAAM,KAAK,GAAG,IAAI,EAAE,KAAK,CAAC;QAC1B,IAAA,aAAM,EAAC,OAAO,KAAK,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACpC,IAAI,KAAK,KAAK,eAAe,EAAE,CAAC;YAC9B,IAAA,aAAM,EAAC,KAAK,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QACtC,CAAC;aAAM,CAAC;YAEN,IAAA,aAAM,EAAE,KAAgB,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QACtD,CAAC;QAED,IAAA,aAAM,EAAC,OAAO,IAAI,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC3C,IAAA,aAAM,EAAC,OAAO,IAAI,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC7C,IAAA,aAAM,EAAC,OAAO,IAAI,EAAE,mBAAmB,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACzD,IAAA,aAAM,EAAC,OAAO,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAEvC,MAAM,KAAK,GAAG,IAAI,EAAE,KAAK,CAAC;QAC1B,IAAA,aAAM,EAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,IAAA,aAAM,EAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnE,IAAA,aAAM,EAAC,KAAK,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACnC,CAAC;QAED,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC;QAC3C,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC;QAC7C,IAAA,aAAM,EAAC,OAAO,WAAW,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC1C,IAAA,aAAM,EAAC,OAAO,YAAY,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAE3C,MAAM,aAAa,GAAG,WAAW,CAAC,WAAqB,CAAC,CAAC;QACzD,MAAM,cAAc,GAAG,WAAW,CAAC,YAAsB,CAAC,CAAC;QAE3D,IAAA,aAAM,EAAC,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAChD,IAAA,aAAM,EAAC,OAAO,cAAc,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACnD,CAAC;YAAS,CAAC;QACT,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC;IACtB,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAA,WAAI,EAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;IAC7D,MAAM,GAAG,GAAG,MAAM,cAAO,CAAC,UAAU,CAAC;QACnC,OAAO;QACP,gBAAgB,EAAE;YAChB,MAAM,EAAE,KAAK;YACb,cAAc,EAAE,kBAAkB;SACnC;KACF,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,uBAAuB,EAAE;YAClD,IAAI,EAAE;gBACJ,KAAK,EAAE,eAAe;gBACtB,QAAQ,EAAE,EAAE;gBACZ,QAAQ,EAAE,GAAG,kBAAkB,SAAS;aACzC;SACF,CAAC,CAAC;QAEH,IAAA,aAAM,EAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/B,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAE9B,IAAA,aAAM,EAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClC,IAAA,aAAM,EAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACpD,IAAA,aAAM,EAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;QACjD,IAAA,aAAM,EAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;QAC/C,IAAA,aAAM,EAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxC,IAAA,aAAM,EAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC9C,IAAA,aAAM,EAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IACzD,CAAC;YAAS,CAAC;QACT,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC;IACtB,CAAC;AACH,CAAC,CAAC,CAAC","sourcesContent":["import { expect, request, test } from \"@playwright/test\";\nimport { getRequiredEnv } from \"../helpers/env\";\n\ntype JwtPayload = {\n exp?: number;\n [key: string]: unknown;\n};\n\nconst baseURL = process.env.API_BASE_URL ?? \"http://localhost:3000\";\nconst TEST_USER_EMAIL = getRequiredEnv(\"TEST_USER_EMAIL\");\nconst TEST_USER_PASSWORD = getRequiredEnv(\"TEST_USER_PASSWORD\");\n\nfunction base64UrlDecode(input: string): string {\n const normalized = input.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const padded =\n normalized.length % 4 === 0\n ? normalized\n : normalized.padEnd(\n normalized.length + (4 - (normalized.length % 4)),\n \"=\"\n );\n return Buffer.from(padded, \"base64\").toString(\"utf-8\");\n}\n\nfunction validateJwt(token: string): JwtPayload {\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n throw new Error(\"JWT must have three dot-separated parts.\");\n }\n\n const headerJson = JSON.parse(base64UrlDecode(parts[0]));\n const payloadJson = JSON.parse(base64UrlDecode(parts[1]));\n\n if (!headerJson || typeof headerJson !== \"object\") {\n throw new Error(\"JWT header must be a JSON object.\");\n }\n\n if (!payloadJson || typeof payloadJson !== \"object\") {\n throw new Error(\"JWT payload must be a JSON object.\");\n }\n\n if (typeof payloadJson.exp !== \"number\") {\n throw new Error(\"JWT payload.exp must be a number.\");\n }\n\n return payloadJson as JwtPayload;\n}\n\ntest(\"API: authenticate succeeds with valid credentials\", async () => {\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: {\n accept: \"*/*\",\n \"content-type\": \"application/json\",\n },\n });\n\n try {\n const res = await api.post(\"/api/iam/authenticate\", {\n data: {\n email: TEST_USER_EMAIL,\n username: \"\",\n password: TEST_USER_PASSWORD,\n },\n });\n\n expect(res.status()).toBe(200);\n const json = await res.json();\n\n expect(json.statusCode).toBe(200);\n expect(Array.isArray(json.message)).toBe(true);\n expect(json.message.length).toBe(0);\n expect(json.error).toBe(\"\");\n\n const user = json.data?.user as Record<string, unknown> | undefined;\n expect(user, \"Expected data.user to be an object.\").toBeTruthy();\n expect(typeof user).toBe(\"object\");\n\n const email = user?.email;\n expect(typeof email).toBe(\"string\");\n if (email === TEST_USER_EMAIL) {\n expect(email).toBe(TEST_USER_EMAIL);\n } else {\n // If your API returns a fixed system email, replace this with an exact match.\n expect((email as string).length).toBeGreaterThan(0);\n }\n\n expect(typeof user?.mobile).toBe(\"string\");\n expect(typeof user?.username).toBe(\"string\");\n expect(typeof user?.forcePasswordChange).toBe(\"boolean\");\n expect(typeof user?.id).toBe(\"number\");\n\n const roles = user?.roles;\n expect(Array.isArray(roles)).toBe(true);\n if (Array.isArray(roles)) {\n expect(roles.every((role) => typeof role === \"string\")).toBe(true);\n expect(roles).toContain(\"Admin\");\n }\n\n const accessToken = json.data?.accessToken;\n const refreshToken = json.data?.refreshToken;\n expect(typeof accessToken).toBe(\"string\");\n expect(typeof refreshToken).toBe(\"string\");\n\n const accessPayload = validateJwt(accessToken as string);\n const refreshPayload = validateJwt(refreshToken as string);\n\n expect(typeof accessPayload.exp).toBe(\"number\");\n expect(typeof refreshPayload.exp).toBe(\"number\");\n } finally {\n await api.dispose();\n }\n});\n\ntest(\"API: authenticate fails with wrong password\", async () => {\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: {\n accept: \"*/*\",\n \"content-type\": \"application/json\",\n },\n });\n\n try {\n const res = await api.post(\"/api/iam/authenticate\", {\n data: {\n email: TEST_USER_EMAIL,\n username: \"\",\n password: `${TEST_USER_PASSWORD}__wrong`,\n },\n });\n\n expect(res.status()).toBe(401);\n const json = await res.json();\n\n expect(json.statusCode).toBe(401);\n expect(json.statusCodeMessage).toBe(\"Unauthorized\");\n expect(json.message).toBe(\"Invalid credentials\");\n expect(json.error).toBe(\"Invalid credentials\");\n expect(json.data?.statusCode).toBe(401);\n expect(json.data?.error).toBe(\"Unauthorized\");\n expect(json.data?.message).toBe(\"Invalid credentials\");\n } finally {\n await api.dispose();\n }\n});\n"]}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const test_1 = require("@playwright/test");
|
|
4
|
-
const auth_1 = require("../helpers/auth");
|
|
5
|
-
const baseURL = process.env.API_BASE_URL ?? "http://localhost:3000";
|
|
6
|
-
async function getCityByName(name) {
|
|
7
|
-
const api = await test_1.request.newContext({
|
|
8
|
-
baseURL,
|
|
9
|
-
extraHTTPHeaders: await (0, auth_1.getAuthHeaders)(baseURL),
|
|
10
|
-
});
|
|
11
|
-
try {
|
|
12
|
-
const res = await api.get(`/api/city-master?filters[name][$eq]=${encodeURIComponent(name)}&limit=1&offset=0`);
|
|
13
|
-
(0, test_1.expect)(res.status()).toBe(200);
|
|
14
|
-
const json = await res.json();
|
|
15
|
-
const record = json?.data?.records?.[0];
|
|
16
|
-
(0, test_1.expect)(record, `Expected city '${name}' in testData`).toBeTruthy();
|
|
17
|
-
(0, test_1.expect)(record.id, "Expected record to have id").toBeTruthy();
|
|
18
|
-
return record;
|
|
19
|
-
}
|
|
20
|
-
finally {
|
|
21
|
-
await api.dispose();
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
test_1.test.describe("CRUDService.findOne (cityMaster)", () => {
|
|
25
|
-
(0, test_1.test)("returns a city by id", async () => {
|
|
26
|
-
const city = await getCityByName("Mumbai");
|
|
27
|
-
const api = await test_1.request.newContext({
|
|
28
|
-
baseURL,
|
|
29
|
-
extraHTTPHeaders: await (0, auth_1.getAuthHeaders)(baseURL),
|
|
30
|
-
});
|
|
31
|
-
try {
|
|
32
|
-
const res = await api.get(`/api/city-master/${city.id}`);
|
|
33
|
-
(0, test_1.expect)(res.status()).toBe(200);
|
|
34
|
-
const json = await res.json();
|
|
35
|
-
(0, test_1.expect)(json?.data?.id).toBe(city.id);
|
|
36
|
-
(0, test_1.expect)(json?.data?.name).toBe("Mumbai");
|
|
37
|
-
(0, test_1.expect)(json?.data?.description).toBe("Mumbai city");
|
|
38
|
-
}
|
|
39
|
-
finally {
|
|
40
|
-
await api.dispose();
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
(0, test_1.test)("supports fields[] selection", async () => {
|
|
44
|
-
const city = await getCityByName("Pune");
|
|
45
|
-
const api = await test_1.request.newContext({
|
|
46
|
-
baseURL,
|
|
47
|
-
extraHTTPHeaders: await (0, auth_1.getAuthHeaders)(baseURL),
|
|
48
|
-
});
|
|
49
|
-
try {
|
|
50
|
-
const res = await api.get(`/api/city-master/${city.id}?fields[]=id&fields[]=name`);
|
|
51
|
-
(0, test_1.expect)(res.status()).toBe(200);
|
|
52
|
-
const json = await res.json();
|
|
53
|
-
(0, test_1.expect)(json?.data?.id).toBe(city.id);
|
|
54
|
-
(0, test_1.expect)(json?.data?.name).toBe("Pune");
|
|
55
|
-
(0, test_1.expect)(json?.data?.description).toBeUndefined();
|
|
56
|
-
(0, test_1.expect)(json?.data?.state).toBeUndefined();
|
|
57
|
-
}
|
|
58
|
-
finally {
|
|
59
|
-
await api.dispose();
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
(0, test_1.test)("supports populate=state", async () => {
|
|
63
|
-
const city = await getCityByName("Bengaluru");
|
|
64
|
-
const api = await test_1.request.newContext({
|
|
65
|
-
baseURL,
|
|
66
|
-
extraHTTPHeaders: await (0, auth_1.getAuthHeaders)(baseURL),
|
|
67
|
-
});
|
|
68
|
-
try {
|
|
69
|
-
const res = await api.get(`/api/city-master/${city.id}?populate=state`);
|
|
70
|
-
(0, test_1.expect)(res.status()).toBe(200);
|
|
71
|
-
const json = await res.json();
|
|
72
|
-
(0, test_1.expect)(json?.data?.name).toBe("Bengaluru");
|
|
73
|
-
(0, test_1.expect)(json?.data?.state).toBeTruthy();
|
|
74
|
-
(0, test_1.expect)(json?.data?.state?.name).toBe("Karnataka");
|
|
75
|
-
}
|
|
76
|
-
finally {
|
|
77
|
-
await api.dispose();
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
(0, test_1.test)("returns 404 for missing id", async () => {
|
|
81
|
-
const api = await test_1.request.newContext({
|
|
82
|
-
baseURL,
|
|
83
|
-
extraHTTPHeaders: await (0, auth_1.getAuthHeaders)(baseURL),
|
|
84
|
-
});
|
|
85
|
-
try {
|
|
86
|
-
const res = await api.get(`/api/city-master/99999999`);
|
|
87
|
-
(0, test_1.expect)(res.status()).toBe(404);
|
|
88
|
-
const json = await res.json();
|
|
89
|
-
(0, test_1.expect)(String(json?.message)).toContain("cityMaster");
|
|
90
|
-
(0, test_1.expect)(String(json?.message)).toContain("not found");
|
|
91
|
-
}
|
|
92
|
-
finally {
|
|
93
|
-
await api.dispose();
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
//# sourceMappingURL=crud-service.findOne.cityMaster.spec.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"crud-service.findOne.cityMaster.spec.js","sourceRoot":"","sources":["../../tests/api/crud-service.findOne.cityMaster.spec.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, request, test } from \"@playwright/test\";\nimport { getAuthHeaders } from \"../helpers/auth\";\n\nconst baseURL = process.env.API_BASE_URL ?? \"http://localhost:3000\";\n\nasync function getCityByName(name: string) {\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: await getAuthHeaders(baseURL),\n });\n try {\n const res = await api.get(\n `/api/city-master?filters[name][$eq]=${encodeURIComponent(name)}&limit=1&offset=0`\n );\n expect(res.status()).toBe(200);\n const json = await res.json();\n\n const record = json?.data?.records?.[0];\n expect(record, `Expected city '${name}' in testData`).toBeTruthy();\n expect(record.id, \"Expected record to have id\").toBeTruthy();\n\n return record;\n } finally {\n await api.dispose();\n }\n}\n\ntest.describe(\"CRUDService.findOne (cityMaster)\", () => {\n test(\"returns a city by id\", async () => {\n const city = await getCityByName(\"Mumbai\");\n\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: await getAuthHeaders(baseURL),\n });\n try {\n const res = await api.get(`/api/city-master/${city.id}`);\n expect(res.status()).toBe(200);\n\n const json = await res.json();\n expect(json?.data?.id).toBe(city.id);\n expect(json?.data?.name).toBe(\"Mumbai\");\n expect(json?.data?.description).toBe(\"Mumbai city\");\n } finally {\n await api.dispose();\n }\n });\n\n test(\"supports fields[] selection\", async () => {\n const city = await getCityByName(\"Pune\");\n\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: await getAuthHeaders(baseURL),\n });\n try {\n const res = await api.get(\n `/api/city-master/${city.id}?fields[]=id&fields[]=name`\n );\n expect(res.status()).toBe(200);\n\n const json = await res.json();\n expect(json?.data?.id).toBe(city.id);\n expect(json?.data?.name).toBe(\"Pune\");\n expect(json?.data?.description).toBeUndefined();\n expect(json?.data?.state).toBeUndefined();\n } finally {\n await api.dispose();\n }\n });\n\n test(\"supports populate=state\", async () => {\n const city = await getCityByName(\"Bengaluru\");\n\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: await getAuthHeaders(baseURL),\n });\n try {\n const res = await api.get(`/api/city-master/${city.id}?populate=state`);\n expect(res.status()).toBe(200);\n\n const json = await res.json();\n expect(json?.data?.name).toBe(\"Bengaluru\");\n expect(json?.data?.state).toBeTruthy();\n expect(json?.data?.state?.name).toBe(\"Karnataka\");\n } finally {\n await api.dispose();\n }\n });\n\n test(\"returns 404 for missing id\", async () => {\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: await getAuthHeaders(baseURL),\n });\n try {\n const res = await api.get(`/api/city-master/99999999`);\n expect(res.status()).toBe(404);\n\n const json = await res.json();\n expect(String(json?.message)).toContain(\"cityMaster\");\n expect(String(json?.message)).toContain(\"not found\");\n } finally {\n await api.dispose();\n }\n });\n});\n"]}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const test_1 = require("@playwright/test");
|
|
4
|
-
(0, test_1.test)("GET /api/ping returns pong", async () => {
|
|
5
|
-
const baseURL = process.env.BASE_URL || "http://localhost:3000";
|
|
6
|
-
if (!baseURL) {
|
|
7
|
-
throw new Error("baseURL is not configured. Set API_BASE_URL or use the default.");
|
|
8
|
-
}
|
|
9
|
-
const api = await test_1.request.newContext({ baseURL });
|
|
10
|
-
const res = await api.get("/api/ping");
|
|
11
|
-
(0, test_1.expect)(res.status()).toBe(200);
|
|
12
|
-
const body = await res.json();
|
|
13
|
-
(0, test_1.expect)(body).toEqual({
|
|
14
|
-
statusCode: 200,
|
|
15
|
-
message: [],
|
|
16
|
-
error: "",
|
|
17
|
-
data: { pong: "v1.0.2" },
|
|
18
|
-
});
|
|
19
|
-
await api.dispose();
|
|
20
|
-
});
|
|
21
|
-
//# sourceMappingURL=ping.spec.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ping.spec.js","sourceRoot":"","sources":["../../tests/api/ping.spec.ts"],"names":[],"mappings":";;AAAA,2CAAyD;AAEzD,IAAA,WAAI,EAAC,4BAA4B,EAAE,KAAK,IAAI,EAAE;IAC5C,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,uBAAuB,CAAC;IAChE,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAC;IACrF,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,cAAO,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;IAElD,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAEvC,IAAA,aAAM,EAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAE/B,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAA,aAAM,EAAC,IAAI,CAAC,CAAC,OAAO,CAAC;QACnB,UAAU,EAAE,GAAG;QACf,OAAO,EAAE,EAAE;QACX,KAAK,EAAE,EAAE;QACT,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;KACzB,CAAC,CAAC;IAEH,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC;AACtB,CAAC,CAAC,CAAC","sourcesContent":["import { expect, request, test } from \"@playwright/test\";\n\ntest(\"GET /api/ping returns pong\", async () => {\n const baseURL = process.env.BASE_URL || \"http://localhost:3000\";\n if (!baseURL) {\n throw new Error(\"baseURL is not configured. Set API_BASE_URL or use the default.\");\n }\n const api = await request.newContext({ baseURL });\n\n const res = await api.get(\"/api/ping\");\n\n expect(res.status()).toBe(200);\n\n const body = await res.json();\n expect(body).toEqual({\n statusCode: 200,\n message: [],\n error: \"\",\n data: { pong: \"v1.0.2\" },\n });\n\n await api.dispose();\n});\n"]}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getAccessToken = getAccessToken;
|
|
4
|
-
exports.getAuthHeaders = getAuthHeaders;
|
|
5
|
-
const test_1 = require("@playwright/test");
|
|
6
|
-
const env_1 = require("./env");
|
|
7
|
-
async function getAccessToken(baseURL) {
|
|
8
|
-
const TEST_USER_EMAIL = (0, env_1.getRequiredEnv)("TEST_USER_EMAIL");
|
|
9
|
-
const TEST_USER_PASSWORD = (0, env_1.getRequiredEnv)("TEST_USER_PASSWORD");
|
|
10
|
-
const api = await test_1.request.newContext({
|
|
11
|
-
baseURL,
|
|
12
|
-
extraHTTPHeaders: {
|
|
13
|
-
accept: "*/*",
|
|
14
|
-
"content-type": "application/json",
|
|
15
|
-
},
|
|
16
|
-
});
|
|
17
|
-
try {
|
|
18
|
-
const res = await api.post("/api/iam/authenticate", {
|
|
19
|
-
data: {
|
|
20
|
-
email: TEST_USER_EMAIL,
|
|
21
|
-
username: "",
|
|
22
|
-
password: TEST_USER_PASSWORD,
|
|
23
|
-
},
|
|
24
|
-
});
|
|
25
|
-
(0, test_1.expect)(res.status()).toBe(200);
|
|
26
|
-
const json = await res.json();
|
|
27
|
-
const token = json?.data?.accessToken;
|
|
28
|
-
(0, test_1.expect)(token, "Expected access token from authenticate endpoint.").toBeTruthy();
|
|
29
|
-
return token;
|
|
30
|
-
}
|
|
31
|
-
finally {
|
|
32
|
-
await api.dispose();
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
async function getAuthHeaders(baseURL) {
|
|
36
|
-
const token = await getAccessToken(baseURL);
|
|
37
|
-
return {
|
|
38
|
-
authorization: `Bearer ${token}`,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
//# sourceMappingURL=auth.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../tests/helpers/auth.ts"],"names":[],"mappings":";;AAGA,wCA6BC;AAED,wCAKC;AAvCD,2CAAmD;AACnD,+BAAuC;AAEhC,KAAK,UAAU,cAAc,CAAC,OAAe;IAClD,MAAM,eAAe,GAAG,IAAA,oBAAc,EAAC,iBAAiB,CAAC,CAAC;IAC1D,MAAM,kBAAkB,GAAG,IAAA,oBAAc,EAAC,oBAAoB,CAAC,CAAC;IAEhE,MAAM,GAAG,GAAG,MAAM,cAAO,CAAC,UAAU,CAAC;QACnC,OAAO;QACP,gBAAgB,EAAE;YAChB,MAAM,EAAE,KAAK;YACb,cAAc,EAAE,kBAAkB;SACnC;KACF,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,uBAAuB,EAAE;YAClD,IAAI,EAAE;gBACJ,KAAK,EAAE,eAAe;gBACtB,QAAQ,EAAE,EAAE;gBACZ,QAAQ,EAAE,kBAAkB;aAC7B;SACF,CAAC,CAAC;QAEH,IAAA,aAAM,EAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/B,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,MAAM,KAAK,GAAG,IAAI,EAAE,IAAI,EAAE,WAAiC,CAAC;QAC5D,IAAA,aAAM,EAAC,KAAK,EAAE,mDAAmD,CAAC,CAAC,UAAU,EAAE,CAAC;QAChF,OAAO,KAAe,CAAC;IACzB,CAAC;YAAS,CAAC;QACT,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC;IACtB,CAAC;AACH,CAAC;AAEM,KAAK,UAAU,cAAc,CAAC,OAAe;IAClD,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,CAAC;IAC5C,OAAO;QACL,aAAa,EAAE,UAAU,KAAK,EAAE;KACjC,CAAC;AACJ,CAAC","sourcesContent":["import { expect, request } from \"@playwright/test\";\nimport { getRequiredEnv } from \"./env\";\n\nexport async function getAccessToken(baseURL: string) {\n const TEST_USER_EMAIL = getRequiredEnv(\"TEST_USER_EMAIL\");\n const TEST_USER_PASSWORD = getRequiredEnv(\"TEST_USER_PASSWORD\");\n\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: {\n accept: \"*/*\",\n \"content-type\": \"application/json\",\n },\n });\n\n try {\n const res = await api.post(\"/api/iam/authenticate\", {\n data: {\n email: TEST_USER_EMAIL,\n username: \"\",\n password: TEST_USER_PASSWORD,\n },\n });\n\n expect(res.status()).toBe(200);\n const json = await res.json();\n const token = json?.data?.accessToken as string | undefined;\n expect(token, \"Expected access token from authenticate endpoint.\").toBeTruthy();\n return token as string;\n } finally {\n await api.dispose();\n }\n}\n\nexport async function getAuthHeaders(baseURL: string) {\n const token = await getAccessToken(baseURL);\n return {\n authorization: `Bearer ${token}`,\n };\n}\n"]}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getRequiredEnv = getRequiredEnv;
|
|
4
|
-
function getRequiredEnv(name) {
|
|
5
|
-
const value = process.env[name];
|
|
6
|
-
if (!value) {
|
|
7
|
-
throw new Error(`Missing required env var: ${name}`);
|
|
8
|
-
}
|
|
9
|
-
return value;
|
|
10
|
-
}
|
|
11
|
-
//# sourceMappingURL=env.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../tests/helpers/env.ts"],"names":[],"mappings":";;AAAA,wCAMC;AAND,SAAgB,cAAc,CAAC,IAAY;IACzC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,EAAE,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["export function getRequiredEnv(name: string): string {\n const value = process.env[name];\n if (!value) {\n throw new Error(`Missing required env var: ${name}`);\n }\n return value;\n}\n"]}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
# Grouping & Aggregation Enhancements (Code Review Summary)
|
|
2
|
-
|
|
3
|
-
This document explains the recent changes to grouping/aggregation in the CRUD helper/service, why they were made, and how to use them (with examples on `PincodeMaster`).
|
|
4
|
-
|
|
5
|
-
## What Changed and Why
|
|
6
|
-
- **Dedicated grouping pipeline**: Grouping no longer reuses the record-level query (which had `SELECT entity.*`, pagination, and default order). A separate path builds clean group queries to avoid SQL errors and ensure correct counts.
|
|
7
|
-
- **Multiple group-by fields**: The one-field limit was removed. You can now group on multiple fields (including relations) in the requested order.
|
|
8
|
-
- **Relation-safe grouping**: Group-by fields can traverse many-to-one relations (e.g., `state.name`, `city.name`). The helper reuses existing joins (from filters) or adds the necessary joins and aliases.
|
|
9
|
-
- **Date granularity**: Grouping supports `day`, `week`, `month`, `year` granularities in a DB-aware way (Postgres, MySQL/MariaDB, SQL Server).
|
|
10
|
-
- **Aggregates**: Supports a core, DB-agnostic set: `count`, `count_distinct`, `sum`, `avg`, `min`, `max`. If `aggregates` is omitted, it defaults to `count(*)`.
|
|
11
|
-
- **Group sorting/pagination**: Sorting and pagination are applied to group rows (not entity rows). Record pagination is kept separate for non-grouped queries.
|
|
12
|
-
- **Ordered group names**: Group names follow the order of `groupBy` fields. Relation values and date/grouped values are included in sequence.
|
|
13
|
-
- **Formatted date group labels**: You can add an optional format specifier to a date groupBy field: `field:granularity:format`. Supported formats: `MMM`, `MMMM`, `YYYY`, `YYYY-MM`, `YYYY-MM-DD` (defaults to the raw value if omitted).
|
|
14
|
-
- **Count of groups**: Group counts are computed without pagination interference, so `meta.totalRecords` reflects total groups.
|
|
15
|
-
- **DTO update**: `BasicFilterDto` now includes optional `aggregates?: string[]`.
|
|
16
|
-
|
|
17
|
-
## Caveats
|
|
18
|
-
- `populateGroup` is **not** supported when grouping on relation fields (e.g., `state.name`, `city.name`). Use it for scalar group-by fields only; otherwise fetch group metadata and then retrieve records in a separate call with the group key.
|
|
19
|
-
- Sorting works for group keys without extra colons (e.g., `state.name`) and for aggregate aliases (e.g., `id_max`). For date bucket group keys with granularity/format (`createdAt:month:YYYY`), the sort parser treats the last segment as the order; results may vary by driver and may not sort as expected in all cases.
|
|
20
|
-
|
|
21
|
-
## Usage Examples (PincodeMaster)
|
|
22
|
-
Assume `PincodeMaster` has many-to-one `state` and `city` relations and a `createdAt` timestamp.
|
|
23
|
-
|
|
24
|
-
### 1) Group by State and City
|
|
25
|
-
```
|
|
26
|
-
GET /api/pincode-master?offset=0&limit=200&groupBy[0]=state.name&groupBy[1]=city.name
|
|
27
|
-
```
|
|
28
|
-
Returns groups for every state/city combination with default `count(*)` aggregate.
|
|
29
|
-
|
|
30
|
-
### 2) Group by Relation + Filter on Relation
|
|
31
|
-
```
|
|
32
|
-
GET /api/pincode-master?offset=0&limit=200&groupBy[0]=state.name&filters[state][name][$eq]=Maharashtra
|
|
33
|
-
```
|
|
34
|
-
Groups by state name but only for rows where state is Maharashtra.
|
|
35
|
-
|
|
36
|
-
### 3) Multiple Group Fields (Relation + Scalar)
|
|
37
|
-
```
|
|
38
|
-
GET /api/pincode-master?offset=0&limit=200&groupBy[0]=state.name&groupBy[1]=city.name&groupBy[2]=pincode
|
|
39
|
-
```
|
|
40
|
-
Group names are ordered: state → city → pincode.
|
|
41
|
-
|
|
42
|
-
### 4) Group by Date with Granularity (Month)
|
|
43
|
-
```
|
|
44
|
-
GET /api/pincode-master?offset=0&limit=200&groupBy[0]=createdAt:month
|
|
45
|
-
```
|
|
46
|
-
Groups by month (driver-aware), default group labels are raw date buckets.
|
|
47
|
-
|
|
48
|
-
### 5) Date Granularity with Formatting
|
|
49
|
-
```
|
|
50
|
-
GET /api/pincode-master?offset=0&limit=200&groupBy[0]=createdAt:month:MMM
|
|
51
|
-
```
|
|
52
|
-
Group labels use short month names (Jan, Feb, …). For full names: `createdAt:month:MMMM`. Other formats: `YYYY`, `YYYY-MM`, `YYYY-MM-DD`.
|
|
53
|
-
|
|
54
|
-
### 6) Aggregates (Count Distinct)
|
|
55
|
-
```
|
|
56
|
-
GET /api/pincode-master?offset=0&limit=200&groupBy[0]=state.name&aggregates[0]=pincode:count_distinct
|
|
57
|
-
```
|
|
58
|
-
Shows distinct pincodes per state.
|
|
59
|
-
|
|
60
|
-
### 7) Aggregates (Multiple)
|
|
61
|
-
```
|
|
62
|
-
GET /api/pincode-master?offset=0&limit=200&groupBy[0]=state.name&groupBy[1]=city.name&aggregates[0]=id:count&aggregates[1]=id:count_distinct
|
|
63
|
-
```
|
|
64
|
-
Returns both total rows and distinct IDs per state/city group.
|
|
65
|
-
|
|
66
|
-
### 8) Date Granularity + Relations + Aggregates
|
|
67
|
-
```
|
|
68
|
-
GET /api/pincode-master?offset=0&limit=200&groupBy[0]=createdAt:year&groupBy[1]=state.name&aggregates[0]=pincode:count_distinct
|
|
69
|
-
```
|
|
70
|
-
Distinct pincodes per state, per year.
|
|
71
|
-
|
|
72
|
-
### 9) Filters with Grouping (Many-to-One)
|
|
73
|
-
```
|
|
74
|
-
GET /api/pincode-master?offset=0&limit=200&groupBy[0]=state.name&groupBy[1]=city.name&filters[state][name][$eq]=Nagaland
|
|
75
|
-
```
|
|
76
|
-
Groups only rows where state.name = Nagaland.
|
|
77
|
-
|
|
78
|
-
### 10) Group Sorting and Pagination
|
|
79
|
-
- Sorting applies to the group rows. Example:
|
|
80
|
-
```
|
|
81
|
-
GET /api/pincode-master?offset=0&limit=50&groupBy[0]=state.name&sort[0]=state.name:ASC
|
|
82
|
-
```
|
|
83
|
-
- Pagination (`offset/limit`) limits the number of groups returned; total group count remains in `meta.totalRecords`.
|
|
84
|
-
|
|
85
|
-
## Notes and Behavior
|
|
86
|
-
- If `aggregates` is omitted, `COUNT(*)` is added automatically.
|
|
87
|
-
- Group names reflect `groupBy` order and apply formatting when specified.
|
|
88
|
-
- Group queries reuse joins from filters when possible; otherwise, they create necessary joins for relation paths.
|
|
89
|
-
- Non-grouped find behavior remains unchanged.
|