Initial support for associated records (implemented insert, delete).

Include "api" on audit.
This commit is contained in:
2023-03-27 09:52:39 -05:00
parent 17d4c81cc3
commit ba805a4c92
15 changed files with 1052 additions and 85 deletions

View File

@ -26,6 +26,7 @@ import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import com.kingsrook.qqq.api.model.APIVersion;
import com.kingsrook.qqq.api.model.APIVersionRange;
import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecInput;
@ -66,8 +67,10 @@ 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.Association;
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.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.YamlUtils;
@ -204,7 +207,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
boolean updateCapability = table.isCapabilityEnabled(tableBackend, Capability.TABLE_UPDATE);
boolean deleteCapability = table.isCapabilityEnabled(tableBackend, Capability.TABLE_DELETE);
boolean insertCapability = table.isCapabilityEnabled(tableBackend, Capability.TABLE_INSERT);
boolean countCapability = table.isCapabilityEnabled(tableBackend, Capability.TABLE_COUNT);
boolean countCapability = table.isCapabilityEnabled(tableBackend, Capability.TABLE_COUNT); // todo - look at this - if table doesn't have count, don't include it in its input/output, etc
if(!queryCapability && !getCapability && !updateCapability && !deleteCapability && !insertCapability)
{
@ -221,6 +224,9 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
String primaryKeyApiName = ApiFieldMetaData.getEffectiveApiFieldName(primaryKeyField);
List<QFieldMetaData> tableApiFields = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(version)).getFields();
///////////////////////////////
// permissions for the table //
///////////////////////////////
String tableReadPermissionName = PermissionsHelper.getTablePermissionName(tableName, TablePermissionSubType.READ);
if(StringUtils.hasContent(tableReadPermissionName))
{
@ -256,13 +262,15 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
.withName(tableLabel)
.withDescription("Operations on the " + tableLabel + " table."));
//////////////////////////////////////
// build the schemas for this table //
//////////////////////////////////////
//////////////////////////////////////////////////////////////////
// build the schemas for this table //
// start with the full table minus its pkey (e.g., for posting) //
//////////////////////////////////////////////////////////////////
LinkedHashMap<String, Schema> tableFieldsWithoutPrimaryKey = new LinkedHashMap<>();
componentSchemas.put(tableApiName + "WithoutPrimaryKey", new Schema()
Schema tableWithoutPrimaryKeySchema = new Schema()
.withType("object")
.withProperties(tableFieldsWithoutPrimaryKey));
.withProperties(tableFieldsWithoutPrimaryKey);
componentSchemas.put(tableApiName + "WithoutPrimaryKey", tableWithoutPrimaryKeySchema);
for(QFieldMetaData field : tableApiFields)
{
@ -271,46 +279,26 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
continue;
}
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);
Schema fieldSchema = getFieldSchema(table, field);
tableFieldsWithoutPrimaryKey.put(ApiFieldMetaData.getEffectiveApiFieldName(field), fieldSchema);
}
//////////////////////////////////
// recursively add associations //
//////////////////////////////////
addAssociations(table, tableWithoutPrimaryKeySchema);
/////////////////////////////////////////////////
// full version of table (w/o pkey + the pkey) //
/////////////////////////////////////////////////
componentSchemas.put(tableApiName, new Schema()
.withType("object")
.withAllOf(ListBuilder.of(new Schema().withRef("#/components/schemas/" + tableApiName + "WithoutPrimaryKey")))
.withProperties(MapBuilder.of(
primaryKeyApiName, new Schema()
.withType(getFieldType(table.getField(primaryKeyName)))
.withFormat(getFieldFormat(table.getField(primaryKeyName)))
.withDescription(primaryKeyLabel + " for the " + tableLabel + ". Primary Key.")
))
);
.withProperties(MapBuilder.of(primaryKeyApiName, getFieldSchema(table, table.getField(primaryKeyName)))));
//////////////////////////////////////////////////////////////////////////////
// table as a search result (the base search result, plus the table itself) //
//////////////////////////////////////////////////////////////////////////////
componentSchemas.put(tableApiName + "SearchResult", new Schema()
.withType("object")
.withAllOf(ListBuilder.of(new Schema().withRef("#/components/schemas/baseSearchResultFields")))
@ -577,6 +565,59 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
/*******************************************************************************
**
*******************************************************************************/
private static void addAssociations(QTableMetaData table, Schema tableWithoutPrimaryKeySchema)
{
for(Association association : CollectionUtils.nonNullList(table.getAssociations()))
{
String associatedTableName = association.getAssociatedTableName();
QTableMetaData associatedTable = QContext.getQInstance().getTable(associatedTableName);
ApiTableMetaData associatedApiTableMetaData = Objects.requireNonNullElse(ApiTableMetaData.of(associatedTable), new ApiTableMetaData());
String associatedTableApiName = StringUtils.hasContent(associatedApiTableMetaData.getApiTableName()) ? associatedApiTableMetaData.getApiTableName() : associatedTableName;
tableWithoutPrimaryKeySchema.getProperties().put(association.getName(), new Schema()
.withType("array")
.withItems(new Schema().withRef("#/components/schemas/" + associatedTableApiName)));
}
}
/*******************************************************************************
**
*******************************************************************************/
private Schema getFieldSchema(QTableMetaData table, QFieldMetaData field)
{
Schema fieldSchema = new Schema()
.withType(getFieldType(table.getField(field.getName())))
.withFormat(getFieldFormat(table.getField(field.getName())))
.withDescription(field.getLabel() + " for the " + table.getLabel() + ".");
if(StringUtils.hasContent(field.getPossibleValueSourceName()))
{
QPossibleValueSource possibleValueSource = QContext.getQInstance().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 = QContext.getQInstance().getTable(possibleValueSource.getTableName());
fieldSchema.setDescription(fieldSchema.getDescription() + " Values in this field come from the primary key of the " + sourceTable.getLabel() + " table");
}
}
return fieldSchema;
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -32,11 +32,16 @@ import java.util.stream.Collectors;
import com.kingsrook.qqq.api.javalin.QBadRequestException;
import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsInput;
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
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.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.Pair;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import org.json.JSONArray;
import org.json.JSONObject;
@ -94,6 +99,13 @@ public class QRecordApiAdapter
List<String> unrecognizedFieldNames = new ArrayList<>();
QRecord qRecord = new QRecord();
Map<String, Association> associationMap = new HashMap<>();
QTableMetaData table = QContext.getQInstance().getTable(tableName);
for(Association association : CollectionUtils.nonNullList(table.getAssociations()))
{
associationMap.put(association.getName(), association);
}
//////////////////////////////////////////
// iterate over keys in the json object //
//////////////////////////////////////////
@ -117,6 +129,30 @@ public class QRecordApiAdapter
qRecord.setValue(field.getName(), value);
}
}
else if(associationMap.containsKey(jsonKey))
{
Association association = associationMap.get(jsonKey);
Object value = jsonObject.get(jsonKey);
if(value instanceof JSONArray jsonArray)
{
for(Object subObject : jsonArray)
{
if(subObject instanceof JSONObject subJsonObject)
{
QRecord subRecord = apiJsonObjectToQRecord(subJsonObject, association.getAssociatedTableName(), apiVersion);
qRecord.withAssociatedRecord(association.getName(), subRecord);
}
else
{
throw (new QBadRequestException("Found a " + value.getClass().getSimpleName() + " in the array under key " + jsonKey + ", but a JSON object is required here."));
}
}
}
else
{
throw (new QBadRequestException("Found a " + value.getClass().getSimpleName() + " at key " + jsonKey + ", but a JSON array is required here."));
}
}
else
{
///////////////////////////////////////////////////

View File

@ -76,6 +76,7 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
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.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ExceptionUtils;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
@ -253,7 +254,7 @@ public class QJavalinApiHandler
String version = context.pathParam("version");
GenerateOpenApiSpecInput input = new GenerateOpenApiSpecInput().withVersion(version);
if(context.pathParam("tableName") != null)
if(StringUtils.hasContent(context.pathParam("tableName")))
{
input.setTableName(context.pathParam("tableName"));
}
@ -273,9 +274,10 @@ public class QJavalinApiHandler
/*******************************************************************************
**
*******************************************************************************/
public static void setupSession(Context context, AbstractActionInput input) throws QModuleDispatchException, QAuthenticationException
public static void setupSession(Context context, AbstractActionInput input, String version) throws QModuleDispatchException, QAuthenticationException
{
QJavalinImplementation.setupSession(context, input);
QSession session = QJavalinImplementation.setupSession(context, input);
session.setValue("apiVersion", version);
}
@ -296,8 +298,8 @@ public class QJavalinApiHandler
GetInput getInput = new GetInput();
setupSession(context, getInput);
QJavalinAccessLogger.logStart("get", logPair("table", tableName), logPair("primaryKey", primaryKey));
setupSession(context, getInput, version);
QJavalinAccessLogger.logStart("apiGet", logPair("table", tableName), logPair("primaryKey", primaryKey));
getInput.setTableName(tableName);
// i think not for api... getInput.setShouldGenerateDisplayValues(true);
@ -354,7 +356,7 @@ public class QJavalinApiHandler
String tableName = table.getName();
QueryInput queryInput = new QueryInput();
setupSession(context, queryInput);
setupSession(context, queryInput, version);
QJavalinAccessLogger.logStart("apiQuery", logPair("table", tableName));
queryInput.setTableName(tableName);
@ -785,8 +787,8 @@ public class QJavalinApiHandler
InsertInput insertInput = new InsertInput();
setupSession(context, insertInput);
QJavalinAccessLogger.logStart("insert", logPair("table", tableName));
setupSession(context, insertInput, version);
QJavalinAccessLogger.logStart("apiInsert", logPair("table", tableName));
insertInput.setTableName(tableName);
@ -852,8 +854,8 @@ public class QJavalinApiHandler
InsertInput insertInput = new InsertInput();
setupSession(context, insertInput);
QJavalinAccessLogger.logStart("bulkInsert", logPair("table", tableName));
setupSession(context, insertInput, version);
QJavalinAccessLogger.logStart("apiBulkInsert", logPair("table", tableName));
insertInput.setTableName(tableName);
@ -958,8 +960,8 @@ public class QJavalinApiHandler
UpdateInput updateInput = new UpdateInput();
setupSession(context, updateInput);
QJavalinAccessLogger.logStart("bulkUpdate", logPair("table", tableName));
setupSession(context, updateInput, version);
QJavalinAccessLogger.logStart("apiBulkUpdate", logPair("table", tableName));
updateInput.setTableName(tableName);
@ -1063,8 +1065,8 @@ public class QJavalinApiHandler
DeleteInput deleteInput = new DeleteInput();
setupSession(context, deleteInput);
QJavalinAccessLogger.logStart("bulkDelete", logPair("table", tableName));
setupSession(context, deleteInput, version);
QJavalinAccessLogger.logStart("apiBulkDelete", logPair("table", tableName));
deleteInput.setTableName(tableName);
@ -1182,8 +1184,8 @@ public class QJavalinApiHandler
UpdateInput updateInput = new UpdateInput();
setupSession(context, updateInput);
QJavalinAccessLogger.logStart("update", logPair("table", tableName));
setupSession(context, updateInput, version);
QJavalinAccessLogger.logStart("apiUpdate", logPair("table", tableName));
updateInput.setTableName(tableName);
@ -1268,8 +1270,8 @@ public class QJavalinApiHandler
DeleteInput deleteInput = new DeleteInput();
setupSession(context, deleteInput);
QJavalinAccessLogger.logStart("delete", logPair("table", tableName));
setupSession(context, deleteInput, version);
QJavalinAccessLogger.logStart("apiDelete", logPair("table", tableName));
deleteInput.setTableName(tableName);
deleteInput.setPrimaryKeys(List.of(primaryKey));

View File

@ -27,6 +27,7 @@ import com.kingsrook.qqq.api.model.APIVersion;
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData;
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
@ -34,6 +35,10 @@ import com.kingsrook.qqq.backend.core.model.metadata.authentication.Auth0Authent
import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
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.joins.JoinOn;
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
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.tables.UniqueKey;
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule;
@ -45,10 +50,15 @@ import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.Mem
public class TestUtils
{
public static final String MEMORY_BACKEND_NAME = "memory";
public static final String TABLE_NAME_PERSON = "person";
public static final String V2023_Q1 = "2023.Q1";
public static final String TABLE_NAME_PERSON = "person";
public static final String TABLE_NAME_ORDER = "order";
public static final String TABLE_NAME_LINE_ITEM = "orderLine";
public static final String TABLE_NAME_LINE_ITEM_EXTRINSIC = "orderLineExtrinsic";
public static final String TABLE_NAME_ORDER_EXTRINSIC = "orderExtrinsic";
public static final String V2022_Q4 = "2022.Q4";
public static final String V2023_Q1 = "2023.Q1";
public static final String V2023_Q2 = "2023.Q2";
public static final String CURRENT_API_VERSION = V2023_Q1;
@ -64,6 +74,15 @@ public class TestUtils
qInstance.addBackend(defineMemoryBackend());
qInstance.addTable(defineTablePerson());
qInstance.addTable(defineTableOrder());
qInstance.addTable(defineTableLineItem());
qInstance.addTable(defineTableLineItemExtrinsic());
qInstance.addTable(defineTableOrderExtrinsic());
qInstance.addJoin(defineJoinOrderLineItem());
qInstance.addJoin(defineJoinLineItemLineItemExtrinsic());
qInstance.addJoin(defineJoinOrderOrderExtrinsic());
qInstance.setAuthentication(new Auth0AuthenticationMetaData().withType(QAuthenticationType.FULLY_ANONYMOUS).withName("anonymous"));
qInstance.withMiddlewareMetaData(new ApiInstanceMetaData()
@ -139,4 +158,137 @@ public class TestUtils
;
}
/*******************************************************************************
** Define the order table used in standard tests.
*******************************************************************************/
public static QTableMetaData defineTableOrder()
{
return new QTableMetaData()
.withName(TABLE_NAME_ORDER)
.withBackendName(MEMORY_BACKEND_NAME)
.withMiddlewareMetaData(new ApiTableMetaData().withInitialVersion(V2022_Q4))
.withPrimaryKeyField("id")
.withAssociation(new Association().withName("orderLines").withAssociatedTableName(TABLE_NAME_LINE_ITEM).withJoinName("orderLineItem"))
.withAssociation(new Association().withName("extrinsics").withAssociatedTableName(TABLE_NAME_ORDER_EXTRINSIC).withJoinName("orderOrderExtrinsic"))
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false))
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withIsEditable(false))
.withField(new QFieldMetaData("orderNo", QFieldType.STRING))
.withField(new QFieldMetaData("orderDate", QFieldType.DATE))
.withField(new QFieldMetaData("storeId", QFieldType.INTEGER))
.withField(new QFieldMetaData("total", QFieldType.DECIMAL).withDisplayFormat(DisplayFormat.CURRENCY));
}
/*******************************************************************************
** Define the lineItem table used in standard tests.
*******************************************************************************/
public static QTableMetaData defineTableLineItem()
{
return new QTableMetaData()
.withName(TABLE_NAME_LINE_ITEM)
.withBackendName(MEMORY_BACKEND_NAME)
.withMiddlewareMetaData(new ApiTableMetaData().withInitialVersion(V2022_Q4))
.withPrimaryKeyField("id")
.withAssociation(new Association().withName("extrinsics").withAssociatedTableName(TABLE_NAME_LINE_ITEM_EXTRINSIC).withJoinName("lineItemLineItemExtrinsic"))
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false))
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withIsEditable(false))
.withField(new QFieldMetaData("orderId", QFieldType.INTEGER))
.withField(new QFieldMetaData("lineNumber", QFieldType.STRING))
.withField(new QFieldMetaData("sku", QFieldType.STRING))
.withField(new QFieldMetaData("quantity", QFieldType.INTEGER));
}
/*******************************************************************************
** Define the lineItemExtrinsic table used in standard tests.
*******************************************************************************/
public static QTableMetaData defineTableLineItemExtrinsic()
{
return new QTableMetaData()
.withName(TABLE_NAME_LINE_ITEM_EXTRINSIC)
.withBackendName(MEMORY_BACKEND_NAME)
.withMiddlewareMetaData(new ApiTableMetaData().withInitialVersion(V2022_Q4))
.withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false))
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withIsEditable(false))
.withField(new QFieldMetaData("lineItemId", QFieldType.INTEGER))
.withField(new QFieldMetaData("key", QFieldType.STRING))
.withField(new QFieldMetaData("value", QFieldType.STRING));
}
/*******************************************************************************
** Define the orderExtrinsic table used in standard tests.
*******************************************************************************/
public static QTableMetaData defineTableOrderExtrinsic()
{
return new QTableMetaData()
.withName(TABLE_NAME_ORDER_EXTRINSIC)
.withBackendName(MEMORY_BACKEND_NAME)
.withMiddlewareMetaData(new ApiTableMetaData().withInitialVersion(V2022_Q4))
.withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false))
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withIsEditable(false))
.withField(new QFieldMetaData("orderId", QFieldType.INTEGER))
.withField(new QFieldMetaData("key", QFieldType.STRING))
.withField(new QFieldMetaData("value", QFieldType.STRING));
}
/*******************************************************************************
**
*******************************************************************************/
public static QJoinMetaData defineJoinOrderLineItem()
{
return new QJoinMetaData()
.withName("orderLineItem")
.withType(JoinType.ONE_TO_MANY)
.withLeftTable(TABLE_NAME_ORDER)
.withRightTable(TABLE_NAME_LINE_ITEM)
.withJoinOn(new JoinOn("id", "orderId"))
.withOrderBy(new QFilterOrderBy("lineNumber"));
}
/*******************************************************************************
**
*******************************************************************************/
public static QJoinMetaData defineJoinLineItemLineItemExtrinsic()
{
return new QJoinMetaData()
.withName("lineItemLineItemExtrinsic")
.withType(JoinType.ONE_TO_MANY)
.withLeftTable(TABLE_NAME_LINE_ITEM)
.withRightTable(TABLE_NAME_LINE_ITEM_EXTRINSIC)
.withJoinOn(new JoinOn("id", "lineItemId"))
.withOrderBy(new QFilterOrderBy("key"));
}
/*******************************************************************************
**
*******************************************************************************/
public static QJoinMetaData defineJoinOrderOrderExtrinsic()
{
return new QJoinMetaData()
.withName("orderOrderExtrinsic")
.withType(JoinType.ONE_TO_MANY)
.withLeftTable(TABLE_NAME_ORDER)
.withRightTable(TABLE_NAME_ORDER_EXTRINSIC)
.withJoinOn(new JoinOn("id", "orderId"))
.withOrderBy(new QFilterOrderBy("key"));
}
}

