From 78eb3155580fd27bccf6851fec31cb950746b25c Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Tue, 27 May 2025 11:29:24 -0500 Subject: [PATCH] initial build of table meta-data, query, and count specs, IO, executors --- .../javalin/executors/TableCountExecutor.java | 103 ++++ .../executors/TableMetaDataExecutor.java | 67 +++ .../javalin/executors/TableQueryExecutor.java | 109 ++++ .../javalin/executors/io/TableCountInput.java | 63 ++ .../io/TableCountOutputInterface.java | 40 ++ .../executors/io/TableMetaDataInput.java | 66 +++ .../io/TableMetaDataOutputInterface.java | 37 ++ .../javalin/executors/io/TableQueryInput.java | 31 + .../executors/io/TableQueryOrCountInput.java | 172 ++++++ .../io/TableQueryOutputInterface.java | 38 ++ .../javalin/specs/v1/MiddlewareVersionV1.java | 1 + .../javalin/specs/v1/TableCountSpecV1.java | 163 ++++++ .../javalin/specs/v1/TableMetaDataSpecV1.java | 143 +++++ .../javalin/specs/v1/TableQuerySpecV1.java | 181 ++++++ .../v1/responses/TableCountResponseV1.java | 102 ++++ .../v1/responses/TableMetaDataResponseV1.java | 84 +++ .../v1/responses/TableQueryResponseV1.java | 82 +++ .../v1/responses/components/ExposedJoin.java | 108 ++++ .../responses/components/FilterCriteria.java | 99 ++++ .../v1/responses/components/OrderBy.java | 82 +++ .../v1/responses/components/OutputRecord.java | 110 ++++ .../v1/responses/components/QueryFilter.java | 120 ++++ .../v1/responses/components/QueryJoin.java | 125 ++++ .../responses/components/TableMetaData.java | 158 +++++ .../components/TableMetaDataLight.java | 2 +- .../v1/responses/components/TableSection.java | 161 +++++ .../v1/responses/components/TableVariant.java | 141 +++++ .../specs/v1/utils/QuerySpecUtils.java | 228 +++----- .../javalin/specs/v1/utils/TagsV1.java | 5 +- .../main/resources/openapi/v1/openapi.yaml | 550 ++++++++++++++++++ .../qqq/backend/javalin/TestUtils.java | 43 ++ .../javalin/specs/SpecTestBase.java | 35 +- .../specs/v1/TableCountSpecV1Test.java | 237 ++++++++ .../specs/v1/TableMetaDataSpecV1Test.java | 102 ++++ .../specs/v1/TableQuerySpecV1Test.java | 232 ++++++++ 35 files changed, 3872 insertions(+), 148 deletions(-) create mode 100644 qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/TableCountExecutor.java create mode 100644 qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/TableMetaDataExecutor.java create mode 100644 qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/TableQueryExecutor.java create mode 100644 qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableCountInput.java create mode 100644 qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableCountOutputInterface.java create mode 100644 qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableMetaDataInput.java create mode 100644 qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableMetaDataOutputInterface.java create mode 100644 qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableQueryInput.java create mode 100644 qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableQueryOrCountInput.java create mode 100644 qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableQueryOutputInterface.java create mode 100644 qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/TableCountSpecV1.java create mode 100644 qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/TableMetaDataSpecV1.java create mode 100644 qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/TableQuerySpecV1.java create mode 100644 qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/TableCountResponseV1.java create mode 100644 qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/TableMetaDataResponseV1.java create mode 100644 qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/TableQueryResponseV1.java create mode 100644 qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/ExposedJoin.java create mode 100644 qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/FilterCriteria.java create mode 100644 qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/OrderBy.java create mode 100644 qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/OutputRecord.java create mode 100644 qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/QueryFilter.java create mode 100644 qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/QueryJoin.java create mode 100644 qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/TableMetaData.java create mode 100644 qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/TableSection.java create mode 100644 qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/TableVariant.java create mode 100644 qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/TableCountSpecV1Test.java create mode 100644 qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/TableMetaDataSpecV1Test.java create mode 100644 qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/TableQuerySpecV1Test.java diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/TableCountExecutor.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/TableCountExecutor.java new file mode 100644 index 00000000..0bc8d96f --- /dev/null +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/TableCountExecutor.java @@ -0,0 +1,103 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.executors; + + +import java.util.Collections; +import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper; +import com.kingsrook.qqq.backend.core.actions.permissions.TablePermissionSubType; +import com.kingsrook.qqq.backend.core.actions.tables.CountAction; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException; +import com.kingsrook.qqq.backend.core.logging.QLogger; +import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint; +import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput; +import com.kingsrook.qqq.backend.core.utils.ExceptionUtils; +import com.kingsrook.qqq.backend.core.utils.ValueUtils; +import com.kingsrook.qqq.middleware.javalin.executors.io.TableCountInput; +import com.kingsrook.qqq.middleware.javalin.executors.io.TableCountOutputInterface; +import org.apache.commons.lang3.BooleanUtils; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class TableCountExecutor extends AbstractMiddlewareExecutor +{ + private static final QLogger LOG = QLogger.getLogger(TableCountExecutor.class); + + protected static final Integer DEFAULT_QUERY_TIMEOUT_SECONDS = 60; + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public void execute(TableCountInput input, TableCountOutputInterface output) throws QException + { + try + { + ExecutorSessionUtils.setTableVariantInSession(input.getTableVariant()); + + CountInput countInput = new CountInput(); + countInput.setTableName(input.getTableName()); + + PermissionsHelper.checkTablePermissionThrowing(countInput, TablePermissionSubType.READ); + + countInput.setFilter(input.getFilter()); + countInput.setQueryJoins(input.getJoins()); + countInput.setIncludeDistinctCount(input.getIncludeDistinct()); + countInput.withQueryHint(QueryHint.MAY_USE_READ_ONLY_BACKEND); + + if(countInput.getFilter() != null) + { + // todo + countInput.getFilter().interpretValues(Collections.emptyMap()); + } + + CountOutput countOutput = new CountAction().execute(countInput); + output.setCount(ValueUtils.getValueAsLong(countOutput.getCount())); + + if(BooleanUtils.isTrue(input.getIncludeDistinct())) + { + output.setDistinctCount(ValueUtils.getValueAsLong(countOutput.getDistinctCount())); + } + } + catch(QException e) + { + QUserFacingException userFacingException = ExceptionUtils.findClassInRootChain(e, QUserFacingException.class); + if(userFacingException != null) + { + throw userFacingException; + } + + throw (e); + } + catch(Exception e) + { + throw (new QException("Unexpected error occurred while executing count query: " + e.getMessage(), e)); + } + } + +} diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/TableMetaDataExecutor.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/TableMetaDataExecutor.java new file mode 100644 index 00000000..3c17294b --- /dev/null +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/TableMetaDataExecutor.java @@ -0,0 +1,67 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.executors; + + +import com.kingsrook.qqq.backend.core.actions.metadata.TableMetaDataAction; +import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper; +import com.kingsrook.qqq.backend.core.actions.permissions.TablePermissionSubType; +import com.kingsrook.qqq.backend.core.context.QContext; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException; +import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataOutput; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; +import com.kingsrook.qqq.middleware.javalin.executors.io.TableMetaDataInput; +import com.kingsrook.qqq.middleware.javalin.executors.io.TableMetaDataOutputInterface; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class TableMetaDataExecutor extends AbstractMiddlewareExecutor +{ + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public void execute(TableMetaDataInput input, TableMetaDataOutputInterface output) throws QException + { + com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataInput tableMetaDataInput = new com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataInput(); + + String tableName = input.getTableName(); + QTableMetaData table = QContext.getQInstance().getTable(tableName); + if(table == null) + { + throw (new QNotFoundException("Table [" + tableName + "] was not found.")); + } + + tableMetaDataInput.setTableName(tableName); + PermissionsHelper.checkTablePermissionThrowing(tableMetaDataInput, TablePermissionSubType.READ); + + TableMetaDataAction tableMetaDataAction = new TableMetaDataAction(); + TableMetaDataOutput tableMetaDataOutput = tableMetaDataAction.execute(tableMetaDataInput); + + output.setTableMetaData(tableMetaDataOutput.getTable()); + } + +} diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/TableQueryExecutor.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/TableQueryExecutor.java new file mode 100644 index 00000000..e798972f --- /dev/null +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/TableQueryExecutor.java @@ -0,0 +1,109 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.executors; + + +import java.util.Collections; +import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper; +import com.kingsrook.qqq.backend.core.actions.permissions.TablePermissionSubType; +import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; +import com.kingsrook.qqq.backend.core.context.QContext; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException; +import com.kingsrook.qqq.backend.core.logging.QLogger; +import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput; +import com.kingsrook.qqq.backend.core.utils.ExceptionUtils; +import com.kingsrook.qqq.backend.javalin.QJavalinMetaData; +import com.kingsrook.qqq.backend.javalin.QJavalinUtils; +import com.kingsrook.qqq.middleware.javalin.executors.io.TableQueryInput; +import com.kingsrook.qqq.middleware.javalin.executors.io.TableQueryOutputInterface; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class TableQueryExecutor extends AbstractMiddlewareExecutor +{ + private static final QLogger LOG = QLogger.getLogger(TableQueryExecutor.class); + + protected static final Integer DEFAULT_QUERY_TIMEOUT_SECONDS = 60; + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public void execute(TableQueryInput input, TableQueryOutputInterface output) throws QException + { + try + { + ExecutorSessionUtils.setTableVariantInSession(input.getTableVariant()); + + QueryInput queryInput = new QueryInput(); + queryInput.setTableName(input.getTableName()); + + PermissionsHelper.checkTablePermissionThrowing(queryInput, TablePermissionSubType.READ); + + queryInput.setFilter(input.getFilter()); + queryInput.setQueryJoins(input.getJoins()); + queryInput.setShouldGenerateDisplayValues(true); + queryInput.setShouldTranslatePossibleValues(true); + queryInput.setTimeoutSeconds(DEFAULT_QUERY_TIMEOUT_SECONDS); // todo param + queryInput.withQueryHint(QueryHint.MAY_USE_READ_ONLY_BACKEND); + + if(queryInput.getFilter() != null) + { + // todo + queryInput.getFilter().interpretValues(Collections.emptyMap()); + } + + if(queryInput.getFilter() == null || queryInput.getFilter().getLimit() == null) + { + QJavalinUtils.handleQueryNullLimit(QJavalinMetaData.of(QContext.getQInstance()), queryInput, null); + } + + QueryOutput queryOutput = new QueryAction().execute(queryInput); + + // todo not sure QValueFormatter.setBlobValuesToDownloadUrls(QContext.getQInstance().getTable(input.getTableName()), queryOutput.getRecords()); + + output.setRecords(queryOutput.getRecords()); + } + catch(QException e) + { + QUserFacingException userFacingException = ExceptionUtils.findClassInRootChain(e, QUserFacingException.class); + if(userFacingException != null) + { + throw userFacingException; + } + + throw (e); + } + catch(Exception e) + { + throw (new QException("Unexpected error occurred while executing query: " + e.getMessage(), e)); + } + } + +} diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableCountInput.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableCountInput.java new file mode 100644 index 00000000..36d3381d --- /dev/null +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableCountInput.java @@ -0,0 +1,63 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.executors.io; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class TableCountInput extends TableQueryOrCountInput +{ + private Boolean includeDistinct = false; + + + + /******************************************************************************* + ** Getter for includeDistinct + *******************************************************************************/ + public Boolean getIncludeDistinct() + { + return (this.includeDistinct); + } + + + + /******************************************************************************* + ** Setter for includeDistinct + *******************************************************************************/ + public void setIncludeDistinct(Boolean includeDistinct) + { + this.includeDistinct = includeDistinct; + } + + + + /******************************************************************************* + ** Fluent setter for includeDistinct + *******************************************************************************/ + public TableCountInput withIncludeDistinct(Boolean includeDistinct) + { + this.includeDistinct = includeDistinct; + return (this); + } + +} diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableCountOutputInterface.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableCountOutputInterface.java new file mode 100644 index 00000000..c6936dbe --- /dev/null +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableCountOutputInterface.java @@ -0,0 +1,40 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.executors.io; + + +/******************************************************************************* + ** + *******************************************************************************/ +public interface TableCountOutputInterface extends AbstractMiddlewareOutputInterface +{ + /*************************************************************************** + ** + ***************************************************************************/ + void setCount(Long count); + + /*************************************************************************** + ** + ***************************************************************************/ + void setDistinctCount(Long count); + +} diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableMetaDataInput.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableMetaDataInput.java new file mode 100644 index 00000000..4418f64f --- /dev/null +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableMetaDataInput.java @@ -0,0 +1,66 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.executors.io; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class TableMetaDataInput extends AbstractMiddlewareInput +{ + private String tableName; + + + + /******************************************************************************* + ** Getter for tableName + ** + *******************************************************************************/ + public String getTableName() + { + return tableName; + } + + + + /******************************************************************************* + ** Setter for tableName + ** + *******************************************************************************/ + public void setTableName(String tableName) + { + this.tableName = tableName; + } + + + + /******************************************************************************* + ** Fluent setter for tableName + ** + *******************************************************************************/ + public TableMetaDataInput withTableName(String tableName) + { + this.tableName = tableName; + return (this); + } + +} diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableMetaDataOutputInterface.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableMetaDataOutputInterface.java new file mode 100644 index 00000000..1cc85005 --- /dev/null +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableMetaDataOutputInterface.java @@ -0,0 +1,37 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.executors.io; + + +import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendTableMetaData; + + +/******************************************************************************* + ** + *******************************************************************************/ +public interface TableMetaDataOutputInterface extends AbstractMiddlewareOutputInterface +{ + /*************************************************************************** + ** + ***************************************************************************/ + void setTableMetaData(QFrontendTableMetaData tableMetaData); +} diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableQueryInput.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableQueryInput.java new file mode 100644 index 00000000..5218537f --- /dev/null +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableQueryInput.java @@ -0,0 +1,31 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.executors.io; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class TableQueryInput extends TableQueryOrCountInput +{ + +} diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableQueryOrCountInput.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableQueryOrCountInput.java new file mode 100644 index 00000000..f3bd650e --- /dev/null +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableQueryOrCountInput.java @@ -0,0 +1,172 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.executors.io; + + +import java.util.List; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin; +import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components.TableVariant; + + +/******************************************************************************* + ** + *******************************************************************************/ +public abstract class TableQueryOrCountInput extends AbstractMiddlewareInput +{ + private String tableName; + private QQueryFilter filter; + private List joins; + + private TableVariant tableVariant; + + + + /******************************************************************************* + ** Getter for tableName + ** + *******************************************************************************/ + public String getTableName() + { + return tableName; + } + + + + /******************************************************************************* + ** Setter for tableName + ** + *******************************************************************************/ + public void setTableName(String tableName) + { + this.tableName = tableName; + } + + + + /******************************************************************************* + ** Fluent setter for tableName + ** + *******************************************************************************/ + public TableQueryOrCountInput withTableName(String tableName) + { + this.tableName = tableName; + return (this); + } + + + + /******************************************************************************* + ** Getter for filter + ** + *******************************************************************************/ + public QQueryFilter getFilter() + { + return filter; + } + + + + /******************************************************************************* + ** Setter for filter + ** + *******************************************************************************/ + public void setFilter(QQueryFilter filter) + { + this.filter = filter; + } + + + + /******************************************************************************* + ** Fluent setter for filter + ** + *******************************************************************************/ + public TableQueryOrCountInput withFilter(QQueryFilter filter) + { + this.filter = filter; + return (this); + } + + + + /******************************************************************************* + ** Getter for joins + *******************************************************************************/ + public List getJoins() + { + return (this.joins); + } + + + + /******************************************************************************* + ** Setter for joins + *******************************************************************************/ + public void setJoins(List joins) + { + this.joins = joins; + } + + + + /******************************************************************************* + ** Fluent setter for joins + *******************************************************************************/ + public TableQueryOrCountInput withJoins(List joins) + { + this.joins = joins; + return (this); + } + + + /******************************************************************************* + ** Getter for tableVariant + *******************************************************************************/ + public TableVariant getTableVariant() + { + return (this.tableVariant); + } + + + + /******************************************************************************* + ** Setter for tableVariant + *******************************************************************************/ + public void setTableVariant(TableVariant tableVariant) + { + this.tableVariant = tableVariant; + } + + + + /******************************************************************************* + ** Fluent setter for tableVariant + *******************************************************************************/ + public TableQueryOrCountInput withTableVariant(TableVariant tableVariant) + { + this.tableVariant = tableVariant; + return (this); + } + + +} diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableQueryOutputInterface.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableQueryOutputInterface.java new file mode 100644 index 00000000..93776a30 --- /dev/null +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/TableQueryOutputInterface.java @@ -0,0 +1,38 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.executors.io; + + +import java.util.List; +import com.kingsrook.qqq.backend.core.model.data.QRecord; + + +/******************************************************************************* + ** + *******************************************************************************/ +public interface TableQueryOutputInterface extends AbstractMiddlewareOutputInterface +{ + /*************************************************************************** + ** + ***************************************************************************/ + void setRecords(List records); +} diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/MiddlewareVersionV1.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/MiddlewareVersionV1.java index 6ea85ed3..ab3eeba4 100644 --- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/MiddlewareVersionV1.java +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/MiddlewareVersionV1.java @@ -44,6 +44,7 @@ public class MiddlewareVersionV1 extends AbstractMiddlewareVersion list.add(new TableMetaDataSpecV1()); list.add(new TableQuerySpecV1()); + list.add(new TableCountSpecV1()); list.add(new ProcessMetaDataSpecV1()); list.add(new ProcessInitSpecV1()); diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/TableCountSpecV1.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/TableCountSpecV1.java new file mode 100644 index 00000000..350d2b62 --- /dev/null +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/TableCountSpecV1.java @@ -0,0 +1,163 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.specs.v1; + + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import com.kingsrook.qqq.middleware.javalin.executors.TableCountExecutor; +import com.kingsrook.qqq.middleware.javalin.executors.io.TableCountInput; +import com.kingsrook.qqq.middleware.javalin.specs.AbstractEndpointSpec; +import com.kingsrook.qqq.middleware.javalin.specs.BasicOperation; +import com.kingsrook.qqq.middleware.javalin.specs.BasicResponse; +import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.TableCountResponseV1; +import com.kingsrook.qqq.middleware.javalin.specs.v1.utils.QuerySpecUtils; +import com.kingsrook.qqq.middleware.javalin.specs.v1.utils.TagsV1; +import com.kingsrook.qqq.openapi.model.Example; +import com.kingsrook.qqq.openapi.model.HttpMethod; +import com.kingsrook.qqq.openapi.model.In; +import com.kingsrook.qqq.openapi.model.Parameter; +import com.kingsrook.qqq.openapi.model.RequestBody; +import com.kingsrook.qqq.openapi.model.Schema; +import com.kingsrook.qqq.openapi.model.Type; +import io.javalin.http.Context; +import org.json.JSONObject; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class TableCountSpecV1 extends AbstractEndpointSpec +{ + + /*************************************************************************** + ** + ***************************************************************************/ + public BasicOperation defineBasicOperation() + { + return new BasicOperation() + .withPath("/table/{tableName}/count") + .withHttpMethod(HttpMethod.POST) + .withTag(TagsV1.TABLES) + .withShortSummary("Count records in a table") + .withLongDescription(""" + Execute a query against a table, returning the number of records that match a filter.""" + ); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public List defineRequestParameters() + { + return List.of( + new Parameter() + .withName("tableName") + .withDescription("Name of the table to count.") + .withRequired(true) + .withSchema(new Schema().withType(Type.STRING)) + .withExample("person") + .withIn(In.PATH), + new Parameter() + .withName("includeDistinct") + .withDescription("Whether or not to also return the count distinct records from the main table (e.g., in case of a to-many join; by default, do not).") + .withRequired(true) + .withSchema(new Schema().withType(Type.BOOLEAN)) + .withExample("true") + .withIn(In.QUERY) + ); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public RequestBody defineRequestBody() + { + return (QuerySpecUtils.defineQueryOrCountRequestBody()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public TableCountInput buildInput(Context context) throws Exception + { + TableCountInput input = new TableCountInput(); + input.setTableName(getRequestParam(context, "tableName")); + + JSONObject requestBody = getRequestBodyAsJsonObject(context); + if(requestBody != null) + { + input.setFilter(QuerySpecUtils.getFilterFromRequestBody(requestBody)); + input.setJoins(QuerySpecUtils.getJoinsFromRequestBody(requestBody)); + input.setTableVariant(QuerySpecUtils.getTableVariantFromRequestBody(requestBody)); + } + + String includeDistinctParam = getRequestParam(context, "includeDistinct"); + if("true".equals(includeDistinctParam)) + { + input.setIncludeDistinct(true); + } + + return (input); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public Map defineComponentSchemas() + { + return Map.of(TableCountResponseV1.class.getSimpleName(), new TableCountResponseV1().toSchema()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public BasicResponse defineBasicSuccessResponse() + { + Map examples = new LinkedHashMap<>(); + examples.put("TODO", new Example() + .withValue(new TableCountResponseV1().withCount(42L))); + + return new BasicResponse(""" + The number (count) of records matching the query""", + TableCountResponseV1.class.getSimpleName(), + examples + ); + } + +} diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/TableMetaDataSpecV1.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/TableMetaDataSpecV1.java new file mode 100644 index 00000000..4a52c6ca --- /dev/null +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/TableMetaDataSpecV1.java @@ -0,0 +1,143 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.specs.v1; + + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendTableMetaData; +import com.kingsrook.qqq.backend.core.utils.JsonUtils; +import com.kingsrook.qqq.middleware.javalin.executors.TableMetaDataExecutor; +import com.kingsrook.qqq.middleware.javalin.executors.io.TableMetaDataInput; +import com.kingsrook.qqq.middleware.javalin.specs.AbstractEndpointSpec; +import com.kingsrook.qqq.middleware.javalin.specs.BasicOperation; +import com.kingsrook.qqq.middleware.javalin.specs.BasicResponse; +import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.TableMetaDataResponseV1; +import com.kingsrook.qqq.middleware.javalin.specs.v1.utils.TagsV1; +import com.kingsrook.qqq.openapi.model.Example; +import com.kingsrook.qqq.openapi.model.HttpMethod; +import com.kingsrook.qqq.openapi.model.In; +import com.kingsrook.qqq.openapi.model.Parameter; +import com.kingsrook.qqq.openapi.model.Schema; +import com.kingsrook.qqq.openapi.model.Type; +import io.javalin.http.Context; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class TableMetaDataSpecV1 extends AbstractEndpointSpec +{ + + /*************************************************************************** + ** + ***************************************************************************/ + public BasicOperation defineBasicOperation() + { + return new BasicOperation() + .withPath("/metaData/table/{tableName}") + .withHttpMethod(HttpMethod.GET) + .withTag(TagsV1.TABLES) + .withShortSummary("Get table metaData") + .withLongDescription(""" + Load the full metadata for a single table, including all fields, which a frontend + needs to display to users.""" + ); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public List defineRequestParameters() + { + return List.of( + new Parameter() + .withName("tableName") + .withDescription("Name of the table to load.") + .withRequired(true) + .withSchema(new Schema().withType(Type.STRING)) + .withExample("person") + .withIn(In.PATH) + ); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public TableMetaDataInput buildInput(Context context) throws Exception + { + TableMetaDataInput input = new TableMetaDataInput(); + input.setTableName(getRequestParam(context, "tableName")); + return (input); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public Map defineComponentSchemas() + { + return Map.of(TableMetaDataResponseV1.class.getSimpleName(), new TableMetaDataResponseV1().toSchema()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public BasicResponse defineBasicSuccessResponse() + { + QFrontendTableMetaData frontendTableMetaData = null; // todo + + Map examples = new LinkedHashMap<>(); + examples.put("TODO", new Example() + .withValue(new TableMetaDataResponseV1().withTableMetaData(frontendTableMetaData))); + + return new BasicResponse(""" + The full table metadata""", + TableMetaDataResponseV1.class.getSimpleName(), + examples + ); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public void handleOutput(Context context, TableMetaDataResponseV1 output) throws Exception + { + context.result(JsonUtils.toJson(output.getTableMetaData())); + } + +} diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/TableQuerySpecV1.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/TableQuerySpecV1.java new file mode 100644 index 00000000..37bbc478 --- /dev/null +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/TableQuerySpecV1.java @@ -0,0 +1,181 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.specs.v1; + + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.core.utils.CollectionUtils; +import com.kingsrook.qqq.backend.core.utils.JsonUtils; +import com.kingsrook.qqq.middleware.javalin.executors.TableQueryExecutor; +import com.kingsrook.qqq.middleware.javalin.executors.io.TableQueryInput; +import com.kingsrook.qqq.middleware.javalin.specs.AbstractEndpointSpec; +import com.kingsrook.qqq.middleware.javalin.specs.BasicOperation; +import com.kingsrook.qqq.middleware.javalin.specs.BasicResponse; +import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.TableQueryResponseV1; +import com.kingsrook.qqq.middleware.javalin.specs.v1.utils.QuerySpecUtils; +import com.kingsrook.qqq.middleware.javalin.specs.v1.utils.TagsV1; +import com.kingsrook.qqq.openapi.model.Example; +import com.kingsrook.qqq.openapi.model.HttpMethod; +import com.kingsrook.qqq.openapi.model.In; +import com.kingsrook.qqq.openapi.model.Parameter; +import com.kingsrook.qqq.openapi.model.RequestBody; +import com.kingsrook.qqq.openapi.model.Schema; +import com.kingsrook.qqq.openapi.model.Type; +import io.javalin.http.Context; +import org.json.JSONObject; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class TableQuerySpecV1 extends AbstractEndpointSpec +{ + + /*************************************************************************** + ** + ***************************************************************************/ + public BasicOperation defineBasicOperation() + { + return new BasicOperation() + .withPath("/table/{tableName}/query") + .withHttpMethod(HttpMethod.POST) + .withTag(TagsV1.TABLES) + .withShortSummary("Query for records from a table") + .withLongDescription(""" + Execute a query against a table, returning records that match a filter.""" + ); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public List defineRequestParameters() + { + return List.of( + new Parameter() + .withName("tableName") + .withDescription("Name of the table to query.") + .withRequired(true) + .withSchema(new Schema().withType(Type.STRING)) + .withExample("person") + .withIn(In.PATH) + ); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public RequestBody defineRequestBody() + { + return (QuerySpecUtils.defineQueryOrCountRequestBody()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public TableQueryInput buildInput(Context context) throws Exception + { + TableQueryInput input = new TableQueryInput(); + input.setTableName(getRequestParam(context, "tableName")); + + JSONObject requestBody = getRequestBodyAsJsonObject(context); + if(requestBody != null) + { + input.setFilter(QuerySpecUtils.getFilterFromRequestBody(requestBody)); + input.setJoins(QuerySpecUtils.getJoinsFromRequestBody(requestBody)); + input.setTableVariant(QuerySpecUtils.getTableVariantFromRequestBody(requestBody)); + } + + return (input); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public Map defineComponentSchemas() + { + return Map.of(TableQueryResponseV1.class.getSimpleName(), new TableQueryResponseV1().toSchema()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public BasicResponse defineBasicSuccessResponse() + { + Map examples = new LinkedHashMap<>(); + examples.put("TODO", new Example() + .withValue(new TableQueryResponseV1().withRecords(List.of(new QRecord() + .withRecordLabel("Item 17") + .withTableName("item") + .withValue("id", 17).withValue("quantity", 1000).withValue("storeId", 42) + .withDisplayValue("id", "17").withDisplayValue("quantity", "1,000").withDisplayValue("storeId", "QQQ-Mart") + )))); + + return new BasicResponse(""" + The records matching query""", + TableQueryResponseV1.class.getSimpleName(), + examples + ); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public void handleOutput(Context context, TableQueryResponseV1 tableQueryResponseV1) throws Exception + { + if(CollectionUtils.nullSafeIsEmpty(tableQueryResponseV1.getRecords())) + { + //////////////////////////////////////////////////////////////////////////////////// + // special case here, where we want an empty list to be returned for the case // + // with no records found by default our serialization doesn't include empty lists // + //////////////////////////////////////////////////////////////////////////////////// + context.result(JsonUtils.toJson(tableQueryResponseV1, objectMapper -> objectMapper + .setSerializationInclusion(JsonInclude.Include.ALWAYS))); + } + else + { + super.handleOutput(context, tableQueryResponseV1); + } + } +} diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/TableCountResponseV1.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/TableCountResponseV1.java new file mode 100644 index 00000000..5e46ac47 --- /dev/null +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/TableCountResponseV1.java @@ -0,0 +1,102 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.specs.v1.responses; + + +import com.kingsrook.qqq.middleware.javalin.executors.io.TableCountOutputInterface; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.ToSchema; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIDescription; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class TableCountResponseV1 implements TableCountOutputInterface, ToSchema +{ + @OpenAPIDescription("Number (count) of records that satisfy the query request") + private Long count; + + @OpenAPIDescription("Number (count) of distinct records that satisfy the query request. Only included if requested.") + private Long distinctCount; + + + /******************************************************************************* + ** Getter for count + *******************************************************************************/ + public Long getCount() + { + return (this.count); + } + + + + /******************************************************************************* + ** Setter for count + *******************************************************************************/ + public void setCount(Long count) + { + this.count = count; + } + + + + /******************************************************************************* + ** Fluent setter for count + *******************************************************************************/ + public TableCountResponseV1 withCount(Long count) + { + this.count = count; + return (this); + } + + + /******************************************************************************* + ** Getter for distinctCount + *******************************************************************************/ + public Long getDistinctCount() + { + return (this.distinctCount); + } + + + + /******************************************************************************* + ** Setter for distinctCount + *******************************************************************************/ + public void setDistinctCount(Long distinctCount) + { + this.distinctCount = distinctCount; + } + + + + /******************************************************************************* + ** Fluent setter for distinctCount + *******************************************************************************/ + public TableCountResponseV1 withDistinctCount(Long distinctCount) + { + this.distinctCount = distinctCount; + return (this); + } + + +} diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/TableMetaDataResponseV1.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/TableMetaDataResponseV1.java new file mode 100644 index 00000000..780891da --- /dev/null +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/TableMetaDataResponseV1.java @@ -0,0 +1,84 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.specs.v1.responses; + + +import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendTableMetaData; +import com.kingsrook.qqq.middleware.javalin.executors.io.TableMetaDataOutputInterface; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.SchemaBuilder; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.ToSchema; +import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components.TableMetaData; +import com.kingsrook.qqq.openapi.model.Schema; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class TableMetaDataResponseV1 implements TableMetaDataOutputInterface, ToSchema +{ + private TableMetaData tableMetaData; + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public void setTableMetaData(QFrontendTableMetaData frontendTableMetaData) + { + this.tableMetaData = new TableMetaData(frontendTableMetaData); + } + + + + /******************************************************************************* + ** Fluent setter for frontendTableMetaData + ** + *******************************************************************************/ + public TableMetaDataResponseV1 withTableMetaData(QFrontendTableMetaData frontendTableMetaData) + { + setTableMetaData(frontendTableMetaData); + return (this); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public Schema toSchema() + { + return new SchemaBuilder().classToSchema(TableMetaData.class); + } + + + + /******************************************************************************* + ** Getter for tableMetaData + ** + *******************************************************************************/ + public TableMetaData getTableMetaData() + { + return tableMetaData; + } +} diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/TableQueryResponseV1.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/TableQueryResponseV1.java new file mode 100644 index 00000000..ab8dd426 --- /dev/null +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/TableQueryResponseV1.java @@ -0,0 +1,82 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.specs.v1.responses; + + +import java.util.List; +import java.util.stream.Collectors; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.middleware.javalin.executors.io.TableQueryOutputInterface; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.ToSchema; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIDescription; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIListItems; +import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components.OutputRecord; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class TableQueryResponseV1 implements TableQueryOutputInterface, ToSchema +{ + @OpenAPIDescription("List of records that satisfy the query request") + @OpenAPIListItems(value = OutputRecord.class, useRef = true) + private List records; + + + + /******************************************************************************* + ** Setter for records + *******************************************************************************/ + @Override + public void setRecords(List records) + { + if(records == null) + { + this.records = null; + } + else + { + this.records = records.stream().map(qr -> new OutputRecord(qr)).collect(Collectors.toList()); + } + } + + + /******************************************************************************* + ** Setter for records + *******************************************************************************/ + public TableQueryResponseV1 withRecords(List records) + { + setRecords(records); + return this; + } + + + + /******************************************************************************* + ** Getter for records + *******************************************************************************/ + public List getRecords() + { + return (this.records); + } + +} diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/ExposedJoin.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/ExposedJoin.java new file mode 100644 index 00000000..88569e60 --- /dev/null +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/ExposedJoin.java @@ -0,0 +1,108 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components; + + +import java.util.List; +import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendExposedJoin; +import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.ToSchema; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIDescription; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIExclude; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIListItems; + + +/*************************************************************************** + ** + ***************************************************************************/ +public class ExposedJoin implements ToSchema +{ + @OpenAPIExclude() + private QFrontendExposedJoin wrapped; + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public ExposedJoin(QFrontendExposedJoin section) + { + this.wrapped = section; + } + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public ExposedJoin() + { + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("User-facing label to display for this join") + public String getLabel() + { + return (this.wrapped.getLabel()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Whether or not this join is 'to many' in nature") + public Boolean getIsMany() + { + return (this.wrapped.getIsMany()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("The meta-data for the joined table") + public TableMetaData getJoinTable() + { + return (new TableMetaData(this.wrapped.getJoinTable())); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("A list of joins that travel from the base table to the exposed join table") + @OpenAPIListItems(value = QJoinMetaData.class, useRef = false) + public List getJoinPath() + { + return (this.wrapped.getJoinPath()); + } + +} diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/FilterCriteria.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/FilterCriteria.java new file mode 100644 index 00000000..611ddacd --- /dev/null +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/FilterCriteria.java @@ -0,0 +1,99 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components; + + +import java.util.List; +import java.util.stream.Collectors; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria; +import com.kingsrook.qqq.backend.core.utils.CollectionUtils; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.ToSchema; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIDescription; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIExclude; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIListItems; + + +/*************************************************************************** + ** + ***************************************************************************/ +public class FilterCriteria implements ToSchema +{ + @OpenAPIExclude() + private QFilterCriteria wrapped; + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public FilterCriteria(QFilterCriteria wrapped) + { + this.wrapped = wrapped; + } + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public FilterCriteria() + { + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Field name that this criteria applies to") + public String getFieldName() + { + return (this.wrapped.getFieldName()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Logical operator that applies to this criteria") + public QCriteriaOperator getOperator() + { + return (this.wrapped.getOperator()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Values to apply via the operator to the field") + @OpenAPIListItems(value = String.class) + public List getValues() + { + return (CollectionUtils.nonNullList(this.wrapped.getValues()).stream().map(String::valueOf).collect(Collectors.toList())); + } + +} diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/OrderBy.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/OrderBy.java new file mode 100644 index 00000000..41ea518f --- /dev/null +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/OrderBy.java @@ -0,0 +1,82 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components; + + +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.ToSchema; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIDescription; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIExclude; + + +/*************************************************************************** + ** + ***************************************************************************/ +public class OrderBy implements ToSchema +{ + @OpenAPIExclude() + private QFilterOrderBy wrapped; + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public OrderBy(QFilterOrderBy wrapped) + { + this.wrapped = wrapped; + } + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public OrderBy() + { + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Field name that this order-by applies to") + public String getFieldName() + { + return (this.wrapped.getFieldName()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Whether this order-by is ascending or descending") + public Boolean getIsAscending() + { + return (this.wrapped.getIsAscending()); + } + +} diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/OutputRecord.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/OutputRecord.java new file mode 100644 index 00000000..7f1ed43e --- /dev/null +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/OutputRecord.java @@ -0,0 +1,110 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components; + + +import java.io.Serializable; +import java.util.Map; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.ToSchema; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIDescription; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIExclude; + + +/*************************************************************************** + ** + ***************************************************************************/ +public class OutputRecord implements ToSchema +{ + @OpenAPIExclude() + private QRecord wrapped; + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public OutputRecord(QRecord wrapped) + { + this.wrapped = wrapped; + } + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public OutputRecord() + { + } + + + + /******************************************************************************* + ** Getter for tableName + ** + *******************************************************************************/ + @OpenAPIDescription("Name of the table that the record is from.") + public String getTableName() + { + return this.wrapped.getTableName(); + } + + + + /******************************************************************************* + ** Getter for tableName + ** + *******************************************************************************/ + @OpenAPIDescription("Label to identify the record to a user.") + public String getRecordLabel() + { + return this.wrapped.getRecordLabel(); + } + + + + /******************************************************************************* + ** Getter for values + ** + *******************************************************************************/ + @OpenAPIDescription("Raw values that make up the record. Keys are Strings, which match the table's field names. Values can be any type, as per the table's fields.") + public Map getValues() + { + return this.wrapped.getValues(); + } + + + + /******************************************************************************* + ** Getter for displayValues + ** + *******************************************************************************/ + @OpenAPIDescription("Formatted string versions of the values that make up the record. Keys are Strings, which match the table's field names. Values are all Strings.") + public Map getDisplayValues() + { + return this.wrapped.getDisplayValues(); + } + +} diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/QueryFilter.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/QueryFilter.java new file mode 100644 index 00000000..7e729418 --- /dev/null +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/QueryFilter.java @@ -0,0 +1,120 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components; + + +import java.util.List; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; +import com.kingsrook.qqq.backend.core.utils.CollectionUtils; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.ToSchema; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIDescription; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIExclude; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIListItems; + + +/*************************************************************************** + ** + ***************************************************************************/ +public class QueryFilter implements ToSchema +{ + @OpenAPIExclude() + private QQueryFilter wrapped; + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public QueryFilter(QQueryFilter wrapped) + { + this.wrapped = wrapped; + } + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public QueryFilter() + { + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Field level criteria that make up the query filter") + @OpenAPIListItems(value = FilterCriteria.class, useRef = true) + public List getCriteria() + { + return (CollectionUtils.nonNullList(this.wrapped.getCriteria()).stream().map(s -> new FilterCriteria(s)).toList()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("How the query's results should be ordered (sorted).") + @OpenAPIListItems(value = OrderBy.class, useRef = true) + public List getOrderBys() + { + return (CollectionUtils.nonNullList(this.wrapped.getOrderBys()).stream().map(s -> new OrderBy(s)).toList()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Boolean operator to apply between criteria") + public QQueryFilter.BooleanOperator getBooleanOperator() + { + return (this.wrapped.getBooleanOperator()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Records to skip (e.g., to implement pagination)") + public Integer getSkip() + { + return (this.wrapped.getSkip()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Maximum number of results to return.") + public Integer getLimit() + { + return (this.wrapped.getLimit()); + } + +} diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/QueryJoin.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/QueryJoin.java new file mode 100644 index 00000000..59f7ffe3 --- /dev/null +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/QueryJoin.java @@ -0,0 +1,125 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components; + + +import com.kingsrook.qqq.middleware.javalin.schemabuilder.ToSchema; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIDescription; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIExclude; + + +/*************************************************************************** + ** + ***************************************************************************/ +public class QueryJoin implements ToSchema +{ + @OpenAPIExclude() + private com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin wrapped; + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public QueryJoin(com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin wrapped) + { + this.wrapped = wrapped; + } + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public QueryJoin() + { + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Table being joined into the query by this QueryJoin") + public String getJoinTable() + { + return (this.wrapped.getJoinTable()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Base table (or an alias) that this QueryJoin is joined against") + public String getBaseTableOrAlias() + { + return (this.wrapped.getBaseTableOrAlias()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Name of a join to use in case the baseTable and joinTable have more than one") + public String getJoinName() + { + return (this.wrapped.getJoinMetaData() == null ? null : this.wrapped.getJoinMetaData().getName()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Alias to apply to this table in the join query") + public String getAlias() + { + return (this.wrapped.getAlias()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Whether or not to select values from the join table") + public Boolean getSelect() + { + return (this.wrapped.getSelect()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Type of join being performed (SQL semantics)") + public com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin.Type getType() + { + return (this.wrapped.getType()); + } + +} diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/TableMetaData.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/TableMetaData.java new file mode 100644 index 00000000..893afda5 --- /dev/null +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/TableMetaData.java @@ -0,0 +1,158 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components; + + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendTableMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.sharing.ShareableTableMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QSupplementalTableMetaData; +import com.kingsrook.qqq.backend.core.utils.CollectionUtils; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.ToSchema; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIDescription; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIIncludeProperties; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIListItems; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIMapValueType; + + +/*************************************************************************** + ** + ***************************************************************************/ +@OpenAPIIncludeProperties(ancestorClasses = TableMetaDataLight.class) +public class TableMetaData extends TableMetaDataLight implements ToSchema +{ + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public TableMetaData(QFrontendTableMetaData wrapped) + { + super(wrapped); + } + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public TableMetaData() + { + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Fields in this table") + @OpenAPIMapValueType(value = FieldMetaData.class, useRef = true) + public Map getFields() + { + return (CollectionUtils.nonNullMap(this.wrapped.getFields()).values().stream() + .collect(Collectors.toMap(f -> f.getName(), f -> new FieldMetaData(f)))); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Name of the primary key field in this table") + public String getPrimaryKeyField() + { + return (wrapped.getPrimaryKeyField()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Whether or not this table's backend uses variants.") + public Boolean getUsesVariants() + { + return (wrapped.getUsesVariants()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Sections to organize fields on screens for this record") + @OpenAPIListItems(value = TableSection.class, useRef = true) + public List getSections() + { + if(wrapped.getSections() == null) + { + return (null); + } + + return (wrapped.getSections().stream().map(s -> new TableSection(s)).toList()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Sections to organize fields on screens for this record") + @OpenAPIListItems(value = ExposedJoin.class, useRef = true) + public List getExposedJoins() + { + if(wrapped.getExposedJoins() == null) + { + return (null); + } + + return (wrapped.getExposedJoins().stream().map(s -> new ExposedJoin(s)).toList()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Additional meta data about the table, not necessarily known to QQQ.") + public Map getSupplementalMetaData() + { + return (wrapped.getSupplementalTableMetaData()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("For tables that support the sharing feature, meta-data about the sharing setup.") + @OpenAPIListItems(value = ShareableTableMetaData.class, useRef = false) + public ShareableTableMetaData getShareableTableMetaData() + { + return (wrapped.getShareableTableMetaData()); + } + +} diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/TableMetaDataLight.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/TableMetaDataLight.java index c9ea3a8a..e70df644 100644 --- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/TableMetaDataLight.java +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/TableMetaDataLight.java @@ -40,7 +40,7 @@ import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIMap public class TableMetaDataLight implements ToSchema { @OpenAPIExclude() - private QFrontendTableMetaData wrapped; + protected QFrontendTableMetaData wrapped; diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/TableSection.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/TableSection.java new file mode 100644 index 00000000..17d18917 --- /dev/null +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/TableSection.java @@ -0,0 +1,161 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components; + + +import java.util.List; +import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.ToSchema; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIDescription; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIExclude; + + +/*************************************************************************** + ** + ***************************************************************************/ +public class TableSection implements ToSchema +{ + @OpenAPIExclude() + private QFieldSection wrapped; + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public TableSection(QFieldSection section) + { + this.wrapped = section; + } + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public TableSection() + { + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Unique identifier for this section within this table") + public String getName() + { + return (this.wrapped.getName()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("User-facing label to display for this section") + public String getLabel() + { + return (this.wrapped.getLabel()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Importance of this section (T1, T2, or T3)") + public String getTier() + { + return (this.wrapped.getTier().name()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("List of field names to include in this section.") + public List getFieldNames() + { + return (this.wrapped.getFieldNames()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Name of a widget in this QQQ instance to include in this section (instead of fields).") + public String getWidgetName() + { + return (this.wrapped.getWidgetName()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Icon to display for the table") + public Icon getIcon() + { + return (this.wrapped.getIcon() == null ? null : new Icon(this.wrapped.getIcon())); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Whether or not to hide this section") + public Boolean isHidden() + { + return (this.wrapped.getIsHidden()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Layout suggestion, for how many columns of a 12-grid this section should use.") + public Integer getGridColumns() + { + return (this.wrapped.getGridColumns()); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @OpenAPIDescription("Help Contents for this section table.") // todo describe more + public List getHelpContents() + { + return (this.wrapped.getHelpContents()); + } + +} diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/TableVariant.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/TableVariant.java new file mode 100644 index 00000000..7ea932e6 --- /dev/null +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/TableVariant.java @@ -0,0 +1,141 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components; + + +import com.kingsrook.qqq.middleware.javalin.schemabuilder.ToSchema; +import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIDescription; + + +/*************************************************************************** + ** + ***************************************************************************/ +public class TableVariant implements ToSchema +{ + @OpenAPIDescription(""" + Specification for the variant type (variantTypeKey from QQQ BackendVariantsConfig)""") + private String type; + + @OpenAPIDescription(""" + Identifier for the variant option (record) being used.""") + private String id; + + @OpenAPIDescription(""" + A user-facing name (or label) for the variant option being used.""") + private String name; + + + + + /******************************************************************************* + ** Getter for type + *******************************************************************************/ + public String getType() + { + return (this.type); + } + + + + /******************************************************************************* + ** Setter for type + *******************************************************************************/ + public void setType(String type) + { + this.type = type; + } + + + + /******************************************************************************* + ** Fluent setter for type + *******************************************************************************/ + public TableVariant withType(String type) + { + this.type = type; + return (this); + } + + + + /******************************************************************************* + ** Getter for id + *******************************************************************************/ + public String getId() + { + return (this.id); + } + + + + /******************************************************************************* + ** Setter for id + *******************************************************************************/ + public void setId(String id) + { + this.id = id; + } + + + + /******************************************************************************* + ** Fluent setter for id + *******************************************************************************/ + public TableVariant withId(String id) + { + this.id = id; + return (this); + } + + + + /******************************************************************************* + ** Getter for name + *******************************************************************************/ + public String getName() + { + return (this.name); + } + + + + /******************************************************************************* + ** Setter for name + *******************************************************************************/ + public void setName(String name) + { + this.name = name; + } + + + + /******************************************************************************* + ** Fluent setter for name + *******************************************************************************/ + public TableVariant withName(String name) + { + this.name = name; + return (this); + } + + +} diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/utils/QuerySpecUtils.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/utils/QuerySpecUtils.java index d648ba54..eb2130ed 100644 --- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/utils/QuerySpecUtils.java +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/utils/QuerySpecUtils.java @@ -24,20 +24,19 @@ package com.kingsrook.qqq.middleware.javalin.specs.v1.utils; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Map; import com.kingsrook.qqq.backend.core.context.QContext; -import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator; -import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin; +import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData; import com.kingsrook.qqq.backend.core.utils.JsonUtils; -import com.kingsrook.qqq.backend.core.utils.StringUtils; -import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder; -import com.kingsrook.qqq.middleware.javalin.executors.io.QueryMiddlewareInput; +import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components.TableVariant; +import com.kingsrook.qqq.openapi.model.Content; +import com.kingsrook.qqq.openapi.model.RequestBody; import com.kingsrook.qqq.openapi.model.Schema; import com.kingsrook.qqq.openapi.model.Type; +import io.javalin.http.ContentType; import org.json.JSONArray; import org.json.JSONObject; @@ -51,99 +50,28 @@ public class QuerySpecUtils /*************************************************************************** ** ***************************************************************************/ - public static Schema defineQueryJoinsSchema() + public static RequestBody defineQueryOrCountRequestBody() { - Schema queryJoinsSchema = new Schema() - .withType(Type.ARRAY) - .withItems(new Schema() - .withProperties(MapBuilder.of( - "joinTable", new Schema() - .withType(Type.STRING), - "select", new Schema() - .withType(Type.BOOLEAN), - "type", new Schema() - .withType(Type.STRING) - .withEnumValues(Arrays.stream(QueryJoin.Type.values()).map(o -> o.name()).toList()), - "alias", new Schema() - .withType(Type.STRING), - "baseTableOrAlias", new Schema() - .withType(Type.STRING) - )) - ); - return queryJoinsSchema; - } - - - - /*************************************************************************** - ** - ***************************************************************************/ - public static Schema defineQQueryFilterSchema() - { - Schema qQueryFilterSchema = new Schema() - .withType(Type.OBJECT) - .withExample(List.of( - JsonUtils.toJson(new QQueryFilter(new QFilterCriteria("id", QCriteriaOperator.LESS_THAN, 5))) - )) - .withProperties(MapBuilder.of( - "criteria", new Schema() - .withType(Type.ARRAY) - .withItems(new Schema() - .withProperties(MapBuilder.of( - "fieldName", new Schema() - .withType(Type.STRING), - "operator", new Schema() - .withType(Type.STRING) - .withEnumValues(Arrays.stream(QCriteriaOperator.values()).map(o -> o.name()).toList()), - "values", new Schema() - .withType(Type.ARRAY) - .withItems(new Schema().withOneOf(List.of( - new Schema().withType(Type.INTEGER), - new Schema().withType(Type.STRING) - ))) - )) - ), - "orderBys", new Schema() - .withType(Type.ARRAY) - .withItems(new Schema() - .withProperties(MapBuilder.of( - "fieldName", new Schema() - .withType(Type.STRING), - "isAscending", new Schema() - .withType(Type.BOOLEAN))) - ), - "booleanOperator", new Schema().withType(Type.STRING).withEnumValues(Arrays.stream(QQueryFilter.BooleanOperator.values()).map(o -> o.name()).toList()), - "skip", new Schema().withType(Type.INTEGER), - "limit", new Schema().withType(Type.INTEGER) - // todo - subfilters?? - )); - return qQueryFilterSchema; - } - - - - /*************************************************************************** - ** - ***************************************************************************/ - public static Schema getQueryResponseSchema() - { - Schema schema = new Schema() - .withDescription("Records found by the query. May be empty.") - .withType(Type.OBJECT) - .withProperties(MapBuilder.of( - "records", new Schema() - .withType(Type.ARRAY) - .withItems(new Schema() + return new RequestBody() + .withContent(Map.of( + ContentType.APPLICATION_JSON.getMimeType(), new Content() + .withSchema(new Schema() .withType(Type.OBJECT) - .withProperties(MapBuilder.of( - "recordLabel", new Schema().withType(Type.STRING), - "tableName", new Schema().withType(Type.STRING), - "values", new Schema().withType(Type.OBJECT).withDescription("Keys for each field in the table"), - "displayValues", new Schema().withType(Type.OBJECT) + .withProperties(Map.of( + "filter", new Schema() + .withDescription("QueryFilter to specify matching records to be returned by the query") + .withRef("#/components/schemas/QueryFilter"), + "joins", new Schema() + .withDescription("QueryJoin objects to specify tables to be joined into the query") + .withType(Type.ARRAY) + .withItems(new Schema() + .withRef("#/components/schemas/QueryJoin")), + "tableVariant", new Schema() + .withDescription("For tables that use variant backends, specification of which variant to use.") + .withRef("#/components/schemas/TableVariant") )) ) )); - return schema; } @@ -151,58 +79,76 @@ public class QuerySpecUtils /*************************************************************************** ** ***************************************************************************/ - public static QueryMiddlewareInput buildInput(Map paramMap) throws IOException + public static QQueryFilter getFilterFromRequestBody(JSONObject requestBody) throws IOException { - - QQueryFilter filter = null; - String filterParam = paramMap.get("filter"); - if(StringUtils.hasContent(filterParam)) + if(requestBody.has("filter")) { - filter = JsonUtils.toObject(filterParam, QQueryFilter.class); - } - - List queryJoins = null; - String queryJoinsParam = paramMap.get("queryJoins"); - if(StringUtils.hasContent(queryJoinsParam)) - { - queryJoins = new ArrayList<>(); - - JSONArray queryJoinsJSON = new JSONArray(queryJoinsParam); - for(int i = 0; i < queryJoinsJSON.length(); i++) + Object filterFromJson = requestBody.get("filter"); + if(filterFromJson instanceof JSONObject filterJsonObject) { - QueryJoin queryJoin = new QueryJoin(); - queryJoins.add(queryJoin); - - JSONObject jsonObject = queryJoinsJSON.getJSONObject(i); - queryJoin.setJoinTable(jsonObject.optString("joinTable")); - - if(jsonObject.has("baseTableOrAlias") && !jsonObject.isNull("baseTableOrAlias")) - { - queryJoin.setBaseTableOrAlias(jsonObject.optString("baseTableOrAlias")); - } - - if(jsonObject.has("alias") && !jsonObject.isNull("alias")) - { - queryJoin.setAlias(jsonObject.optString("alias")); - } - - queryJoin.setSelect(jsonObject.optBoolean("select")); - - if(jsonObject.has("type") && !jsonObject.isNull("type")) - { - queryJoin.setType(QueryJoin.Type.valueOf(jsonObject.getString("type"))); - } - - if(jsonObject.has("joinName") && !jsonObject.isNull("joinName")) - { - queryJoin.setJoinMetaData(QContext.getQInstance().getJoin(jsonObject.getString("joinName"))); - } + return (JsonUtils.toObject(filterJsonObject.toString(), QQueryFilter.class)); } } - return new QueryMiddlewareInput() - .withTable(paramMap.get("table")) - .withFilter(filter) - .withQueryJoins(queryJoins); + return (null); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + public static List getJoinsFromRequestBody(JSONObject requestBody) throws IOException + { + if(requestBody.has("joins")) + { + Object joinsFromJson = requestBody.get("joins"); + if(joinsFromJson instanceof JSONArray joinsJsonArray) + { + List joins = new ArrayList<>(); + for(int i = 0; i < joinsJsonArray.length(); i++) + { + JSONObject joinJsonObject = joinsJsonArray.getJSONObject(i); + QJoinMetaData joinMetaData = null; + if(joinJsonObject.has("joinName")) + { + String joinName = joinJsonObject.getString("joinName"); + joinMetaData = QContext.getQInstance().getJoin(joinName); + joinJsonObject.remove("joinName"); + } + + QueryJoin queryJoin = JsonUtils.toObject(joinJsonObject.toString(), QueryJoin.class); + queryJoin.setJoinMetaData(joinMetaData); + + joins.add(queryJoin); + } + return (joins); + } + } + + return null; + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + public static TableVariant getTableVariantFromRequestBody(JSONObject requestBody) + { + if(requestBody.has("tableVariant")) + { + Object variantFromJson = requestBody.get("tableVariant"); + if(variantFromJson instanceof JSONObject variantJsonObject) + { + TableVariant tableVariant = new TableVariant(); + tableVariant.setType(variantJsonObject.optString("type")); + tableVariant.setId(variantJsonObject.optString("id")); + tableVariant.setName(variantJsonObject.optString("name")); + return (tableVariant); + } + } + + return null; } } diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/utils/TagsV1.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/utils/TagsV1.java index 93fbdb20..6f85fbcc 100644 --- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/utils/TagsV1.java +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/utils/TagsV1.java @@ -32,10 +32,7 @@ public enum TagsV1 implements TagsInterface { AUTHENTICATION("Authentication"), GENERAL("General"), - QUERY("Query"), - INSERT("Insert"), - UPDATE("Update"), - DELETE("Delete"), + TABLES("Tables"), PROCESSES("Processes"), REPORTS("Reports"), WIDGETS("Widgets"); diff --git a/qqq-middleware-javalin/src/main/resources/openapi/v1/openapi.yaml b/qqq-middleware-javalin/src/main/resources/openapi/v1/openapi.yaml index b0aebaf4..3f91da88 100644 --- a/qqq-middleware-javalin/src/main/resources/openapi/v1/openapi.yaml +++ b/qqq-middleware-javalin/src/main/resources/openapi/v1/openapi.yaml @@ -130,6 +130,42 @@ components: description: "Description of the error" type: "string" type: "object" + ExposedJoin: + properties: + isMany: + description: "Whether or not this join is 'to many' in nature" + type: "boolean" + joinPath: + description: "A list of joins that travel from the base table to the exposed\ + \ join table" + items: + properties: + joinOns: + type: "array" + leftTable: + type: "string" + name: + type: "string" + orderBys: + type: "array" + rightTable: + type: "string" + type: + enum: + - "ONE_TO_ONE" + - "ONE_TO_MANY" + - "MANY_TO_ONE" + - "MANY_TO_MANY" + type: "string" + type: "object" + type: "array" + joinTable: + $ref: "#/components/schemas/TableMetaData" + description: "The meta-data for the joined table" + label: + description: "User-facing label to display for this join" + type: "string" + type: "object" FieldAdornment: properties: type: @@ -193,6 +229,45 @@ components: description: "Data-type for this field" type: "string" type: "object" + FilterCriteria: + properties: + fieldName: + description: "Field name that this criteria applies to" + type: "string" + operator: + description: "Logical operator that applies to this criteria" + enum: + - "EQUALS" + - "NOT_EQUALS" + - "NOT_EQUALS_OR_IS_NULL" + - "IN" + - "NOT_IN" + - "IS_NULL_OR_IN" + - "LIKE" + - "NOT_LIKE" + - "STARTS_WITH" + - "ENDS_WITH" + - "CONTAINS" + - "NOT_STARTS_WITH" + - "NOT_ENDS_WITH" + - "NOT_CONTAINS" + - "LESS_THAN" + - "LESS_THAN_OR_EQUALS" + - "GREATER_THAN" + - "GREATER_THAN_OR_EQUALS" + - "IS_BLANK" + - "IS_NOT_BLANK" + - "BETWEEN" + - "NOT_BETWEEN" + - "TRUE" + - "FALSE" + type: "string" + values: + description: "Values to apply via the operator to the field" + items: + type: "string" + type: "array" + type: "object" FrontendComponent: properties: type: @@ -353,6 +428,34 @@ components: \ has permission to see that they exist)." type: "object" type: "object" + OrderBy: + properties: + fieldName: + description: "Field name that this order-by applies to" + type: "string" + isAscending: + description: "Whether this order-by is ascending or descending" + type: "boolean" + type: "object" + OutputRecord: + properties: + displayValues: + description: "Formatted string versions of the values that make up the record.\ + \ Keys are Strings, which match the table's field names. Values are all\ + \ Strings." + type: "object" + recordLabel: + description: "Label to identify the record to a user." + type: "string" + tableName: + description: "Name of the table that the record is from." + type: "string" + values: + description: "Raw values that make up the record. Keys are Strings, which\ + \ match the table's field names. Values can be any type, as per the table's\ + \ fields." + type: "object" + type: "object" ProcessMetaData: properties: frontendSteps: @@ -720,6 +823,157 @@ components: \ additional fields will be set." type: "string" type: "object" + QueryFilter: + properties: + booleanOperator: + description: "Boolean operator to apply between criteria" + enum: + - "AND" + - "OR" + type: "string" + criteria: + description: "Field level criteria that make up the query filter" + items: + $ref: "#/components/schemas/FilterCriteria" + type: "array" + limit: + description: "Maximum number of results to return." + type: "number" + orderBys: + description: "How the query's results should be ordered (sorted)." + items: + $ref: "#/components/schemas/OrderBy" + type: "array" + skip: + description: "Records to skip (e.g., to implement pagination)" + type: "number" + type: "object" + QueryJoin: + properties: + alias: + description: "Alias to apply to this table in the join query" + type: "string" + baseTableOrAlias: + description: "Base table (or an alias) that this QueryJoin is joined against" + type: "string" + joinName: + description: "Name of a join to use in case the baseTable and joinTable\ + \ have more than one" + type: "string" + joinTable: + description: "Table being joined into the query by this QueryJoin" + type: "string" + select: + description: "Whether or not to select values from the join table" + type: "boolean" + type: + description: "Type of join being performed (SQL semantics)" + enum: + - "INNER" + - "LEFT" + - "RIGHT" + - "FULL" + type: "string" + type: "object" + TableCountResponseV1: + properties: + count: + description: "Number (count) of records that satisfy the query request" + type: "number" + distinctCount: + description: "Number (count) of distinct records that satisfy the query\ + \ request. Only included if requested." + type: "number" + type: "object" + TableMetaData: + properties: + capabilities: + description: "List of strings describing actions that are supported by the\ + \ backend application for the table." + items: + type: "string" + type: "array" + deletePermission: + description: "Boolean to indicate if the user has delete permission for\ + \ the table." + type: "boolean" + editPermission: + description: "Boolean to indicate if the user has edit permission for the\ + \ table." + type: "boolean" + exposedJoins: + description: "Sections to organize fields on screens for this record" + items: + $ref: "#/components/schemas/ExposedJoin" + type: "array" + fields: + additionalProperties: + $ref: "#/components/schemas/FieldMetaData" + description: "Fields in this table" + type: "object" + helpContents: + description: "Help Contents for this table." + type: "object" + icon: + $ref: "#/components/schemas/Icon" + description: "Icon to display for the table" + insertPermission: + description: "Boolean to indicate if the user has insert permission for\ + \ the table." + type: "boolean" + isHidden: + description: "Boolean indicator of whether the table should be shown to\ + \ users or not" + type: "boolean" + label: + description: "User-facing name for this table" + type: "string" + name: + description: "Unique name for this table within the QQQ Instance" + type: "string" + primaryKeyField: + description: "Name of the primary key field in this table" + type: "string" + readPermission: + description: "Boolean to indicate if the user has read permission for the\ + \ table." + type: "boolean" + sections: + description: "Sections to organize fields on screens for this record" + items: + $ref: "#/components/schemas/TableSection" + type: "array" + shareableTableMetaData: + description: "For tables that support the sharing feature, meta-data about\ + \ the sharing setup." + properties: + assetIdFieldName: + type: "string" + audiencePossibleValueSourceName: + type: "string" + audienceTypes: + type: "object" + audienceTypesPossibleValueSourceName: + type: "string" + scopeFieldName: + type: "string" + sharedRecordTableName: + type: "string" + thisTableOwnerIdFieldName: + type: "string" + type: "object" + supplementalMetaData: + description: "Additional meta data about the table, not necessarily known\ + \ to QQQ." + type: "object" + usesVariants: + description: "Whether or not this table's backend uses variants." + type: "boolean" + variantTableLabel: + description: "If the table uses variants, this is the user-facing label\ + \ for the table that supplies variants for this table." + type: "string" + type: "object" TableMetaDataLight: properties: capabilities: @@ -765,6 +1019,157 @@ components: \ for the table that supplies variants for this table." type: "string" type: "object" + TableMetaDataResponseV1: + properties: + capabilities: + description: "List of strings describing actions that are supported by the\ + \ backend application for the table." + items: + type: "string" + type: "array" + deletePermission: + description: "Boolean to indicate if the user has delete permission for\ + \ the table." + type: "boolean" + editPermission: + description: "Boolean to indicate if the user has edit permission for the\ + \ table." + type: "boolean" + exposedJoins: + description: "Sections to organize fields on screens for this record" + items: + $ref: "#/components/schemas/ExposedJoin" + type: "array" + fields: + additionalProperties: + $ref: "#/components/schemas/FieldMetaData" + description: "Fields in this table" + type: "object" + helpContents: + description: "Help Contents for this table." + type: "object" + icon: + description: "Icon to display for the table" + properties: + color: + description: "A color code to use for displaying the icon" + type: "string" + name: + description: "A material UI icon name." + type: "string" + path: + description: "A path to an image file that can be requested from the\ + \ server, to serve as the icon image instead of a material UI icon." + type: "string" + type: "object" + insertPermission: + description: "Boolean to indicate if the user has insert permission for\ + \ the table." + type: "boolean" + isHidden: + description: "Boolean indicator of whether the table should be shown to\ + \ users or not" + type: "boolean" + label: + description: "User-facing name for this table" + type: "string" + name: + description: "Unique name for this table within the QQQ Instance" + type: "string" + primaryKeyField: + description: "Name of the primary key field in this table" + type: "string" + readPermission: + description: "Boolean to indicate if the user has read permission for the\ + \ table." + type: "boolean" + sections: + description: "Sections to organize fields on screens for this record" + items: + $ref: "#/components/schemas/TableSection" + type: "array" + shareableTableMetaData: + description: "For tables that support the sharing feature, meta-data about\ + \ the sharing setup." + properties: + assetIdFieldName: + type: "string" + audiencePossibleValueSourceName: + type: "string" + audienceTypes: + type: "object" + audienceTypesPossibleValueSourceName: + type: "string" + scopeFieldName: + type: "string" + sharedRecordTableName: + type: "string" + thisTableOwnerIdFieldName: + type: "string" + type: "object" + supplementalMetaData: + description: "Additional meta data about the table, not necessarily known\ + \ to QQQ." + type: "object" + usesVariants: + description: "Whether or not this table's backend uses variants." + type: "boolean" + variantTableLabel: + description: "If the table uses variants, this is the user-facing label\ + \ for the table that supplies variants for this table." + type: "string" + type: "object" + TableQueryResponseV1: + properties: + records: + description: "List of records that satisfy the query request" + items: + $ref: "#/components/schemas/OutputRecord" + type: "array" + type: "object" + TableSection: + properties: + fieldNames: + description: "List of field names to include in this section." + type: "array" + gridColumns: + description: "Layout suggestion, for how many columns of a 12-grid this\ + \ section should use." + type: "number" + helpContents: + description: "Help Contents for this section table." + type: "array" + icon: + $ref: "#/components/schemas/Icon" + description: "Icon to display for the table" + label: + description: "User-facing label to display for this section" + type: "string" + name: + description: "Unique identifier for this section within this table" + type: "string" + tier: + description: "Importance of this section (T1, T2, or T3)" + type: "string" + widgetName: + description: "Name of a widget in this QQQ instance to include in this section\ + \ (instead of fields)." + type: "string" + type: "object" + TableVariant: + properties: + id: + description: "Identifier for the variant option (record) being used." + type: "string" + name: + description: "A user-facing name (or label) for the variant option being\ + \ used." + type: "string" + type: + description: "Specification for the variant type (variantTypeKey from QQQ\ + \ BackendVariantsConfig)" + type: "string" + type: "object" WidgetBlock: properties: blockId: @@ -1532,6 +1937,151 @@ paths: summary: "Get instance metaData" tags: - "General" + /qqq/v1/metaData/table/{tableName}: + get: + description: "Load the full metadata for a single table, including all fields,\ + \ which a frontend\nneeds to display to users." + parameters: + - description: "Name of the table to load." + example: "person" + in: "path" + name: "tableName" + required: true + schema: + type: "string" + responses: + 200: + content: + application/json: + examples: + TODO: {} + schema: + $ref: "#/components/schemas/TableMetaDataResponseV1" + description: "The full table metadata" + security: + - sessionUuidCookie: + - "N/A" + summary: "Get table metaData" + tags: + - "Tables" + /qqq/v1/table/{tableName}/query: + post: + description: "Execute a query against a table, returning records that match\ + \ a filter." + parameters: + - description: "Name of the table to query." + example: "person" + in: "path" + name: "tableName" + required: true + schema: + type: "string" + requestBody: + content: + application/json: + schema: + properties: + tableVariant: + $ref: "#/components/schemas/TableVariant" + description: "For tables that use variant backends, specification\ + \ of which variant to use." + filter: + $ref: "#/components/schemas/QueryFilter" + description: "QueryFilter to specify matching records to be returned\ + \ by the query" + joins: + description: "QueryJoin objects to specify tables to be joined into\ + \ the query" + items: + $ref: "#/components/schemas/QueryJoin" + type: "array" + type: "object" + required: false + responses: + 200: + content: + application/json: + examples: + TODO: + value: + records: + - displayValues: + id: "17" + quantity: "1,000" + storeId: "QQQ-Mart" + recordLabel: "Item 17" + tableName: "item" + values: + id: 17 + quantity: 1000 + storeId: 42 + schema: + $ref: "#/components/schemas/TableQueryResponseV1" + description: "The records matching query" + security: + - sessionUuidCookie: + - "N/A" + summary: "Query for records from a table" + tags: + - "Tables" + /qqq/v1/table/{tableName}/count: + post: + description: "Execute a query against a table, returning the number of records\ + \ that match a filter." + parameters: + - description: "Name of the table to count." + example: "person" + in: "path" + name: "tableName" + required: true + schema: + type: "string" + - description: "Whether or not to also return the count distinct records from\ + \ the main table (e.g., in case of a to-many join; by default, do not)." + example: "true" + in: "query" + name: "includeDistinct" + required: true + schema: + type: "boolean" + requestBody: + content: + application/json: + schema: + properties: + tableVariant: + $ref: "#/components/schemas/TableVariant" + description: "For tables that use variant backends, specification\ + \ of which variant to use." + filter: + $ref: "#/components/schemas/QueryFilter" + description: "QueryFilter to specify matching records to be returned\ + \ by the query" + joins: + description: "QueryJoin objects to specify tables to be joined into\ + \ the query" + items: + $ref: "#/components/schemas/QueryJoin" + type: "array" + type: "object" + required: false + responses: + 200: + content: + application/json: + examples: + TODO: + value: + count: 42 + schema: + $ref: "#/components/schemas/TableCountResponseV1" + description: "The number (count) of records matching the query" + security: + - sessionUuidCookie: + - "N/A" + summary: "Count records in a table" + tags: + - "Tables" /qqq/v1/metaData/process/{processName}: get: description: "Load the full metadata for a single process, including all screens\ diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/TestUtils.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/TestUtils.java index 59ae8177..61e4cdaa 100644 --- a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/TestUtils.java +++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/TestUtils.java @@ -87,11 +87,13 @@ import com.kingsrook.qqq.backend.core.model.metadata.reporting.ReportType; import com.kingsrook.qqq.backend.core.model.metadata.tables.AssociatedScript; import com.kingsrook.qqq.backend.core.model.metadata.tables.Association; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.variants.BackendVariantsConfig; import com.kingsrook.qqq.backend.core.model.savedviews.SavedViewsMetaDataProvider; import com.kingsrook.qqq.backend.core.model.scripts.ScriptsMetaDataProvider; import com.kingsrook.qqq.backend.core.model.session.QSession; import com.kingsrook.qqq.backend.core.model.statusmessages.QWarningMessage; import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule; +import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryModuleBackendVariantSetting; import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackendStep; import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager; import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager; @@ -114,6 +116,10 @@ public class TestUtils public static final String TABLE_NAME_PERSON = "person"; public static final String TABLE_NAME_PET = "pet"; + public static final String MEMORY_BACKEND_WITH_VARIANTS_NAME = "memoryWithVariants"; + public static final String TABLE_NAME_MEMORY_VARIANT_OPTIONS = "memoryVariantOptions"; + public static final String TABLE_NAME_MEMORY_VARIANT_DATA = "memoryVariantData"; + public static final String PROCESS_NAME_GREET_PEOPLE_INTERACTIVE = "greetInteractive"; public static final String PROCESS_NAME_SIMPLE_SLEEP = "simpleSleep"; public static final String PROCESS_NAME_SIMPLE_THROW = "simpleThrow"; @@ -191,6 +197,8 @@ public class TestUtils qInstance.addPossibleValueSource(definePossibleValueSourcePerson()); defineWidgets(qInstance); + defineMemoryBackendVariantUseCases(qInstance); + List routeProviders = new ArrayList<>(); routeProviders.add(new JavalinRouteProviderMetaData() .withHostedPath("/statically-served") @@ -832,4 +840,39 @@ public class TestUtils ))); } } + + + + /*************************************************************************** + ** + ***************************************************************************/ + private static void defineMemoryBackendVariantUseCases(QInstance qInstance) + { + qInstance.addBackend(new QBackendMetaData() + .withName(MEMORY_BACKEND_WITH_VARIANTS_NAME) + .withBackendType(MemoryBackendModule.class) + .withUsesVariants(true) + .withBackendVariantsConfig(new BackendVariantsConfig() + .withVariantTypeKey(TABLE_NAME_MEMORY_VARIANT_OPTIONS) + .withOptionsTableName(TABLE_NAME_MEMORY_VARIANT_OPTIONS) + .withBackendSettingSourceFieldNameMap(Map.of(MemoryModuleBackendVariantSetting.PRIMARY_KEY, "id")) + )); + + qInstance.addTable(new QTableMetaData() + .withName(TABLE_NAME_MEMORY_VARIANT_DATA) + .withBackendName(MEMORY_BACKEND_WITH_VARIANTS_NAME) + .withPrimaryKeyField("id") + .withField(new QFieldMetaData("id", QFieldType.INTEGER)) + .withField(new QFieldMetaData("name", QFieldType.STRING)) + ); + + qInstance.addTable(new QTableMetaData() + .withName(TABLE_NAME_MEMORY_VARIANT_OPTIONS) + .withBackendName(BACKEND_NAME_MEMORY) // note, the version without variants! + .withPrimaryKeyField("id") + .withField(new QFieldMetaData("id", QFieldType.INTEGER)) + .withField(new QFieldMetaData("name", QFieldType.STRING)) + ); + } + } diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/SpecTestBase.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/SpecTestBase.java index a51e6b90..8425d3da 100644 --- a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/SpecTestBase.java +++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/SpecTestBase.java @@ -109,6 +109,8 @@ public abstract class SpecTestBase service = null; } + TestMiddlewareVersion testMiddlewareVersion = new TestMiddlewareVersion(); + if(service == null) { service = Javalin.create(config -> @@ -117,12 +119,12 @@ public abstract class SpecTestBase AbstractEndpointSpec spec = getSpec(); spec.setQInstance(qInstance); - config.router.apiBuilder(() -> spec.defineRoute(getVersion())); + config.router.apiBuilder(() -> spec.defineRoute(testMiddlewareVersion, getVersion())); for(AbstractEndpointSpec additionalSpec : getAdditionalSpecs()) { additionalSpec.setQInstance(qInstance); - config.router.apiBuilder(() -> additionalSpec.defineRoute(getVersion())); + config.router.apiBuilder(() -> additionalSpec.defineRoute(testMiddlewareVersion, getVersion())); } } ).start(PORT); @@ -133,6 +135,35 @@ public abstract class SpecTestBase + /*************************************************************************** + ** + ***************************************************************************/ + private static class TestMiddlewareVersion extends AbstractMiddlewareVersion + { + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public String getVersion() + { + return "test"; + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public List> getEndpointSpecs() + { + return List.of(); + } + } + + + /******************************************************************************* ** *******************************************************************************/ diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/TableCountSpecV1Test.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/TableCountSpecV1Test.java new file mode 100644 index 00000000..9e9bfef8 --- /dev/null +++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/TableCountSpecV1Test.java @@ -0,0 +1,237 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.specs.v1; + + +import java.util.List; +import java.util.Map; +import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; +import com.kingsrook.qqq.backend.core.context.QContext; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession; +import com.kingsrook.qqq.backend.core.utils.JsonUtils; +import com.kingsrook.qqq.backend.javalin.TestUtils; +import com.kingsrook.qqq.middleware.javalin.specs.AbstractEndpointSpec; +import com.kingsrook.qqq.middleware.javalin.specs.SpecTestBase; +import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components.TableVariant; +import io.javalin.http.ContentType; +import kong.unirest.HttpResponse; +import kong.unirest.Unirest; +import org.eclipse.jetty.http.HttpStatus; +import org.json.JSONObject; +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + + +/******************************************************************************* + ** Unit test for TableCountSpecV1 + *******************************************************************************/ +class TableCountSpecV1Test extends SpecTestBase +{ + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + protected AbstractEndpointSpec getSpec() + { + return new TableCountSpecV1(); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + protected String getVersion() + { + return "v1"; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void test() + { + HttpResponse response = Unirest.post(getBaseUrlAndPath() + "/table/person/count") + .contentType(ContentType.APPLICATION_JSON.getMimeType()) + .body(JsonUtils.toJson(Map.of("filter", new QQueryFilter(new QFilterCriteria("lastName", QCriteriaOperator.EQUALS, "Kelkhoff"))))) + .asString(); + + assertEquals(200, response.getStatus()); + JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody()); + assertEquals(2, jsonObject.getInt("count")); + assertFalse(jsonObject.has("distinctCount")); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testIncludingDistinct() + { + HttpResponse response = Unirest.post(getBaseUrlAndPath() + "/table/person/count?includeDistinct=true") + .contentType(ContentType.APPLICATION_JSON.getMimeType()) + .body(JsonUtils.toJson(Map.of("filter", new QQueryFilter(new QFilterCriteria("lastName", QCriteriaOperator.EQUALS, "Kelkhoff"))))) + .asString(); + + assertEquals(200, response.getStatus()); + JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody()); + assertEquals(2, jsonObject.getInt("count")); + assertEquals(2, jsonObject.getInt("distinctCount")); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testNoRecordsFound() + { + HttpResponse response = Unirest.post(getBaseUrlAndPath() + "/table/person/count") + .contentType(ContentType.APPLICATION_JSON.getMimeType()) + .body(JsonUtils.toJson(Map.of("filter", new QQueryFilter(new QFilterCriteria("lastName", QCriteriaOperator.EQUALS, "Unkelkhoff"))))) + .asString(); + + assertEquals(200, response.getStatus()); + JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody()); + assertEquals(0, jsonObject.getInt("count")); + } + + + + /******************************************************************************* + ** test the for a non-real name + ** + *******************************************************************************/ + @Test + public void testTableNotFound() + { + HttpResponse response = Unirest.post(getBaseUrlAndPath() + "/table/notAnActualTable/count").asString(); + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // expect a non-existing table to 403, the same as one that does exist but that you don't have permission to // + // to kinda hide from someone what is or isn't a real table (as a security thing i guess) // + /////////////////////////////////////////////////////////////////////////////////////////////////////////////// + assertEquals(HttpStatus.FORBIDDEN_403, response.getStatus()); + JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody()); + assertEquals(1, jsonObject.keySet().size(), "Number of top-level keys"); + String error = jsonObject.getString("error"); + assertThat(error).contains("Permission denied"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testPostingTableVariant() throws QException + { + //////////////////////////////////// + // insert our two variant options // + //////////////////////////////////// + QContext.init(TestUtils.defineInstance(), new QSystemUserSession()); + new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_MEMORY_VARIANT_OPTIONS).withRecords(List.of( + new QRecord().withValue("id", 1).withValue("name", "People"), + new QRecord().withValue("id", 2).withValue("name", "Planets") + ))); + + //////////////////////////////////////// + // insert some data into each variant // + //////////////////////////////////////// + QContext.getQSession().setBackendVariants(Map.of(TestUtils.TABLE_NAME_MEMORY_VARIANT_OPTIONS, 1)); + new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_MEMORY_VARIANT_DATA).withRecords(List.of( + new QRecord().withValue("name", "Tom"), + new QRecord().withValue("name", "Sally") + ))); + + QContext.getQSession().setBackendVariants(Map.of(TestUtils.TABLE_NAME_MEMORY_VARIANT_OPTIONS, 2)); + new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_MEMORY_VARIANT_DATA).withRecords(List.of( + new QRecord().withValue("name", "Mars"), + new QRecord().withValue("name", "Jupiter"), + new QRecord().withValue("name", "Saturn") + ))); + + ////////////////////////////////////////// + // count with no variant - expect error // + ////////////////////////////////////////// + String url = getBaseUrlAndPath() + "/table/" + TestUtils.TABLE_NAME_MEMORY_VARIANT_DATA + "/count"; + HttpResponse response = Unirest.post(url) + .contentType(ContentType.APPLICATION_JSON.getMimeType()) + .body(JsonUtils.toJson(Map.of())) + .asString(); + assertEquals(500, response.getStatus()); + JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody()); + assertEquals("Could not find Backend Variant information in session under key 'memoryVariantOptions' for Backend 'memoryWithVariants'", jsonObject.getString("error")); + + ////////////////////////////// + // count for people variant // + ////////////////////////////// + response = Unirest.post(url) + .contentType(ContentType.APPLICATION_JSON.getMimeType()) + .body(JsonUtils.toJson(Map.of("tableVariant", new TableVariant().withType("memoryVariantOptions").withId("1")))) + .asString(); + + assertEquals(200, response.getStatus()); + jsonObject = JsonUtils.toJSONObject(response.getBody()); + assertEquals(2, jsonObject.getInt("count")); + + /////////////////////////////// + // count for planets variant // + /////////////////////////////// + response = Unirest.post(url) + .contentType(ContentType.APPLICATION_JSON.getMimeType()) + .body(JsonUtils.toJson(Map.of("tableVariant", new TableVariant().withType("memoryVariantOptions").withId("2")))) + .asString(); + + assertEquals(200, response.getStatus()); + jsonObject = JsonUtils.toJSONObject(response.getBody()); + assertEquals(3, jsonObject.getInt("count")); + + /////////////////////////////////////////////// + // count with unknown variant - expect error // + /////////////////////////////////////////////// + response = Unirest.post(url) + .contentType(ContentType.APPLICATION_JSON.getMimeType()) + .body(JsonUtils.toJson(Map.of("tableVariant", new TableVariant().withType("memoryVariantOptions").withId("3")))) + .asString(); + assertEquals(500, response.getStatus()); + jsonObject = JsonUtils.toJSONObject(response.getBody()); + assertEquals("Could not find Backend Variant in table memoryVariantOptions with id '3'", jsonObject.getString("error")); + } + +} \ No newline at end of file diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/TableMetaDataSpecV1Test.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/TableMetaDataSpecV1Test.java new file mode 100644 index 00000000..fa1d4e44 --- /dev/null +++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/TableMetaDataSpecV1Test.java @@ -0,0 +1,102 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.specs.v1; + + +import com.kingsrook.qqq.backend.core.utils.JsonUtils; +import com.kingsrook.qqq.middleware.javalin.specs.AbstractEndpointSpec; +import com.kingsrook.qqq.middleware.javalin.specs.SpecTestBase; +import kong.unirest.HttpResponse; +import kong.unirest.Unirest; +import org.eclipse.jetty.http.HttpStatus; +import org.json.JSONObject; +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + + +/******************************************************************************* + ** Unit test for TableMetaDataSpecV1 + *******************************************************************************/ +class TableMetaDataSpecV1Test extends SpecTestBase +{ + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + protected AbstractEndpointSpec getSpec() + { + return new TableMetaDataSpecV1(); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + protected String getVersion() + { + return "v1"; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void test() + { + HttpResponse response = Unirest.get(getBaseUrlAndPath() + "/metaData/table/person").asString(); + + assertEquals(200, response.getStatus()); + JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody()); + assertEquals("person", jsonObject.getString("name")); + assertEquals("Person", jsonObject.getString("label")); + + JSONObject fields = jsonObject.getJSONObject("fields"); + JSONObject firstNameField = fields.getJSONObject("firstName"); + assertEquals("firstName", firstNameField.getString("name")); + assertEquals("First Name", firstNameField.getString("label")); + } + + + + /******************************************************************************* + ** test the table-level meta-data endpoint for a non-real name + ** + *******************************************************************************/ + @Test + public void testNotFound() + { + HttpResponse response = Unirest.get(getBaseUrlAndPath() + "/metaData/table/notAnActualTable").asString(); + + assertEquals(HttpStatus.NOT_FOUND_404, response.getStatus()); + JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody()); + assertEquals(1, jsonObject.keySet().size(), "Number of top-level keys"); + String error = jsonObject.getString("error"); + assertThat(error).contains("Table").contains("notAnActualTable").contains("not found"); + } + +} \ No newline at end of file diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/TableQuerySpecV1Test.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/TableQuerySpecV1Test.java new file mode 100644 index 00000000..07b120fb --- /dev/null +++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/TableQuerySpecV1Test.java @@ -0,0 +1,232 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin.specs.v1; + + +import java.util.List; +import java.util.Map; +import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; +import com.kingsrook.qqq.backend.core.context.QContext; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession; +import com.kingsrook.qqq.backend.core.utils.JsonUtils; +import com.kingsrook.qqq.backend.javalin.TestUtils; +import com.kingsrook.qqq.middleware.javalin.specs.AbstractEndpointSpec; +import com.kingsrook.qqq.middleware.javalin.specs.SpecTestBase; +import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components.TableVariant; +import io.javalin.http.ContentType; +import kong.unirest.HttpResponse; +import kong.unirest.Unirest; +import org.eclipse.jetty.http.HttpStatus; +import org.json.JSONArray; +import org.json.JSONObject; +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + + +/******************************************************************************* + ** Unit test for TableQuerySpecV1 + *******************************************************************************/ +class TableQuerySpecV1Test extends SpecTestBase +{ + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + protected AbstractEndpointSpec getSpec() + { + return new TableQuerySpecV1(); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + protected String getVersion() + { + return "v1"; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void test() + { + HttpResponse response = Unirest.post(getBaseUrlAndPath() + "/table/person/query") + .contentType(ContentType.APPLICATION_JSON.getMimeType()) + .body(JsonUtils.toJson(Map.of("filter", new QQueryFilter(new QFilterCriteria("lastName", QCriteriaOperator.EQUALS, "Kelkhoff"))))) + .asString(); + + assertEquals(200, response.getStatus()); + JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody()); + JSONArray records = jsonObject.getJSONArray("records"); + assertThat(records.length()).isGreaterThanOrEqualTo(1); + + JSONObject record = records.getJSONObject(0); + assertThat(record.getString("recordLabel")).contains("Kelkhoff"); + assertThat(record.getString("tableName")).isEqualTo("person"); + assertThat(record.getJSONObject("values").getString("lastName")).isEqualTo("Kelkhoff"); + assertThat(record.getJSONObject("displayValues").getString("lastName")).isEqualTo("Kelkhoff"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testNoRecordsFound() + { + HttpResponse response = Unirest.post(getBaseUrlAndPath() + "/table/person/query") + .contentType(ContentType.APPLICATION_JSON.getMimeType()) + .body(JsonUtils.toJson(Map.of("filter", new QQueryFilter(new QFilterCriteria("lastName", QCriteriaOperator.EQUALS, "Unkelkhoff"))))) + .asString(); + + assertEquals(200, response.getStatus()); + JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody()); + JSONArray records = jsonObject.getJSONArray("records"); + assertThat(records.length()).isZero(); + } + + + + /******************************************************************************* + ** test the table-level meta-data endpoint for a non-real name + ** + *******************************************************************************/ + @Test + public void testTableNotFound() + { + HttpResponse response = Unirest.post(getBaseUrlAndPath() + "/table/notAnActualTable/query").asString(); + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // expect a non-existing table to 403, the same as one that does exist but that you don't have permission to // + // to kinda hide from someone what is or isn't a real table (as a security thing i guess) // + /////////////////////////////////////////////////////////////////////////////////////////////////////////////// + assertEquals(HttpStatus.FORBIDDEN_403, response.getStatus()); + JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody()); + assertEquals(1, jsonObject.keySet().size(), "Number of top-level keys"); + String error = jsonObject.getString("error"); + assertThat(error).contains("Permission denied"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testPostingTableVariant() throws QException + { + //////////////////////////////////// + // insert our two variant options // + //////////////////////////////////// + QContext.init(TestUtils.defineInstance(), new QSystemUserSession()); + new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_MEMORY_VARIANT_OPTIONS).withRecords(List.of( + new QRecord().withValue("id", 1).withValue("name", "People"), + new QRecord().withValue("id", 2).withValue("name", "Planets") + ))); + + //////////////////////////////////////// + // insert some data into each variant // + //////////////////////////////////////// + QContext.getQSession().setBackendVariants(Map.of(TestUtils.TABLE_NAME_MEMORY_VARIANT_OPTIONS, 1)); + new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_MEMORY_VARIANT_DATA).withRecords(List.of( + new QRecord().withValue("name", "Tom"), + new QRecord().withValue("name", "Sally") + ))); + + QContext.getQSession().setBackendVariants(Map.of(TestUtils.TABLE_NAME_MEMORY_VARIANT_OPTIONS, 2)); + new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_MEMORY_VARIANT_DATA).withRecords(List.of( + new QRecord().withValue("name", "Mars"), + new QRecord().withValue("name", "Jupiter"), + new QRecord().withValue("name", "Saturn") + ))); + + ////////////////////////////////////////// + // query with no variant - expect error // + ////////////////////////////////////////// + String url = getBaseUrlAndPath() + "/table/" + TestUtils.TABLE_NAME_MEMORY_VARIANT_DATA + "/query"; + HttpResponse response = Unirest.post(url) + .contentType(ContentType.APPLICATION_JSON.getMimeType()) + .body(JsonUtils.toJson(Map.of())) + .asString(); + assertEquals(500, response.getStatus()); + JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody()); + assertEquals("Could not find Backend Variant information in session under key 'memoryVariantOptions' for Backend 'memoryWithVariants'", jsonObject.getString("error")); + + ////////////////////////////// + // query for people variant // + ////////////////////////////// + response = Unirest.post(url) + .contentType(ContentType.APPLICATION_JSON.getMimeType()) + .body(JsonUtils.toJson(Map.of("tableVariant", new TableVariant().withType("memoryVariantOptions").withId("1")))) + .asString(); + + assertEquals(200, response.getStatus()); + jsonObject = JsonUtils.toJSONObject(response.getBody()); + JSONArray records = jsonObject.getJSONArray("records"); + assertThat(records.length()).isEqualTo(2); + JSONObject record = records.getJSONObject(0); + assertThat(record.getJSONObject("values").getString("name")).isEqualTo("Tom"); + + /////////////////////////////// + // query for planets variant // + /////////////////////////////// + response = Unirest.post(url) + .contentType(ContentType.APPLICATION_JSON.getMimeType()) + .body(JsonUtils.toJson(Map.of("tableVariant", new TableVariant().withType("memoryVariantOptions").withId("2")))) + .asString(); + + assertEquals(200, response.getStatus()); + jsonObject = JsonUtils.toJSONObject(response.getBody()); + records = jsonObject.getJSONArray("records"); + assertThat(records.length()).isEqualTo(3); + record = records.getJSONObject(0); + assertThat(record.getJSONObject("values").getString("name")).isEqualTo("Mars"); + + /////////////////////////////////////////////// + // query with unknown variant - expect error // + /////////////////////////////////////////////// + response = Unirest.post(url) + .contentType(ContentType.APPLICATION_JSON.getMimeType()) + .body(JsonUtils.toJson(Map.of("tableVariant", new TableVariant().withType("memoryVariantOptions").withId("3")))) + .asString(); + assertEquals(500, response.getStatus()); + jsonObject = JsonUtils.toJSONObject(response.getBody()); + assertEquals("Could not find Backend Variant in table memoryVariantOptions with id '3'", jsonObject.getString("error")); + + } + +} \ No newline at end of file