initial build of table meta-data, query, and count specs, IO, executors

This commit is contained in:
2025-05-27 11:29:24 -05:00
parent 83684d8f2e
commit 78eb315558
35 changed files with 3872 additions and 148 deletions

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<TableCountInput, TableCountOutputInterface>
{
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));
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<TableMetaDataInput, TableMetaDataOutputInterface>
{
/***************************************************************************
**
***************************************************************************/
@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());
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<TableQueryInput, TableQueryOutputInterface>
{
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));
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.middleware.javalin.executors.io;
/*******************************************************************************
**
*******************************************************************************/
public interface TableCountOutputInterface extends AbstractMiddlewareOutputInterface
{
/***************************************************************************
**
***************************************************************************/
void setCount(Long count);
/***************************************************************************
**
***************************************************************************/
void setDistinctCount(Long count);
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.middleware.javalin.executors.io;
/*******************************************************************************
**
*******************************************************************************/
public class TableQueryInput extends TableQueryOrCountInput
{
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<QueryJoin> 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<QueryJoin> getJoins()
{
return (this.joins);
}
/*******************************************************************************
** Setter for joins
*******************************************************************************/
public void setJoins(List<QueryJoin> joins)
{
this.joins = joins;
}
/*******************************************************************************
** Fluent setter for joins
*******************************************************************************/
public TableQueryOrCountInput withJoins(List<QueryJoin> 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);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<QRecord> records);
}

View File

@ -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());

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<TableCountInput, TableCountResponseV1, TableCountExecutor>
{
/***************************************************************************
**
***************************************************************************/
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<Parameter> 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<String, Schema> defineComponentSchemas()
{
return Map.of(TableCountResponseV1.class.getSimpleName(), new TableCountResponseV1().toSchema());
}
/***************************************************************************
**
***************************************************************************/
@Override
public BasicResponse defineBasicSuccessResponse()
{
Map<String, Example> 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
);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<TableMetaDataInput, TableMetaDataResponseV1, TableMetaDataExecutor>
{
/***************************************************************************
**
***************************************************************************/
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<Parameter> 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<String, Schema> defineComponentSchemas()
{
return Map.of(TableMetaDataResponseV1.class.getSimpleName(), new TableMetaDataResponseV1().toSchema());
}
/***************************************************************************
**
***************************************************************************/
@Override
public BasicResponse defineBasicSuccessResponse()
{
QFrontendTableMetaData frontendTableMetaData = null; // todo
Map<String, Example> 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()));
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<TableQueryInput, TableQueryResponseV1, TableQueryExecutor>
{
/***************************************************************************
**
***************************************************************************/
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<Parameter> 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<String, Schema> defineComponentSchemas()
{
return Map.of(TableQueryResponseV1.class.getSimpleName(), new TableQueryResponseV1().toSchema());
}
/***************************************************************************
**
***************************************************************************/
@Override
public BasicResponse defineBasicSuccessResponse()
{
Map<String, Example> 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);
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<OutputRecord> records;
/*******************************************************************************
** Setter for records
*******************************************************************************/
@Override
public void setRecords(List<QRecord> 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<QRecord> records)
{
setRecords(records);
return this;
}
/*******************************************************************************
** Getter for records
*******************************************************************************/
public List<OutputRecord> getRecords()
{
return (this.records);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<QJoinMetaData> getJoinPath()
{
return (this.wrapped.getJoinPath());
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String> getValues()
{
return (CollectionUtils.nonNullList(this.wrapped.getValues()).stream().map(String::valueOf).collect(Collectors.toList()));
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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());
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String, Serializable> 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<String, String> getDisplayValues()
{
return this.wrapped.getDisplayValues();
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<FilterCriteria> 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<OrderBy> 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());
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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());
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String, FieldMetaData> 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<TableSection> 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<ExposedJoin> 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<String, QSupplementalTableMetaData> 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());
}
}

View File

@ -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;

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String> 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<QHelpContent> getHelpContents()
{
return (this.wrapped.getHelpContents());
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -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<String, String> 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<QueryJoin> 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<QueryJoin> getJoinsFromRequestBody(JSONObject requestBody) throws IOException
{
if(requestBody.has("joins"))
{
Object joinsFromJson = requestBody.get("joins");
if(joinsFromJson instanceof JSONArray joinsJsonArray)
{
List<QueryJoin> 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;
}
}

View File

@ -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");

View File

@ -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\

View File

@ -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<JavalinRouteProviderMetaData> 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))
);
}
}

View File

@ -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<AbstractEndpointSpec<?, ?, ?>> getEndpointSpecs()
{
return List.of();
}
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String> 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<String> 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<String> 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<String> 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<String> 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"));
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String> 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<String> 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");
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String> 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<String> 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<String> 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<String> 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"));
}
}