From 8051d6853c300f22c5db0a8487fb1c224c51535b Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 8 Mar 2022 17:29:49 +0000 Subject: [PATCH] Adding API communications to nextjs app. --- examples/nextjs-api-sales/README.md | 17 +- .../nextjs-api-sales/config/budibase.json | 5 + .../nextjs-api-sales/db/docker-compose.yml | 17 + examples/nextjs-api-sales/db/init.sql | 23 + .../nextjs-api-sales/definitions/index.ts | 7 + .../nextjs-api-sales/definitions/openapi.ts | 943 ++++++++++++++++++ examples/nextjs-api-sales/next.config.js | 9 +- examples/nextjs-api-sales/package.json | 1 + examples/nextjs-api-sales/pages/api/hello.ts | 13 - examples/nextjs-api-sales/pages/api/sales.ts | 66 ++ examples/nextjs-api-sales/yarn.lock | 39 + 11 files changed, 1109 insertions(+), 31 deletions(-) create mode 100644 examples/nextjs-api-sales/config/budibase.json create mode 100644 examples/nextjs-api-sales/db/docker-compose.yml create mode 100644 examples/nextjs-api-sales/db/init.sql create mode 100644 examples/nextjs-api-sales/definitions/index.ts create mode 100644 examples/nextjs-api-sales/definitions/openapi.ts delete mode 100644 examples/nextjs-api-sales/pages/api/hello.ts create mode 100644 examples/nextjs-api-sales/pages/api/sales.ts diff --git a/examples/nextjs-api-sales/README.md b/examples/nextjs-api-sales/README.md index c87e0421d2..328a299684 100644 --- a/examples/nextjs-api-sales/README.md +++ b/examples/nextjs-api-sales/README.md @@ -16,19 +16,4 @@ You can start editing the page by modifying `pages/index.tsx`. The page auto-upd [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. -The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. +The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. \ No newline at end of file diff --git a/examples/nextjs-api-sales/config/budibase.json b/examples/nextjs-api-sales/config/budibase.json new file mode 100644 index 0000000000..838c3ec419 --- /dev/null +++ b/examples/nextjs-api-sales/config/budibase.json @@ -0,0 +1,5 @@ +{ + "apiKey": "", + "appName": "", + "host": "http://localhost:10000" +} \ No newline at end of file diff --git a/examples/nextjs-api-sales/db/docker-compose.yml b/examples/nextjs-api-sales/db/docker-compose.yml new file mode 100644 index 0000000000..fdf189911f --- /dev/null +++ b/examples/nextjs-api-sales/db/docker-compose.yml @@ -0,0 +1,17 @@ +version: "3.8" +services: + db: + container_name: postgres + image: postgres + restart: always + environment: + POSTGRES_USER: root + POSTGRES_PASSWORD: root + POSTGRES_DB: postgres + ports: + - "5432:5432" + volumes: + - pg_data:/var/lib/postgresql/data/ + - ./init.sql:/docker-entrypoint-initdb.d/init.sql +volumes: + pg_data: diff --git a/examples/nextjs-api-sales/db/init.sql b/examples/nextjs-api-sales/db/init.sql new file mode 100644 index 0000000000..462d64ff30 --- /dev/null +++ b/examples/nextjs-api-sales/db/init.sql @@ -0,0 +1,23 @@ +CREATE TABLE IF NOT EXISTS sales_people ( + person_id INT NOT NULL, + name varchar(200) NOT NULL, + PRIMARY KEY (person_id) +); + +CREATE TABLE IF NOT EXISTS sales ( + sale_id INT NOT NULL, + sale_name varchar(200) NOT NULL, + sold_by INT, + PRIMARY KEY (sale_id), + CONSTRAINT sold_by_fk + FOREIGN KEY(sold_by) + REFERENCES sales_people(person_id) +); + +INSERT INTO sales_people +select id, concat('Sales person ', id) +FROM GENERATE_SERIES(1, 50) as id; + +INSERT INTO sales +select id, concat('Sale ', id), floor(random() * 50 + 1)::int +FROM GENERATE_SERIES(1, 200) as id; \ No newline at end of file diff --git a/examples/nextjs-api-sales/definitions/index.ts b/examples/nextjs-api-sales/definitions/index.ts new file mode 100644 index 0000000000..4eba86ca73 --- /dev/null +++ b/examples/nextjs-api-sales/definitions/index.ts @@ -0,0 +1,7 @@ +import { components } from "./openapi" + +export type App = components["schemas"]["applicationOutput"]["data"] +export type AppSearch = { + data: App[] +} +export type RowSearch = components["schemas"]["searchOutput"] \ No newline at end of file diff --git a/examples/nextjs-api-sales/definitions/openapi.ts b/examples/nextjs-api-sales/definitions/openapi.ts new file mode 100644 index 0000000000..71a9298040 --- /dev/null +++ b/examples/nextjs-api-sales/definitions/openapi.ts @@ -0,0 +1,943 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/applications": { + post: { + parameters: { + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** Returns the created application. */ + 200: { + content: { + "application/json": components["schemas"]["applicationOutput"] + } + } + } + requestBody: { + content: { + "application/json": components["schemas"]["application"] + } + } + } + } + "/applications/{appId}": { + get: { + parameters: { + path: { + /** The ID of the app which this request is targeting. */ + appId: components["parameters"]["appIdUrl"] + } + } + responses: { + /** Returns the retrieved application. */ + 200: { + content: { + "application/json": components["schemas"]["applicationOutput"] + } + } + } + } + put: { + parameters: { + path: { + /** The ID of the app which this request is targeting. */ + appId: components["parameters"]["appIdUrl"] + } + } + responses: { + /** Returns the updated application. */ + 200: { + content: { + "application/json": components["schemas"]["applicationOutput"] + } + } + } + requestBody: { + content: { + "application/json": components["schemas"]["application"] + } + } + } + delete: { + parameters: { + path: { + /** The ID of the app which this request is targeting. */ + appId: components["parameters"]["appIdUrl"] + } + } + responses: { + /** Returns the deleted application. */ + 200: { + content: { + "application/json": components["schemas"]["applicationOutput"] + } + } + } + } + } + "/applications/search": { + /** Based on application properties (currently only name) search for applications. */ + post: { + parameters: { + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** Returns the applications that were found based on the search parameters. */ + 200: { + content: { + "application/json": { + data: components["schemas"]["application"][] + } + } + } + } + requestBody: { + content: { + "application/json": components["schemas"]["nameSearch"] + } + } + } + } + "/queries/{queryId}": { + /** Queries which have been created within a Budibase app can be executed using this, */ + post: { + parameters: { + path: { + /** The ID of the query which this request is targeting. */ + queryId: components["parameters"]["queryId"] + } + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** Returns the result of the query execution. */ + 200: { + content: { + "application/json": components["schemas"]["executeQueryOutput"] + } + } + } + requestBody: { + content: { + "application/json": components["schemas"]["executeQuery"] + } + } + } + } + "/queries/search": { + /** Based on query properties (currently only name) search for queries. */ + post: { + parameters: { + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** Returns the queries found based on the search parameters. */ + 200: { + content: { + "application/json": { + data: components["schemas"]["query"][] + } + } + } + } + requestBody: { + content: { + "application/json": components["schemas"]["nameSearch"] + } + } + } + } + "/tables/{tableId}/rows": { + /** Creates a row within the specified table. */ + post: { + parameters: { + path: { + /** The ID of the table which this request is targeting. */ + tableId: components["parameters"]["tableId"] + } + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** Returns the created row, including the ID which has been generated for it. This can be found in the Budibase portal, viewed under the developer information. */ + 200: { + content: { + "application/json": components["schemas"]["rowOutput"] + } + } + } + requestBody: { + content: { + "application/json": components["schemas"]["row"] + } + } + } + } + "/tables/{tableId}/rows/{rowId}": { + /** This gets a single row, it will be enriched with the full related rows, rather than the squashed "primaryDisplay" format returned by the search endpoint. */ + get: { + parameters: { + path: { + /** The ID of the table which this request is targeting. */ + tableId: components["parameters"]["tableId"] + /** The ID of the row which this request is targeting. */ + rowId: components["parameters"]["rowId"] + } + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** Returns the retrieved row. */ + 200: { + content: { + "application/json": components["schemas"]["rowOutput"] + } + } + } + } + /** Updates a row within the specified table. */ + put: { + parameters: { + path: { + /** The ID of the table which this request is targeting. */ + tableId: components["parameters"]["tableId"] + /** The ID of the row which this request is targeting. */ + rowId: components["parameters"]["rowId"] + } + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** Returns the created row, including the ID which has been generated for it. */ + 200: { + content: { + "application/json": components["schemas"]["rowOutput"] + } + } + } + requestBody: { + content: { + "application/json": components["schemas"]["row"] + } + } + } + /** Deletes a row within the specified table. */ + delete: { + parameters: { + path: { + /** The ID of the table which this request is targeting. */ + tableId: components["parameters"]["tableId"] + /** The ID of the row which this request is targeting. */ + rowId: components["parameters"]["rowId"] + } + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** Returns the deleted row, including the ID which has been generated for it. */ + 200: { + content: { + "application/json": components["schemas"]["rowOutput"] + } + } + } + } + } + "/tables/{tableId}/rows/search": { + post: { + parameters: { + path: { + /** The ID of the table which this request is targeting. */ + tableId: components["parameters"]["tableId"] + } + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** The response will contain an array of rows that match the search parameters. */ + 200: { + content: { + "application/json": components["schemas"]["searchOutput"] + } + } + } + requestBody: { + content: { + "application/json": { + query: { + /** + * @description A map of field name to the string to search for, this will look for rows that have a value starting with the string value. + * @example [object Object] + */ + string?: { [key: string]: string } + /** @description A fuzzy search, only supported by internal tables. */ + fuzzy?: { [key: string]: unknown } + /** + * @description Searches within a range, the format of this must be columnName -> [low, high]. + * @example [object Object] + */ + range?: { [key: string]: unknown } + /** @description Searches for rows that have a column value that is exactly the value set. */ + equal?: { [key: string]: unknown } + /** @description Searches for any row which does not contain the specified column value. */ + notEqual?: { [key: string]: unknown } + /** + * @description Searches for rows which do not contain the specified column. The object should simply contain keys of the column names, these can map to any value. + * @example [object Object] + */ + empty?: { [key: string]: unknown } + /** @description Searches for rows which have the specified column. */ + notEmpty?: { [key: string]: unknown } + /** @description Searches for rows which have a column value that is any of the specified values. The format of this must be columnName -> [value1, value2]. */ + oneOf?: { [key: string]: unknown } + } + /** @description Enables pagination, by default this is disabled. */ + paginate?: boolean + /** @description If retrieving another page, the bookmark from the previous request must be supplied. */ + bookmark?: string | number + /** @description The maximum number of rows to return, useful when paginating, for internal tables this will be limited to 1000, for SQL tables it will be 5000. */ + limit?: number + /** @description A set of parameters describing the sort behaviour of the search. */ + sort?: { + /** + * @description The order of the sort, by default this is ascending. + * @enum {string} + */ + order?: "ascending" | "descending" + /** @description The name of the column by which the rows will be sorted. */ + column?: string + /** + * @description Defines whether the column should be treated as a string or as numbers when sorting. + * @enum {string} + */ + type?: "string" | "number" + } + } + } + } + } + } + "/tables": { + /** Create a table, this could be internal or external. */ + post: { + parameters: { + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** Returns the created table, including the ID which has been generated for it. This can be internal or external data sources. */ + 200: { + content: { + "application/json": components["schemas"]["tableOutput"] + } + } + } + requestBody: { + content: { + "application/json": components["schemas"]["table"] + } + } + } + } + "/tables/{tableId}": { + /** Lookup a table, this could be internal or external. */ + get: { + parameters: { + path: { + /** The ID of the table which this request is targeting. */ + tableId: components["parameters"]["tableId"] + } + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** Returns the retrieved table. */ + 200: { + content: { + "application/json": components["schemas"]["tableOutput"] + } + } + } + } + /** Update a table, this could be internal or external. */ + put: { + parameters: { + path: { + /** The ID of the table which this request is targeting. */ + tableId: components["parameters"]["tableId"] + } + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** Returns the updated table. */ + 200: { + content: { + "application/json": components["schemas"]["tableOutput"] + } + } + } + requestBody: { + content: { + "application/json": components["schemas"]["table"] + } + } + } + /** Delete a table, this could be internal or external. */ + delete: { + parameters: { + path: { + /** The ID of the table which this request is targeting. */ + tableId: components["parameters"]["tableId"] + } + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** Returns the deleted table. */ + 200: { + content: { + "application/json": components["schemas"]["tableOutput"] + } + } + } + } + } + "/tables/search": { + /** Based on table properties (currently only name) search for tables. This could be an internal or an external table. */ + post: { + parameters: { + header: { + /** The ID of the app which this request is targeting. */ + "x-budibase-app-id": components["parameters"]["appId"] + } + } + responses: { + /** Returns the found tables, based on the search parameters. */ + 200: { + content: { + "application/json": { + data: components["schemas"]["table"][] + } + } + } + } + requestBody: { + content: { + "application/json": components["schemas"]["nameSearch"] + } + } + } + } + "/users": { + post: { + responses: { + /** Returns the created user. */ + 200: { + content: { + "application/json": components["schemas"]["userOutput"] + } + } + } + requestBody: { + content: { + "application/json": components["schemas"]["user"] + } + } + } + } + "/users/{userId}": { + get: { + parameters: { + path: { + /** The ID of the user which this request is targeting. */ + userId: components["parameters"]["userId"] + } + } + responses: { + /** Returns the retrieved user. */ + 200: { + content: { + "application/json": components["schemas"]["userOutput"] + } + } + } + } + put: { + parameters: { + path: { + /** The ID of the user which this request is targeting. */ + userId: components["parameters"]["userId"] + } + } + responses: { + /** Returns the updated user. */ + 200: { + content: { + "application/json": components["schemas"]["userOutput"] + } + } + } + requestBody: { + content: { + "application/json": components["schemas"]["user"] + } + } + } + delete: { + parameters: { + path: { + /** The ID of the user which this request is targeting. */ + userId: components["parameters"]["userId"] + } + } + responses: { + /** Returns the deleted user. */ + 200: { + content: { + "application/json": components["schemas"]["userOutput"] + } + } + } + } + } + "/users/search": { + /** Based on user properties (currently only name) search for users. */ + post: { + responses: { + /** Returns the found users based on search parameters. */ + 200: { + content: { + "application/json": { + data: components["schemas"]["user"][] + } + } + } + } + requestBody: { + content: { + "application/json": components["schemas"]["nameSearch"] + } + } + } + } +} + +export interface components { + schemas: { + application: { + /** @description The name of the app. */ + name: string + /** @description The URL by which the app is accessed, this must be URL encoded. */ + url: string + } + applicationOutput: { + data: { + /** @description The name of the app. */ + name: string + /** @description The URL by which the app is accessed, this must be URL encoded. */ + url: string + /** @description The ID of the app. */ + _id: string + /** + * @description The status of the app, stating it if is the development or published version. + * @enum {string} + */ + status: "development" | "published" + /** @description States when the app was created, will be constant. Stored in ISO format. */ + createdAt: string + /** @description States the last time the app was updated - stored in ISO format. */ + updatedAt: string + /** @description States the version of the Budibase client this app is currently based on. */ + version: string + /** @description In a multi-tenant environment this will state the tenant this app is within. */ + tenantId?: string + /** @description The user this app is currently being built by. */ + lockedBy?: { [key: string]: unknown } + } + } + /** @description The row to be created/updated, based on the table schema. */ + row: { [key: string]: unknown } + searchOutput: { + /** @description An array of rows, these will each contain an _id field which can be used to update or delete them. */ + data: { [key: string]: unknown }[] + /** @description If pagination in use, this should be provided. */ + bookmark?: string | number + /** @description If pagination in use, this will determine if there is another page to fetch. */ + hasNextPage?: boolean + } + rowOutput: { + /** @description The row to be created/updated, based on the table schema. */ + data: { + /** @description The ID of the row. */ + _id: string + /** @description The ID of the table this row comes from. */ + tableId: string + } & { [key: string]: unknown } + } + /** @description The table to be created/updated. */ + table: { + /** @description The name of the table. */ + name: string + /** @description The name of the column which should be used in relationship tags when relating to this table. */ + primaryDisplay?: string + schema: { + [key: string]: + | { + /** + * @description A relationship column. + * @enum {string} + */ + type?: "link" + /** @description A constraint can be applied to the column which will be validated against when a row is saved. */ + constraints?: { + /** @enum {string} */ + type?: "string" | "number" | "object" | "boolean" + /** @description Defines whether the column is required or not. */ + presence?: boolean + } + /** @description The name of the column. */ + name?: string + /** @description Defines whether the column is automatically generated. */ + autocolumn?: boolean + /** @description The name of the column which a relationship column is related to in another table. */ + fieldName?: string + /** @description The ID of the table which a relationship column is related to. */ + tableId?: string + /** + * @description Defines the type of relationship that this column will be used for. + * @enum {string} + */ + relationshipType?: "one-to-many" | "many-to-one" | "many-to-many" + /** @description When using a SQL table that contains many to many relationships this defines the table the relationships are linked through. */ + through?: string + /** @description When using a SQL table that contains a one to many relationship this defines the foreign key. */ + foreignKey?: string + /** @description When using a SQL table that utilises a through table, this defines the primary key in the through table for this table. */ + throughFrom?: string + /** @description When using a SQL table that utilises a through table, this defines the primary key in the through table for the related table. */ + throughTo?: string + } + | { + /** + * @description A formula column. + * @enum {string} + */ + type?: "formula" + /** @description A constraint can be applied to the column which will be validated against when a row is saved. */ + constraints?: { + /** @enum {string} */ + type?: "string" | "number" | "object" | "boolean" + /** @description Defines whether the column is required or not. */ + presence?: boolean + } + /** @description The name of the column. */ + name?: string + /** @description Defines whether the column is automatically generated. */ + autocolumn?: boolean + /** @description Defines a Handlebars or JavaScript formula to use, note that Javascript formulas are expected to be provided in the base64 format. */ + formula?: string + /** + * @description Defines whether this is a static or dynamic formula. + * @enum {string} + */ + formulaType?: "static" | "dynamic" + } + | { + /** + * @description Defines the type of the column, most explain themselves, a link column is a relationship. + * @enum {string} + */ + type?: + | "string" + | "longform" + | "options" + | "number" + | "boolean" + | "array" + | "datetime" + | "attachment" + | "link" + | "formula" + | "auto" + | "json" + | "internal" + /** @description A constraint can be applied to the column which will be validated against when a row is saved. */ + constraints?: { + /** @enum {string} */ + type?: "string" | "number" | "object" | "boolean" + /** @description Defines whether the column is required or not. */ + presence?: boolean + } + /** @description The name of the column. */ + name?: string + /** @description Defines whether the column is automatically generated. */ + autocolumn?: boolean + } + } + } + tableOutput: { + /** @description The table to be created/updated. */ + data: { + /** @description The name of the table. */ + name: string + /** @description The name of the column which should be used in relationship tags when relating to this table. */ + primaryDisplay?: string + schema: { + [key: string]: + | { + /** + * @description A relationship column. + * @enum {string} + */ + type?: "link" + /** @description A constraint can be applied to the column which will be validated against when a row is saved. */ + constraints?: { + /** @enum {string} */ + type?: "string" | "number" | "object" | "boolean" + /** @description Defines whether the column is required or not. */ + presence?: boolean + } + /** @description The name of the column. */ + name?: string + /** @description Defines whether the column is automatically generated. */ + autocolumn?: boolean + /** @description The name of the column which a relationship column is related to in another table. */ + fieldName?: string + /** @description The ID of the table which a relationship column is related to. */ + tableId?: string + /** + * @description Defines the type of relationship that this column will be used for. + * @enum {string} + */ + relationshipType?: + | "one-to-many" + | "many-to-one" + | "many-to-many" + /** @description When using a SQL table that contains many to many relationships this defines the table the relationships are linked through. */ + through?: string + /** @description When using a SQL table that contains a one to many relationship this defines the foreign key. */ + foreignKey?: string + /** @description When using a SQL table that utilises a through table, this defines the primary key in the through table for this table. */ + throughFrom?: string + /** @description When using a SQL table that utilises a through table, this defines the primary key in the through table for the related table. */ + throughTo?: string + } + | { + /** + * @description A formula column. + * @enum {string} + */ + type?: "formula" + /** @description A constraint can be applied to the column which will be validated against when a row is saved. */ + constraints?: { + /** @enum {string} */ + type?: "string" | "number" | "object" | "boolean" + /** @description Defines whether the column is required or not. */ + presence?: boolean + } + /** @description The name of the column. */ + name?: string + /** @description Defines whether the column is automatically generated. */ + autocolumn?: boolean + /** @description Defines a Handlebars or JavaScript formula to use, note that Javascript formulas are expected to be provided in the base64 format. */ + formula?: string + /** + * @description Defines whether this is a static or dynamic formula. + * @enum {string} + */ + formulaType?: "static" | "dynamic" + } + | { + /** + * @description Defines the type of the column, most explain themselves, a link column is a relationship. + * @enum {string} + */ + type?: + | "string" + | "longform" + | "options" + | "number" + | "boolean" + | "array" + | "datetime" + | "attachment" + | "link" + | "formula" + | "auto" + | "json" + | "internal" + /** @description A constraint can be applied to the column which will be validated against when a row is saved. */ + constraints?: { + /** @enum {string} */ + type?: "string" | "number" | "object" | "boolean" + /** @description Defines whether the column is required or not. */ + presence?: boolean + } + /** @description The name of the column. */ + name?: string + /** @description Defines whether the column is automatically generated. */ + autocolumn?: boolean + } + } + /** @description The ID of the table. */ + _id: string + } + } + /** @description The query body must contain the required parameters for the query, this depends on query type, setup and bindings. */ + executeQuery: { [key: string]: unknown } + executeQueryOutput: { + /** @description The data response from the query. */ + data: { [key: string]: unknown }[] + /** @description Extra information that is not part of the main data, e.g. headers. */ + extra?: { + /** @description If carrying out a REST request, this will contain the response headers. */ + headers?: { [key: string]: unknown } + /** @description The raw query response, as a string. */ + raw?: string + } + /** @description If pagination is supported, this will contain the bookmark/anchor information for it. */ + pagination?: { [key: string]: unknown } + } + query: { + /** @description The ID of the query. */ + _id: string + /** @description The ID of the data source the query belongs to. */ + datasourceId?: string + /** @description The bindings which are required to perform this query. */ + parameters?: string[] + /** @description The fields that are used to perform this query, e.g. the sql statement */ + fields?: { [key: string]: unknown } + /** + * @description The verb that describes this query. + * @enum {undefined} + */ + queryVerb?: "create" | "read" | "update" | "delete" + /** @description The name of the query. */ + name: string + /** @description The schema of the data returned when the query is executed. */ + schema: { [key: string]: unknown } + /** @description The JavaScript transformer function, applied after the query responds with data. */ + transformer?: string + /** @description Whether the query has readable data. */ + readable?: boolean + } + user: { + /** @description The email address of the user, this must be unique. */ + email: string + /** @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. */ + password?: string + /** + * @description The status of the user, if they are active. + * @enum {string} + */ + status?: "active" + /** @description The first name of the user */ + firstName?: string + /** @description The last name of the user */ + lastName?: string + /** @description If set to true forces the user to reset their password on first login. */ + forceResetPassword?: boolean + /** @description Describes if the user is a builder user or not. */ + builder?: { + /** @description If set to true the user will be able to build any app in the system. */ + global?: boolean + } + /** @description Describes if the user is an admin user or not. */ + admin?: { + /** @description If set to true the user will be able to administrate the system. */ + global?: boolean + } + /** @description Contains the roles of the user per app (assuming they are not a builder user). */ + roles: { [key: string]: string } + } + userOutput: { + data: { + /** @description The email address of the user, this must be unique. */ + email: string + /** @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. */ + password?: string + /** + * @description The status of the user, if they are active. + * @enum {string} + */ + status?: "active" + /** @description The first name of the user */ + firstName?: string + /** @description The last name of the user */ + lastName?: string + /** @description If set to true forces the user to reset their password on first login. */ + forceResetPassword?: boolean + /** @description Describes if the user is a builder user or not. */ + builder?: { + /** @description If set to true the user will be able to build any app in the system. */ + global?: boolean + } + /** @description Describes if the user is an admin user or not. */ + admin?: { + /** @description If set to true the user will be able to administrate the system. */ + global?: boolean + } + /** @description Contains the roles of the user per app (assuming they are not a builder user). */ + roles: { [key: string]: string } + /** @description The ID of the user. */ + _id: string + } + } + nameSearch: { + /** @description The name to be used when searching - this will be used in a case insensitive starts with match. */ + name: string + } + } + parameters: { + /** @description The ID of the table which this request is targeting. */ + tableId: string + /** @description The ID of the row which this request is targeting. */ + rowId: string + /** @description The ID of the app which this request is targeting. */ + appId: string + /** @description The ID of the app which this request is targeting. */ + appIdUrl: string + /** @description The ID of the query which this request is targeting. */ + queryId: string + /** @description The ID of the user which this request is targeting. */ + userId: string + } +} + +export interface operations {} + +export interface external {} diff --git a/examples/nextjs-api-sales/next.config.js b/examples/nextjs-api-sales/next.config.js index 12c41deacd..61b35090e4 100644 --- a/examples/nextjs-api-sales/next.config.js +++ b/examples/nextjs-api-sales/next.config.js @@ -1,9 +1,14 @@ -const path = require("path") +const { join } = require("path") /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, sassOptions: { - includePaths: [path.join(__dirname, "styles")] + includePaths: [join(__dirname, "styles")] + }, + serverRuntimeConfig: { + apiKey: "", + appName: "", + host: "http://localhost:10000" } } diff --git a/examples/nextjs-api-sales/package.json b/examples/nextjs-api-sales/package.json index 4212ea6c5b..a278a3050a 100644 --- a/examples/nextjs-api-sales/package.json +++ b/examples/nextjs-api-sales/package.json @@ -11,6 +11,7 @@ "dependencies": { "bulma": "^0.9.3", "next": "12.1.0", + "node-fetch": "^3.2.2", "node-sass": "^7.0.1", "react": "17.0.2", "react-dom": "17.0.2" diff --git a/examples/nextjs-api-sales/pages/api/hello.ts b/examples/nextjs-api-sales/pages/api/hello.ts deleted file mode 100644 index f8bcc7e5ca..0000000000 --- a/examples/nextjs-api-sales/pages/api/hello.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import type { NextApiRequest, NextApiResponse } from 'next' - -type Data = { - name: string -} - -export default function handler( - req: NextApiRequest, - res: NextApiResponse -) { - res.status(200).json({ name: 'John Doe' }) -} diff --git a/examples/nextjs-api-sales/pages/api/sales.ts b/examples/nextjs-api-sales/pages/api/sales.ts new file mode 100644 index 0000000000..41b9c0d08e --- /dev/null +++ b/examples/nextjs-api-sales/pages/api/sales.ts @@ -0,0 +1,66 @@ +import getConfig from "next/config" +import fetch from "node-fetch" +import { App, AppSearch, RowSearch } from "../../definitions" + +const { serverRuntimeConfig } = getConfig() +const apiKey = serverRuntimeConfig["apiKey"] +const appName = serverRuntimeConfig["appName"] +const host = serverRuntimeConfig["host"] + +async function makeCall(method: string, url: string, opts?: { body?: any, appId?: string } = {}): Promise { + const fetchOpts: any = { + method, + headers: { + "x-budibase-api-key": apiKey, + } + } + if (opts?.appId) { + fetchOpts.headers["x-budibase-app-id"] = opts.appId + } + if (opts?.body) { + fetchOpts.body = JSON.stringify(opts?.body) + fetchOpts.headers["Content-Type"] = "application/json" + } + const response = await fetch(`${host}/public/v1/${url}`, fetchOpts) + if (response.status === 200) { + return response.json() + } else { + throw new Error(await response.text()) + } +} + +async function getApp(): Promise { + const apps: AppSearch = await makeCall("post", "applications/search", { + body: { + name: appName, + } + }) + if (!Array.isArray(apps?.data)) { + throw new Error("Fatal error, no apps found.") + } + const app = apps.data.find((app: App) => app.name === appName) + if (!app) { + throw new Error("Could not find app, please make sure app name in config is correct.") + } + return app +} + +async function getSales(req: any) { + const { _id: appId } = await getApp() +} + +async function saveSale(req: any) { + const { _id: appId } = await getApp() +} + +export default async function handler(req: any, res: any) { + let response: any = {} + if (req.method === "POST") { + response = await saveSale(req) + } else if (req.method === "GET") { + response = await getSales(req) + } else { + res.status(404) + } + res.status(200).json(response) +} \ No newline at end of file diff --git a/examples/nextjs-api-sales/yarn.lock b/examples/nextjs-api-sales/yarn.lock index d23f480c44..b1a66a4ac5 100644 --- a/examples/nextjs-api-sales/yarn.lock +++ b/examples/nextjs-api-sales/yarn.lock @@ -673,6 +673,11 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +data-uri-to-buffer@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz#b5db46aea50f6176428ac05b73be39a57701a64b" + integrity sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA== + debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: version "4.3.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" @@ -1098,6 +1103,14 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +fetch-blob@^3.1.2, fetch-blob@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.1.4.tgz#e8c6567f80ad7fc22fd302e7dcb72bafde9c1717" + integrity sha512-Eq5Xv5+VlSrYWEqKrusxY1C3Hm/hjeAsCGVG3ft7pZahlUAChpGZT/Ms1WmSLnEAisEXszjzu/s+ce6HZB2VHA== + dependencies: + node-domexception "^1.0.0" + web-streams-polyfill "^3.0.3" + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -1154,6 +1167,13 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" +formdata-polyfill@^4.0.10: + version "4.0.10" + resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" + integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== + dependencies: + fetch-blob "^3.1.2" + fs-minipass@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" @@ -1995,6 +2015,20 @@ next@12.1.0: "@next/swc-win32-ia32-msvc" "12.1.0" "@next/swc-win32-x64-msvc" "12.1.0" +node-domexception@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + +node-fetch@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.2.tgz#16d33fbe32ca7c6ca1ca8ba5dfea1dd885c59f04" + integrity sha512-Cwhq1JFIoon15wcIkFzubVNFE5GvXGV82pKf4knXXjvGmn7RJKcypeuqcVNZMGDZsAFWyIRya/anwAJr7TWJ7w== + dependencies: + data-uri-to-buffer "^4.0.0" + fetch-blob "^3.1.4" + formdata-polyfill "^4.0.10" + node-gyp@^8.4.1: version "8.4.1" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.4.1.tgz#3d49308fc31f768180957d6b5746845fbd429937" @@ -2964,6 +2998,11 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +web-streams-polyfill@^3.0.3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz#a6b74026b38e4885869fb5c589e90b95ccfc7965" + integrity sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA== + which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"