View File

@ -37,6 +37,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
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.QFilterOrderBy;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
@ -59,6 +60,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/*******************************************************************************
@ -407,12 +409,55 @@ class QJavalinApiHandlerTest extends BaseTest
JSONObject jsonObject = new JSONObject(response.getBody());
assertEquals(1, jsonObject.getInt("id"));
QRecord record = getPersonRecord(1);
QRecord record = getRecord(TestUtils.TABLE_NAME_PERSON, 1);
assertEquals("Moe", record.getValueString("firstName"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testInsert201WithAssociatedRecords() throws QException
{
HttpResponse<String> response = Unirest.post(BASE_URL + "/api/" + VERSION + "/order/")
.body("""
{"orderNo": "ORD123", "storeId": 47, "orderLines":
[
{"lineNumber": 1, "sku": "BASIC1", "quantity": 17, "extrinsics": [{"key": "size", "value": "Large"}]},
{"lineNumber": 2, "sku": "BASIC2", "quantity": 23}
], "extrinsics":
[
{"key": "storeName", "value": "My Shopify"},
{"key": "shopifyOrderNo", "value": "#2820503"}
]
}
""")
.asString();
System.out.println(response.getBody());
assertEquals(HttpStatus.CREATED_201, response.getStatus());
JSONObject jsonObject = new JSONObject(response.getBody());
assertEquals(1, jsonObject.getInt("id"));
QRecord record = getRecord(TestUtils.TABLE_NAME_ORDER, 1);
assertEquals("ORD123", record.getValueString("orderNo"));
List<QRecord> lines = queryTable(TestUtils.TABLE_NAME_LINE_ITEM);
assertEquals(2, lines.size());
assertTrue(lines.stream().allMatch(r -> r.getValueInteger("orderId").equals(1)));
assertTrue(lines.stream().anyMatch(r -> r.getValueString("sku").equals("BASIC1")));
assertTrue(lines.stream().anyMatch(r -> r.getValueString("sku").equals("BASIC2")));
List<QRecord> orderExtrinsics = queryTable(TestUtils.TABLE_NAME_ORDER_EXTRINSIC);
assertEquals(2, orderExtrinsics.size());
assertTrue(orderExtrinsics.stream().allMatch(r -> r.getValueInteger("orderId").equals(1)));
assertTrue(orderExtrinsics.stream().anyMatch(r -> r.getValueString("key").equals("storeName") && r.getValueString("value").equals("My Shopify")));
assertTrue(orderExtrinsics.stream().anyMatch(r -> r.getValueString("key").equals("shopifyOrderNo") && r.getValueString("value").equals("#2820503")));
}
/*******************************************************************************
**
*******************************************************************************/
@ -462,7 +507,7 @@ class QJavalinApiHandlerTest extends BaseTest
///////////////////////////////////
// assert it didn't get inserted //
///////////////////////////////////
QRecord personRecord = getPersonRecord(1);
QRecord personRecord = getRecord(TestUtils.TABLE_NAME_PERSON, 1);
assertNull(personRecord);
///////////////////////////////////////////
@ -511,16 +556,16 @@ class QJavalinApiHandlerTest extends BaseTest
assertEquals(HttpStatus.BAD_REQUEST_400, jsonArray.getJSONObject(3).getInt("statusCode"));
assertEquals("Error inserting Person: Another record already exists with this Email", jsonArray.getJSONObject(3).getString("error"));
QRecord record = getPersonRecord(1);
QRecord record = getRecord(TestUtils.TABLE_NAME_PERSON, 1);
assertEquals("Moe", record.getValueString("firstName"));
record = getPersonRecord(2);
record = getRecord(TestUtils.TABLE_NAME_PERSON, 2);
assertEquals("Barney", record.getValueString("firstName"));
record = getPersonRecord(3);
record = getRecord(TestUtils.TABLE_NAME_PERSON, 3);
assertEquals("CM", record.getValueString("firstName"));
record = getPersonRecord(4);
record = getRecord(TestUtils.TABLE_NAME_PERSON, 4);
assertNull(record);
}
@ -559,7 +604,7 @@ class QJavalinApiHandlerTest extends BaseTest
/////////////////////////////////
// assert nothing got inserted //
/////////////////////////////////
QRecord personRecord = getPersonRecord(1);
QRecord personRecord = getRecord(TestUtils.TABLE_NAME_PERSON, 1);
assertNull(personRecord);
//////////////////////////////////////////
@ -592,7 +637,7 @@ class QJavalinApiHandlerTest extends BaseTest
assertEquals(HttpStatus.NO_CONTENT_204, response.getStatus());
assertFalse(StringUtils.hasContent(response.getBody()));
QRecord record = getPersonRecord(1);
QRecord record = getRecord(TestUtils.TABLE_NAME_PERSON, 1);
assertEquals("Charles", record.getValueString("firstName"));
}
@ -665,7 +710,7 @@ class QJavalinApiHandlerTest extends BaseTest
///////////////////////////////////
// assert it didn't get updated. //
///////////////////////////////////
QRecord personRecord = getPersonRecord(1);
QRecord personRecord = getRecord(TestUtils.TABLE_NAME_PERSON, 1);
assertEquals("Mo", personRecord.getValueString("firstName"));
response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/person/1")
@ -706,10 +751,10 @@ class QJavalinApiHandlerTest extends BaseTest
assertEquals(HttpStatus.BAD_REQUEST_400, jsonArray.getJSONObject(2).getInt("statusCode"));
assertEquals("Error updating Person: Missing value in primary key field", jsonArray.getJSONObject(2).getString("error"));
QRecord record = getPersonRecord(1);
QRecord record = getRecord(TestUtils.TABLE_NAME_PERSON, 1);
assertEquals("homer@simpson.com", record.getValueString("email"));
record = getPersonRecord(2);
record = getRecord(TestUtils.TABLE_NAME_PERSON, 2);
assertEquals("marge@simpson.com", record.getValueString("email"));
QueryInput queryInput = new QueryInput();
@ -754,7 +799,7 @@ class QJavalinApiHandlerTest extends BaseTest
////////////////////////////////
// assert nothing got updated //
////////////////////////////////
QRecord personRecord = getPersonRecord(1);
QRecord personRecord = getRecord(TestUtils.TABLE_NAME_PERSON, 1);
assertNull(personRecord);
//////////////////////////////////////////
@ -834,7 +879,7 @@ class QJavalinApiHandlerTest extends BaseTest
////////////////////////////////
// assert nothing got deleted //
////////////////////////////////
QRecord personRecord = getPersonRecord(1);
QRecord personRecord = getRecord(TestUtils.TABLE_NAME_PERSON, 1);
assertNull(personRecord);
//////////////////////////////////////////
@ -877,7 +922,7 @@ class QJavalinApiHandlerTest extends BaseTest
assertEquals(HttpStatus.NO_CONTENT_204, response.getStatus());
assertFalse(StringUtils.hasContent(response.getBody()));
QRecord record = getPersonRecord(1);
QRecord record = getRecord(TestUtils.TABLE_NAME_PERSON, 1);
assertNull(record);
}
@ -895,6 +940,43 @@ class QJavalinApiHandlerTest extends BaseTest
/*******************************************************************************
**
*******************************************************************************/
@Test
void testDeleteAssociations() throws QException
{
InsertInput insertInput = new InsertInput();
insertInput.setTableName(TestUtils.TABLE_NAME_ORDER);
insertInput.setRecords(List.of(new QRecord().withValue("id", 1).withValue("orderNo", "ORD123").withValue("storeId", 47)
.withAssociatedRecord("orderLines", new QRecord().withValue("lineNumber", 1).withValue("sku", "BASIC1").withValue("quantity", 42)
.withAssociatedRecord("extrinsics", new QRecord().withValue("key", "Size").withValue("value", "Medium"))
.withAssociatedRecord("extrinsics", new QRecord().withValue("key", "Discount").withValue("value", "3.50"))
.withAssociatedRecord("extrinsics", new QRecord().withValue("key", "Color").withValue("value", "Red")))
.withAssociatedRecord("orderLines", new QRecord().withValue("lineNumber", 2).withValue("sku", "BASIC2").withValue("quantity", 42)
.withAssociatedRecord("extrinsics", new QRecord().withValue("key", "Size").withValue("value", "Medium")))
.withAssociatedRecord("orderLines", new QRecord().withValue("lineNumber", 3).withValue("sku", "BASIC3").withValue("quantity", 42))
.withAssociatedRecord("extrinsics", new QRecord().withValue("key", "shopifyOrderNo").withValue("value", "#1032"))
));
new InsertAction().execute(insertInput);
assertEquals(1, queryTable(TestUtils.TABLE_NAME_ORDER).size());
assertEquals(4, queryTable(TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC).size());
assertEquals(3, queryTable(TestUtils.TABLE_NAME_LINE_ITEM).size());
assertEquals(1, queryTable(TestUtils.TABLE_NAME_ORDER_EXTRINSIC).size());
HttpResponse<String> response = Unirest.delete(BASE_URL + "/api/" + VERSION + "/order/1").asString();
assertEquals(HttpStatus.NO_CONTENT_204, response.getStatus());
assertFalse(StringUtils.hasContent(response.getBody()));
assertEquals(0, queryTable(TestUtils.TABLE_NAME_ORDER).size());
assertEquals(0, queryTable(TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC).size());
assertEquals(0, queryTable(TestUtils.TABLE_NAME_LINE_ITEM).size());
assertEquals(0, queryTable(TestUtils.TABLE_NAME_ORDER_EXTRINSIC).size());
}
/*******************************************************************************
**
*******************************************************************************/
@ -919,10 +1001,10 @@ class QJavalinApiHandlerTest extends BaseTest
/*******************************************************************************
**
*******************************************************************************/
private static QRecord getPersonRecord(Integer id) throws QException
private static QRecord getRecord(String tableName, Integer id) throws QException
{
GetInput getInput = new GetInput();
getInput.setTableName(TestUtils.TABLE_NAME_PERSON);
getInput.setTableName(tableName);
getInput.setPrimaryKey(id);
GetOutput getOutput = new GetAction().execute(getInput);
QRecord record = getOutput.getRecord();
@ -931,6 +1013,20 @@ class QJavalinApiHandlerTest extends BaseTest
/*******************************************************************************
**
*******************************************************************************/
private static List<QRecord> queryTable(String tableName) throws QException
{
QueryInput queryInput = new QueryInput();
queryInput.setTableName(tableName);
queryInput.setFilter(new QQueryFilter().withOrderBy(new QFilterOrderBy("id")));
QueryOutput queryOutput = new QueryAction().execute(queryInput);
return (queryOutput.getRecords());
}
/*******************************************************************************
**
*******************************************************************************/