prostgles-server 2.0.112 → 2.0.116

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/lib/DboBuilder.ts CHANGED
@@ -447,7 +447,7 @@ export class ViewHandler {
447
447
  columns: TableSchema["columns"];
448
448
  columnsForTypes: ColumnInfo[];
449
449
  column_names: string[];
450
- tableOrViewInfo: TableOrViewInfo;
450
+ tableOrViewInfo: TableSchema;// TableOrViewInfo;
451
451
  colSet: ColSet;
452
452
  tsColumnDefs: string[] = [];
453
453
  joins: Join[];
@@ -3151,6 +3151,12 @@ export type TableSchema = {
3151
3151
  })[];
3152
3152
  is_view: boolean;
3153
3153
  parent_tables: string[];
3154
+ privileges: {
3155
+ insert: boolean;
3156
+ select: boolean;
3157
+ update: boolean;
3158
+ delete: boolean;
3159
+ }
3154
3160
  }
3155
3161
 
3156
3162
  type PGConstraint = {
@@ -3185,7 +3191,37 @@ async function getConstraints(db: DB, schema: string = "public"): Promise<PGCons
3185
3191
  async function getTablesForSchemaPostgresSQL(db: DB, schema: string = "public"): Promise<TableSchema[]>{
3186
3192
  const query =
3187
3193
  `
3188
- SELECT t.table_schema as schema, t.table_name as name
3194
+ SELECT jsonb_build_object(
3195
+ 'insert', EXISTS (
3196
+ SELECT 1
3197
+ FROM information_schema.role_table_grants rg
3198
+ WHERE rg.table_name = t.table_name
3199
+ AND rg.privilege_type = 'INSERT'
3200
+ AND rg.is_grantable = 'YES'
3201
+ ),
3202
+ 'select', EXISTS (
3203
+ SELECT 1
3204
+ FROM information_schema.role_table_grants rg
3205
+ WHERE rg.table_name = t.table_name
3206
+ AND rg.privilege_type = 'SELECT'
3207
+ AND rg.is_grantable = 'YES'
3208
+ ),
3209
+ 'update', EXISTS (
3210
+ SELECT 1
3211
+ FROM information_schema.role_table_grants rg
3212
+ WHERE rg.table_name = t.table_name
3213
+ AND rg.privilege_type = 'UPDATE'
3214
+ AND rg.is_grantable = 'YES'
3215
+ ),
3216
+ 'delete', EXISTS (
3217
+ SELECT 1
3218
+ FROM information_schema.role_table_grants rg
3219
+ WHERE rg.table_name = t.table_name
3220
+ AND rg.privilege_type = 'DELETE'
3221
+ AND rg.is_grantable = 'YES'
3222
+ )
3223
+ ) as privileges
3224
+ , t.table_schema as schema, t.table_name as name
3189
3225
  , cc.table_oid as oid
3190
3226
  , json_agg((SELECT x FROM (
3191
3227
  SELECT cc.column_name as name,
@@ -3322,7 +3358,7 @@ export function isPlainObject(o) {
3322
3358
  return Object(o) === o && Object.getPrototypeOf(o) === Object.prototype;
3323
3359
  }
3324
3360
 
3325
- function postgresToTsType(udt_data_type: PG_COLUMN_UDT_DATA_TYPE): keyof typeof TS_PG_Types {
3361
+ export function postgresToTsType(udt_data_type: PG_COLUMN_UDT_DATA_TYPE): keyof typeof TS_PG_Types {
3326
3362
  return Object.keys(TS_PG_Types).find(k => {
3327
3363
  return TS_PG_Types[k].includes(udt_data_type) || !TS_PG_Types[k].length;
3328
3364
  }) as keyof typeof TS_PG_Types;
package/lib/Prostgles.ts CHANGED
@@ -1093,20 +1093,25 @@ type DboTableCommand = Request & DboTable & {
1093
1093
  const RULE_TO_METHODS = [
1094
1094
  {
1095
1095
  rule: "getColumns",
1096
+ sqlRule: "select",
1096
1097
  methods: ["getColumns"],
1097
1098
  no_limits: true,
1098
1099
  allowed_params: [],
1100
+ table_only: false,
1099
1101
  hint: ` expecting false | true | undefined`
1100
1102
  },
1101
1103
  {
1102
1104
  rule: "getInfo",
1105
+ sqlRule: "select",
1103
1106
  methods: ["getInfo"],
1104
1107
  no_limits: true,
1105
1108
  allowed_params: [],
1109
+ table_only: false,
1106
1110
  hint: ` expecting false | true | undefined`
1107
1111
  },
1108
1112
  {
1109
1113
  rule: "insert",
1114
+ sqlRule: "insert",
1110
1115
  methods: ["insert", "upsert"],
1111
1116
  no_limits: <SelectRule>{ fields: "*" },
1112
1117
  table_only: true,
@@ -1115,6 +1120,7 @@ const RULE_TO_METHODS = [
1115
1120
  },
1116
1121
  {
1117
1122
  rule: "update",
1123
+ sqlRule: "update",
1118
1124
  methods: ["update", "upsert", "updateBatch"],
1119
1125
  no_limits: <UpdateRule>{ fields: "*", filterFields: "*", returningFields: "*" },
1120
1126
  table_only: true,
@@ -1123,13 +1129,16 @@ const RULE_TO_METHODS = [
1123
1129
  },
1124
1130
  {
1125
1131
  rule: "select",
1132
+ sqlRule: "select",
1126
1133
  methods: ["findOne", "find", "count"],
1127
1134
  no_limits: <SelectRule>{ fields: "*", filterFields: "*" },
1135
+ table_only: false,
1128
1136
  allowed_params: <Array<keyof SelectRule>>["fields", "filterFields", "forcedFilter", "validate", "maxLimit"] ,
1129
1137
  hint: ` expecting "*" | true | { fields: ( string | string[] | {} ) }`
1130
1138
  },
1131
1139
  {
1132
1140
  rule: "delete",
1141
+ sqlRule: "delete",
1133
1142
  methods: ["delete", "remove"],
1134
1143
  no_limits: <DeleteRule>{ filterFields: "*" } ,
1135
1144
  table_only: true,
@@ -1137,20 +1146,24 @@ const RULE_TO_METHODS = [
1137
1146
  hint: ` expecting "*" | true | { filterFields: ( string | string[] | {} ) } \n Will use "select", "update", "delete" and "insert" rules`
1138
1147
  },
1139
1148
  {
1140
- rule: "sync", methods: ["sync", "unsync"],
1149
+ rule: "sync",
1150
+ sqlRule: "select",
1151
+ methods: ["sync", "unsync"],
1141
1152
  no_limits: null,
1142
1153
  table_only: true,
1143
1154
  allowed_params: <Array<keyof SyncRule>>["id_fields", "synced_field", "sync_type", "allow_delete", "throttle", "batch_size"],
1144
1155
  hint: ` expecting "*" | true | { id_fields: string[], synced_field: string }`
1145
1156
  },
1146
1157
  {
1147
- rule: "subscribe", methods: ["unsubscribe", "subscribe", "subscribeOne"],
1158
+ rule: "subscribe",
1159
+ sqlRule: "select",
1160
+ methods: ["unsubscribe", "subscribe", "subscribeOne"],
1148
1161
  no_limits: <SubscribeRule>{ throttle: 0 },
1149
1162
  table_only: true,
1150
1163
  allowed_params: <Array<keyof SubscribeRule>>["throttle"],
1151
1164
  hint: ` expecting "*" | true | { throttle: number } \n Will use "select" rules`
1152
1165
  }
1153
- ];
1166
+ ] as const;
1154
1167
  // const ALL_PUBLISH_METHODS = ["update", "upsert", "delete", "insert", "find", "findOne", "subscribe", "unsubscribe", "sync", "unsync", "remove"];
1155
1168
  // const ALL_PUBLISH_METHODS = RULE_TO_METHODS.map(r => r.methods).flat();
1156
1169
 
@@ -1239,7 +1252,7 @@ export class PublishParser {
1239
1252
 
1240
1253
  if(!command || !tableName) throw "command OR tableName are missing";
1241
1254
 
1242
- let rtm = RULE_TO_METHODS.find(rtms => rtms.methods.includes(command));
1255
+ let rtm = RULE_TO_METHODS.find(rtms => (rtms.methods as any).includes(command));
1243
1256
  if(!rtm){
1244
1257
  throw "Invalid command: " + command;
1245
1258
  }
@@ -1285,7 +1298,9 @@ export class PublishParser {
1285
1298
  let table_rules = _publish[tableName];// applyParamsIfFunc(_publish[tableName], localParams, this.dbo, this.db, user);
1286
1299
 
1287
1300
  /* Get view or table specific rules */
1288
- const is_view = (this.dbo[tableName] as TableHandler | ViewHandler).is_view,
1301
+ const tHandler = (this.dbo[tableName] as TableHandler | ViewHandler);
1302
+
1303
+ const is_view = tHandler.is_view,
1289
1304
  MY_RULES = RULE_TO_METHODS.filter(r => !is_view || !r.table_only);
1290
1305
 
1291
1306
  // if(tableName === "various") console.warn(1033, MY_RULES)
@@ -1295,7 +1310,14 @@ export class PublishParser {
1295
1310
  if([true, "*"].includes(table_rules as any)){
1296
1311
  table_rules = {};
1297
1312
  MY_RULES.map(r => {
1298
- table_rules[r.rule] = { ...r.no_limits };
1313
+ /** Check PG User privileges */
1314
+ if(
1315
+ tHandler.tableOrViewInfo.privileges[r.sqlRule]
1316
+ ){
1317
+ table_rules[r.rule] = { ...r.no_limits };
1318
+ }
1319
+
1320
+
1299
1321
  });
1300
1322
  // if(tableName === "various") console.warn(1042, table_rules)
1301
1323
  }
@@ -1315,7 +1337,7 @@ export class PublishParser {
1315
1337
 
1316
1338
  if(table_rules[r.rule]){
1317
1339
  /* Add implied methods if not falsy */
1318
- r.methods.map(method => {
1340
+ (r.methods as any).map(method => {
1319
1341
  if(table_rules[method] === undefined){
1320
1342
  const publishedTable = (table_rules as PublishTable);
1321
1343
  if(method === "updateBatch" && !publishedTable.update){
@@ -1340,11 +1362,17 @@ export class PublishParser {
1340
1362
 
1341
1363
  ruleKeys.filter(m => table_rules[m])
1342
1364
  .find(method => {
1343
- let rm = MY_RULES.find(r => r.rule === method || r.methods.includes(method));
1365
+ let rm = MY_RULES.find(r => r.rule === method || (r.methods as any).includes(method));
1344
1366
  if(!rm){
1345
1367
  throw `Invalid rule in publish.${tableName} -> ${method} \nExpecting any of: ${flat(MY_RULES.map(r => [r.rule, ...r.methods])).join(", ")}`;
1346
1368
  }
1347
1369
 
1370
+ /** Check user privileges */
1371
+ if(!tHandler.tableOrViewInfo.privileges[rm.sqlRule]){
1372
+ delete table_rules[method];
1373
+ return;
1374
+ }
1375
+
1348
1376
  /* Check RULES for invalid params */
1349
1377
  /* Methods do not have params -> They use them from rules */
1350
1378
  if(method === rm.rule){
@@ -4,7 +4,7 @@
4
4
  * Licensed under the MIT License. See LICENSE in the project root for license information.
5
5
  *--------------------------------------------------------------------------------------------*/
6
6
 
7
- import { pgp, Filter, LocalParams, isPlainObject, TableHandler, ViewHandler } from "./DboBuilder";
7
+ import { pgp, Filter, LocalParams, isPlainObject, TableHandler, ViewHandler, postgresToTsType } from "./DboBuilder";
8
8
  import { TableRule, flat } from "./Prostgles";
9
9
  import { SelectParamsBasic as SelectParams, isEmpty, FieldFilter, asName, TextFilter_FullTextSearchFilterKeys, TS_PG_Types, ColumnInfo } from "prostgles-types";
10
10
  import { get } from "./utils";
@@ -694,28 +694,49 @@ export const FUNCTIONS: FunctionSpec[] = [
694
694
  if(returnType === "index"){
695
695
  res = `CASE WHEN position(${term} IN ${col}) > 0 THEN position(${term} IN ${col}) - 1 ELSE -1 END`;
696
696
 
697
- } else if(returnType === "boolean"){
698
- res = `CASE WHEN position(${term} IN ${col}) > 0 THEN TRUE ELSE FALSE END`;
697
+ // } else if(returnType === "boolean"){
698
+ // res = `CASE WHEN position(${term} IN ${col}) > 0 THEN TRUE ELSE FALSE END`;
699
699
 
700
- } else if(returnType === "object"){
700
+ } else if(returnType === "object" || returnType === "boolean"){
701
+ const hasChars = Boolean(term && /[a-z]/i.test(term));
701
702
  res = `CASE
702
703
  ${cols.map(c => {
703
704
  const colInfo = allColumns.find(ac => ac.name === c);
704
- const colNameEscaped = asNameAlias(c, tableAlias)
705
+ return {
706
+ key: c,
707
+ colInfo
708
+ }
709
+ }).filter(c =>
710
+ /** Exclude numeric columns when the search tern contains a character */
711
+ !hasChars ||
712
+ c.colInfo?.udt_name &&
713
+ postgresToTsType(c.colInfo.udt_name) !== "number"
714
+ )
715
+ .map(c => {
716
+ const colNameEscaped = asNameAlias(c.key, tableAlias)
705
717
  let colSelect = `${colNameEscaped}::TEXT`;
706
- if(colInfo?.udt_name.startsWith("timestamp")){
707
- colSelect = `( CASE WHEN ${colNameEscaped} IS NULL THEN '' ELSE concat_ws(' ',
718
+ const isTstamp = c.colInfo?.udt_name.startsWith("timestamp");
719
+ if(isTstamp || c.colInfo?.udt_name === "date"){
720
+ colSelect = `( CASE WHEN ${colNameEscaped} IS NULL THEN ''
721
+ ELSE concat_ws(' ',
708
722
  ${colNameEscaped}::TEXT,
709
- to_char(${colNameEscaped}, 'Day Month '),
710
- 'Q' || to_char(${colNameEscaped}, 'Q'),
711
- 'WK' || to_char(${colNameEscaped}, 'WW')
723
+ ${isTstamp? `'TZ' || trim(to_char(${colNameEscaped}, 'OF')), `: ''}
724
+ trim(to_char(${colNameEscaped}, 'Day Month')),
725
+ 'Q' || trim(to_char(${colNameEscaped}, 'Q')),
726
+ 'WK' || trim(to_char(${colNameEscaped}, 'WW'))
712
727
  ) END)`
713
728
  }
714
729
  let colTxt = `COALESCE(${colSelect}, '')`; // position(${term} IN ${colTxt}) > 0
730
+ if(returnType === "boolean"){
731
+ return `
732
+ WHEN ${colTxt} ${matchCase? "LIKE" : "ILIKE"} ${asValue('%' + rawTerm + '%')}
733
+ THEN TRUE
734
+ `
735
+ }
715
736
  return `
716
737
  WHEN ${colTxt} ${matchCase? "LIKE" : "ILIKE"} ${asValue('%' + rawTerm + '%')}
717
738
  THEN json_build_object(
718
- ${asValue(c)},
739
+ ${asValue(c.key)},
719
740
  ${makeTextMatcherArray(
720
741
  colTxt,
721
742
  term
@@ -723,7 +744,7 @@ export const FUNCTIONS: FunctionSpec[] = [
723
744
  )::jsonb
724
745
  `
725
746
  }).join(" ")}
726
- ELSE NULL
747
+ ELSE ${(returnType === "boolean")? "FALSE" : "NULL"}
727
748
 
728
749
  END`;
729
750
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prostgles-server",
3
- "version": "2.0.112",
3
+ "version": "2.0.116",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1 +1 @@
1
- 6272
1
+ 18519
@@ -23,7 +23,7 @@
23
23
  },
24
24
  "../..": {
25
25
  "name": "prostgles-server",
26
- "version": "2.0.111",
26
+ "version": "2.0.115",
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
29
  "@aws-sdk/client-s3": "^3.32.0",