mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-21 14:38:43 +00:00
Add LIKE criteria operator; more api endpoints to understand versions, get swagger json; more field name mapping
This commit is contained in:
@ -63,6 +63,9 @@ import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
@ -106,7 +109,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
||||
)
|
||||
.withServers(ListBuilder.of(new Server()
|
||||
.withDescription("Localhost development")
|
||||
.withUrl("http://localhost:8000/api")
|
||||
.withUrl("http://localhost:8000/api/" + version)
|
||||
));
|
||||
|
||||
openAPI.setTags(new ArrayList<>());
|
||||
@ -163,6 +166,12 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
||||
{
|
||||
String tableName = table.getName();
|
||||
|
||||
if(input.getTableName() != null && !input.getTableName().equals(tableName))
|
||||
{
|
||||
LOG.debug("Omitting table [" + tableName + "] because it is not the requested table [" + input.getTableName() + "]");
|
||||
continue;
|
||||
}
|
||||
|
||||
if(table.getIsHidden())
|
||||
{
|
||||
LOG.debug("Omitting table [" + tableName + "] because it is marked as hidden");
|
||||
@ -209,11 +218,9 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
||||
String primaryKeyName = table.getPrimaryKeyField();
|
||||
QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField());
|
||||
String primaryKeyLabel = primaryKeyField.getLabel();
|
||||
String primaryKeyApiName = ApiFieldMetaData.getEffectiveApiFieldName(primaryKeyField);
|
||||
List<QFieldMetaData> tableApiFields = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(version)).getFields();
|
||||
|
||||
ApiFieldMetaData apiFieldMetaData = ApiFieldMetaData.of(primaryKeyField);
|
||||
String primaryKeyApiName = (apiFieldMetaData != null && StringUtils.hasContent(apiFieldMetaData.getApiFieldName())) ? apiFieldMetaData.getApiFieldName() : primaryKeyName;
|
||||
|
||||
String tableReadPermissionName = PermissionsHelper.getTablePermissionName(tableName, TablePermissionSubType.READ);
|
||||
if(StringUtils.hasContent(tableReadPermissionName))
|
||||
{
|
||||
@ -257,18 +264,40 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
||||
.withType("object")
|
||||
.withProperties(tableFieldsWithoutPrimaryKey));
|
||||
|
||||
for(QFieldMetaData tableApiField : tableApiFields)
|
||||
for(QFieldMetaData field : tableApiFields)
|
||||
{
|
||||
if(primaryKeyName.equals(tableApiField.getName()))
|
||||
if(primaryKeyName.equals(field.getName()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
tableFieldsWithoutPrimaryKey.put(tableApiField.getName(), new Schema()
|
||||
.withType(getFieldType(table.getField(tableApiField.getName())))
|
||||
.withFormat(getFieldFormat(table.getField(tableApiField.getName())))
|
||||
.withDescription(tableApiField.getLabel() + " for the " + tableLabel + ".")
|
||||
);
|
||||
String apiFieldName = ApiFieldMetaData.getEffectiveApiFieldName(field);
|
||||
|
||||
Schema fieldSchema = new Schema()
|
||||
.withType(getFieldType(table.getField(field.getName())))
|
||||
.withFormat(getFieldFormat(table.getField(field.getName())))
|
||||
.withDescription(field.getLabel() + " for the " + tableLabel + ".");
|
||||
|
||||
if(StringUtils.hasContent(field.getPossibleValueSourceName()))
|
||||
{
|
||||
QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(field.getPossibleValueSourceName());
|
||||
if(QPossibleValueSourceType.ENUM.equals(possibleValueSource.getType()))
|
||||
{
|
||||
List<String> enumValues = new ArrayList<>();
|
||||
for(QPossibleValue<?> enumValue : possibleValueSource.getEnumValues())
|
||||
{
|
||||
enumValues.add(enumValue.getId() + "=" + enumValue.getLabel());
|
||||
}
|
||||
fieldSchema.setEnumValues(enumValues);
|
||||
}
|
||||
else if(QPossibleValueSourceType.TABLE.equals(possibleValueSource.getType()))
|
||||
{
|
||||
QTableMetaData sourceTable = qInstance.getTable(possibleValueSource.getTableName());
|
||||
fieldSchema.setDescription(fieldSchema.getDescription() + " Values in this field come from the primary key of the " + sourceTable.getLabel() + " table");
|
||||
}
|
||||
}
|
||||
|
||||
tableFieldsWithoutPrimaryKey.put(apiFieldName, fieldSchema);
|
||||
}
|
||||
|
||||
componentSchemas.put(tableApiName, new Schema()
|
||||
|
@ -67,12 +67,7 @@ public class QRecordApiAdapter
|
||||
|
||||
// todo - what about display values / possible values?
|
||||
|
||||
String apiFieldName = apiFieldMetaData.getApiFieldName();
|
||||
if(!StringUtils.hasContent(apiFieldName))
|
||||
{
|
||||
apiFieldName = field.getName();
|
||||
}
|
||||
|
||||
String apiFieldName = ApiFieldMetaData.getEffectiveApiFieldName(field);
|
||||
if(StringUtils.hasContent(apiFieldMetaData.getReplacedByFieldName()))
|
||||
{
|
||||
outputRecord.put(apiFieldName, record.getValue(apiFieldMetaData.getReplacedByFieldName()));
|
||||
@ -149,16 +144,7 @@ public class QRecordApiAdapter
|
||||
Pair<String, String> key = new Pair<>(tableName, apiVersion);
|
||||
if(!fieldMapCache.containsKey(key))
|
||||
{
|
||||
Map<String, QFieldMetaData> map = getTableApiFieldList(tableName, apiVersion).stream().collect(Collectors.toMap(f ->
|
||||
{
|
||||
ApiFieldMetaData apiFieldMetaData = ApiFieldMetaData.of(f);
|
||||
String apiFieldName = apiFieldMetaData.getApiFieldName();
|
||||
if(!StringUtils.hasContent(apiFieldName))
|
||||
{
|
||||
apiFieldName = f.getName();
|
||||
}
|
||||
return (apiFieldName);
|
||||
}, f -> f));
|
||||
Map<String, QFieldMetaData> map = getTableApiFieldList(tableName, apiVersion).stream().collect(Collectors.toMap(f -> (ApiFieldMetaData.getEffectiveApiFieldName(f)), f -> f));
|
||||
fieldMapCache.put(key, map);
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.api.actions.GenerateOpenApiSpecAction;
|
||||
import com.kingsrook.qqq.api.actions.QRecordApiAdapter;
|
||||
import com.kingsrook.qqq.api.model.APIVersion;
|
||||
@ -129,10 +130,14 @@ public class QJavalinApiHandler
|
||||
{
|
||||
ApiBuilder.path("/api/{version}", () -> // todo - configurable, that /api/ bit?
|
||||
{
|
||||
ApiBuilder.get("/openapi.yaml", QJavalinApiHandler::doSpec);
|
||||
ApiBuilder.get("/openapi.yaml", QJavalinApiHandler::doSpecYaml);
|
||||
ApiBuilder.get("/openapi.json", QJavalinApiHandler::doSpecJson);
|
||||
|
||||
ApiBuilder.path("/{tableName}", () ->
|
||||
{
|
||||
ApiBuilder.get("/openapi.yaml", QJavalinApiHandler::doSpecYaml);
|
||||
ApiBuilder.get("/openapi.json", QJavalinApiHandler::doSpecJson);
|
||||
|
||||
ApiBuilder.post("/", QJavalinApiHandler::doInsert);
|
||||
|
||||
ApiBuilder.get("/query", QJavalinApiHandler::doQuery);
|
||||
@ -151,6 +156,10 @@ public class QJavalinApiHandler
|
||||
});
|
||||
});
|
||||
|
||||
ApiBuilder.get("/api/versions.json", QJavalinApiHandler::doVersions);
|
||||
|
||||
ApiBuilder.before("/*", QJavalinApiHandler::setupCORS);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// default all other /api/ requests (for the methods we support) to a standard 404 response //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -163,6 +172,45 @@ public class QJavalinApiHandler
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void doVersions(Context context)
|
||||
{
|
||||
ApiInstanceMetaData apiInstanceMetaData = ApiInstanceMetaData.of(qInstance);
|
||||
|
||||
Map<String, Object> rs = new HashMap<>();
|
||||
rs.put("supportedVersions", apiInstanceMetaData.getSupportedVersions().stream().map(String::valueOf).collect(Collectors.toList()));
|
||||
rs.put("currentVersion", apiInstanceMetaData.getCurrentVersion().toString());
|
||||
|
||||
context.contentType(ContentType.APPLICATION_JSON);
|
||||
context.result(JsonUtils.toJson(rs));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void setupCORS(Context context)
|
||||
{
|
||||
if(StringUtils.hasContent(context.header("Origin")))
|
||||
{
|
||||
context.res().setHeader("Access-Control-Allow-Origin", context.header("Origin"));
|
||||
context.res().setHeader("Vary", "Origin");
|
||||
}
|
||||
else
|
||||
{
|
||||
context.res().setHeader("Access-Control-Allow-Origin", "*");
|
||||
}
|
||||
|
||||
context.header("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, PATCH, OPTIONS");
|
||||
context.header("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Authorization, Accept, content-type, authorization, accept");
|
||||
context.header("Access-Control-Allow-Credentials", "true");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -176,7 +224,7 @@ public class QJavalinApiHandler
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void doSpec(Context context)
|
||||
private static void doSpecYaml(Context context)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -194,6 +242,34 @@ public class QJavalinApiHandler
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void doSpecJson(Context context)
|
||||
{
|
||||
try
|
||||
{
|
||||
QContext.init(qInstance, null);
|
||||
String version = context.pathParam("version");
|
||||
GenerateOpenApiSpecInput input = new GenerateOpenApiSpecInput().withVersion(version);
|
||||
|
||||
if(context.pathParam("tableName") != null)
|
||||
{
|
||||
input.setTableName(context.pathParam("tableName"));
|
||||
}
|
||||
|
||||
GenerateOpenApiSpecOutput output = new GenerateOpenApiSpecAction().execute(input);
|
||||
context.contentType(ContentType.JSON);
|
||||
context.result(output.getJson());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
handleException(context, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -576,22 +652,20 @@ public class QJavalinApiHandler
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// order of these is important (e.g., because some are a sub-string of others!!) //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
EQ("=", QCriteriaOperator.EQUALS, QCriteriaOperator.NOT_EQUALS, true, 1),
|
||||
LTE("<=", QCriteriaOperator.LESS_THAN_OR_EQUALS, QCriteriaOperator.GREATER_THAN, false, 1),
|
||||
GTE(">=", QCriteriaOperator.GREATER_THAN_OR_EQUALS, QCriteriaOperator.LESS_THAN, false, 1),
|
||||
LT("<", QCriteriaOperator.LESS_THAN, QCriteriaOperator.GREATER_THAN_OR_EQUALS, false, 1),
|
||||
GT(">", QCriteriaOperator.GREATER_THAN, QCriteriaOperator.LESS_THAN_OR_EQUALS, false, 1),
|
||||
EMPTY("EMPTY", QCriteriaOperator.IS_BLANK, QCriteriaOperator.IS_NOT_BLANK, true, 0),
|
||||
BETWEEN("BETWEEN ", QCriteriaOperator.BETWEEN, QCriteriaOperator.NOT_BETWEEN, true, 2),
|
||||
IN("IN ", QCriteriaOperator.IN, QCriteriaOperator.NOT_IN, true, null),
|
||||
// todo MATCHES
|
||||
;
|
||||
EQ("=", QCriteriaOperator.EQUALS, QCriteriaOperator.NOT_EQUALS, 1),
|
||||
LTE("<=", QCriteriaOperator.LESS_THAN_OR_EQUALS, null, 1),
|
||||
GTE(">=", QCriteriaOperator.GREATER_THAN_OR_EQUALS, null, 1),
|
||||
LT("<", QCriteriaOperator.LESS_THAN, null, 1),
|
||||
GT(">", QCriteriaOperator.GREATER_THAN, null, 1),
|
||||
EMPTY("EMPTY", QCriteriaOperator.IS_BLANK, QCriteriaOperator.IS_NOT_BLANK, 0),
|
||||
BETWEEN("BETWEEN ", QCriteriaOperator.BETWEEN, QCriteriaOperator.NOT_BETWEEN, 2),
|
||||
IN("IN ", QCriteriaOperator.IN, QCriteriaOperator.NOT_IN, null),
|
||||
LIKE("LIKE ", QCriteriaOperator.LIKE, QCriteriaOperator.NOT_LIKE, 1);
|
||||
|
||||
|
||||
private final String prefix;
|
||||
private final QCriteriaOperator positiveOperator;
|
||||
private final QCriteriaOperator negativeOperator;
|
||||
private final boolean supportsNot;
|
||||
private final Integer noOfValues; // null means many (IN)
|
||||
|
||||
|
||||
@ -599,12 +673,11 @@ public class QJavalinApiHandler
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
Operator(String prefix, QCriteriaOperator positiveOperator, QCriteriaOperator negativeOperator, boolean supportsNot, Integer noOfValues)
|
||||
Operator(String prefix, QCriteriaOperator positiveOperator, QCriteriaOperator negativeOperator, Integer noOfValues)
|
||||
{
|
||||
this.prefix = prefix;
|
||||
this.positiveOperator = positiveOperator;
|
||||
this.negativeOperator = negativeOperator;
|
||||
this.supportsNot = supportsNot;
|
||||
this.noOfValues = noOfValues;
|
||||
}
|
||||
}
|
||||
@ -635,7 +708,7 @@ public class QJavalinApiHandler
|
||||
if(value.startsWith(op.prefix))
|
||||
{
|
||||
selectedOperator = op;
|
||||
if(!selectedOperator.supportsNot && isNot)
|
||||
if(selectedOperator.negativeOperator == null && isNot)
|
||||
{
|
||||
throw (new QBadRequestException("Unsupported operator: !" + selectedOperator.prefix));
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
public class GenerateOpenApiSpecInput extends AbstractActionInput
|
||||
{
|
||||
private String version;
|
||||
private String tableName;
|
||||
|
||||
|
||||
|
||||
@ -63,4 +64,35 @@ public class GenerateOpenApiSpecInput extends AbstractActionInput
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tableName
|
||||
*******************************************************************************/
|
||||
public String getTableName()
|
||||
{
|
||||
return (this.tableName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for tableName
|
||||
*******************************************************************************/
|
||||
public void setTableName(String tableName)
|
||||
{
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for tableName
|
||||
*******************************************************************************/
|
||||
public GenerateOpenApiSpecInput withTableName(String tableName)
|
||||
{
|
||||
this.tableName = tableName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ package com.kingsrook.qqq.api.model.metadata.fields;
|
||||
import com.kingsrook.qqq.api.ApiMiddlewareType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QMiddlewareFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -63,6 +64,22 @@ public class ApiFieldMetaData extends QMiddlewareFieldMetaData
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String getEffectiveApiFieldName(QFieldMetaData field)
|
||||
{
|
||||
ApiFieldMetaData apiFieldMetaData = ApiFieldMetaData.of(field);
|
||||
if(apiFieldMetaData != null && StringUtils.hasContent(apiFieldMetaData.apiFieldName))
|
||||
{
|
||||
return (apiFieldMetaData.apiFieldName);
|
||||
}
|
||||
|
||||
return (field.getName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for initialVersion
|
||||
*******************************************************************************/
|
||||
|
Reference in New Issue
Block a user