aws-service-stack 0.18.370 → 0.18.371

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 (64) hide show
  1. package/README.md +149 -0
  2. package/dist/_examples/controller/property/property-crud.d.ts +4 -0
  3. package/dist/_examples/controller/property/property-crud.js +58 -0
  4. package/dist/_examples/controller/property/property-crud.js.map +1 -0
  5. package/dist/_examples/controller/property/property.config.d.ts +10 -0
  6. package/dist/_examples/controller/property/property.config.js +55 -0
  7. package/dist/_examples/controller/property/property.config.js.map +1 -0
  8. package/dist/_examples/controller/property/property.controller.d.ts +14 -0
  9. package/dist/_examples/controller/property/property.controller.js +72 -0
  10. package/dist/_examples/controller/property/property.controller.js.map +1 -0
  11. package/dist/_examples/controller/property/property.permissions.d.ts +2 -0
  12. package/dist/_examples/controller/property/property.permissions.js +19 -0
  13. package/dist/_examples/controller/property/property.permissions.js.map +1 -0
  14. package/dist/controller/controller-api.d.ts +8 -0
  15. package/dist/controller/controller-api.js +39 -1
  16. package/dist/controller/controller-api.js.map +1 -1
  17. package/dist/controller/controller-role.d.ts +56 -0
  18. package/dist/controller/controller-role.js +140 -0
  19. package/dist/controller/controller-role.js.map +1 -0
  20. package/dist/controller/index.d.ts +1 -0
  21. package/dist/controller/index.js +1 -0
  22. package/dist/controller/index.js.map +1 -1
  23. package/dist/function/cognito/cognito.function.d.ts +5 -0
  24. package/dist/function/cognito/cognito.function.js +22 -0
  25. package/dist/function/cognito/cognito.function.js.map +1 -1
  26. package/dist/function/cognito/index.d.ts +3 -1
  27. package/dist/function/cognito/index.js +2 -0
  28. package/dist/function/cognito/index.js.map +1 -1
  29. package/dist/function/index.d.ts +2 -0
  30. package/dist/model/base.config.d.ts +12 -1
  31. package/dist/model/base.config.js +20 -0
  32. package/dist/model/base.config.js.map +1 -1
  33. package/dist/model/base.model.d.ts +19 -0
  34. package/dist/model/base.model.js +14 -1
  35. package/dist/model/base.model.js.map +1 -1
  36. package/dist/model/index.d.ts +1 -0
  37. package/dist/model/index.js.map +1 -1
  38. package/dist/model/role.model.d.ts +20 -0
  39. package/dist/model/role.model.js +12 -0
  40. package/dist/model/role.model.js.map +1 -0
  41. package/dist/model/validation.model.d.ts +1 -1
  42. package/dist/model/validation.model.js.map +1 -1
  43. package/dist/service/index.d.ts +3 -0
  44. package/dist/service/index.js +3 -0
  45. package/dist/service/index.js.map +1 -1
  46. package/dist/service/permission.cache.d.ts +24 -0
  47. package/dist/service/permission.cache.js +61 -0
  48. package/dist/service/permission.cache.js.map +1 -0
  49. package/dist/service/permission.repo.d.ts +16 -0
  50. package/dist/service/permission.repo.js +63 -0
  51. package/dist/service/permission.repo.js.map +1 -0
  52. package/dist/service/permission.service.d.ts +39 -0
  53. package/dist/service/permission.service.js +151 -0
  54. package/dist/service/permission.service.js.map +1 -0
  55. package/dist/utils/date.util.d.ts +0 -13
  56. package/dist/utils/date.util.js +0 -35
  57. package/dist/utils/date.util.js.map +1 -1
  58. package/dist/utils/index.d.ts +0 -1
  59. package/dist/utils/index.js +0 -1
  60. package/dist/utils/index.js.map +1 -1
  61. package/package.json +31 -31
  62. package/dist/utils/data.util.d.ts +0 -20
  63. package/dist/utils/data.util.js +0 -73
  64. package/dist/utils/data.util.js.map +0 -1
@@ -33,6 +33,9 @@ class EntityConfigImpl {
33
33
  ENDPOINT_POLICY; // Path-based permission configuration
34
34
  ADMIN_GROUP_NAME; // Admin group name for Cognito
35
35
  TRACE_CHANGE;
36
+ SCOPE_MAP;
37
+ ROLE_TABLE;
38
+ ROLE_PATH;
36
39
  OWNER_PARENT_ID_FIELD_NAME;
37
40
  OWNER_ID_FIELD_NAME;
38
41
  constructor(basePath, adminGroupName) {
@@ -55,6 +58,20 @@ class EntityConfigImpl {
55
58
  this.OPEN_SEARCH = new OpenSearchConfig(domain, index);
56
59
  return this;
57
60
  }
61
+ /** Set scope map for RBAC scope-based filtering */
62
+ setScopes(scopeMap) {
63
+ this.SCOPE_MAP = scopeMap;
64
+ return this;
65
+ }
66
+ /** Set the DynamoDB table name for RBAC role permissions */
67
+ setRoleTable(tableName) {
68
+ this.ROLE_TABLE = tableName;
69
+ return this;
70
+ }
71
+ setRolePath(path) {
72
+ this.ROLE_PATH = path;
73
+ return this;
74
+ }
58
75
  /** Set path-based policies */
59
76
  setPolicies(policies) {
60
77
  this.ENDPOINT_POLICY = policies;
@@ -71,6 +88,9 @@ class EntityConfigImpl {
71
88
  ADMIN_GROUP_NAME: this.ADMIN_GROUP_NAME,
72
89
  OWNER_PARENT_ID_FIELD_NAME: this.OWNER_PARENT_ID_FIELD_NAME,
73
90
  OWNER_ID_FIELD_NAME: this.OWNER_ID_FIELD_NAME,
91
+ SCOPE_MAP: this.SCOPE_MAP,
92
+ ROLE_TABLE: this.ROLE_TABLE,
93
+ ROLE_PATH: this.ROLE_PATH,
74
94
  };
75
95
  }
76
96
  }
@@ -1 +1 @@
1
- {"version":3,"file":"base.config.js","sourceRoot":"","sources":["../../src/model/base.config.ts"],"names":[],"mappings":";;;AAEA,qCAAqC;AACrC,MAAa,cAAc;IACzB,IAAI,CAAS,CAAC,sBAAsB;IACpC,uBAAuB,CAAU;IACjC,gBAAgB,CAAS,CAAC,iCAAiC;IAC3D,GAAG,CAAiB;IAEpB,YAAY,SAAiB,EAAE,cAAsB,EAAE,QAAwB,EAAE,eAAwB;QACvG,IAAI,CAAC,IAAI,GAAG,SAAS,CAAC;QACtB,IAAI,CAAC,uBAAuB,GAAG,eAAe,CAAC;QAC/C,IAAI,CAAC,gBAAgB,GAAG,cAAc,CAAC;QACvC,IAAI,CAAC,GAAG,GAAG,QAAQ,CAAC;IACtB,CAAC;CACF;AAZD,wCAYC;AAED,uCAAuC;AACvC,MAAa,gBAAgB;IAC3B,MAAM,CAAS;IACf,KAAK,CAAS;IAEd,YAAY,MAAc,EAAE,SAAiB;QAC3C,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;IACzB,CAAC;CACF;AARD,4CAQC;AAcD,0CAA0C;AAC1C,MAAa,gBAAgB;IAC3B,SAAS,CAAS,CAAC,uCAAuC;IAC1D,SAAS,CAAiB,CAAC,yBAAyB;IACpD,WAAW,CAAmB,CAAC,2BAA2B;IAC1D,eAAe,CAAmB,CAAC,sCAAsC;IACzE,gBAAgB,CAAW,CAAC,+BAA+B;IAE3D,YAAY,CAAe;IAE3B,0BAA0B,CAAU;IACpC,mBAAmB,CAAS;IAE5B,YAAY,QAAgB,EAAE,cAAyB;QACrD,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;QAC1B,IAAI,CAAC,gBAAgB,GAAG,cAAc,IAAI,CAAC,OAAO,CAAC,CAAC;QACpD,IAAI,CAAC,eAAe,GAAG,EAAE,CAAC;IAC5B,CAAC;IAED,cAAc,CAAC,WAAwB;QACrC,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC;IAClC,CAAC;IAED,iCAAiC;IACjC,WAAW,CACT,SAAiB,EACjB,cAAsB,EACtB,QAAwB,EACxB,oBAA6B;QAE7B,IAAI,CAAC,SAAS,GAAG,IAAI,cAAc,CAAC,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,oBAAoB,CAAC,CAAC;QAC/F,IAAI,CAAC,0BAA0B,GAAG,oBAAoB,CAAC;QACvD,IAAI,CAAC,mBAAmB,GAAG,cAAc,CAAC;QAC1C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,mCAAmC;IACnC,aAAa,CAAC,MAAc,EAAE,KAAa;QACzC,IAAI,CAAC,WAAW,GAAG,IAAI,gBAAgB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACvD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,8BAA8B;IAC9B,WAAW,CAAC,QAA0B;QACpC,IAAI,CAAC,eAAe,GAAG,QAAQ,CAAC;QAChC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,6EAA6E;IAC7E,QAAQ;QACN,OAAO;YACL,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,eAAe,EAAE,IAAI,CAAC,eAAe;YACrC,YAAY,EAAE,IAAI,CAAC,YAAY;YAC/B,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;YACvC,0BAA0B,EAAE,IAAI,CAAC,0BAA0B;YAC3D,mBAAmB,EAAE,IAAI,CAAC,mBAAmB;SAC9C,CAAC;IACJ,CAAC;CACF;AA5DD,4CA4DC","sourcesContent":["import { DynamoIndexMap, EndpointPolicy, TraceChange } from \"@chinggis/core\";\n\n/** Generic DynamoDB Configuration */\nexport class DynamoDBConfig {\n NAME: string; // DynamoDB table name\n OWNER_PARENT_FIELD_NAME?: string;\n OWNER_FIELD_NAME: string; // DynamoDB index map for queries\n MAP: DynamoIndexMap;\n\n constructor(tableName: string, ownerFieldName: string, indexMap: DynamoIndexMap, parentFieldName?: string) {\n this.NAME = tableName;\n this.OWNER_PARENT_FIELD_NAME = parentFieldName;\n this.OWNER_FIELD_NAME = ownerFieldName;\n this.MAP = indexMap;\n }\n}\n\n/** Generic OpenSearch Configuration */\nexport class OpenSearchConfig {\n DOMAIN: string;\n INDEX: string;\n\n constructor(domain: string, indexName: string) {\n this.DOMAIN = domain;\n this.INDEX = indexName;\n }\n}\n\n/** Configuration interface for type safety */\nexport interface EntityConfig {\n BASE_PATH: string;\n DYNAMO_DB: DynamoDBConfig;\n OPEN_SEARCH: OpenSearchConfig;\n ENDPOINT_POLICY: EndpointPolicy[];\n ADMIN_GROUP_NAME: string[];\n OWNER_PARENT_ID_FIELD_NAME?: string;\n OWNER_ID_FIELD_NAME?: string;\n TRACE_CHANGE?: TraceChange;\n}\n\n/** Generic Entity Configuration Class **/\nexport class EntityConfigImpl implements EntityConfig {\n BASE_PATH: string; //Entity name for logging and debugging\n DYNAMO_DB: DynamoDBConfig; // DynamoDB configuration\n OPEN_SEARCH: OpenSearchConfig; // OpenSearch configuration\n ENDPOINT_POLICY: EndpointPolicy[]; // Path-based permission configuration\n ADMIN_GROUP_NAME: string[]; // Admin group name for Cognito\n\n TRACE_CHANGE?: TraceChange;\n\n OWNER_PARENT_ID_FIELD_NAME?: string;\n OWNER_ID_FIELD_NAME: string;\n\n constructor(basePath: string, adminGroupName?: string[]) {\n this.BASE_PATH = basePath;\n this.ADMIN_GROUP_NAME = adminGroupName || [\"admin\"];\n this.ENDPOINT_POLICY = [];\n }\n\n setTraceChange(traceChange: TraceChange): void {\n this.TRACE_CHANGE = traceChange;\n }\n\n /** Set DynamoDB configuration */\n setDynamoDB(\n tableName: string,\n ownerFieldName: string,\n indexMap: DynamoIndexMap,\n ownerParentFieldName?: string,\n ): this {\n this.DYNAMO_DB = new DynamoDBConfig(tableName, ownerFieldName, indexMap, ownerParentFieldName);\n this.OWNER_PARENT_ID_FIELD_NAME = ownerParentFieldName;\n this.OWNER_ID_FIELD_NAME = ownerFieldName;\n return this;\n }\n\n /** Set OpenSearch configuration */\n setOpenSearch(domain: string, index: string): this {\n this.OPEN_SEARCH = new OpenSearchConfig(domain, index);\n return this;\n }\n\n /** Set path-based policies */\n setPolicies(policies: EndpointPolicy[]): this {\n this.ENDPOINT_POLICY = policies;\n return this;\n }\n\n /** Get configuration as a plain object (for BaseController compatibility) */\n toObject(): EntityConfig {\n return {\n BASE_PATH: this.BASE_PATH,\n DYNAMO_DB: this.DYNAMO_DB,\n OPEN_SEARCH: this.OPEN_SEARCH,\n ENDPOINT_POLICY: this.ENDPOINT_POLICY,\n TRACE_CHANGE: this.TRACE_CHANGE,\n ADMIN_GROUP_NAME: this.ADMIN_GROUP_NAME,\n OWNER_PARENT_ID_FIELD_NAME: this.OWNER_PARENT_ID_FIELD_NAME,\n OWNER_ID_FIELD_NAME: this.OWNER_ID_FIELD_NAME,\n };\n }\n}\n"]}
1
+ {"version":3,"file":"base.config.js","sourceRoot":"","sources":["../../src/model/base.config.ts"],"names":[],"mappings":"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","sourcesContent":["import { DynamoIndexMap, EndpointPolicy, ScopeMap, TraceChange } from \"@chinggis/core\";\n\n/** Generic DynamoDB Configuration */\nexport class DynamoDBConfig {\n NAME: string; // DynamoDB table name\n OWNER_PARENT_FIELD_NAME?: string;\n OWNER_FIELD_NAME: string; // DynamoDB index map for queries\n MAP: DynamoIndexMap;\n\n constructor(tableName: string, ownerFieldName: string, indexMap: DynamoIndexMap, parentFieldName?: string) {\n this.NAME = tableName;\n this.OWNER_PARENT_FIELD_NAME = parentFieldName;\n this.OWNER_FIELD_NAME = ownerFieldName;\n this.MAP = indexMap;\n }\n}\n\n/** Generic OpenSearch Configuration */\nexport class OpenSearchConfig {\n DOMAIN: string;\n INDEX: string;\n\n constructor(domain: string, indexName: string) {\n this.DOMAIN = domain;\n this.INDEX = indexName;\n }\n}\n\n/** Configuration interface for type safety */\nexport interface EntityConfig {\n BASE_PATH: string;\n DYNAMO_DB: DynamoDBConfig;\n OPEN_SEARCH: OpenSearchConfig;\n ENDPOINT_POLICY: EndpointPolicy[];\n ADMIN_GROUP_NAME: string[];\n OWNER_PARENT_ID_FIELD_NAME?: string;\n OWNER_ID_FIELD_NAME?: string;\n TRACE_CHANGE?: TraceChange;\n SCOPE_MAP?: ScopeMap;\n ROLE_TABLE?: string;\n ROLE_PATH?: string;\n}\n\n/** Generic Entity Configuration Class **/\nexport class EntityConfigImpl implements EntityConfig {\n BASE_PATH: string; //Entity name for logging and debugging\n DYNAMO_DB: DynamoDBConfig; // DynamoDB configuration\n OPEN_SEARCH: OpenSearchConfig; // OpenSearch configuration\n ENDPOINT_POLICY: EndpointPolicy[]; // Path-based permission configuration\n ADMIN_GROUP_NAME: string[]; // Admin group name for Cognito\n\n TRACE_CHANGE?: TraceChange;\n SCOPE_MAP?: ScopeMap;\n ROLE_TABLE?: string;\n ROLE_PATH?: string;\n\n OWNER_PARENT_ID_FIELD_NAME?: string;\n OWNER_ID_FIELD_NAME: string;\n\n constructor(basePath: string, adminGroupName?: string[]) {\n this.BASE_PATH = basePath;\n this.ADMIN_GROUP_NAME = adminGroupName || [\"admin\"];\n this.ENDPOINT_POLICY = [];\n }\n\n setTraceChange(traceChange: TraceChange): void {\n this.TRACE_CHANGE = traceChange;\n }\n\n /** Set DynamoDB configuration */\n setDynamoDB(\n tableName: string,\n ownerFieldName: string,\n indexMap: DynamoIndexMap,\n ownerParentFieldName?: string,\n ): this {\n this.DYNAMO_DB = new DynamoDBConfig(tableName, ownerFieldName, indexMap, ownerParentFieldName);\n this.OWNER_PARENT_ID_FIELD_NAME = ownerParentFieldName;\n this.OWNER_ID_FIELD_NAME = ownerFieldName;\n return this;\n }\n\n /** Set OpenSearch configuration */\n setOpenSearch(domain: string, index: string): this {\n this.OPEN_SEARCH = new OpenSearchConfig(domain, index);\n return this;\n }\n\n /** Set scope map for RBAC scope-based filtering */\n setScopes(scopeMap: ScopeMap): this {\n this.SCOPE_MAP = scopeMap;\n return this;\n }\n\n /** Set the DynamoDB table name for RBAC role permissions */\n setRoleTable(tableName: string): this {\n this.ROLE_TABLE = tableName;\n return this;\n }\n\n setRolePath(path: string): this {\n this.ROLE_PATH = path;\n return this;\n }\n\n /** Set path-based policies */\n setPolicies(policies: EndpointPolicy[]): this {\n this.ENDPOINT_POLICY = policies;\n return this;\n }\n\n /** Get configuration as a plain object (for BaseController compatibility) */\n toObject(): EntityConfig {\n return {\n BASE_PATH: this.BASE_PATH,\n DYNAMO_DB: this.DYNAMO_DB,\n OPEN_SEARCH: this.OPEN_SEARCH,\n ENDPOINT_POLICY: this.ENDPOINT_POLICY,\n TRACE_CHANGE: this.TRACE_CHANGE,\n ADMIN_GROUP_NAME: this.ADMIN_GROUP_NAME,\n OWNER_PARENT_ID_FIELD_NAME: this.OWNER_PARENT_ID_FIELD_NAME,\n OWNER_ID_FIELD_NAME: this.OWNER_ID_FIELD_NAME,\n SCOPE_MAP: this.SCOPE_MAP,\n ROLE_TABLE: this.ROLE_TABLE,\n ROLE_PATH: this.ROLE_PATH,\n };\n }\n}\n"]}
@@ -45,3 +45,22 @@ export declare enum InclusionMode {
45
45
  INCLUDE = "include",
46
46
  EXCLUDE = "exclude"
47
47
  }
48
+ export interface RolePermission {
49
+ role: string;
50
+ permissions: string[];
51
+ }
52
+ /**
53
+ * Map of scope name → { filterField, claimKey }.
54
+ * Key is scope name (e.g. "branch", "agent", "org").
55
+ *
56
+ * Usage:
57
+ * new ScopeMap()
58
+ * .set("branch", { filterField: "branchId", claimKey: "custom:branch" })
59
+ * .set("agent", { filterField: "agentId", claimKey: "custom:agent" })
60
+ * .set("org", { filterField: "orgId", claimKey: "custom:org" })
61
+ */
62
+ export declare class ScopeMap extends Map<string, {
63
+ filterField: string;
64
+ claimKey?: string;
65
+ }> {
66
+ }
@@ -1,9 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.InclusionMode = void 0;
3
+ exports.ScopeMap = exports.InclusionMode = void 0;
4
4
  var InclusionMode;
5
5
  (function (InclusionMode) {
6
6
  InclusionMode["INCLUDE"] = "include";
7
7
  InclusionMode["EXCLUDE"] = "exclude";
8
8
  })(InclusionMode || (exports.InclusionMode = InclusionMode = {}));
9
+ /**
10
+ * Map of scope name → { filterField, claimKey }.
11
+ * Key is scope name (e.g. "branch", "agent", "org").
12
+ *
13
+ * Usage:
14
+ * new ScopeMap()
15
+ * .set("branch", { filterField: "branchId", claimKey: "custom:branch" })
16
+ * .set("agent", { filterField: "agentId", claimKey: "custom:agent" })
17
+ * .set("org", { filterField: "orgId", claimKey: "custom:org" })
18
+ */
19
+ class ScopeMap extends Map {
20
+ }
21
+ exports.ScopeMap = ScopeMap;
9
22
  //# sourceMappingURL=base.model.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"base.model.js","sourceRoot":"","sources":["../../src/model/base.model.ts"],"names":[],"mappings":";;;AAwDA,IAAY,aAGX;AAHD,WAAY,aAAa;IACvB,oCAAmB,CAAA;IACnB,oCAAmB,CAAA;AACrB,CAAC,EAHW,aAAa,6BAAb,aAAa,QAGxB","sourcesContent":["import { RequestType } from \"./validation.model\";\nimport { Action } from \"./http.model\";\n\nexport interface BaseEntity {\n id?: string;\n createdAt?: string;\n updatedAt?: string;\n\n createdBy?: any;\n updatedBy?: any;\n\n ownerId?: string;\n ownerParentId?: string;\n\n permission?: Map<string, Permission[]>;\n\n history?: ChangeHistory[];\n\n year?: string;\n yearMonth?: string;\n}\n\nexport interface ChangedField {\n fieldName: string;\n oldValue: string;\n newValue: string;\n}\n\nexport interface ChangeHistory {\n action: string;\n user: string;\n date: string;\n changes?: ChangedField[];\n}\n\nexport interface Permission {\n role: RequestType;\n actions: Action[];\n}\n\nexport interface Attachment {\n thumbnail: string;\n original: string;\n}\n\nexport interface List<T> {\n items: Array<T>;\n total?: number;\n lastKey?: string;\n}\n\nexport interface TraceChange {\n fields: string[];\n mode: InclusionMode;\n}\n\nexport enum InclusionMode {\n INCLUDE = \"include\",\n EXCLUDE = \"exclude\",\n}\n"]}
1
+ {"version":3,"file":"base.model.js","sourceRoot":"","sources":["../../src/model/base.model.ts"],"names":[],"mappings":";;;AAwDA,IAAY,aAGX;AAHD,WAAY,aAAa;IACvB,oCAAmB,CAAA;IACnB,oCAAmB,CAAA;AACrB,CAAC,EAHW,aAAa,6BAAb,aAAa,QAGxB;AAOD;;;;;;;;;GASG;AACH,MAAa,QAAS,SAAQ,GAAuD;CAAG;AAAxF,4BAAwF","sourcesContent":["import { RequestType } from \"./validation.model\";\nimport { Action } from \"./http.model\";\n\nexport interface BaseEntity {\n id?: string;\n createdAt?: string;\n updatedAt?: string;\n\n createdBy?: any;\n updatedBy?: any;\n\n ownerId?: string;\n ownerParentId?: string;\n\n permission?: Map<string, Permission[]>;\n\n history?: ChangeHistory[];\n\n year?: string;\n yearMonth?: string;\n}\n\nexport interface ChangedField {\n fieldName: string;\n oldValue: string;\n newValue: string;\n}\n\nexport interface ChangeHistory {\n action: string;\n user: string;\n date: string;\n changes?: ChangedField[];\n}\n\nexport interface Permission {\n role: RequestType;\n actions: Action[];\n}\n\nexport interface Attachment {\n thumbnail: string;\n original: string;\n}\n\nexport interface List<T> {\n items: Array<T>;\n total?: number;\n lastKey?: string;\n}\n\nexport interface TraceChange {\n fields: string[];\n mode: InclusionMode;\n}\n\nexport enum InclusionMode {\n INCLUDE = \"include\",\n EXCLUDE = \"exclude\",\n}\n\nexport interface RolePermission {\n role: string;\n permissions: string[];\n}\n\n/**\n * Map of scope name → { filterField, claimKey }.\n * Key is scope name (e.g. \"branch\", \"agent\", \"org\").\n *\n * Usage:\n * new ScopeMap()\n * .set(\"branch\", { filterField: \"branchId\", claimKey: \"custom:branch\" })\n * .set(\"agent\", { filterField: \"agentId\", claimKey: \"custom:agent\" })\n * .set(\"org\", { filterField: \"orgId\", claimKey: \"custom:org\" })\n */\nexport class ScopeMap extends Map<string, { filterField: string; claimKey?: string }> {}\n"]}
@@ -5,3 +5,4 @@ export * from "./filter.model";
5
5
  export * from "./validation.model";
6
6
  export * from "./dynamodb.model";
7
7
  export * from "./base.config";
8
+ export { Permission as RolePermission } from "./role.model";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/model/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,uDAAqC;AACrC,+CAA6B;AAC7B,+CAA6B;AAC7B,iDAA+B;AAC/B,qDAAmC;AACnC,mDAAiC;AACjC,gDAA8B","sourcesContent":["export * from \"./cognito-user.model\";\nexport * from \"./http.model\";\nexport * from \"./base.model\";\nexport * from \"./filter.model\";\nexport * from \"./validation.model\";\nexport * from \"./dynamodb.model\";\nexport * from \"./base.config\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/model/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,uDAAqC;AACrC,+CAA6B;AAC7B,+CAA6B;AAC7B,iDAA+B;AAC/B,qDAAmC;AACnC,mDAAiC;AACjC,gDAA8B","sourcesContent":["export * from \"./cognito-user.model\";\nexport * from \"./http.model\";\nexport * from \"./base.model\";\nexport * from \"./filter.model\";\nexport * from \"./validation.model\";\nexport * from \"./dynamodb.model\";\nexport * from \"./base.config\";\nexport { Permission as RolePermission } from \"./role.model\";\n"]}
@@ -0,0 +1,20 @@
1
+ import { BaseEntity } from "./base.model";
2
+ export interface Permission extends BaseEntity {
3
+ role: string;
4
+ resource: string;
5
+ scope: string;
6
+ method: {
7
+ get?: boolean;
8
+ post?: boolean;
9
+ patch?: boolean;
10
+ put?: boolean;
11
+ delete?: boolean;
12
+ };
13
+ permissionKey: string;
14
+ }
15
+ /**
16
+ * Build a fully-qualified permission key.
17
+ * Format: `${role}.${resource}.${scope}.${method}`
18
+ * Example: `OwnerRole.PropertyTable.BranchScope.GET`
19
+ */
20
+ export declare function buildPermissionKey(role: string, resource: string, scope: string, method: string): string;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildPermissionKey = buildPermissionKey;
4
+ /**
5
+ * Build a fully-qualified permission key.
6
+ * Format: `${role}.${resource}.${scope}.${method}`
7
+ * Example: `OwnerRole.PropertyTable.BranchScope.GET`
8
+ */
9
+ function buildPermissionKey(role, resource, scope, method) {
10
+ return `${role}.${resource}.${scope}.${method}`;
11
+ }
12
+ //# sourceMappingURL=role.model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role.model.js","sourceRoot":"","sources":["../../src/model/role.model.ts"],"names":[],"mappings":";;AAqBA,gDAEC;AAPD;;;;GAIG;AACH,SAAgB,kBAAkB,CAAC,IAAY,EAAE,QAAgB,EAAE,KAAa,EAAE,MAAc;IAC9F,OAAO,GAAG,IAAI,IAAI,QAAQ,IAAI,KAAK,IAAI,MAAM,EAAE,CAAC;AAClD,CAAC","sourcesContent":["import { BaseEntity } from \"./base.model\";\n\nexport interface Permission extends BaseEntity {\n role: string; // DB Index: Manager | User etc\n resource: string; // DB Index: Property | Profile etc\n scope: string; // DB Index: Organization | Branch | Agent\n method: {\n get?: boolean;\n post?: boolean;\n patch?: boolean;\n put?: boolean;\n delete?: boolean;\n };\n permissionKey: string; // DB Index: role#resourse#scope Manager#Property#Branch\n}\n\n/**\n * Build a fully-qualified permission key.\n * Format: `${role}.${resource}.${scope}.${method}`\n * Example: `OwnerRole.PropertyTable.BranchScope.GET`\n */\nexport function buildPermissionKey(role: string, resource: string, scope: string, method: string): string {\n return `${role}.${resource}.${scope}.${method}`;\n}\n"]}
@@ -7,7 +7,7 @@ export interface ResponseFields {
7
7
  export interface EndpointPolicy {
8
8
  method: HttpMethod;
9
9
  path: string;
10
- access: Access[];
10
+ access?: Access[];
11
11
  validator?: any;
12
12
  response?: ResponseFields;
13
13
  }
@@ -1 +1 @@
1
- {"version":3,"file":"validation.model.js","sourceRoot":"","sources":["../../src/model/validation.model.ts"],"names":[],"mappings":";;;AAgBA,IAAY,WAKX;AALD,WAAY,WAAW;IACrB,8BAAe,CAAA;IACf,4BAAa,CAAA;IACb,8BAAe,CAAA;IACf,gCAAiB,CAAA;AACnB,CAAC,EALW,WAAW,2BAAX,WAAW,QAKtB;AAED,IAAY,MAMX;AAND,WAAY,MAAM;IAChB,2BAAiB,CAAA;IACjB,uBAAa,CAAA;IACb,yBAAe,CAAA;IACf,yBAAe,CAAA;IACf,2BAAiB,CAAA;AACnB,CAAC,EANW,MAAM,sBAAN,MAAM,QAMjB","sourcesContent":["import { HttpMethod } from \"@chinggis/core\";\n\nexport interface ResponseFields {\n include?: string[];\n exclude?: string[];\n}\n\n/** Path-based permission definition */\nexport interface EndpointPolicy {\n method: HttpMethod;\n path: string;\n access: Access[];\n validator?: any;\n response?: ResponseFields;\n}\n\nexport enum RequestType {\n GUEST = \"guest\",\n USER = \"user\",\n ADMIN = \"admin\",\n SYSTEM = \"system\",\n}\n\nexport enum Access {\n PUBLIC = \"public\",\n USER = \"user\",\n OWNER = \"owner\",\n ADMIN = \"admin\",\n SYSTEM = \"system\",\n}\n"]}
1
+ {"version":3,"file":"validation.model.js","sourceRoot":"","sources":["../../src/model/validation.model.ts"],"names":[],"mappings":";;;AAgBA,IAAY,WAKX;AALD,WAAY,WAAW;IACrB,8BAAe,CAAA;IACf,4BAAa,CAAA;IACb,8BAAe,CAAA;IACf,gCAAiB,CAAA;AACnB,CAAC,EALW,WAAW,2BAAX,WAAW,QAKtB;AAED,IAAY,MAMX;AAND,WAAY,MAAM;IAChB,2BAAiB,CAAA;IACjB,uBAAa,CAAA;IACb,yBAAe,CAAA;IACf,yBAAe,CAAA;IACf,2BAAiB,CAAA;AACnB,CAAC,EANW,MAAM,sBAAN,MAAM,QAMjB","sourcesContent":["import { HttpMethod } from \"@chinggis/core\";\n\nexport interface ResponseFields {\n include?: string[];\n exclude?: string[];\n}\n\n/** Path-based permission definition */\nexport interface EndpointPolicy {\n method: HttpMethod;\n path: string;\n access?: Access[];\n validator?: any;\n response?: ResponseFields;\n}\n\nexport enum RequestType {\n GUEST = \"guest\",\n USER = \"user\",\n ADMIN = \"admin\",\n SYSTEM = \"system\",\n}\n\nexport enum Access {\n PUBLIC = \"public\",\n USER = \"user\",\n OWNER = \"owner\",\n ADMIN = \"admin\",\n SYSTEM = \"system\",\n}\n"]}
@@ -7,3 +7,6 @@ export * from "./crud.service";
7
7
  export * from "./crud.service.interface";
8
8
  export * from "./stream.service";
9
9
  export * from "./stream.service.interface";
10
+ export * from "./permission.cache";
11
+ export * from "./permission.repo";
12
+ export * from "./permission.service";
@@ -23,4 +23,7 @@ __exportStar(require("./crud.service"), exports);
23
23
  __exportStar(require("./crud.service.interface"), exports);
24
24
  __exportStar(require("./stream.service"), exports);
25
25
  __exportStar(require("./stream.service.interface"), exports);
26
+ __exportStar(require("./permission.cache"), exports);
27
+ __exportStar(require("./permission.repo"), exports);
28
+ __exportStar(require("./permission.service"), exports);
26
29
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/service/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,iDAA+B;AAC/B,iDAA+B;AAC/B,2DAAyC;AACzC,mDAAiC;AACjC,6DAA2C;AAC3C,iDAA+B;AAC/B,2DAAyC;AACzC,mDAAiC;AACjC,6DAA2C","sourcesContent":["export * from \"./api.services\";\nexport * from \"./base.service\";\nexport * from \"./base.service.interface\";\nexport * from \"./socket.service\";\nexport * from \"./socket.service.interface\";\nexport * from \"./crud.service\";\nexport * from \"./crud.service.interface\";\nexport * from \"./stream.service\";\nexport * from \"./stream.service.interface\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/service/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,iDAA+B;AAC/B,iDAA+B;AAC/B,2DAAyC;AACzC,mDAAiC;AACjC,6DAA2C;AAC3C,iDAA+B;AAC/B,2DAAyC;AACzC,mDAAiC;AACjC,6DAA2C;AAC3C,qDAAmC;AACnC,oDAAkC;AAClC,uDAAqC","sourcesContent":["export * from \"./api.services\";\nexport * from \"./base.service\";\nexport * from \"./base.service.interface\";\nexport * from \"./socket.service\";\nexport * from \"./socket.service.interface\";\nexport * from \"./crud.service\";\nexport * from \"./crud.service.interface\";\nexport * from \"./stream.service\";\nexport * from \"./stream.service.interface\";\nexport * from \"./permission.cache\";\nexport * from \"./permission.repo\";\nexport * from \"./permission.service\";\n"]}
@@ -0,0 +1,24 @@
1
+ import { Permission } from "../model/role.model";
2
+ /**
3
+ * In-memory read-through cache for Permission entities.
4
+ *
5
+ * - Keyed by permissionKey (deterministic, namespaced).
6
+ * - TTL-based expiry per entry.
7
+ * - Survives across warm Lambda invocations (module-level singleton).
8
+ * - Handles concurrent fetches for the same key via in-flight dedup.
9
+ */
10
+ export declare class PermissionCache {
11
+ private readonly store;
12
+ private readonly inflight;
13
+ private readonly ttlMs;
14
+ constructor(ttlMs?: number);
15
+ get(key: string): Permission | null;
16
+ set(key: string, value: Permission): void;
17
+ /**
18
+ * Read-through: returns cached value or calls `fetcher` exactly once.
19
+ * Concurrent calls for the same key share a single in-flight promise.
20
+ */
21
+ getOrFetch(key: string, fetcher: () => Promise<Permission | null>): Promise<Permission | null>;
22
+ invalidate(key: string): void;
23
+ clear(): void;
24
+ }
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PermissionCache = void 0;
4
+ const DEFAULT_TTL_MS = 15 * 60 * 1000; // 15 minutes
5
+ /**
6
+ * In-memory read-through cache for Permission entities.
7
+ *
8
+ * - Keyed by permissionKey (deterministic, namespaced).
9
+ * - TTL-based expiry per entry.
10
+ * - Survives across warm Lambda invocations (module-level singleton).
11
+ * - Handles concurrent fetches for the same key via in-flight dedup.
12
+ */
13
+ class PermissionCache {
14
+ store = new Map();
15
+ inflight = new Map();
16
+ ttlMs;
17
+ constructor(ttlMs = DEFAULT_TTL_MS) {
18
+ this.ttlMs = ttlMs;
19
+ }
20
+ get(key) {
21
+ const entry = this.store.get(key);
22
+ if (!entry)
23
+ return null;
24
+ if (Date.now() > entry.expiresAt) {
25
+ this.store.delete(key);
26
+ return null;
27
+ }
28
+ return entry.value;
29
+ }
30
+ set(key, value) {
31
+ this.store.set(key, { value, expiresAt: Date.now() + this.ttlMs });
32
+ }
33
+ /**
34
+ * Read-through: returns cached value or calls `fetcher` exactly once.
35
+ * Concurrent calls for the same key share a single in-flight promise.
36
+ */
37
+ async getOrFetch(key, fetcher) {
38
+ const cached = this.get(key);
39
+ if (cached)
40
+ return cached;
41
+ const existing = this.inflight.get(key);
42
+ if (existing)
43
+ return existing;
44
+ const promise = fetcher().then((result) => {
45
+ this.inflight.delete(key);
46
+ if (result)
47
+ this.set(key, result);
48
+ return result;
49
+ });
50
+ this.inflight.set(key, promise);
51
+ return promise;
52
+ }
53
+ invalidate(key) {
54
+ this.store.delete(key);
55
+ }
56
+ clear() {
57
+ this.store.clear();
58
+ }
59
+ }
60
+ exports.PermissionCache = PermissionCache;
61
+ //# sourceMappingURL=permission.cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission.cache.js","sourceRoot":"","sources":["../../src/service/permission.cache.ts"],"names":[],"mappings":";;;AAOA,MAAM,cAAc,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,aAAa;AAEpD;;;;;;;GAOG;AACH,MAAa,eAAe;IACT,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;IACtC,QAAQ,GAAG,IAAI,GAAG,EAAsC,CAAC;IACzD,KAAK,CAAS;IAE/B,YAAY,QAAgB,cAAc;QACxC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAED,GAAG,CAAC,GAAW;QACb,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;YACjC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACvB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,KAAK,CAAC,KAAK,CAAC;IACrB,CAAC;IAED,GAAG,CAAC,GAAW,EAAE,KAAiB;QAChC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IACrE,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,UAAU,CAAC,GAAW,EAAE,OAAyC;QACrE,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;QAE1B,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;QAE9B,MAAM,OAAO,GAAG,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;YACxC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC1B,IAAI,MAAM;gBAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAClC,OAAO,MAAM,CAAC;QAChB,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAChC,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,UAAU,CAAC,GAAW;QACpB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAED,KAAK;QACH,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;CACF;AAnDD,0CAmDC","sourcesContent":["import { Permission } from \"../model/role.model\";\n\ninterface CacheEntry {\n value: Permission;\n expiresAt: number;\n}\n\nconst DEFAULT_TTL_MS = 15 * 60 * 1000; // 15 minutes\n\n/**\n * In-memory read-through cache for Permission entities.\n *\n * - Keyed by permissionKey (deterministic, namespaced).\n * - TTL-based expiry per entry.\n * - Survives across warm Lambda invocations (module-level singleton).\n * - Handles concurrent fetches for the same key via in-flight dedup.\n */\nexport class PermissionCache {\n private readonly store = new Map<string, CacheEntry>();\n private readonly inflight = new Map<string, Promise<Permission | null>>();\n private readonly ttlMs: number;\n\n constructor(ttlMs: number = DEFAULT_TTL_MS) {\n this.ttlMs = ttlMs;\n }\n\n get(key: string): Permission | null {\n const entry = this.store.get(key);\n if (!entry) return null;\n if (Date.now() > entry.expiresAt) {\n this.store.delete(key);\n return null;\n }\n return entry.value;\n }\n\n set(key: string, value: Permission): void {\n this.store.set(key, { value, expiresAt: Date.now() + this.ttlMs });\n }\n\n /**\n * Read-through: returns cached value or calls `fetcher` exactly once.\n * Concurrent calls for the same key share a single in-flight promise.\n */\n async getOrFetch(key: string, fetcher: () => Promise<Permission | null>): Promise<Permission | null> {\n const cached = this.get(key);\n if (cached) return cached;\n\n const existing = this.inflight.get(key);\n if (existing) return existing;\n\n const promise = fetcher().then((result) => {\n this.inflight.delete(key);\n if (result) this.set(key, result);\n return result;\n });\n\n this.inflight.set(key, promise);\n return promise;\n }\n\n invalidate(key: string): void {\n this.store.delete(key);\n }\n\n clear(): void {\n this.store.clear();\n }\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import { Permission } from "../model/role.model";
2
+ import { List } from "../model/base.model";
3
+ /**
4
+ * Repository layer for Permission table.
5
+ * Pure DB operations — no caching, no business logic.
6
+ */
7
+ export declare class PermissionRepo {
8
+ private readonly db;
9
+ constructor(tableName: string);
10
+ findById(id: string): Promise<Permission | null>;
11
+ findByPermissionKey(permissionKey: string): Promise<Permission | null>;
12
+ save(permission: Partial<Permission>): Promise<Permission>;
13
+ update(permission: Partial<Permission>): Promise<Permission>;
14
+ delete(id: string): Promise<boolean>;
15
+ scan(): Promise<List<Partial<Permission>>>;
16
+ }
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PermissionRepo = void 0;
4
+ const base_db_repo_1 = require("../repositories/base-db.repo");
5
+ const dynamodb_model_1 = require("../model/dynamodb.model");
6
+ const dynamodb_utils_1 = require("../utils/dynamodb.utils");
7
+ function createPermissionIndexMap() {
8
+ const map = new dynamodb_model_1.DynamoIndexMap();
9
+ map.partitionKey = "id";
10
+ map.set("byPermissionKey", { field: "permissionKey", rFields: ["role", "resource", "scope"], fieldSeparator: "#" });
11
+ map.mapFields = ["method"];
12
+ return map;
13
+ }
14
+ /**
15
+ * Repository layer for Permission table.
16
+ * Pure DB operations — no caching, no business logic.
17
+ */
18
+ class PermissionRepo {
19
+ db;
20
+ constructor(tableName) {
21
+ this.db = new base_db_repo_1.BaseRepoDBImpl();
22
+ this.db.setTable(tableName);
23
+ this.db.setIndexMap(createPermissionIndexMap());
24
+ }
25
+ async findById(id) {
26
+ return this.db.findById(id);
27
+ }
28
+ async findByPermissionKey(permissionKey) {
29
+ return this.db.findOne({
30
+ indexName: "byPermissionKey",
31
+ indexValue: permissionKey,
32
+ });
33
+ }
34
+ async save(permission) {
35
+ if (!permission.id)
36
+ permission.id = (0, dynamodb_utils_1.generateUUID)();
37
+ const now = new Date().toISOString();
38
+ if (!permission.createdAt)
39
+ permission.createdAt = now;
40
+ permission.updatedAt = now;
41
+ // Build composite key: role#resource#scope
42
+ if (permission.role && permission.resource && permission.scope) {
43
+ permission.permissionKey = `${permission.role}#${permission.resource}#${permission.scope}`;
44
+ }
45
+ return this.db.save(permission);
46
+ }
47
+ async update(permission) {
48
+ permission.updatedAt = new Date().toISOString();
49
+ // Rebuild composite key if components changed
50
+ if (permission.role && permission.resource && permission.scope) {
51
+ permission.permissionKey = `${permission.role}#${permission.resource}#${permission.scope}`;
52
+ }
53
+ return this.db.update(permission);
54
+ }
55
+ async delete(id) {
56
+ return this.db.delete(id);
57
+ }
58
+ async scan() {
59
+ return this.db.scan({});
60
+ }
61
+ }
62
+ exports.PermissionRepo = PermissionRepo;
63
+ //# sourceMappingURL=permission.repo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission.repo.js","sourceRoot":"","sources":["../../src/service/permission.repo.ts"],"names":[],"mappings":";;;AAAA,+DAA8D;AAC9D,4DAAyD;AAEzD,4DAAuD;AAGvD,SAAS,wBAAwB;IAC/B,MAAM,GAAG,GAAG,IAAI,+BAAc,EAAE,CAAC;IACjC,GAAG,CAAC,YAAY,GAAG,IAAI,CAAC;IACxB,GAAG,CAAC,GAAG,CAAC,iBAAiB,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC,CAAC;IACpH,GAAG,CAAC,SAAS,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC3B,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;GAGG;AACH,MAAa,cAAc;IACR,EAAE,CAA6B;IAEhD,YAAY,SAAiB;QAC3B,IAAI,CAAC,EAAE,GAAG,IAAI,6BAAc,EAAc,CAAC;QAC3C,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QAC5B,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,wBAAwB,EAAE,CAAC,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,EAAU;QACvB,OAAO,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,mBAAmB,CAAC,aAAqB;QAC7C,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;YACrB,SAAS,EAAE,iBAAiB;YAC5B,UAAU,EAAE,aAAa;SAC1B,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,UAA+B;QACxC,IAAI,CAAC,UAAU,CAAC,EAAE;YAAE,UAAU,CAAC,EAAE,GAAG,IAAA,6BAAY,GAAE,CAAC;QAEnD,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACrC,IAAI,CAAC,UAAU,CAAC,SAAS;YAAE,UAAU,CAAC,SAAS,GAAG,GAAG,CAAC;QACtD,UAAU,CAAC,SAAS,GAAG,GAAG,CAAC;QAE3B,2CAA2C;QAC3C,IAAI,UAAU,CAAC,IAAI,IAAI,UAAU,CAAC,QAAQ,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC;YAC/D,UAAU,CAAC,aAAa,GAAG,GAAG,UAAU,CAAC,IAAI,IAAI,UAAU,CAAC,QAAQ,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC;QAC7F,CAAC;QAED,OAAO,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,UAA+B;QAC1C,UAAU,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAEhD,8CAA8C;QAC9C,IAAI,UAAU,CAAC,IAAI,IAAI,UAAU,CAAC,QAAQ,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC;YAC/D,UAAU,CAAC,aAAa,GAAG,GAAG,UAAU,CAAC,IAAI,IAAI,UAAU,CAAC,QAAQ,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC;QAC7F,CAAC;QAED,OAAO,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,EAAU;QACrB,OAAO,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,IAAI;QACR,OAAO,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1B,CAAC;CACF;AArDD,wCAqDC","sourcesContent":["import { BaseRepoDBImpl } from \"../repositories/base-db.repo\";\nimport { DynamoIndexMap } from \"../model/dynamodb.model\";\nimport { Permission } from \"../model/role.model\";\nimport { generateUUID } from \"../utils/dynamodb.utils\";\nimport { List } from \"../model/base.model\";\n\nfunction createPermissionIndexMap(): DynamoIndexMap {\n const map = new DynamoIndexMap();\n map.partitionKey = \"id\";\n map.set(\"byPermissionKey\", { field: \"permissionKey\", rFields: [\"role\", \"resource\", \"scope\"], fieldSeparator: \"#\" });\n map.mapFields = [\"method\"];\n return map;\n}\n\n/**\n * Repository layer for Permission table.\n * Pure DB operations — no caching, no business logic.\n */\nexport class PermissionRepo {\n private readonly db: BaseRepoDBImpl<Permission>;\n\n constructor(tableName: string) {\n this.db = new BaseRepoDBImpl<Permission>();\n this.db.setTable(tableName);\n this.db.setIndexMap(createPermissionIndexMap());\n }\n\n async findById(id: string): Promise<Permission | null> {\n return this.db.findById(id);\n }\n\n async findByPermissionKey(permissionKey: string): Promise<Permission | null> {\n return this.db.findOne({\n indexName: \"byPermissionKey\",\n indexValue: permissionKey,\n });\n }\n\n async save(permission: Partial<Permission>): Promise<Permission> {\n if (!permission.id) permission.id = generateUUID();\n\n const now = new Date().toISOString();\n if (!permission.createdAt) permission.createdAt = now;\n permission.updatedAt = now;\n\n // Build composite key: role#resource#scope\n if (permission.role && permission.resource && permission.scope) {\n permission.permissionKey = `${permission.role}#${permission.resource}#${permission.scope}`;\n }\n\n return this.db.save(permission);\n }\n\n async update(permission: Partial<Permission>): Promise<Permission> {\n permission.updatedAt = new Date().toISOString();\n\n // Rebuild composite key if components changed\n if (permission.role && permission.resource && permission.scope) {\n permission.permissionKey = `${permission.role}#${permission.resource}#${permission.scope}`;\n }\n\n return this.db.update(permission);\n }\n\n async delete(id: string): Promise<boolean> {\n return this.db.delete(id);\n }\n\n async scan(): Promise<List<Partial<Permission>>> {\n return this.db.scan({});\n }\n}\n"]}
@@ -0,0 +1,39 @@
1
+ import { Permission } from "../model/role.model";
2
+ /**
3
+ * PermissionService — cache + orchestration layer for Permission entities.
4
+ *
5
+ * Design decisions:
6
+ * - Read-through cache via PermissionCache (in-flight dedup, TTL).
7
+ * - Repository handles only DB I/O.
8
+ * - Cache invalidation on every mutation.
9
+ * - ~100 roles total, read-heavy → caching makes sense.
10
+ * - Cache keyed by `perm:{permissionKey}` for deterministic, namespaced lookup.
11
+ */
12
+ export declare class PermissionService {
13
+ private readonly repo;
14
+ private readonly cache;
15
+ constructor(tableName: string, cacheTtlMs?: number);
16
+ /** Fetch Permission by composite key (role#resource#scope). Read-through cached. */
17
+ getPermissionByKey(permissionKey: string): Promise<Permission | null>;
18
+ /** Fetch Permission by id. Read-through cached. */
19
+ getPermissionById(id: string): Promise<Permission | null>;
20
+ /**
21
+ * Check if the given role+resource+scope has a specific HTTP method allowed.
22
+ *
23
+ * Builds the permissionKey `role#resource#scope`, fetches the Permission,
24
+ * and checks the method map.
25
+ *
26
+ * @returns `true` if allowed, `false` otherwise.
27
+ */
28
+ hasPermission(role: string, resource: string, scope: string, method: string): Promise<boolean>;
29
+ createPermission(input: {
30
+ role: string;
31
+ resource: string;
32
+ scope: string;
33
+ method: Permission["method"];
34
+ }): Promise<Permission>;
35
+ updatePermission(id: string, updates: Partial<Pick<Permission, "role" | "resource" | "scope" | "method">>): Promise<Permission>;
36
+ deletePermission(id: string): Promise<boolean>;
37
+ listPermissions(): Promise<Permission[]>;
38
+ clearCache(): void;
39
+ }
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PermissionService = void 0;
4
+ const exception_1 = require("../exception");
5
+ const permission_cache_1 = require("./permission.cache");
6
+ const permission_repo_1 = require("./permission.repo");
7
+ /**
8
+ * PermissionService — cache + orchestration layer for Permission entities.
9
+ *
10
+ * Design decisions:
11
+ * - Read-through cache via PermissionCache (in-flight dedup, TTL).
12
+ * - Repository handles only DB I/O.
13
+ * - Cache invalidation on every mutation.
14
+ * - ~100 roles total, read-heavy → caching makes sense.
15
+ * - Cache keyed by `perm:{permissionKey}` for deterministic, namespaced lookup.
16
+ */
17
+ class PermissionService {
18
+ repo;
19
+ cache;
20
+ constructor(tableName, cacheTtlMs) {
21
+ this.repo = new permission_repo_1.PermissionRepo(tableName);
22
+ this.cache = new permission_cache_1.PermissionCache(cacheTtlMs);
23
+ }
24
+ // ---------------------------------------------------------------------------
25
+ // Core lookup
26
+ // ---------------------------------------------------------------------------
27
+ /** Fetch Permission by composite key (role#resource#scope). Read-through cached. */
28
+ async getPermissionByKey(permissionKey) {
29
+ if (!permissionKey)
30
+ return null;
31
+ return this.cache.getOrFetch(`perm:${permissionKey}`, () => this.repo.findByPermissionKey(permissionKey));
32
+ }
33
+ /** Fetch Permission by id. Read-through cached. */
34
+ async getPermissionById(id) {
35
+ if (!id)
36
+ return null;
37
+ return this.cache.getOrFetch(`id:${id}`, () => this.repo.findById(id));
38
+ }
39
+ // ---------------------------------------------------------------------------
40
+ // Permission check
41
+ // ---------------------------------------------------------------------------
42
+ /**
43
+ * Check if the given role+resource+scope has a specific HTTP method allowed.
44
+ *
45
+ * Builds the permissionKey `role#resource#scope`, fetches the Permission,
46
+ * and checks the method map.
47
+ *
48
+ * @returns `true` if allowed, `false` otherwise.
49
+ */
50
+ async hasPermission(role, resource, scope, method) {
51
+ if (!role || !resource || !scope || !method)
52
+ return false;
53
+ const permissionKey = `${role}#${resource}#${scope}`;
54
+ const permission = await this.getPermissionByKey(permissionKey);
55
+ if (!permission?.method)
56
+ return false;
57
+ const methodLower = method.toUpperCase();
58
+ return permission.method[methodLower] === true;
59
+ }
60
+ // ---------------------------------------------------------------------------
61
+ // CRUD — create
62
+ // ---------------------------------------------------------------------------
63
+ async createPermission(input) {
64
+ if (!input.role?.trim()) {
65
+ throw new exception_1.ErrorHttp({ code: 400, error: "BadRequest" }, "Role is required");
66
+ }
67
+ if (!input.resource?.trim()) {
68
+ throw new exception_1.ErrorHttp({ code: 400, error: "BadRequest" }, "Resource is required");
69
+ }
70
+ if (!input.scope?.trim()) {
71
+ throw new exception_1.ErrorHttp({ code: 400, error: "BadRequest" }, "Scope is required");
72
+ }
73
+ // Duplicate check
74
+ const permissionKey = `${input.role}#${input.resource}#${input.scope}`;
75
+ const existing = await this.repo.findByPermissionKey(permissionKey);
76
+ if (existing) {
77
+ throw new exception_1.ErrorHttp({ code: 409, error: "Conflict" }, `Permission "${permissionKey}" already exists`);
78
+ }
79
+ const saved = await this.repo.save({
80
+ role: input.role.trim(),
81
+ resource: input.resource.trim(),
82
+ scope: input.scope.trim(),
83
+ method: input.method ?? {},
84
+ permissionKey,
85
+ });
86
+ this.cache.clear();
87
+ return saved;
88
+ }
89
+ // ---------------------------------------------------------------------------
90
+ // CRUD — update
91
+ // ---------------------------------------------------------------------------
92
+ async updatePermission(id, updates) {
93
+ if (!id)
94
+ throw new exception_1.ErrorHttp({ code: 400, error: "BadRequest" }, "Permission id is required");
95
+ const existing = await this.repo.findById(id);
96
+ if (!existing)
97
+ throw new exception_1.ErrorHttp({ code: 404, error: "NotFound" }, `Permission "${id}" not found`);
98
+ const data = { id };
99
+ if (updates.role !== undefined)
100
+ data.role = updates.role.trim();
101
+ if (updates.resource !== undefined)
102
+ data.resource = updates.resource.trim();
103
+ if (updates.scope !== undefined)
104
+ data.scope = updates.scope.trim();
105
+ if (updates.method !== undefined)
106
+ data.method = updates.method;
107
+ // Rebuild key if any key component changed
108
+ const role = data.role ?? existing.role;
109
+ const resource = data.resource ?? existing.resource;
110
+ const scope = data.scope ?? existing.scope;
111
+ data.permissionKey = `${role}#${resource}#${scope}`;
112
+ // Check for duplicate key if components changed
113
+ if (data.permissionKey !== existing.permissionKey) {
114
+ const conflict = await this.repo.findByPermissionKey(data.permissionKey);
115
+ if (conflict) {
116
+ throw new exception_1.ErrorHttp({ code: 409, error: "Conflict" }, `Permission "${data.permissionKey}" already exists`);
117
+ }
118
+ }
119
+ const updated = await this.repo.update(data);
120
+ this.cache.clear();
121
+ return updated;
122
+ }
123
+ // ---------------------------------------------------------------------------
124
+ // CRUD — delete
125
+ // ---------------------------------------------------------------------------
126
+ async deletePermission(id) {
127
+ if (!id)
128
+ throw new exception_1.ErrorHttp({ code: 400, error: "BadRequest" }, "Permission id is required");
129
+ const existing = await this.repo.findById(id);
130
+ if (!existing)
131
+ throw new exception_1.ErrorHttp({ code: 404, error: "NotFound" }, `Permission "${id}" not found`);
132
+ const result = await this.repo.delete(id);
133
+ this.cache.clear();
134
+ return result;
135
+ }
136
+ // ---------------------------------------------------------------------------
137
+ // CRUD — list
138
+ // ---------------------------------------------------------------------------
139
+ async listPermissions() {
140
+ const result = await this.repo.scan();
141
+ return (result.items ?? []);
142
+ }
143
+ // ---------------------------------------------------------------------------
144
+ // Cache control (for testing)
145
+ // ---------------------------------------------------------------------------
146
+ clearCache() {
147
+ this.cache.clear();
148
+ }
149
+ }
150
+ exports.PermissionService = PermissionService;
151
+ //# sourceMappingURL=permission.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission.service.js","sourceRoot":"","sources":["../../src/service/permission.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Permission } from \"../model/role.model\";\nimport { ErrorHttp } from \"../exception\";\nimport { PermissionCache } from \"./permission.cache\";\nimport { PermissionRepo } from \"./permission.repo\";\n\n/**\n * PermissionService — cache + orchestration layer for Permission entities.\n *\n * Design decisions:\n * - Read-through cache via PermissionCache (in-flight dedup, TTL).\n * - Repository handles only DB I/O.\n * - Cache invalidation on every mutation.\n * - ~100 roles total, read-heavy → caching makes sense.\n * - Cache keyed by `perm:{permissionKey}` for deterministic, namespaced lookup.\n */\nexport class PermissionService {\n private readonly repo: PermissionRepo;\n private readonly cache: PermissionCache;\n\n constructor(tableName: string, cacheTtlMs?: number) {\n this.repo = new PermissionRepo(tableName);\n this.cache = new PermissionCache(cacheTtlMs);\n }\n\n // ---------------------------------------------------------------------------\n // Core lookup\n // ---------------------------------------------------------------------------\n\n /** Fetch Permission by composite key (role#resource#scope). Read-through cached. */\n async getPermissionByKey(permissionKey: string): Promise<Permission | null> {\n if (!permissionKey) return null;\n\n return this.cache.getOrFetch(`perm:${permissionKey}`, () => this.repo.findByPermissionKey(permissionKey));\n }\n\n /** Fetch Permission by id. Read-through cached. */\n async getPermissionById(id: string): Promise<Permission | null> {\n if (!id) return null;\n\n return this.cache.getOrFetch(`id:${id}`, () => this.repo.findById(id));\n }\n\n // ---------------------------------------------------------------------------\n // Permission check\n // ---------------------------------------------------------------------------\n\n /**\n * Check if the given role+resource+scope has a specific HTTP method allowed.\n *\n * Builds the permissionKey `role#resource#scope`, fetches the Permission,\n * and checks the method map.\n *\n * @returns `true` if allowed, `false` otherwise.\n */\n async hasPermission(role: string, resource: string, scope: string, method: string): Promise<boolean> {\n if (!role || !resource || !scope || !method) return false;\n\n const permissionKey = `${role}#${resource}#${scope}`;\n const permission = await this.getPermissionByKey(permissionKey);\n if (!permission?.method) return false;\n\n const methodLower = method.toUpperCase() as keyof Permission[\"method\"];\n\n return permission.method[methodLower] === true;\n }\n\n // ---------------------------------------------------------------------------\n // CRUD — create\n // ---------------------------------------------------------------------------\n\n async createPermission(input: {\n role: string;\n resource: string;\n scope: string;\n method: Permission[\"method\"];\n }): Promise<Permission> {\n if (!input.role?.trim()) {\n throw new ErrorHttp({ code: 400, error: \"BadRequest\" }, \"Role is required\");\n }\n if (!input.resource?.trim()) {\n throw new ErrorHttp({ code: 400, error: \"BadRequest\" }, \"Resource is required\");\n }\n if (!input.scope?.trim()) {\n throw new ErrorHttp({ code: 400, error: \"BadRequest\" }, \"Scope is required\");\n }\n\n // Duplicate check\n const permissionKey = `${input.role}#${input.resource}#${input.scope}`;\n const existing = await this.repo.findByPermissionKey(permissionKey);\n if (existing) {\n throw new ErrorHttp({ code: 409, error: \"Conflict\" }, `Permission \"${permissionKey}\" already exists`);\n }\n\n const saved = await this.repo.save({\n role: input.role.trim(),\n resource: input.resource.trim(),\n scope: input.scope.trim(),\n method: input.method ?? {},\n permissionKey,\n });\n\n this.cache.clear();\n return saved;\n }\n\n // ---------------------------------------------------------------------------\n // CRUD — update\n // ---------------------------------------------------------------------------\n\n async updatePermission(\n id: string,\n updates: Partial<Pick<Permission, \"role\" | \"resource\" | \"scope\" | \"method\">>,\n ): Promise<Permission> {\n if (!id) throw new ErrorHttp({ code: 400, error: \"BadRequest\" }, \"Permission id is required\");\n\n const existing = await this.repo.findById(id);\n if (!existing) throw new ErrorHttp({ code: 404, error: \"NotFound\" }, `Permission \"${id}\" not found`);\n\n const data: Partial<Permission> = { id };\n if (updates.role !== undefined) data.role = updates.role.trim();\n if (updates.resource !== undefined) data.resource = updates.resource.trim();\n if (updates.scope !== undefined) data.scope = updates.scope.trim();\n if (updates.method !== undefined) data.method = updates.method;\n\n // Rebuild key if any key component changed\n const role = data.role ?? existing.role;\n const resource = data.resource ?? existing.resource;\n const scope = data.scope ?? existing.scope;\n data.permissionKey = `${role}#${resource}#${scope}`;\n\n // Check for duplicate key if components changed\n if (data.permissionKey !== existing.permissionKey) {\n const conflict = await this.repo.findByPermissionKey(data.permissionKey);\n if (conflict) {\n throw new ErrorHttp({ code: 409, error: \"Conflict\" }, `Permission \"${data.permissionKey}\" already exists`);\n }\n }\n\n const updated = await this.repo.update(data);\n\n this.cache.clear();\n return updated;\n }\n\n // ---------------------------------------------------------------------------\n // CRUD — delete\n // ---------------------------------------------------------------------------\n\n async deletePermission(id: string): Promise<boolean> {\n if (!id) throw new ErrorHttp({ code: 400, error: \"BadRequest\" }, \"Permission id is required\");\n\n const existing = await this.repo.findById(id);\n if (!existing) throw new ErrorHttp({ code: 404, error: \"NotFound\" }, `Permission \"${id}\" not found`);\n\n const result = await this.repo.delete(id);\n\n this.cache.clear();\n return result;\n }\n\n // ---------------------------------------------------------------------------\n // CRUD — list\n // ---------------------------------------------------------------------------\n\n async listPermissions(): Promise<Permission[]> {\n const result = await this.repo.scan();\n return (result.items ?? []) as Permission[];\n }\n\n // ---------------------------------------------------------------------------\n // Cache control (for testing)\n // ---------------------------------------------------------------------------\n\n clearCache(): void {\n this.cache.clear();\n }\n}\n"]}