From 716bc5acf1cbec4fab5ef61a8aab33e1f811dd71 Mon Sep 17 00:00:00 2001
From: mike12345567 <me@michaeldrury.co.uk>
Date: Fri, 25 Feb 2022 19:26:19 +0000
Subject: [PATCH] Rounding out user schema and query schema as required.

---
 packages/server/specs/openapi.json            | 164 +++++++++++++++---
 packages/server/specs/openapi.yaml            | 119 +++++++++++--
 packages/server/specs/resources/query.js      |  16 +-
 packages/server/specs/resources/user.js       |  66 ++++++-
 .../src/api/controllers/public/users.ts       |   8 +
 .../server/src/api/routes/public/users.ts     |   7 -
 6 files changed, 338 insertions(+), 42 deletions(-)

diff --git a/packages/server/specs/openapi.json b/packages/server/specs/openapi.json
index b89bd0617b..45ec142fd9 100644
--- a/packages/server/specs/openapi.json
+++ b/packages/server/specs/openapi.json
@@ -940,19 +940,158 @@
         ]
       },
       "query": {
+        "description": "The query body must contain the required parameters for the query, this depends on query type, setup and bindings.",
         "type": "object",
-        "properties": {}
+        "additionalProperties": {
+          "oneOf": [
+            {
+              "type": "string"
+            },
+            {
+              "type": "object"
+            },
+            {
+              "type": "integer"
+            },
+            {
+              "type": "array"
+            },
+            {
+              "type": "boolean"
+            }
+          ]
+        }
       },
       "user": {
         "type": "object",
-        "properties": {}
+        "properties": {
+          "email": {
+            "description": "The email address of the user, this must be unique.",
+            "type": "string"
+          },
+          "password": {
+            "description": "The password of the user if using password based login - this will never be returned. This can be left out of subsequent requests (updates) and will be enriched back into the user structure.",
+            "type": "string"
+          },
+          "status": {
+            "description": "The status of the user, if they are active.",
+            "type": "string",
+            "enum": [
+              "active"
+            ]
+          },
+          "firstName": {
+            "description": "The first name of the user",
+            "type": "string"
+          },
+          "lastName": {
+            "description": "The last name of the user",
+            "type": "string"
+          },
+          "forceResetPassword": {
+            "description": "If set to true forces the user to reset their password on first login.",
+            "type": "boolean"
+          },
+          "builder": {
+            "description": "Describes if the user is a builder user or not.",
+            "type": "object",
+            "properties": {
+              "global": {
+                "description": "If set to true the user will be able to build any app in the system.",
+                "type": "boolean"
+              }
+            }
+          },
+          "admin": {
+            "description": "Describes if the user is an admin user or not.",
+            "type": "object",
+            "properties": {
+              "global": {
+                "description": "If set to true the user will be able to administrate the system.",
+                "type": "boolean"
+              }
+            }
+          },
+          "roles": {
+            "description": "Contains the roles of the user per app (assuming they are not a builder user).",
+            "type": "object",
+            "additionalProperties": {
+              "type": "string",
+              "description": "A map of app ID (production app ID, minus the _dev component) to a role ID, e.g. ADMIN."
+            }
+          }
+        },
+        "required": [
+          "email",
+          "roles"
+        ]
       },
       "userOutput": {
         "type": "object",
         "properties": {
           "user": {
             "type": "object",
-            "properties": {}
+            "properties": {
+              "email": {
+                "description": "The email address of the user, this must be unique.",
+                "type": "string"
+              },
+              "password": {
+                "description": "The password of the user if using password based login - this will never be returned. This can be left out of subsequent requests (updates) and will be enriched back into the user structure.",
+                "type": "string"
+              },
+              "status": {
+                "description": "The status of the user, if they are active.",
+                "type": "string",
+                "enum": [
+                  "active"
+                ]
+              },
+              "firstName": {
+                "description": "The first name of the user",
+                "type": "string"
+              },
+              "lastName": {
+                "description": "The last name of the user",
+                "type": "string"
+              },
+              "forceResetPassword": {
+                "description": "If set to true forces the user to reset their password on first login.",
+                "type": "boolean"
+              },
+              "builder": {
+                "description": "Describes if the user is a builder user or not.",
+                "type": "object",
+                "properties": {
+                  "global": {
+                    "description": "If set to true the user will be able to build any app in the system.",
+                    "type": "boolean"
+                  }
+                }
+              },
+              "admin": {
+                "description": "Describes if the user is an admin user or not.",
+                "type": "object",
+                "properties": {
+                  "global": {
+                    "description": "If set to true the user will be able to administrate the system.",
+                    "type": "boolean"
+                  }
+                }
+              },
+              "roles": {
+                "description": "Contains the roles of the user per app (assuming they are not a builder user).",
+                "type": "object",
+                "additionalProperties": {
+                  "type": "string",
+                  "description": "A map of app ID (production app ID, minus the _dev component) to a role ID, e.g. ADMIN."
+                }
+              }
+            },
+            "required": [
+              "email",
+              "roles"
+            ]
           }
         },
         "required": [
@@ -1832,11 +1971,6 @@
         "tags": [
           "users"
         ],
-        "parameters": [
-          {
-            "$ref": "#/components/parameters/appId"
-          }
-        ],
         "requestBody": {
           "required": true,
           "content": {
@@ -1875,9 +2009,6 @@
         "parameters": [
           {
             "$ref": "#/components/parameters/userId"
-          },
-          {
-            "$ref": "#/components/parameters/appId"
           }
         ],
         "requestBody": {
@@ -1916,9 +2047,6 @@
         "parameters": [
           {
             "$ref": "#/components/parameters/userId"
-          },
-          {
-            "$ref": "#/components/parameters/appId"
           }
         ],
         "responses": {
@@ -1947,9 +2075,6 @@
         "parameters": [
           {
             "$ref": "#/components/parameters/userId"
-          },
-          {
-            "$ref": "#/components/parameters/appId"
           }
         ],
         "responses": {
@@ -1977,11 +2102,6 @@
         "tags": [
           "users"
         ],
-        "parameters": [
-          {
-            "$ref": "#/components/parameters/appId"
-          }
-        ],
         "requestBody": {
           "required": true,
           "content": {
diff --git a/packages/server/specs/openapi.yaml b/packages/server/specs/openapi.yaml
index a8b2ed4a45..f7059fcc8e 100644
--- a/packages/server/specs/openapi.yaml
+++ b/packages/server/specs/openapi.yaml
@@ -688,17 +688,123 @@ components:
       required:
         - table
     query:
+      description: The query body must contain the required parameters for the query,
+        this depends on query type, setup and bindings.
       type: object
-      properties: {}
+      additionalProperties:
+        oneOf:
+          - type: string
+          - type: object
+          - type: integer
+          - type: array
+          - type: boolean
     user:
       type: object
-      properties: {}
+      properties:
+        email:
+          description: The email address of the user, this must be unique.
+          type: string
+        password:
+          description: The password of the user if using password based login - this will
+            never be returned. This can be left out of subsequent requests
+            (updates) and will be enriched back into the user structure.
+          type: string
+        status:
+          description: The status of the user, if they are active.
+          type: string
+          enum:
+            - active
+        firstName:
+          description: The first name of the user
+          type: string
+        lastName:
+          description: The last name of the user
+          type: string
+        forceResetPassword:
+          description: If set to true forces the user to reset their password on first
+            login.
+          type: boolean
+        builder:
+          description: Describes if the user is a builder user or not.
+          type: object
+          properties:
+            global:
+              description: If set to true the user will be able to build any app in the
+                system.
+              type: boolean
+        admin:
+          description: Describes if the user is an admin user or not.
+          type: object
+          properties:
+            global:
+              description: If set to true the user will be able to administrate the system.
+              type: boolean
+        roles:
+          description: Contains the roles of the user per app (assuming they are not a
+            builder user).
+          type: object
+          additionalProperties:
+            type: string
+            description: A map of app ID (production app ID, minus the _dev component) to a
+              role ID, e.g. ADMIN.
+      required:
+        - email
+        - roles
     userOutput:
       type: object
       properties:
         user:
           type: object
-          properties: {}
+          properties:
+            email:
+              description: The email address of the user, this must be unique.
+              type: string
+            password:
+              description: The password of the user if using password based login - this will
+                never be returned. This can be left out of subsequent requests
+                (updates) and will be enriched back into the user structure.
+              type: string
+            status:
+              description: The status of the user, if they are active.
+              type: string
+              enum:
+                - active
+            firstName:
+              description: The first name of the user
+              type: string
+            lastName:
+              description: The last name of the user
+              type: string
+            forceResetPassword:
+              description: If set to true forces the user to reset their password on first
+                login.
+              type: boolean
+            builder:
+              description: Describes if the user is a builder user or not.
+              type: object
+              properties:
+                global:
+                  description: If set to true the user will be able to build any app in the
+                    system.
+                  type: boolean
+            admin:
+              description: Describes if the user is an admin user or not.
+              type: object
+              properties:
+                global:
+                  description: If set to true the user will be able to administrate the system.
+                  type: boolean
+            roles:
+              description: Contains the roles of the user per app (assuming they are not a
+                builder user).
+              type: object
+              additionalProperties:
+                type: string
+                description: A map of app ID (production app ID, minus the _dev component) to a
+                  role ID, e.g. ADMIN.
+          required:
+            - email
+            - roles
       required:
         - user
     nameSearch:
@@ -1236,8 +1342,6 @@ paths:
       summary: Create a new user in the Budibase portal.
       tags:
         - users
-      parameters:
-        - $ref: "#/components/parameters/appId"
       requestBody:
         required: true
         content:
@@ -1261,7 +1365,6 @@ paths:
         - users
       parameters:
         - $ref: "#/components/parameters/userId"
-        - $ref: "#/components/parameters/appId"
       requestBody:
         required: true
         content:
@@ -1284,7 +1387,6 @@ paths:
         - users
       parameters:
         - $ref: "#/components/parameters/userId"
-        - $ref: "#/components/parameters/appId"
       responses:
         "200":
           description: Returns the deleted user.
@@ -1301,7 +1403,6 @@ paths:
         - users
       parameters:
         - $ref: "#/components/parameters/userId"
-        - $ref: "#/components/parameters/appId"
       responses:
         "200":
           description: Returns the retrieved user.
@@ -1317,8 +1418,6 @@ paths:
       summary: Search for a user based on their email/username.
       tags:
         - users
-      parameters:
-        - $ref: "#/components/parameters/appId"
       requestBody:
         required: true
         content:
diff --git a/packages/server/specs/resources/query.js b/packages/server/specs/resources/query.js
index c1f9f1ff83..056c7e785f 100644
--- a/packages/server/specs/resources/query.js
+++ b/packages/server/specs/resources/query.js
@@ -1,4 +1,3 @@
-const { object } = require("./utils")
 const Resource = require("./utils/Resource")
 
 const query = {
@@ -74,7 +73,20 @@ const sqlResponse = {
   },
 }
 
-const querySchema = object({})
+const querySchema = {
+  description:
+    "The query body must contain the required parameters for the query, this depends on query type, setup and bindings.",
+  type: "object",
+  additionalProperties: {
+    oneOf: [
+      { type: "string" },
+      { type: "object" },
+      { type: "integer" },
+      { type: "array" },
+      { type: "boolean" },
+    ],
+  },
+}
 
 module.exports = new Resource()
   .setExamples({
diff --git a/packages/server/specs/resources/user.js b/packages/server/specs/resources/user.js
index f41bd6d7f3..15900bf99d 100644
--- a/packages/server/specs/resources/user.js
+++ b/packages/server/specs/resources/user.js
@@ -27,7 +27,71 @@ const user = {
   },
 }
 
-const userSchema = object({})
+const userSchema = object(
+  {
+    email: {
+      description: "The email address of the user, this must be unique.",
+      type: "string",
+    },
+    password: {
+      description:
+        "The password of the user if using password based login - this will never be returned. This can be" +
+        " left out of subsequent requests (updates) and will be enriched back into the user structure.",
+      type: "string",
+    },
+    status: {
+      description: "The status of the user, if they are active.",
+      type: "string",
+      enum: ["active"],
+    },
+    firstName: {
+      description: "The first name of the user",
+      type: "string",
+    },
+    lastName: {
+      description: "The last name of the user",
+      type: "string",
+    },
+    forceResetPassword: {
+      description:
+        "If set to true forces the user to reset their password on first login.",
+      type: "boolean",
+    },
+    builder: {
+      description: "Describes if the user is a builder user or not.",
+      type: "object",
+      properties: {
+        global: {
+          description:
+            "If set to true the user will be able to build any app in the system.",
+          type: "boolean",
+        },
+      },
+    },
+    admin: {
+      description: "Describes if the user is an admin user or not.",
+      type: "object",
+      properties: {
+        global: {
+          description:
+            "If set to true the user will be able to administrate the system.",
+          type: "boolean",
+        },
+      },
+    },
+    roles: {
+      description:
+        "Contains the roles of the user per app (assuming they are not a builder user).",
+      type: "object",
+      additionalProperties: {
+        type: "string",
+        description:
+          "A map of app ID (production app ID, minus the _dev component) to a role ID, e.g. ADMIN.",
+      },
+    },
+  },
+  { required: ["email", "roles"] }
+)
 
 module.exports = new Resource()
   .setExamples({
diff --git a/packages/server/src/api/controllers/public/users.ts b/packages/server/src/api/controllers/public/users.ts
index c8728c7de7..112369d84c 100644
--- a/packages/server/src/api/controllers/public/users.ts
+++ b/packages/server/src/api/controllers/public/users.ts
@@ -5,6 +5,7 @@ import {
   deleteGlobalUser,
 } from "../../../utilities/workerRequests"
 import { search as stringSearch } from "./utils"
+const { getProdAppID } = require("@budibase/backend-core/db")
 
 function fixUser(ctx: any) {
   if (!ctx.request.body) {
@@ -15,6 +16,13 @@ function fixUser(ctx: any) {
   }
   if (!ctx.request.body.roles) {
     ctx.request.body.roles = {}
+  } else {
+    const newRoles: { [key: string]: string } = {}
+    for (let [appId, role] of Object.entries(ctx.request.body.roles)) {
+      // @ts-ignore
+      newRoles[getProdAppID(appId)] = role
+    }
+    ctx.request.body.roles = newRoles
   }
   return ctx
 }
diff --git a/packages/server/src/api/routes/public/users.ts b/packages/server/src/api/routes/public/users.ts
index 3f500c074f..b603cb7a13 100644
--- a/packages/server/src/api/routes/public/users.ts
+++ b/packages/server/src/api/routes/public/users.ts
@@ -12,8 +12,6 @@ const read = [],
  *     summary: Create a new user in the Budibase portal.
  *     tags:
  *       - users
- *     parameters:
- *       - $ref: '#/components/parameters/appId'
  *     requestBody:
  *       required: true
  *       content:
@@ -42,7 +40,6 @@ write.push(new Endpoint("post", "/users", controller.create))
  *       - users
  *     parameters:
  *       - $ref: '#/components/parameters/userId'
- *       - $ref: '#/components/parameters/appId'
  *     requestBody:
  *       required: true
  *       content:
@@ -71,7 +68,6 @@ write.push(new Endpoint("put", "/users/:userId", controller.update))
  *       - users
  *     parameters:
  *       - $ref: '#/components/parameters/userId'
- *       - $ref: '#/components/parameters/appId'
  *     responses:
  *       200:
  *         description: Returns the deleted user.
@@ -94,7 +90,6 @@ write.push(new Endpoint("delete", "/users/:userId", controller.destroy))
  *       - users
  *     parameters:
  *       - $ref: '#/components/parameters/userId'
- *       - $ref: '#/components/parameters/appId'
  *     responses:
  *       200:
  *         description: Returns the retrieved user.
@@ -115,8 +110,6 @@ read.push(new Endpoint("get", "/users/:userId", controller.read))
  *     summary: Search for a user based on their email/username.
  *     tags:
  *       - users
- *     parameters:
- *       - $ref: '#/components/parameters/appId'
  *     requestBody:
  *       required: true
  *       content: