mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-21 06:28:44 +00:00
Checkpoint; working insert, update, delete
This commit is contained in:
@ -62,6 +62,7 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.YamlUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.ListBuilder;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
|
||||
import io.javalin.http.HttpStatus;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -272,7 +273,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
||||
.withSchema(new Schema().withType("string").withEnumValues(ListBuilder.of("AND", "OR")))
|
||||
))
|
||||
.withResponses(buildStandardErrorResponses())
|
||||
.withResponse(200, new Response()
|
||||
.withResponse(HttpStatus.OK.getCode(), new Response()
|
||||
.withDescription("Successfully searched the " + tableLabel + " table (though may have found 0 records).")
|
||||
.withContent(MapBuilder.of("application/json", new Content()
|
||||
.withSchema(new Schema().withRef("#/components/schemas/" + tableName + "SearchResult"))
|
||||
@ -321,8 +322,8 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
||||
.withSchema(new Schema().withType(getFieldType(primaryKeyField)))
|
||||
))
|
||||
.withResponses(buildStandardErrorResponses())
|
||||
.withResponse(404, buildStandardErrorResponse("The requested " + tableLabel + " record was not found.", "Could not find " + tableLabel + " with " + primaryKeyLabel + " of 47."))
|
||||
.withResponse(200, new Response()
|
||||
.withResponse(HttpStatus.NOT_FOUND.getCode(), buildStandardErrorResponse("The requested " + tableLabel + " record was not found.", "Could not find " + tableLabel + " with " + primaryKeyLabel + " of 47."))
|
||||
.withResponse(HttpStatus.OK.getCode(), new Response()
|
||||
.withDescription("Successfully got the requested " + tableLabel)
|
||||
.withContent(MapBuilder.of("application/json", new Content()
|
||||
.withSchema(new Schema().withRef("#/components/schemas/" + tableName))
|
||||
@ -348,8 +349,8 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
||||
.withSchema(new Schema().withRef("#/components/schemas/" + tableName))
|
||||
)))
|
||||
.withResponses(buildStandardErrorResponses())
|
||||
.withResponse(404, buildStandardErrorResponse("The requested " + tableLabel + " record was not found.", "Could not find " + tableLabel + " with " + primaryKeyLabel + " of 47.")) // todo - 404 on update?
|
||||
.withResponse(200, new Response().withDescription("Successfully updated the requested " + tableLabel))
|
||||
.withResponse(HttpStatus.NOT_FOUND.getCode(), buildStandardErrorResponse("The requested " + tableLabel + " record was not found.", "Could not find " + tableLabel + " with " + primaryKeyLabel + " of 47."))
|
||||
.withResponse(HttpStatus.NO_CONTENT.getCode(), new Response().withDescription("Successfully updated the requested " + tableLabel))
|
||||
.withSecurity(ListBuilder.of(MapBuilder.of("OAuth2", List.of(tableUpdatePermissionName))));
|
||||
|
||||
Method idDelete = new Method()
|
||||
@ -365,8 +366,8 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
||||
.withSchema(new Schema().withType(getFieldType(primaryKeyField)))
|
||||
))
|
||||
.withResponses(buildStandardErrorResponses())
|
||||
.withResponse(404, buildStandardErrorResponse("The requested " + tableLabel + " record was not found.", "Could not find " + tableLabel + " with " + primaryKeyLabel + " of 47.")) // todo - 404 on update?
|
||||
.withResponse(200, new Response().withDescription("Successfully deleted the requested " + tableLabel))
|
||||
.withResponse(HttpStatus.NOT_FOUND.getCode(), buildStandardErrorResponse("The requested " + tableLabel + " record was not found.", "Could not find " + tableLabel + " with " + primaryKeyLabel + " of 47."))
|
||||
.withResponse(HttpStatus.NO_CONTENT.getCode(), new Response().withDescription("Successfully deleted the requested " + tableLabel))
|
||||
.withSecurity(ListBuilder.of(MapBuilder.of("OAuth2", List.of(tableDeletePermissionName))));
|
||||
|
||||
openAPI.getPaths().put("/" + tableName + "/{" + primaryKeyName + "}", new Path()
|
||||
@ -384,7 +385,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
||||
.withSchema(new Schema().withRef("#/components/schemas/" + tableName + "WithoutPrimaryKey"))
|
||||
)))
|
||||
.withResponses(buildStandardErrorResponses())
|
||||
.withResponse(201, new Response()
|
||||
.withResponse(HttpStatus.CREATED.getCode(), new Response()
|
||||
.withDescription("Successfully created the requested " + tableLabel)
|
||||
.withContent(MapBuilder.of("application/json", new Content()
|
||||
.withSchema(new Schema()
|
||||
@ -423,10 +424,10 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
||||
*/
|
||||
}
|
||||
|
||||
componentResponses.put(400, buildStandardErrorResponse("Bad Request. Some portion of the request's content was not acceptable to the server. See error message in body for details.", "Parameter id should be given an integer value, but received string: \"Foo\""));
|
||||
componentResponses.put(401, buildStandardErrorResponse("Unauthorized. The required authentication credentials were missing or invalid.", "The required authentication credentials were missing or invalid."));
|
||||
componentResponses.put(403, buildStandardErrorResponse("Forbidden. You do not have permission to access the requested resource.", "You do not have permission to access the requested resource."));
|
||||
componentResponses.put(500, buildStandardErrorResponse("Internal Server Error. An error occurred in the server processing the request.", "Database connection error. Try again later."));
|
||||
componentResponses.put(HttpStatus.BAD_REQUEST.getCode(), buildStandardErrorResponse("Bad Request. Some portion of the request's content was not acceptable to the server. See error message in body for details.", "Parameter id should be given an integer value, but received string: \"Foo\""));
|
||||
componentResponses.put(HttpStatus.UNAUTHORIZED.getCode(), buildStandardErrorResponse("Unauthorized. The required authentication credentials were missing or invalid.", "The required authentication credentials were missing or invalid."));
|
||||
componentResponses.put(HttpStatus.FORBIDDEN.getCode(), buildStandardErrorResponse("Forbidden. You do not have permission to access the requested resource.", "You do not have permission to access the requested resource."));
|
||||
componentResponses.put(HttpStatus.INTERNAL_SERVER_ERROR.getCode(), buildStandardErrorResponse("Internal Server Error. An error occurred in the server processing the request.", "Database connection error. Try again later."));
|
||||
|
||||
GenerateOpenApiSpecOutput output = new GenerateOpenApiSpecOutput();
|
||||
output.setOpenAPI(openAPI);
|
||||
@ -571,10 +572,10 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
||||
private static Map<Integer, Response> buildStandardErrorResponses()
|
||||
{
|
||||
return MapBuilder.of(
|
||||
400, new Response().withRef("#/components/responses/400"),
|
||||
401, new Response().withRef("#/components/responses/401"),
|
||||
403, new Response().withRef("#/components/responses/403"),
|
||||
500, new Response().withRef("#/components/responses/500")
|
||||
HttpStatus.BAD_REQUEST.getCode(), new Response().withRef("#/components/responses/" + HttpStatus.BAD_REQUEST.getCode()),
|
||||
HttpStatus.UNAUTHORIZED.getCode(), new Response().withRef("#/components/responses/" + HttpStatus.UNAUTHORIZED.getCode()),
|
||||
HttpStatus.FORBIDDEN.getCode(), new Response().withRef("#/components/responses/" + HttpStatus.FORBIDDEN.getCode()),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR.getCode(), new Response().withRef("#/components/responses/" + HttpStatus.INTERNAL_SERVER_ERROR.getCode())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. 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.api.javalin;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Runtime exception
|
||||
*******************************************************************************/
|
||||
public class ApiPathNotFoundException extends RuntimeException
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ApiPathNotFoundException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
@ -40,4 +40,15 @@ public class QBadRequestException extends QException
|
||||
super(message);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QBadRequestException(String message, Throwable root)
|
||||
{
|
||||
super(message, root);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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.ApiMiddlewareType;
|
||||
import com.kingsrook.qqq.api.actions.GenerateOpenApiSpecAction;
|
||||
import com.kingsrook.qqq.api.actions.GetTableApiFieldsAction;
|
||||
@ -43,10 +44,14 @@ import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
|
||||
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.actions.tables.DeleteAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QAuthenticationException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QPermissionDeniedException;
|
||||
@ -55,18 +60,25 @@ import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
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.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
||||
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.insert.InsertOutput;
|
||||
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;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
|
||||
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.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ExceptionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
@ -79,6 +91,7 @@ import io.javalin.apibuilder.EndpointGroup;
|
||||
import io.javalin.http.ContentType;
|
||||
import io.javalin.http.Context;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.json.JSONObject;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
import static com.kingsrook.qqq.backend.javalin.QJavalinImplementation.SLOW_LOG_THRESHOLD_MS;
|
||||
|
||||
@ -132,11 +145,29 @@ public class QJavalinApiHandler
|
||||
// delete("/bulk", QJavalinApiHandler::bulkDelete);
|
||||
});
|
||||
});
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// default all other /api/ requests (for the methods we support) to a standard 404 response //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
ApiBuilder.get("/api/*", QJavalinApiHandler::doPathNotFound);
|
||||
ApiBuilder.delete("/api/*", QJavalinApiHandler::doPathNotFound);
|
||||
ApiBuilder.patch("/api/*", QJavalinApiHandler::doPathNotFound);
|
||||
ApiBuilder.post("/api/*", QJavalinApiHandler::doPathNotFound);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void doPathNotFound(Context context)
|
||||
{
|
||||
handleException(context, new QNotFoundException("Could not find any resources at path " + context.path()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -152,7 +183,7 @@ public class QJavalinApiHandler
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
QJavalinImplementation.handleException(context, e);
|
||||
handleException(context, e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,9 +226,6 @@ public class QJavalinApiHandler
|
||||
|
||||
try
|
||||
{
|
||||
// todo - make sure version is known in this instance
|
||||
// todo - make sure table is supported in this version
|
||||
|
||||
QTableMetaData table = qInstance.getTable(tableName);
|
||||
validateTableAndVersion(context, version, table);
|
||||
|
||||
@ -230,8 +258,7 @@ public class QJavalinApiHandler
|
||||
+ table.getFields().get(table.getPrimaryKeyField()).getLabel() + " of " + primaryKey));
|
||||
}
|
||||
|
||||
List<? extends QFieldMetaData> tableApiFields = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(version)).getFields();
|
||||
LinkedHashMap<String, Serializable> outputRecord = toApiRecord(record, tableApiFields);
|
||||
LinkedHashMap<String, Serializable> outputRecord = toApiRecord(record, tableName, version);
|
||||
|
||||
QJavalinAccessLogger.logEndSuccess();
|
||||
context.result(JsonUtils.toJson(outputRecord));
|
||||
@ -239,7 +266,7 @@ public class QJavalinApiHandler
|
||||
catch(Exception e)
|
||||
{
|
||||
QJavalinAccessLogger.logEndFail(e);
|
||||
QJavalinImplementation.handleException(context, e);
|
||||
handleException(context, e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -258,9 +285,6 @@ public class QJavalinApiHandler
|
||||
{
|
||||
List<String> badRequestMessages = new ArrayList<>();
|
||||
|
||||
// todo - make sure version is known in this instance
|
||||
// todo - make sure table is supported in this version
|
||||
|
||||
QTableMetaData table = qInstance.getTable(tableName);
|
||||
validateTableAndVersion(context, version, table);
|
||||
|
||||
@ -449,11 +473,10 @@ public class QJavalinApiHandler
|
||||
///////////////////////////////
|
||||
// map record fields for api //
|
||||
///////////////////////////////
|
||||
List<? extends QFieldMetaData> tableApiFields = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(version)).getFields();
|
||||
ArrayList<Map<String, Serializable>> records = new ArrayList<>();
|
||||
ArrayList<Map<String, Serializable>> records = new ArrayList<>();
|
||||
for(QRecord record : queryOutput.getRecords())
|
||||
{
|
||||
records.add(toApiRecord(record, tableApiFields));
|
||||
records.add(toApiRecord(record, tableName, version));
|
||||
}
|
||||
output.put("records", records);
|
||||
|
||||
@ -520,7 +543,7 @@ public class QJavalinApiHandler
|
||||
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, 2),
|
||||
IN("IN ", QCriteriaOperator.IN, QCriteriaOperator.NOT_IN, true, null),
|
||||
// todo MATCHES
|
||||
;
|
||||
|
||||
@ -529,14 +552,14 @@ public class QJavalinApiHandler
|
||||
private final QCriteriaOperator positiveOperator;
|
||||
private final QCriteriaOperator negativeOperator;
|
||||
private final boolean supportsNot;
|
||||
private final int noOfValues; // 0 & 1 mean 0 & 1 ... 2 means 1 or more...
|
||||
private final Integer noOfValues; // null means many (IN)
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
Operator(String prefix, QCriteriaOperator positiveOperator, QCriteriaOperator negativeOperator, boolean supportsNot, int noOfValues)
|
||||
Operator(String prefix, QCriteriaOperator positiveOperator, QCriteriaOperator negativeOperator, boolean supportsNot, Integer noOfValues)
|
||||
{
|
||||
this.prefix = prefix;
|
||||
this.positiveOperator = positiveOperator;
|
||||
@ -551,7 +574,7 @@ public class QJavalinApiHandler
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static QFilterCriteria parseQueryParamToCriteria(String name, String value) throws QBadRequestException
|
||||
private static QFilterCriteria parseQueryParamToCriteria(String name, String value) throws QException
|
||||
{
|
||||
///////////////////////////////////
|
||||
// process & discard a leading ! //
|
||||
@ -600,7 +623,11 @@ public class QJavalinApiHandler
|
||||
// todo - quotes? //
|
||||
////////////////////////////////////
|
||||
List<Serializable> criteriaValues;
|
||||
if(selectedOperator.noOfValues == 1)
|
||||
if(selectedOperator.noOfValues == null)
|
||||
{
|
||||
criteriaValues = Arrays.asList(value.split(","));
|
||||
}
|
||||
else if(selectedOperator.noOfValues == 1)
|
||||
{
|
||||
criteriaValues = ListBuilder.of(value);
|
||||
}
|
||||
@ -612,9 +639,17 @@ public class QJavalinApiHandler
|
||||
}
|
||||
criteriaValues = null;
|
||||
}
|
||||
else
|
||||
else if(selectedOperator.noOfValues == 2)
|
||||
{
|
||||
criteriaValues = Arrays.asList(value.split(","));
|
||||
if(criteriaValues.size() != 2)
|
||||
{
|
||||
throw (new QBadRequestException("Operator " + selectedOperator.prefix + " for field " + name + " requires 2 values (received " + criteriaValues.size() + ")"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw (new QException("Unexpected noOfValues [" + selectedOperator.noOfValues + "] in operator [" + selectedOperator + "]"));
|
||||
}
|
||||
|
||||
return (new QFilterCriteria(name, isNot ? selectedOperator.negativeOperator : selectedOperator.positiveOperator, criteriaValues));
|
||||
@ -627,7 +662,57 @@ public class QJavalinApiHandler
|
||||
*******************************************************************************/
|
||||
private static void doInsert(Context context)
|
||||
{
|
||||
String version = context.pathParam("version");
|
||||
String tableName = context.pathParam("tableName");
|
||||
|
||||
try
|
||||
{
|
||||
QTableMetaData table = qInstance.getTable(tableName);
|
||||
validateTableAndVersion(context, version, table);
|
||||
|
||||
InsertInput insertInput = new InsertInput();
|
||||
|
||||
setupSession(context, insertInput);
|
||||
QJavalinAccessLogger.logStart("insert", logPair("table", tableName));
|
||||
|
||||
insertInput.setTableName(tableName);
|
||||
|
||||
PermissionsHelper.checkTablePermissionThrowing(insertInput, TablePermissionSubType.INSERT);
|
||||
|
||||
try
|
||||
{
|
||||
if(!StringUtils.hasContent(context.body()))
|
||||
{
|
||||
throw (new QBadRequestException("Missing required POST body"));
|
||||
}
|
||||
|
||||
JSONObject jsonObject = new JSONObject(context.body());
|
||||
insertInput.setRecords(List.of(toQRecord(jsonObject, tableName, version)));
|
||||
}
|
||||
catch(QBadRequestException qbre)
|
||||
{
|
||||
throw (qbre);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QBadRequestException("Body could not be parsed as a JSON object: " + e.getMessage(), e));
|
||||
}
|
||||
|
||||
InsertAction insertAction = new InsertAction();
|
||||
InsertOutput insertOutput = insertAction.execute(insertInput);
|
||||
|
||||
LinkedHashMap<String, Serializable> outputRecord = new LinkedHashMap<>();
|
||||
outputRecord.put(table.getPrimaryKeyField(), insertOutput.getRecords().get(0).getValue(table.getPrimaryKeyField()));
|
||||
|
||||
QJavalinAccessLogger.logEndSuccess();
|
||||
context.status(HttpStatus.Code.CREATED.getCode());
|
||||
context.result(JsonUtils.toJson(outputRecord));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
QJavalinAccessLogger.logEndFail(e);
|
||||
handleException(context, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -637,7 +722,76 @@ public class QJavalinApiHandler
|
||||
*******************************************************************************/
|
||||
private static void doUpdate(Context context)
|
||||
{
|
||||
String version = context.pathParam("version");
|
||||
String tableName = context.pathParam("tableName");
|
||||
String primaryKey = context.pathParam("primaryKey");
|
||||
|
||||
try
|
||||
{
|
||||
QTableMetaData table = qInstance.getTable(tableName);
|
||||
validateTableAndVersion(context, version, table);
|
||||
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
|
||||
setupSession(context, updateInput);
|
||||
QJavalinAccessLogger.logStart("update", logPair("table", tableName));
|
||||
|
||||
updateInput.setTableName(tableName);
|
||||
|
||||
PermissionsHelper.checkTablePermissionThrowing(updateInput, TablePermissionSubType.EDIT);
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// throw a not found error if the record isn't found //
|
||||
///////////////////////////////////////////////////////
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(tableName);
|
||||
getInput.setPrimaryKey(primaryKey);
|
||||
GetAction getAction = new GetAction();
|
||||
GetOutput getOutput = getAction.execute(getInput);
|
||||
if(getOutput.getRecord() == null)
|
||||
{
|
||||
throw (new QNotFoundException("Could not find " + table.getLabel() + " with "
|
||||
+ table.getFields().get(table.getPrimaryKeyField()).getLabel() + " of " + primaryKey));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if(!StringUtils.hasContent(context.body()))
|
||||
{
|
||||
throw (new QBadRequestException("Missing required POST body"));
|
||||
}
|
||||
|
||||
JSONObject jsonObject = new JSONObject(context.body());
|
||||
QRecord qRecord = toQRecord(jsonObject, tableName, version);
|
||||
qRecord.setValue(table.getPrimaryKeyField(), primaryKey);
|
||||
updateInput.setRecords(List.of(qRecord));
|
||||
}
|
||||
catch(QBadRequestException qbre)
|
||||
{
|
||||
throw (qbre);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QBadRequestException("Body could not be parsed as a JSON object: " + e.getMessage(), e));
|
||||
}
|
||||
|
||||
UpdateAction updateAction = new UpdateAction();
|
||||
UpdateOutput updateOutput = updateAction.execute(updateInput);
|
||||
|
||||
List<String> errors = updateOutput.getRecords().get(0).getErrors();
|
||||
if(CollectionUtils.nullSafeHasContents(errors))
|
||||
{
|
||||
throw (new QException("Error updating " + table.getLabel() + ": " + StringUtils.joinWithCommasAndAnd(errors)));
|
||||
}
|
||||
|
||||
QJavalinAccessLogger.logEndSuccess();
|
||||
context.status(HttpStatus.Code.NO_CONTENT.getCode());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
QJavalinAccessLogger.logEndFail(e);
|
||||
handleException(context, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -647,7 +801,57 @@ public class QJavalinApiHandler
|
||||
*******************************************************************************/
|
||||
private static void doDelete(Context context)
|
||||
{
|
||||
String version = context.pathParam("version");
|
||||
String tableName = context.pathParam("tableName");
|
||||
String primaryKey = context.pathParam("primaryKey");
|
||||
|
||||
try
|
||||
{
|
||||
QTableMetaData table = qInstance.getTable(tableName);
|
||||
validateTableAndVersion(context, version, table);
|
||||
|
||||
DeleteInput deleteInput = new DeleteInput();
|
||||
|
||||
setupSession(context, deleteInput);
|
||||
QJavalinAccessLogger.logStart("delete", logPair("table", tableName));
|
||||
|
||||
deleteInput.setTableName(tableName);
|
||||
deleteInput.setPrimaryKeys(List.of(primaryKey));
|
||||
|
||||
PermissionsHelper.checkTablePermissionThrowing(deleteInput, TablePermissionSubType.DELETE);
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// throw a not found error if the record isn't found //
|
||||
///////////////////////////////////////////////////////
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(tableName);
|
||||
getInput.setPrimaryKey(primaryKey);
|
||||
GetAction getAction = new GetAction();
|
||||
GetOutput getOutput = getAction.execute(getInput);
|
||||
if(getOutput.getRecord() == null)
|
||||
{
|
||||
throw (new QNotFoundException("Could not find " + table.getLabel() + " with "
|
||||
+ table.getFields().get(table.getPrimaryKeyField()).getLabel() + " of " + primaryKey));
|
||||
}
|
||||
|
||||
///////////////////
|
||||
// do the delete //
|
||||
///////////////////
|
||||
DeleteAction deleteAction = new DeleteAction();
|
||||
DeleteOutput deleteOutput = deleteAction.execute(deleteInput);
|
||||
if(CollectionUtils.nullSafeHasContents(deleteOutput.getRecordsWithErrors()))
|
||||
{
|
||||
throw (new QException("Error deleting " + table.getLabel() + ": " + StringUtils.joinWithCommasAndAnd(deleteOutput.getRecordsWithErrors().get(0).getErrors())));
|
||||
}
|
||||
|
||||
QJavalinAccessLogger.logEndSuccess();
|
||||
context.status(HttpStatus.Code.NO_CONTENT.getCode());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
QJavalinAccessLogger.logEndFail(e);
|
||||
handleException(context, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -655,16 +859,52 @@ public class QJavalinApiHandler
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static LinkedHashMap<String, Serializable> toApiRecord(QRecord record, List<? extends QFieldMetaData> tableApiFields)
|
||||
private static LinkedHashMap<String, Serializable> toApiRecord(QRecord record, String tableName, String apiVersion) throws QException
|
||||
{
|
||||
LinkedHashMap<String, Serializable> outputRecord = new LinkedHashMap<>();
|
||||
List<? extends QFieldMetaData> tableApiFields = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(apiVersion)).getFields();
|
||||
LinkedHashMap<String, Serializable> outputRecord = new LinkedHashMap<>();
|
||||
for(QFieldMetaData tableApiField : tableApiFields)
|
||||
{
|
||||
// todo - what about display values / possible values
|
||||
// todo - handle removed-from-this-version fields!!
|
||||
outputRecord.put(tableApiField.getName(), record.getValue(tableApiField.getName()));
|
||||
}
|
||||
return outputRecord;
|
||||
return (outputRecord);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static QRecord toQRecord(JSONObject jsonObject, String tableName, String apiVersion) throws QException
|
||||
{
|
||||
List<String> unrecognizedFieldNames = new ArrayList<>();
|
||||
|
||||
List<? extends QFieldMetaData> tableApiFields = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(apiVersion)).getFields();
|
||||
Map<String, ? extends QFieldMetaData> apiFieldsMap = tableApiFields.stream().collect(Collectors.toMap(f -> f.getName(), f -> f));
|
||||
|
||||
QRecord qRecord = new QRecord();
|
||||
|
||||
for(String jsonKey : jsonObject.keySet())
|
||||
{
|
||||
if(apiFieldsMap.containsKey(jsonKey))
|
||||
{
|
||||
QFieldMetaData field = apiFieldsMap.get(jsonKey);
|
||||
qRecord.setValue(field.getName(), jsonObject.get(jsonKey));
|
||||
}
|
||||
else
|
||||
{
|
||||
unrecognizedFieldNames.add(jsonKey);
|
||||
}
|
||||
}
|
||||
|
||||
if(!unrecognizedFieldNames.isEmpty())
|
||||
{
|
||||
throw (new QBadRequestException("Request body contained " + unrecognizedFieldNames.size() + " unrecognized field name" + StringUtils.plural(unrecognizedFieldNames) + ": " + StringUtils.joinWithCommasAndAnd(unrecognizedFieldNames)));
|
||||
}
|
||||
|
||||
return (qRecord);
|
||||
}
|
||||
|
||||
|
||||
@ -709,6 +949,12 @@ public class QJavalinApiHandler
|
||||
}
|
||||
else
|
||||
{
|
||||
if(e instanceof ApiPathNotFoundException)
|
||||
{
|
||||
respondWithError(context, HttpStatus.Code.NOT_FOUND, e.getMessage()); // 404
|
||||
return;
|
||||
}
|
||||
|
||||
if(e instanceof QAuthenticationException)
|
||||
{
|
||||
respondWithError(context, HttpStatus.Code.UNAUTHORIZED, e.getMessage()); // 401
|
||||
|
Reference in New Issue
Block a user