diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java
index ed46a8ad..efa0ea6b 100644
--- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java
@@ -63,12 +63,10 @@ import com.kingsrook.qqq.backend.core.actions.values.SearchPossibleValueSourceAc
import com.kingsrook.qqq.backend.core.adapters.QInstanceAdapter;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QAuthenticationException;
-import com.kingsrook.qqq.backend.core.exceptions.QBadRequestException;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException;
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
-import com.kingsrook.qqq.backend.core.exceptions.QPermissionDeniedException;
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
import com.kingsrook.qqq.backend.core.logging.QLogger;
@@ -1709,7 +1707,7 @@ public class QJavalinImplementation
}
catch(QUserFacingException e)
{
- handleException(HttpStatus.Code.BAD_REQUEST, context, e);
+ QJavalinUtils.handleException(HttpStatus.Code.BAD_REQUEST, context, e);
return null;
}
return reportFormat;
@@ -1849,67 +1847,7 @@ public class QJavalinImplementation
*******************************************************************************/
public static void handleException(Context context, Exception e)
{
- handleException(null, context, e);
- }
-
-
-
- /*******************************************************************************
- **
- *******************************************************************************/
- public static void handleException(HttpStatus.Code statusCode, Context context, Exception e)
- {
- QUserFacingException userFacingException = ExceptionUtils.findClassInRootChain(e, QUserFacingException.class);
- if(userFacingException != null)
- {
- if(userFacingException instanceof QNotFoundException)
- {
- statusCode = Objects.requireNonNullElse(statusCode, HttpStatus.Code.NOT_FOUND); // 404
- respondWithError(context, statusCode, userFacingException.getMessage());
- }
- else if(userFacingException instanceof QBadRequestException)
- {
- statusCode = Objects.requireNonNullElse(statusCode, HttpStatus.Code.BAD_REQUEST); // 400
- respondWithError(context, statusCode, userFacingException.getMessage());
- }
- else
- {
- LOG.info("User-facing exception", e);
- statusCode = Objects.requireNonNullElse(statusCode, HttpStatus.Code.INTERNAL_SERVER_ERROR); // 500
- respondWithError(context, statusCode, userFacingException.getMessage());
- }
- }
- else
- {
- if(e instanceof QAuthenticationException)
- {
- respondWithError(context, HttpStatus.Code.UNAUTHORIZED, e.getMessage()); // 401
- return;
- }
-
- if(e instanceof QPermissionDeniedException)
- {
- respondWithError(context, HttpStatus.Code.FORBIDDEN, e.getMessage()); // 403
- return;
- }
-
- ////////////////////////////////
- // default exception handling //
- ////////////////////////////////
- LOG.warn("Exception in javalin request", e);
- respondWithError(context, HttpStatus.Code.INTERNAL_SERVER_ERROR, e.getClass().getSimpleName() + " (" + e.getMessage() + ")"); // 500
- }
- }
-
-
-
- /*******************************************************************************
- **
- *******************************************************************************/
- public static void respondWithError(Context context, HttpStatus.Code statusCode, String errorMessage)
- {
- context.status(statusCode.getCode());
- context.result(JsonUtils.toJson(Map.of("error", errorMessage)));
+ QJavalinUtils.handleException(null, context, e);
}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java
index 5863716c..bb00bfab 100644
--- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java
@@ -236,13 +236,13 @@ public class QJavalinProcessHandler
if(inputField.getIsRequired() && !setValue)
{
- QJavalinImplementation.respondWithError(context, HttpStatus.Code.BAD_REQUEST, "Missing query param value for required input field: [" + inputField.getName() + "]");
+ QJavalinUtils.respondWithError(context, HttpStatus.Code.BAD_REQUEST, "Missing query param value for required input field: [" + inputField.getName() + "]");
return;
}
}
catch(Exception e)
{
- QJavalinImplementation.respondWithError(context, HttpStatus.Code.BAD_REQUEST, "Error processing query param [" + inputField.getName() + "]: " + e.getClass().getSimpleName() + " (" + e.getMessage() + ")");
+ QJavalinUtils.respondWithError(context, HttpStatus.Code.BAD_REQUEST, "Error processing query param [" + inputField.getName() + "]: " + e.getClass().getSimpleName() + " (" + e.getMessage() + ")");
return;
}
}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinUtils.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinUtils.java
index daa3fe8b..f335601d 100644
--- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinUtils.java
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinUtils.java
@@ -22,11 +22,21 @@
package com.kingsrook.qqq.backend.javalin;
+import java.util.Map;
import java.util.Objects;
+import com.kingsrook.qqq.backend.core.exceptions.QAuthenticationException;
+import com.kingsrook.qqq.backend.core.exceptions.QBadRequestException;
+import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
+import com.kingsrook.qqq.backend.core.exceptions.QPermissionDeniedException;
+import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
import com.kingsrook.qqq.backend.core.exceptions.QValueException;
+import com.kingsrook.qqq.backend.core.logging.QLogger;
+import com.kingsrook.qqq.backend.core.utils.ExceptionUtils;
+import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import io.javalin.http.Context;
+import org.eclipse.jetty.http.HttpStatus;
/*******************************************************************************
@@ -34,6 +44,10 @@ import io.javalin.http.Context;
*******************************************************************************/
public class QJavalinUtils
{
+ private static final QLogger LOG = QLogger.getLogger(QJavalinUtils.class);
+
+
+
/*******************************************************************************
** Returns Integer if context has a valid int query parameter by the given name,
@@ -178,4 +192,64 @@ public class QJavalinUtils
return value;
}
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static void handleException(HttpStatus.Code statusCode, Context context, Exception e)
+ {
+ QUserFacingException userFacingException = ExceptionUtils.findClassInRootChain(e, QUserFacingException.class);
+ if(userFacingException != null)
+ {
+ if(userFacingException instanceof QNotFoundException)
+ {
+ statusCode = Objects.requireNonNullElse(statusCode, HttpStatus.Code.NOT_FOUND); // 404
+ respondWithError(context, statusCode, userFacingException.getMessage());
+ }
+ else if(userFacingException instanceof QBadRequestException)
+ {
+ statusCode = Objects.requireNonNullElse(statusCode, HttpStatus.Code.BAD_REQUEST); // 400
+ respondWithError(context, statusCode, userFacingException.getMessage());
+ }
+ else
+ {
+ LOG.info("User-facing exception", e);
+ statusCode = Objects.requireNonNullElse(statusCode, HttpStatus.Code.INTERNAL_SERVER_ERROR); // 500
+ respondWithError(context, statusCode, userFacingException.getMessage());
+ }
+ }
+ else
+ {
+ if(e instanceof QAuthenticationException)
+ {
+ respondWithError(context, HttpStatus.Code.UNAUTHORIZED, e.getMessage()); // 401
+ return;
+ }
+
+ if(e instanceof QPermissionDeniedException)
+ {
+ respondWithError(context, HttpStatus.Code.FORBIDDEN, e.getMessage()); // 403
+ return;
+ }
+
+ ////////////////////////////////
+ // default exception handling //
+ ////////////////////////////////
+ LOG.warn("Exception in javalin request", e);
+ respondWithError(context, HttpStatus.Code.INTERNAL_SERVER_ERROR, e.getClass().getSimpleName() + " (" + e.getMessage() + ")"); // 500
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static void respondWithError(Context context, HttpStatus.Code statusCode, String errorMessage)
+ {
+ context.status(statusCode.getCode());
+ context.result(JsonUtils.toJson(Map.of("error", errorMessage)));
+ }
}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/AbstractMiddlewareExecutor.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/AbstractMiddlewareExecutor.java
new file mode 100644
index 00000000..c83a98d5
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/AbstractMiddlewareExecutor.java
@@ -0,0 +1,41 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.executors;
+
+
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.middleware.javalin.executors.io.AbstractMiddlewareInput;
+import com.kingsrook.qqq.middleware.javalin.executors.io.AbstractMiddlewareOutputInterface;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public abstract class AbstractMiddlewareExecutor
+{
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public abstract void execute(INPUT input, OUTPUT output) throws QException;
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/AuthenticationMetaDataExecutor.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/AuthenticationMetaDataExecutor.java
new file mode 100644
index 00000000..a630feb7
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/AuthenticationMetaDataExecutor.java
@@ -0,0 +1,46 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.executors;
+
+
+import com.kingsrook.qqq.backend.core.context.QContext;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.middleware.javalin.executors.io.AuthenticationMetaDataInput;
+import com.kingsrook.qqq.middleware.javalin.executors.io.AuthenticationMetaDataOutputInterface;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class AuthenticationMetaDataExecutor extends AbstractMiddlewareExecutor
+{
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void execute(AuthenticationMetaDataInput input, AuthenticationMetaDataOutputInterface output) throws QException
+ {
+ output.setAuthenticationMetaData(QContext.getQInstance().getAuthentication());
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/ExecutorSessionUtils.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/ExecutorSessionUtils.java
new file mode 100644
index 00000000..7a379fd5
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/ExecutorSessionUtils.java
@@ -0,0 +1,224 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.executors;
+
+
+import java.util.HashMap;
+import java.util.Map;
+import com.kingsrook.qqq.backend.core.context.QContext;
+import com.kingsrook.qqq.backend.core.exceptions.QAuthenticationException;
+import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException;
+import com.kingsrook.qqq.backend.core.logging.QLogger;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.model.session.QSession;
+import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleDispatcher;
+import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface;
+import com.kingsrook.qqq.backend.core.modules.authentication.implementations.Auth0AuthenticationModule;
+import com.kingsrook.qqq.backend.core.utils.StringUtils;
+import io.javalin.http.Context;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class ExecutorSessionUtils
+{
+ private static final QLogger LOG = QLogger.getLogger(ExecutorSessionUtils.class);
+
+ public static final int SESSION_COOKIE_AGE = 60 * 60 * 24;
+ public static final String SESSION_ID_COOKIE_NAME = "sessionId";
+ public static final String API_KEY_NAME = "apiKey";
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static QSession setupSession(Context context, QInstance qInstance) throws QModuleDispatchException, QAuthenticationException
+ {
+ QAuthenticationModuleDispatcher qAuthenticationModuleDispatcher = new QAuthenticationModuleDispatcher();
+ QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(qInstance.getAuthentication());
+
+ try
+ {
+ Map authenticationContext = new HashMap<>();
+
+ String sessionIdCookieValue = context.cookie(SESSION_ID_COOKIE_NAME);
+ String sessionUuidCookieValue = context.cookie(Auth0AuthenticationModule.SESSION_UUID_KEY);
+ String authorizationHeaderValue = context.header("Authorization");
+ String apiKeyHeaderValue = context.header("x-api-key");
+
+ if(StringUtils.hasContent(sessionIdCookieValue))
+ {
+ ///////////////////////////////////////////////////////
+ // sessionId - maybe used by table-based auth module //
+ ///////////////////////////////////////////////////////
+ authenticationContext.put(SESSION_ID_COOKIE_NAME, sessionIdCookieValue);
+ }
+ else if(StringUtils.hasContent(sessionUuidCookieValue))
+ {
+ ///////////////////////////////////////////////////////////////////////////
+ // session UUID - known to be used by auth0 module (in aug. 2023 update) //
+ ///////////////////////////////////////////////////////////////////////////
+ authenticationContext.put(Auth0AuthenticationModule.SESSION_UUID_KEY, sessionUuidCookieValue);
+ }
+ else if(apiKeyHeaderValue != null)
+ {
+ /////////////////////////////////////////////////////////////////
+ // next, look for an api key header: //
+ // this will be used to look up auth0 values via an auth table //
+ /////////////////////////////////////////////////////////////////
+ authenticationContext.put(API_KEY_NAME, apiKeyHeaderValue);
+ }
+ else if(authorizationHeaderValue != null)
+ {
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+ // second, look for the authorization header: //
+ // either with a "Basic " prefix (for a username:password pair) //
+ // or with a "Bearer " prefix (for a token that can be handled the same as a sessionId cookie) //
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+ processAuthorizationValue(authenticationContext, authorizationHeaderValue);
+ }
+ else
+ {
+ try
+ {
+ String authorizationFormValue = context.formParam("Authorization");
+ if(StringUtils.hasContent(authorizationFormValue))
+ {
+ processAuthorizationValue(authenticationContext, authorizationFormValue);
+ }
+ }
+ catch(Exception e)
+ {
+ LOG.info("Exception looking for Authorization formParam", e);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // put the qInstance into context - but no session yet (since, the whole point of this call is to setup the session!) //
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ QContext.init(qInstance, null);
+ QSession session = authenticationModule.createSession(qInstance, authenticationContext);
+ QContext.init(qInstance, session, null, null);
+
+ ///////////////////////////////////////////////////////////////////////////////
+ // todo - this must be moved ... not exactly sure where, but into some spec. //
+ ///////////////////////////////////////////////////////////////////////////////
+ // String tableVariant = QJavalinUtils.getFormParamOrQueryParam(context, "tableVariant");
+ // if(StringUtils.hasContent(tableVariant))
+ // {
+ // JSONObject variant = new JSONObject(tableVariant);
+ // QContext.getQSession().setBackendVariants(MapBuilder.of(variant.getString("type"), variant.getInt("id")));
+ // }
+
+ /////////////////////////////////////////////////////////////////////////////////
+ // if we got a session id cookie in, then send it back with updated cookie age //
+ /////////////////////////////////////////////////////////////////////////////////
+ if(authenticationModule.usesSessionIdCookie())
+ {
+ context.cookie(SESSION_ID_COOKIE_NAME, session.getIdReference(), SESSION_COOKIE_AGE);
+ }
+
+ setUserTimezoneOffsetMinutesInSession(context, session);
+ setUserTimezoneInSession(context, session);
+
+ return (session);
+ }
+ catch(QAuthenticationException qae)
+ {
+ ////////////////////////////////////////////////////////////////////////////////
+ // if exception caught, clear out the cookie so the frontend will reauthorize //
+ ////////////////////////////////////////////////////////////////////////////////
+ if(authenticationModule.usesSessionIdCookie())
+ {
+ context.removeCookie(SESSION_ID_COOKIE_NAME);
+ }
+
+ throw (qae);
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private static void processAuthorizationValue(Map authenticationContext, String authorizationHeaderValue)
+ {
+ String basicPrefix = "Basic ";
+ String bearerPrefix = "Bearer ";
+ if(authorizationHeaderValue.startsWith(basicPrefix))
+ {
+ authorizationHeaderValue = authorizationHeaderValue.replaceFirst(basicPrefix, "");
+ authenticationContext.put(Auth0AuthenticationModule.BASIC_AUTH_KEY, authorizationHeaderValue);
+ }
+ else if(authorizationHeaderValue.startsWith(bearerPrefix))
+ {
+ authorizationHeaderValue = authorizationHeaderValue.replaceFirst(bearerPrefix, "");
+ authenticationContext.put(Auth0AuthenticationModule.ACCESS_TOKEN_KEY, authorizationHeaderValue);
+ }
+ else
+ {
+ LOG.debug("Authorization value did not have Basic or Bearer prefix. [" + authorizationHeaderValue + "]");
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private static void setUserTimezoneOffsetMinutesInSession(Context context, QSession session)
+ {
+ String userTimezoneOffsetMinutes = context.header("X-QQQ-UserTimezoneOffsetMinutes");
+ if(StringUtils.hasContent(userTimezoneOffsetMinutes))
+ {
+ try
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // even though we're putting it in the session as a string, go through parse int, to make sure it's a valid int. //
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ session.setValue(QSession.VALUE_KEY_USER_TIMEZONE_OFFSET_MINUTES, String.valueOf(Integer.parseInt(userTimezoneOffsetMinutes)));
+ }
+ catch(Exception e)
+ {
+ LOG.debug("Received non-integer value for X-QQQ-UserTimezoneOffsetMinutes header: " + userTimezoneOffsetMinutes);
+ }
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private static void setUserTimezoneInSession(Context context, QSession session)
+ {
+ String userTimezone = context.header("X-QQQ-UserTimezone");
+ if(StringUtils.hasContent(userTimezone))
+ {
+ session.setValue(QSession.VALUE_KEY_USER_TIMEZONE, userTimezone);
+ }
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/ManageSessionExecutor.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/ManageSessionExecutor.java
new file mode 100644
index 00000000..64beb3cd
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/ManageSessionExecutor.java
@@ -0,0 +1,75 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.executors;
+
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import com.kingsrook.qqq.backend.core.context.QContext;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.session.QSession;
+import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleDispatcher;
+import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface;
+import com.kingsrook.qqq.backend.core.modules.authentication.implementations.Auth0AuthenticationModule;
+import com.kingsrook.qqq.middleware.javalin.executors.io.ManageSessionInput;
+import com.kingsrook.qqq.middleware.javalin.executors.io.ManageSessionOutputInterface;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class ManageSessionExecutor extends AbstractMiddlewareExecutor
+{
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void execute(ManageSessionInput input, ManageSessionOutputInterface output) throws QException
+ {
+ QAuthenticationModuleDispatcher qAuthenticationModuleDispatcher = new QAuthenticationModuleDispatcher();
+ QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(QContext.getQInstance().getAuthentication());
+
+ Map authContext = new HashMap<>();
+ authContext.put(Auth0AuthenticationModule.ACCESS_TOKEN_KEY, input.getAccessToken());
+ authContext.put(Auth0AuthenticationModule.DO_STORE_USER_SESSION_KEY, "true");
+
+ /////////////////////////////////
+ // (try to) create the session //
+ /////////////////////////////////
+ QSession session = authenticationModule.createSession(QContext.getQInstance(), authContext);
+
+ //////////////////
+ // build output //
+ //////////////////
+ output.setUuid(session.getUuid());
+
+ if(session.getValuesForFrontend() != null)
+ {
+ LinkedHashMap valuesForFrontend = new LinkedHashMap<>(session.getValuesForFrontend());
+ output.setValues(valuesForFrontend);
+ }
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/MetaDataExecutor.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/MetaDataExecutor.java
new file mode 100644
index 00000000..8daeb8dd
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/MetaDataExecutor.java
@@ -0,0 +1,49 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.executors;
+
+
+import com.kingsrook.qqq.backend.core.actions.metadata.MetaDataAction;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
+import com.kingsrook.qqq.middleware.javalin.executors.io.MetaDataInput;
+import com.kingsrook.qqq.middleware.javalin.executors.io.MetaDataOutputInterface;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class MetaDataExecutor extends AbstractMiddlewareExecutor
+{
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void execute(MetaDataInput input, MetaDataOutputInterface output) throws QException
+ {
+ MetaDataAction metaDataAction = new MetaDataAction();
+ MetaDataOutput metaDataOutput = metaDataAction.execute(new com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput());
+ output.setMetaDataOutput(metaDataOutput);
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/ProcessInitOrStepExecutor.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/ProcessInitOrStepExecutor.java
new file mode 100644
index 00000000..adaededc
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/ProcessInitOrStepExecutor.java
@@ -0,0 +1,186 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.executors;
+
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import com.kingsrook.qqq.backend.core.actions.async.AsyncJobManager;
+import com.kingsrook.qqq.backend.core.actions.async.JobGoingAsyncException;
+import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper;
+import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallback;
+import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
+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.QPermissionDeniedException;
+import com.kingsrook.qqq.backend.core.logging.QLogger;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
+import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
+import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
+import com.kingsrook.qqq.backend.core.utils.StringUtils;
+import com.kingsrook.qqq.backend.core.utils.ValueUtils;
+import com.kingsrook.qqq.backend.javalin.QJavalinAccessLogger;
+import com.kingsrook.qqq.middleware.javalin.executors.io.ProcessInitOrStepInput;
+import com.kingsrook.qqq.middleware.javalin.executors.io.ProcessInitOrStepOrStatusOutputInterface;
+import com.kingsrook.qqq.middleware.javalin.executors.utils.ProcessExecutorUtils;
+import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class ProcessInitOrStepExecutor extends AbstractMiddlewareExecutor
+{
+ private static final QLogger LOG = QLogger.getLogger(ProcessInitOrStepExecutor.class);
+
+
+
+ /***************************************************************************
+ ** Note: implementation of the output interface here, it wants to know what
+ ** type it's going to be first, so, be polite and always call .setType before
+ ** any other setters.
+ ***************************************************************************/
+ @Override
+ public void execute(ProcessInitOrStepInput input, ProcessInitOrStepOrStatusOutputInterface output) throws QException
+ {
+ Exception returningException = null;
+
+ String processName = input.getProcessName();
+ String startAfterStep = input.getStartAfterStep();
+ String processUUID = input.getProcessUUID();
+
+ if(processUUID == null)
+ {
+ processUUID = UUID.randomUUID().toString();
+ }
+
+ LOG.info(startAfterStep == null ? "Initiating process [" + processName + "] [" + processUUID + "]"
+ : "Resuming process [" + processName + "] [" + processUUID + "] after step [" + startAfterStep + "]");
+
+ try
+ {
+ RunProcessInput runProcessInput = new RunProcessInput();
+ QContext.pushAction(runProcessInput);
+
+ runProcessInput.setProcessName(processName);
+ runProcessInput.setFrontendStepBehavior(input.getFrontendStepBehavior());
+ runProcessInput.setProcessUUID(processUUID);
+ runProcessInput.setStartAfterStep(startAfterStep);
+ runProcessInput.setValues(Objects.requireNonNullElseGet(input.getValues(), HashMap::new));
+
+ if(input.getRecordsFilter() != null)
+ {
+ runProcessInput.setCallback(new QProcessCallback()
+ {
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public QQueryFilter getQueryFilter()
+ {
+ return (input.getRecordsFilter());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public Map getFieldValues(List fields)
+ {
+ return (Collections.emptyMap());
+ }
+ });
+ }
+
+ String reportName = ValueUtils.getValueAsString(runProcessInput.getValue("reportName"));
+ QJavalinAccessLogger.logStart(startAfterStep == null ? "processInit" : "processStep", logPair("processName", processName), logPair("processUUID", processUUID),
+ StringUtils.hasContent(startAfterStep) ? logPair("startAfterStep", startAfterStep) : null,
+ StringUtils.hasContent(reportName) ? logPair("reportName", reportName) : null);
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////
+ // important to do this check AFTER the runProcessInput is populated with values from context - //
+ // e.g., in case things like a reportName are set in here //
+ //////////////////////////////////////////////////////////////////////////////////////////////////
+ PermissionsHelper.checkProcessPermissionThrowing(runProcessInput, processName);
+
+ ////////////////////////////////////////
+ // run the process as an async action //
+ ////////////////////////////////////////
+ RunProcessOutput runProcessOutput = new AsyncJobManager().startJob(processName, input.getStepTimeoutMillis(), TimeUnit.MILLISECONDS, (callback) ->
+ {
+ runProcessInput.setAsyncJobCallback(callback);
+ return (new RunProcessAction().execute(runProcessInput));
+ });
+
+ LOG.debug("Process result error? " + runProcessOutput.getException());
+ for(QFieldMetaData outputField : QContext.getQInstance().getProcess(runProcessInput.getProcessName()).getOutputFields())
+ {
+ LOG.debug("Process result output value: " + outputField.getName() + ": " + runProcessOutput.getValues().get(outputField.getName()));
+ }
+
+ ProcessExecutorUtils.serializeRunProcessResultForCaller(output, processName, runProcessOutput);
+ QJavalinAccessLogger.logProcessSummary(processName, processUUID, runProcessOutput);
+ }
+ catch(JobGoingAsyncException jgae)
+ {
+ output.setType(ProcessInitOrStepOrStatusOutputInterface.Type.JOB_STARTED);
+ output.setJobUUID(jgae.getJobUUID());
+ }
+ catch(QPermissionDeniedException | QAuthenticationException e)
+ {
+ throw (e);
+ }
+ catch(Exception e)
+ {
+ //////////////////////////////////////////////////////////////////////////////
+ // our other actions in here would do: handleException(context, e); //
+ // which would return a 500 to the client. //
+ // but - other process-step actions, they always return a 200, just with an //
+ // optional error message - so - keep all of the processes consistent. //
+ //////////////////////////////////////////////////////////////////////////////
+ returningException = e;
+ ProcessExecutorUtils.serializeRunProcessExceptionForCaller(output, e);
+ }
+
+ output.setProcessUUID(processUUID);
+
+ if(returningException != null)
+ {
+ QJavalinAccessLogger.logEndFail(returningException);
+ }
+ else
+ {
+ QJavalinAccessLogger.logEndSuccess();
+ }
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/ProcessMetaDataExecutor.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/ProcessMetaDataExecutor.java
new file mode 100644
index 00000000..2b1d4e93
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/ProcessMetaDataExecutor.java
@@ -0,0 +1,65 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.executors;
+
+
+import com.kingsrook.qqq.backend.core.actions.metadata.ProcessMetaDataAction;
+import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper;
+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.ProcessMetaDataOutput;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
+import com.kingsrook.qqq.middleware.javalin.executors.io.ProcessMetaDataInput;
+import com.kingsrook.qqq.middleware.javalin.executors.io.ProcessMetaDataOutputInterface;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class ProcessMetaDataExecutor extends AbstractMiddlewareExecutor
+{
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void execute(ProcessMetaDataInput input, ProcessMetaDataOutputInterface output) throws QException
+ {
+ com.kingsrook.qqq.backend.core.model.actions.metadata.ProcessMetaDataInput processMetaDataInput = new com.kingsrook.qqq.backend.core.model.actions.metadata.ProcessMetaDataInput();
+
+ String processName = input.getProcessName();
+ QProcessMetaData process = QContext.getQInstance().getProcess(processName);
+ if(process == null)
+ {
+ throw (new QNotFoundException("Process [" + processName + "] was not found."));
+ }
+ PermissionsHelper.checkProcessPermissionThrowing(processMetaDataInput, processName);
+
+ processMetaDataInput.setProcessName(processName);
+ ProcessMetaDataAction processMetaDataAction = new ProcessMetaDataAction();
+ ProcessMetaDataOutput processMetaDataOutput = processMetaDataAction.execute(processMetaDataInput);
+
+ output.setProcessMetaData(processMetaDataOutput.getProcess());
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/ProcessStatusExecutor.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/ProcessStatusExecutor.java
new file mode 100644
index 00000000..c0396a83
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/ProcessStatusExecutor.java
@@ -0,0 +1,121 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.executors;
+
+
+import java.util.Optional;
+import com.kingsrook.qqq.backend.core.actions.async.AsyncJobManager;
+import com.kingsrook.qqq.backend.core.actions.async.AsyncJobState;
+import com.kingsrook.qqq.backend.core.actions.async.AsyncJobStatus;
+import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.logging.QLogger;
+import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessState;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
+import com.kingsrook.qqq.backend.javalin.QJavalinAccessLogger;
+import com.kingsrook.qqq.middleware.javalin.executors.io.ProcessInitOrStepOrStatusOutputInterface;
+import com.kingsrook.qqq.middleware.javalin.executors.io.ProcessStatusInput;
+import com.kingsrook.qqq.middleware.javalin.executors.utils.ProcessExecutorUtils;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class ProcessStatusExecutor extends AbstractMiddlewareExecutor
+{
+ private static final QLogger LOG = QLogger.getLogger(ProcessStatusExecutor.class);
+
+
+
+ /***************************************************************************
+ ** Note: implementation of the output interface here, it wants to know what
+ ** type it's going to be first, so, be polite and always call .setType before
+ ** any other setters.
+ ***************************************************************************/
+ @Override
+ public void execute(ProcessStatusInput input, ProcessInitOrStepOrStatusOutputInterface output) throws QException
+ {
+ try
+ {
+ String processName = input.getProcessName();
+ String processUUID = input.getProcessUUID();
+ String jobUUID = input.getJobUUID();
+
+ LOG.debug("Request for status of process " + processUUID + ", job " + jobUUID);
+ Optional optionalJobStatus = new AsyncJobManager().getJobStatus(jobUUID);
+ if(optionalJobStatus.isEmpty())
+ {
+ ProcessExecutorUtils.serializeRunProcessExceptionForCaller(output, new RuntimeException("Could not find status of process step job"));
+ }
+ else
+ {
+ AsyncJobStatus jobStatus = optionalJobStatus.get();
+
+ // resultForCaller.put("jobStatus", jobStatus);
+ LOG.debug("Job status is " + jobStatus.getState() + " for " + jobUUID);
+
+ if(jobStatus.getState().equals(AsyncJobState.COMPLETE))
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////
+ // if the job is complete, get the process result from state provider, and return it //
+ // this output should look like it did if the job finished synchronously!! //
+ ///////////////////////////////////////////////////////////////////////////////////////
+ Optional processState = RunProcessAction.getState(processUUID);
+ if(processState.isPresent())
+ {
+ RunProcessOutput runProcessOutput = new RunProcessOutput(processState.get());
+ ProcessExecutorUtils.serializeRunProcessResultForCaller(output, processName, runProcessOutput);
+ QJavalinAccessLogger.logProcessSummary(processName, processUUID, runProcessOutput);
+ }
+ else
+ {
+ ProcessExecutorUtils.serializeRunProcessExceptionForCaller(output, new RuntimeException("Could not find results for process " + processUUID));
+ }
+ }
+ else if(jobStatus.getState().equals(AsyncJobState.ERROR))
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // if the job had an error (e.g., a process step threw), "nicely" serialize its exception for the caller //
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////
+ if(jobStatus.getCaughtException() != null)
+ {
+ ProcessExecutorUtils.serializeRunProcessExceptionForCaller(output, jobStatus.getCaughtException());
+ }
+ }
+ else
+ {
+ output.setType(ProcessInitOrStepOrStatusOutputInterface.Type.RUNNING);
+ output.setMessage(jobStatus.getMessage());
+ output.setCurrent(jobStatus.getCurrent());
+ output.setTotal(jobStatus.getTotal());
+ }
+ }
+
+ output.setProcessUUID(processUUID);
+ }
+ catch(Exception e)
+ {
+ ProcessExecutorUtils.serializeRunProcessExceptionForCaller(output, e);
+ }
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/AbstractMiddlewareInput.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/AbstractMiddlewareInput.java
new file mode 100644
index 00000000..e939781e
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/AbstractMiddlewareInput.java
@@ -0,0 +1,30 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.executors.io;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class AbstractMiddlewareInput
+{
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/AbstractMiddlewareOutputInterface.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/AbstractMiddlewareOutputInterface.java
new file mode 100644
index 00000000..9bd32404
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/AbstractMiddlewareOutputInterface.java
@@ -0,0 +1,30 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.executors.io;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public interface AbstractMiddlewareOutputInterface
+{
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/AuthenticationMetaDataInput.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/AuthenticationMetaDataInput.java
new file mode 100644
index 00000000..1d97e7bd
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/AuthenticationMetaDataInput.java
@@ -0,0 +1,31 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.executors.io;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class AuthenticationMetaDataInput extends AbstractMiddlewareInput
+{
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/AuthenticationMetaDataOutputInterface.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/AuthenticationMetaDataOutputInterface.java
new file mode 100644
index 00000000..ff6d7456
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/AuthenticationMetaDataOutputInterface.java
@@ -0,0 +1,37 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.executors.io;
+
+
+import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public interface AuthenticationMetaDataOutputInterface extends AbstractMiddlewareOutputInterface
+{
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ void setAuthenticationMetaData(QAuthenticationMetaData qAuthenticationMetaData);
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/EmptyMiddlewareInput.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/EmptyMiddlewareInput.java
new file mode 100644
index 00000000..577f38a0
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/EmptyMiddlewareInput.java
@@ -0,0 +1,31 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.executors.io;
+
+
+/*******************************************************************************
+ ** generic middleware input that has no fields.
+ *******************************************************************************/
+public class EmptyMiddlewareInput extends AbstractMiddlewareInput
+{
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/EmptyMiddlewareOutputInterface.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/EmptyMiddlewareOutputInterface.java
new file mode 100644
index 00000000..a5ddb568
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/EmptyMiddlewareOutputInterface.java
@@ -0,0 +1,31 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.executors.io;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public interface EmptyMiddlewareOutputInterface extends AbstractMiddlewareOutputInterface
+{
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/ManageSessionInput.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/ManageSessionInput.java
new file mode 100644
index 00000000..4fbc0124
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/ManageSessionInput.java
@@ -0,0 +1,63 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.executors.io;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class ManageSessionInput extends AbstractMiddlewareInput
+{
+ private String accessToken;
+
+
+
+ /*******************************************************************************
+ ** Getter for accessToken
+ *******************************************************************************/
+ public String getAccessToken()
+ {
+ return (this.accessToken);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for accessToken
+ *******************************************************************************/
+ public void setAccessToken(String accessToken)
+ {
+ this.accessToken = accessToken;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for accessToken
+ *******************************************************************************/
+ public ManageSessionInput withAccessToken(String accessToken)
+ {
+ this.accessToken = accessToken;
+ return (this);
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/ManageSessionOutputInterface.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/ManageSessionOutputInterface.java
new file mode 100644
index 00000000..676661a5
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/ManageSessionOutputInterface.java
@@ -0,0 +1,45 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.executors.io;
+
+
+import java.io.Serializable;
+import java.util.Map;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public interface ManageSessionOutputInterface extends AbstractMiddlewareOutputInterface
+{
+ /***************************************************************************
+ ** Setter for Uuid
+ ***************************************************************************/
+ void setUuid(String uuid);
+
+
+ /*******************************************************************************
+ ** Setter for values
+ *******************************************************************************/
+ void setValues(Map values);
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/MetaDataInput.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/MetaDataInput.java
new file mode 100644
index 00000000..bdec2641
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/MetaDataInput.java
@@ -0,0 +1,31 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.executors.io;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class MetaDataInput extends AbstractMiddlewareInput
+{
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/MetaDataOutputInterface.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/MetaDataOutputInterface.java
new file mode 100644
index 00000000..748805e1
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/MetaDataOutputInterface.java
@@ -0,0 +1,39 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.executors.io;
+
+
+import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public interface MetaDataOutputInterface extends AbstractMiddlewareOutputInterface
+{
+
+ /*******************************************************************************
+ ** Setter for metaDataOutput
+ *******************************************************************************/
+ void setMetaDataOutput(MetaDataOutput metaDataOutput);
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/ProcessInitOrStepInput.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/ProcessInitOrStepInput.java
new file mode 100644
index 00000000..c138b0bc
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/ProcessInitOrStepInput.java
@@ -0,0 +1,295 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.executors.io;
+
+
+import java.io.Serializable;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
+import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class ProcessInitOrStepInput extends AbstractMiddlewareInput
+{
+ private String processName;
+ private Integer stepTimeoutMillis = 3000;
+ private QQueryFilter recordsFilter;
+
+ private Map values = new LinkedHashMap<>();
+
+ /////////////////////////////////////
+ // used only for 'step' (not init) //
+ /////////////////////////////////////
+ private String processUUID;
+ private String startAfterStep;
+
+ private RunProcessInput.FrontendStepBehavior frontendStepBehavior = RunProcessInput.FrontendStepBehavior.BREAK;
+
+ // todo - file??
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public enum RecordsParam
+ {
+ FILTER_JSON("filterJSON"),
+ RECORD_IDS("recordIds");
+
+
+ private final String value;
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ RecordsParam(String value)
+ {
+ this.value = value;
+ }
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for processName
+ *******************************************************************************/
+ public String getProcessName()
+ {
+ return (this.processName);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for processName
+ *******************************************************************************/
+ public void setProcessName(String processName)
+ {
+ this.processName = processName;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for processName
+ *******************************************************************************/
+ public ProcessInitOrStepInput withProcessName(String processName)
+ {
+ this.processName = processName;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for stepTimeoutMillis
+ *******************************************************************************/
+ public Integer getStepTimeoutMillis()
+ {
+ return (this.stepTimeoutMillis);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for stepTimeoutMillis
+ *******************************************************************************/
+ public void setStepTimeoutMillis(Integer stepTimeoutMillis)
+ {
+ this.stepTimeoutMillis = stepTimeoutMillis;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for stepTimeoutMillis
+ *******************************************************************************/
+ public ProcessInitOrStepInput withStepTimeoutMillis(Integer stepTimeoutMillis)
+ {
+ this.stepTimeoutMillis = stepTimeoutMillis;
+ return (this);
+ }
+
+
+
+
+ /*******************************************************************************
+ ** Getter for recordsFilter
+ *******************************************************************************/
+ public QQueryFilter getRecordsFilter()
+ {
+ return (this.recordsFilter);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for recordsFilter
+ *******************************************************************************/
+ public void setRecordsFilter(QQueryFilter recordsFilter)
+ {
+ this.recordsFilter = recordsFilter;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for recordsFilter
+ *******************************************************************************/
+ public ProcessInitOrStepInput withRecordsFilter(QQueryFilter recordsFilter)
+ {
+ this.recordsFilter = recordsFilter;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for values
+ *******************************************************************************/
+ public Map getValues()
+ {
+ return (this.values);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for values
+ *******************************************************************************/
+ public void setValues(Map values)
+ {
+ this.values = values;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for values
+ *******************************************************************************/
+ public ProcessInitOrStepInput withValues(Map values)
+ {
+ this.values = values;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for frontendStepBehavior
+ *******************************************************************************/
+ public RunProcessInput.FrontendStepBehavior getFrontendStepBehavior()
+ {
+ return (this.frontendStepBehavior);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for frontendStepBehavior
+ *******************************************************************************/
+ public void setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior frontendStepBehavior)
+ {
+ this.frontendStepBehavior = frontendStepBehavior;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for frontendStepBehavior
+ *******************************************************************************/
+ public ProcessInitOrStepInput withFrontendStepBehavior(RunProcessInput.FrontendStepBehavior frontendStepBehavior)
+ {
+ this.frontendStepBehavior = frontendStepBehavior;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for processUUID
+ *******************************************************************************/
+ public String getProcessUUID()
+ {
+ return (this.processUUID);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for processUUID
+ *******************************************************************************/
+ public void setProcessUUID(String processUUID)
+ {
+ this.processUUID = processUUID;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for processUUID
+ *******************************************************************************/
+ public ProcessInitOrStepInput withProcessUUID(String processUUID)
+ {
+ this.processUUID = processUUID;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for startAfterStep
+ *******************************************************************************/
+ public String getStartAfterStep()
+ {
+ return (this.startAfterStep);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for startAfterStep
+ *******************************************************************************/
+ public void setStartAfterStep(String startAfterStep)
+ {
+ this.startAfterStep = startAfterStep;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for startAfterStep
+ *******************************************************************************/
+ public ProcessInitOrStepInput withStartAfterStep(String startAfterStep)
+ {
+ this.startAfterStep = startAfterStep;
+ return (this);
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/ProcessInitOrStepOrStatusOutputInterface.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/ProcessInitOrStepOrStatusOutputInterface.java
new file mode 100644
index 00000000..f7d0d4a5
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/ProcessInitOrStepOrStatusOutputInterface.java
@@ -0,0 +1,100 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.executors.io;
+
+
+import java.io.Serializable;
+import java.util.Map;
+import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessMetaDataAdjustment;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public interface ProcessInitOrStepOrStatusOutputInterface extends AbstractMiddlewareOutputInterface
+{
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ enum Type
+ {
+ COMPLETE, JOB_STARTED, RUNNING, ERROR;
+ }
+
+
+ /*******************************************************************************
+ ** Setter for type
+ *******************************************************************************/
+ void setType(Type type);
+
+ /*******************************************************************************
+ ** Setter for processUUID
+ *******************************************************************************/
+ void setProcessUUID(String processUUID);
+
+ /*******************************************************************************
+ ** Setter for nextStep
+ *******************************************************************************/
+ void setNextStep(String nextStep);
+
+ /*******************************************************************************
+ ** Setter for values
+ *******************************************************************************/
+ void setValues(Map values);
+
+ /*******************************************************************************
+ ** Setter for jobUUID
+ *******************************************************************************/
+ void setJobUUID(String jobUUID);
+
+ /*******************************************************************************
+ ** Setter for message
+ *******************************************************************************/
+ void setMessage(String message);
+
+ /*******************************************************************************
+ ** Setter for current
+ *******************************************************************************/
+ void setCurrent(Integer current);
+
+ /*******************************************************************************
+ ** Setter for total
+ *******************************************************************************/
+ void setTotal(Integer total);
+
+ /*******************************************************************************
+ ** Setter for error
+ *******************************************************************************/
+ void setError(String error);
+
+ /*******************************************************************************
+ ** Setter for userFacingError
+ *******************************************************************************/
+ void setUserFacingError(String userFacingError);
+
+ /*******************************************************************************
+ ** Setter for processMetaDataAdjustment
+ *******************************************************************************/
+ void setProcessMetaDataAdjustment(ProcessMetaDataAdjustment processMetaDataAdjustment);
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/ProcessMetaDataInput.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/ProcessMetaDataInput.java
new file mode 100644
index 00000000..0d92c427
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/ProcessMetaDataInput.java
@@ -0,0 +1,66 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.executors.io;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class ProcessMetaDataInput extends AbstractMiddlewareInput
+{
+ private String processName;
+
+
+
+ /*******************************************************************************
+ ** Getter for processName
+ **
+ *******************************************************************************/
+ public String getProcessName()
+ {
+ return processName;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for processName
+ **
+ *******************************************************************************/
+ public void setProcessName(String processName)
+ {
+ this.processName = processName;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for processName
+ **
+ *******************************************************************************/
+ public ProcessMetaDataInput withProcessName(String processName)
+ {
+ this.processName = processName;
+ return (this);
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/ProcessMetaDataOutputInterface.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/ProcessMetaDataOutputInterface.java
new file mode 100644
index 00000000..eb92f147
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/ProcessMetaDataOutputInterface.java
@@ -0,0 +1,37 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.executors.io;
+
+
+import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendProcessMetaData;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public interface ProcessMetaDataOutputInterface extends AbstractMiddlewareOutputInterface
+{
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ void setProcessMetaData(QFrontendProcessMetaData processMetaData);
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/ProcessStatusInput.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/ProcessStatusInput.java
new file mode 100644
index 00000000..2406d5a3
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/ProcessStatusInput.java
@@ -0,0 +1,127 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.executors.io;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class ProcessStatusInput extends AbstractMiddlewareInput
+{
+ private String processName;
+ private String processUUID;
+ private String jobUUID;
+
+
+
+ /*******************************************************************************
+ ** Getter for processName
+ *******************************************************************************/
+ public String getProcessName()
+ {
+ return (this.processName);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for processName
+ *******************************************************************************/
+ public void setProcessName(String processName)
+ {
+ this.processName = processName;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for processName
+ *******************************************************************************/
+ public ProcessStatusInput withProcessName(String processName)
+ {
+ this.processName = processName;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for processUUID
+ *******************************************************************************/
+ public String getProcessUUID()
+ {
+ return (this.processUUID);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for processUUID
+ *******************************************************************************/
+ public void setProcessUUID(String processUUID)
+ {
+ this.processUUID = processUUID;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for processUUID
+ *******************************************************************************/
+ public ProcessStatusInput withProcessUUID(String processUUID)
+ {
+ this.processUUID = processUUID;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for jobUUID
+ *******************************************************************************/
+ public String getJobUUID()
+ {
+ return (this.jobUUID);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for jobUUID
+ *******************************************************************************/
+ public void setJobUUID(String jobUUID)
+ {
+ this.jobUUID = jobUUID;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for jobUUID
+ *******************************************************************************/
+ public ProcessStatusInput withJobUUID(String jobUUID)
+ {
+ this.jobUUID = jobUUID;
+ return (this);
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/ProcessStatusOutputInterface.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/ProcessStatusOutputInterface.java
new file mode 100644
index 00000000..449e7671
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/ProcessStatusOutputInterface.java
@@ -0,0 +1,31 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.executors.io;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public interface ProcessStatusOutputInterface extends AbstractMiddlewareOutputInterface
+{
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/QueryMiddlewareInput.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/QueryMiddlewareInput.java
new file mode 100644
index 00000000..4060f149
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/QueryMiddlewareInput.java
@@ -0,0 +1,132 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.executors.io;
+
+
+import java.util.List;
+import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
+import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class QueryMiddlewareInput extends AbstractMiddlewareInput
+{
+ private String table;
+ private QQueryFilter filter;
+ private List queryJoins;
+
+
+
+ /*******************************************************************************
+ ** Getter for table
+ *******************************************************************************/
+ public String getTable()
+ {
+ return (this.table);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for table
+ *******************************************************************************/
+ public void setTable(String table)
+ {
+ this.table = table;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for table
+ *******************************************************************************/
+ public QueryMiddlewareInput withTable(String table)
+ {
+ this.table = table;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for filter
+ *******************************************************************************/
+ public QQueryFilter getFilter()
+ {
+ return (this.filter);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for filter
+ *******************************************************************************/
+ public void setFilter(QQueryFilter filter)
+ {
+ this.filter = filter;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for filter
+ *******************************************************************************/
+ public QueryMiddlewareInput withFilter(QQueryFilter filter)
+ {
+ this.filter = filter;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for queryJoins
+ *******************************************************************************/
+ public List getQueryJoins()
+ {
+ return (this.queryJoins);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for queryJoins
+ *******************************************************************************/
+ public void setQueryJoins(List queryJoins)
+ {
+ this.queryJoins = queryJoins;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for queryJoins
+ *******************************************************************************/
+ public QueryMiddlewareInput withQueryJoins(List queryJoins)
+ {
+ this.queryJoins = queryJoins;
+ return (this);
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/package-info.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/package-info.java
new file mode 100644
index 00000000..d342e723
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/io/package-info.java
@@ -0,0 +1,29 @@
+/*
+ * 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 .
+ */
+
+/*******************************************************************************
+ ** These classes are input/output wrappers for middleware executors.
+ **
+ ** Some "empty" implementations are provided, for executors that (more likely)
+ ** take no inputs, or (less likely?) return no outputs.
+ **
+ *******************************************************************************/
+package com.kingsrook.qqq.middleware.javalin.executors.io;
\ No newline at end of file
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/package-info.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/package-info.java
new file mode 100644
index 00000000..50c38d64
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/package-info.java
@@ -0,0 +1,40 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+/*******************************************************************************
+ ** This package contains (hopefully generally) api-version-agnostic classes
+ ** that implement the actual QQQ Middleware. That is to say, subclasses of
+ ** `AbstractMiddlewareExecutor`, which use classes from the `.io` subpackage
+ ** for I/O, to run code in a QQQ server.
+ **
+ ** As new versions of the middleware evolve, the idea is that the spec classes
+ ** for new versions will be responsible for appropriately marshalling data
+ ** in and out of the executors, via the I/O classes, with "feature flags", etc
+ ** added to those input classes as needed (say if v N+1 adds a new feature,
+ ** then a request for v N may omit the feature-flag that turns that feature on).
+ **
+ ** As functionality continues to evolve, the time may come when it's appropriate
+ ** to fork an Executor. Hypothetically, if version 5 of the QueryExecutor
+ ** bears very little resemblance to versions 1 through 4 (due to additional
+ ** pizzazz?) spawn a new QueryWithPizzazzExecutor. Of course, naming here
+ ** will be the hardest part (e.g., avoid NewNewQueryExecutorFinal2B...)
+ *******************************************************************************/
+package com.kingsrook.qqq.middleware.javalin.executors;
\ No newline at end of file
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/utils/ProcessExecutorUtils.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/utils/ProcessExecutorUtils.java
new file mode 100644
index 00000000..9afff3b8
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/executors/utils/ProcessExecutorUtils.java
@@ -0,0 +1,118 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.executors.utils;
+
+
+import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
+import com.kingsrook.qqq.backend.core.logging.QLogger;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
+import com.kingsrook.qqq.backend.core.utils.ExceptionUtils;
+import com.kingsrook.qqq.middleware.javalin.executors.io.ProcessInitOrStepOrStatusOutputInterface;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class ProcessExecutorUtils
+{
+ private static final QLogger LOG = QLogger.getLogger(ProcessExecutorUtils.class);
+
+
+
+ /*******************************************************************************
+ ** Whether a step finished synchronously or asynchronously, return its data
+ ** to the caller the same way.
+ *******************************************************************************/
+ public static void serializeRunProcessResultForCaller(ProcessInitOrStepOrStatusOutputInterface processInitOrStepOutput, String processName, RunProcessOutput runProcessOutput)
+ {
+ processInitOrStepOutput.setType(ProcessInitOrStepOrStatusOutputInterface.Type.COMPLETE);
+
+ if(runProcessOutput.getException().isPresent())
+ {
+ ////////////////////////////////////////////////////////////////
+ // per code coverage, this path may never actually get hit... //
+ ////////////////////////////////////////////////////////////////
+ serializeRunProcessExceptionForCaller(processInitOrStepOutput, runProcessOutput.getException().get());
+ }
+
+ processInitOrStepOutput.setValues(runProcessOutput.getValues());
+ // processInitOrStepOutput.setValues(getValuesForCaller(processName, runProcessOutput));
+
+ runProcessOutput.getProcessState().getNextStepName().ifPresent(nextStep -> processInitOrStepOutput.setNextStep(nextStep));
+
+ if(runProcessOutput.getProcessMetaDataAdjustment() != null)
+ {
+ processInitOrStepOutput.setProcessMetaDataAdjustment(runProcessOutput.getProcessMetaDataAdjustment());
+ }
+ }
+
+ // /***************************************************************************
+ // ** maybe good idea here, but... to only return fields that the frontend steps
+ // ** say they care about. yeah.
+ // ***************************************************************************/
+ // private static Map getValuesForCaller(String processName, RunProcessOutput runProcessOutput)
+ // {
+ // QProcessMetaData process = QContext.getQInstance().getProcess(processName);
+ // Map frontendFields = new LinkedHashMap<>();
+ // for(QStepMetaData step : process.getAllSteps().values())
+ // {
+ // if(step instanceof QFrontendStepMetaData frontendStepMetaData)
+ // {
+ // frontendFields.addAll(frontendStepMetaData.getAllFields());
+ // }
+ // else if(step instanceof QStateMachineStep stateMachineStep)
+ // {
+ // for(QStepMetaData subStep : stateMachineStep.getSubSteps())
+ // {
+ // // recur, etc
+ // }
+ // }
+ // }
+ //
+ // // then, only return ones in the map, eh
+ // }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static void serializeRunProcessExceptionForCaller(ProcessInitOrStepOrStatusOutputInterface processInitOrStepOutput, Exception exception)
+ {
+ processInitOrStepOutput.setType(ProcessInitOrStepOrStatusOutputInterface.Type.ERROR);
+
+ QUserFacingException userFacingException = ExceptionUtils.findClassInRootChain(exception, QUserFacingException.class);
+
+ if(userFacingException != null)
+ {
+ LOG.info("User-facing exception in process", userFacingException);
+ processInitOrStepOutput.setError(userFacingException.getMessage());
+ processInitOrStepOutput.setUserFacingError(userFacingException.getMessage());
+ }
+ else
+ {
+ Throwable rootException = ExceptionUtils.getRootException(exception);
+ LOG.warn("Uncaught Exception in process", exception);
+ processInitOrStepOutput.setError("Error message: " + rootException.getMessage());
+ }
+ }
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/schemabuilder/SchemaBuilder.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/schemabuilder/SchemaBuilder.java
index 5ea0912b..79ac6da6 100644
--- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/schemabuilder/SchemaBuilder.java
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/schemabuilder/SchemaBuilder.java
@@ -34,6 +34,7 @@ import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;
+import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIDescription;
@@ -64,13 +65,57 @@ public class SchemaBuilder
}
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public static class SchemaFromBuilder extends Schema
+ {
+ private Class> originalClass;
+
+
+ /*******************************************************************************
+ ** Getter for originalClass
+ **
+ *******************************************************************************/
+ @JsonIgnore
+ public Class> getOriginalClass()
+ {
+ return originalClass;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for originalClass
+ **
+ *******************************************************************************/
+ public void setOriginalClass(Class> originalClass)
+ {
+ this.originalClass = originalClass;
+ }
+
+
+ /*******************************************************************************
+ ** Fluent setter for originalClass
+ **
+ *******************************************************************************/
+ public SchemaFromBuilder withOriginalClass(Class> originalClass)
+ {
+ this.originalClass = originalClass;
+ return (this);
+ }
+
+ }
+
+
/***************************************************************************
**
***************************************************************************/
private Schema classToSchema(Class> c, AnnotatedElement element)
{
- Schema schema = new Schema();
+ SchemaFromBuilder schema = new SchemaFromBuilder();
+ schema.setOriginalClass(c);
if(c.isEnum())
{
@@ -121,10 +166,10 @@ public class SchemaBuilder
if(openAPIMapKnownEntriesAnnotation != null)
{
schema.withRef("#/components/schemas/" + openAPIMapKnownEntriesAnnotation.value().getSimpleName());
-// if(openAPIMapKnownEntriesAnnotation.additionalProperties())
-// {
-// schema.withAdditionalProperties(true);
-// }
+ // if(openAPIMapKnownEntriesAnnotation.additionalProperties())
+ // {
+ // schema.withAdditionalProperties(true);
+ // }
}
OpenAPIMapValueType openAPIMapValueTypeAnnotation = element.getAnnotation(OpenAPIMapValueType.class);
@@ -142,13 +187,23 @@ public class SchemaBuilder
}
else
{
- OpenAPIOneOf openAPIOneOfAnnotation = element.getAnnotation(OpenAPIOneOf.class);
+ OpenAPIOneOf openAPIOneOfAnnotation = element.getAnnotation(OpenAPIOneOf.class);
+ OpenAPIMapKnownEntries openAPIMapKnownEntriesAnnotation = element.getAnnotation(OpenAPIMapKnownEntries.class);
+
if(openAPIOneOfAnnotation != null)
{
String description = "[" + element + "]";
List oneOfList = processOneOfAnnotation(openAPIOneOfAnnotation, c, description);
schema.withOneOf(oneOfList);
}
+ // todo no, lot like this else if(openAPIMapKnownEntriesAnnotation != null)
+ // todo no, lot like this {
+ // todo no, lot like this schema.withRef("#/components/schemas/" + openAPIMapKnownEntriesAnnotation.value().getSimpleName());
+ // todo no, lot like this // if(openAPIMapKnownEntriesAnnotation.additionalProperties())
+ // todo no, lot like this // {
+ // todo no, lot like this // schema.withAdditionalProperties(true);
+ // todo no, lot like this // }
+ // todo no, lot like this }
else
{
/////////////////////////////////////////////////////////////////////////////////////////////
@@ -296,5 +351,4 @@ public class SchemaBuilder
return oneOfList;
}
-
}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/schemabuilder/annotations/OpenAPIHasAdditionalProperties.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/schemabuilder/annotations/OpenAPIHasAdditionalProperties.java
index a7a1fa19..497dc090 100644
--- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/schemabuilder/annotations/OpenAPIHasAdditionalProperties.java
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/schemabuilder/annotations/OpenAPIHasAdditionalProperties.java
@@ -31,7 +31,7 @@ import java.lang.annotation.Target;
/*******************************************************************************
**
*******************************************************************************/
-@Target({ ElementType.TYPE })
+@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface OpenAPIHasAdditionalProperties
{
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/AbstractEndpointSpec.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/AbstractEndpointSpec.java
new file mode 100644
index 00000000..c9548e94
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/AbstractEndpointSpec.java
@@ -0,0 +1,545 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs;
+
+
+import java.io.Serializable;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import com.kingsrook.qqq.backend.core.context.QContext;
+import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException;
+import com.kingsrook.qqq.backend.core.logging.QLogger;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
+import com.kingsrook.qqq.backend.core.utils.ValueUtils;
+import com.kingsrook.qqq.backend.javalin.QJavalinUtils;
+import com.kingsrook.qqq.middleware.javalin.executors.AbstractMiddlewareExecutor;
+import com.kingsrook.qqq.middleware.javalin.executors.ExecutorSessionUtils;
+import com.kingsrook.qqq.middleware.javalin.executors.io.AbstractMiddlewareInput;
+import com.kingsrook.qqq.middleware.javalin.executors.io.AbstractMiddlewareOutputInterface;
+import com.kingsrook.qqq.openapi.model.Content;
+import com.kingsrook.qqq.openapi.model.Method;
+import com.kingsrook.qqq.openapi.model.Parameter;
+import com.kingsrook.qqq.openapi.model.RequestBody;
+import com.kingsrook.qqq.openapi.model.Response;
+import com.kingsrook.qqq.openapi.model.Schema;
+import io.javalin.apibuilder.ApiBuilder;
+import io.javalin.http.ContentType;
+import io.javalin.http.Context;
+import io.javalin.http.Handler;
+import org.apache.commons.lang.NotImplementedException;
+import org.json.JSONObject;
+
+
+/*******************************************************************************
+ ** Base class for individual endpoint specs.
+ ** e.g., one path, that has one "spec" (a "Method" in openapi structure),
+ ** with one implementation (executor + input & output)
+ *******************************************************************************/
+public abstract class AbstractEndpointSpec<
+ INPUT extends AbstractMiddlewareInput,
+ OUTPUT extends AbstractMiddlewareOutputInterface,
+ EXECUTOR extends AbstractMiddlewareExecutor>
+{
+ private static final QLogger LOG = QLogger.getLogger(AbstractEndpointSpec.class);
+
+ protected QInstance qInstance;
+
+ private List memoizedRequestParameters = null;
+ private RequestBody memoizedRequestBody = null;
+
+
+
+ /***************************************************************************
+ ** build the endpoint's input object from a javalin context
+ ***************************************************************************/
+ public abstract INPUT buildInput(Context context) throws Exception;
+
+
+ /***************************************************************************
+ ** build the endpoint's http response (written to the javalin context) from
+ ** an execution output object
+ ***************************************************************************/
+ public abstract void handleOutput(Context context, OUTPUT output) throws Exception;
+
+
+
+ /***************************************************************************
+ ** Construct a new instance of the executor class, based on type-argument
+ ***************************************************************************/
+ @SuppressWarnings("unchecked")
+ public EXECUTOR newExecutor()
+ {
+ Object object = newObjectFromTypeArgument(2);
+ return (EXECUTOR) object;
+ }
+
+
+
+ /***************************************************************************
+ ** Construct a new instance of the output class, based on type-argument
+ ***************************************************************************/
+ @SuppressWarnings("unchecked")
+ public OUTPUT newOutput()
+ {
+ Object object = newObjectFromTypeArgument(1);
+ return (OUTPUT) object;
+ }
+
+
+
+ /***************************************************************************
+ ** credit: https://www.baeldung.com/java-generic-type-find-class-runtime
+ ***************************************************************************/
+ private Object newObjectFromTypeArgument(int argumentIndex)
+ {
+ try
+ {
+ Type superClass = getClass().getGenericSuperclass();
+ Type actualTypeArgument = ((ParameterizedType) superClass).getActualTypeArguments()[argumentIndex];
+ String className = actualTypeArgument.getTypeName().replaceAll("<.*", "");
+ Class> aClass = Class.forName(className);
+ return (aClass.getConstructor().newInstance());
+ }
+ catch(Exception e)
+ {
+ throw (new QRuntimeException("Failed to reflectively create new object from type argument", e));
+ }
+ }
+
+
+
+ /***************************************************************************
+ ** define a javalin route for the spec
+ ***************************************************************************/
+ public void defineRoute(String versionBasePath)
+ {
+ CompleteOperation completeOperation = defineCompleteOperation();
+
+ String fullPath = "/qqq/" + versionBasePath + completeOperation.getPath();
+ fullPath = fullPath.replaceAll("/+", "/");
+
+ final Handler handler = context -> serveRequest(context);
+
+ switch(completeOperation.getHttpMethod())
+ {
+ case GET -> ApiBuilder.get(fullPath, handler);
+ case POST -> ApiBuilder.post(fullPath, handler);
+ case PUT -> ApiBuilder.put(fullPath, handler);
+ case PATCH -> ApiBuilder.patch(fullPath, handler);
+ case DELETE -> ApiBuilder.delete(fullPath, handler);
+ }
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public OUTPUT serveRequest(Context context) throws Exception
+ {
+ try
+ {
+ if(isSecured())
+ {
+ ExecutorSessionUtils.setupSession(context, qInstance);
+ }
+ else
+ {
+ QContext.setQInstance(qInstance);
+ }
+
+ INPUT input = buildInput(context);
+ EXECUTOR executor = newExecutor();
+ OUTPUT output = newOutput();
+ executor.execute(input, output);
+ handleOutput(context, output);
+ return (output);
+ }
+ catch(Exception e)
+ {
+ handleException(context, e);
+ return (null);
+ }
+ finally
+ {
+ QContext.clear();
+ }
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ protected void handleException(Context context, Exception e)
+ {
+ QJavalinUtils.handleException(null, context, e);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public Method defineMethod()
+ {
+ BasicOperation basicOperation = defineBasicOperation();
+
+ Method method = new Method()
+ .withTag(basicOperation.getTag().getText())
+ .withSummary(basicOperation.getShortSummary())
+ .withDescription(basicOperation.getLongDescription())
+ .withParameters(defineRequestParameters())
+ .withRequestBody(defineRequestBody())
+ .withResponses(defineResponses());
+
+ customizeMethod(method);
+
+ return (method);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ protected void customizeMethod(Method method)
+ {
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public BasicOperation defineBasicOperation()
+ {
+ throw new NotImplementedException(getClass().getSimpleName() + " did not implement defineBasicOperation or defineMethod");
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public CompleteOperation defineCompleteOperation()
+ {
+ CompleteOperation completeOperation = new CompleteOperation(defineBasicOperation());
+ completeOperation.setMethod(defineMethod());
+ return completeOperation;
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public boolean isSecured()
+ {
+ return (true);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public BasicResponse defineBasicSuccessResponse()
+ {
+ return (null);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public List defineAdditionalBasicResponses()
+ {
+ return (Collections.emptyList());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public Map defineResponses()
+ {
+ BasicResponse standardSuccessResponse = defineBasicSuccessResponse();
+ List basicResponseList = defineAdditionalBasicResponses();
+
+ List allBasicResponses = new ArrayList<>();
+ if(standardSuccessResponse != null)
+ {
+ allBasicResponses.add(standardSuccessResponse);
+ }
+
+ if(basicResponseList != null)
+ {
+ allBasicResponses.addAll(basicResponseList);
+ }
+
+ Map rs = new HashMap<>();
+ for(BasicResponse basicResponse : allBasicResponses)
+ {
+ Response responseObject = rs.computeIfAbsent(basicResponse.status().getCode(), (k) -> new Response());
+ responseObject.withDescription(basicResponse.description());
+ Map content = responseObject.getContent();
+ if(content == null)
+ {
+ content = new HashMap<>();
+ responseObject.setContent(content);
+ }
+
+ content.put(basicResponse.contentType(), new Content()
+ .withSchema(new Schema().withRefToSchema(basicResponse.schemaRefName()))
+ .withExamples(basicResponse.examples())
+ );
+ }
+
+ return rs;
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public List defineRequestParameters()
+ {
+ return Collections.emptyList();
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public RequestBody defineRequestBody()
+ {
+ return null;
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public Map defineComponentSchemas()
+ {
+ return Collections.emptyMap();
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ protected String getRequestParam(Context context, String name)
+ {
+ for(Parameter parameter : CollectionUtils.nonNullList(getMemoizedRequestParameters()))
+ {
+ if(parameter.getName().equals(name))
+ {
+ String value = switch(parameter.getIn())
+ {
+ case "path" -> context.pathParam(parameter.getName());
+ case "query" -> context.queryParam(parameter.getName());
+ default -> throw new IllegalStateException("Unexpected 'in' value for parameter [" + parameter.getName() + "]: " + parameter.getIn());
+ };
+
+ // todo - validate value vs. required?
+ // todo - validate value vs. schema?
+
+ return (value);
+ }
+ }
+
+ RequestBody requestBody = getMemoizedRequestBody();
+ if(requestBody != null)
+ {
+ String requestContentType = context.contentType();
+ if(requestContentType != null)
+ {
+ requestContentType = requestContentType.toLowerCase().replaceAll(" *;.*", "");
+ }
+
+ Content contentSpec = requestBody.getContent().get(requestContentType);
+ if(contentSpec != null && "object".equals(contentSpec.getSchema().getType()))
+ {
+ if(contentSpec.getSchema().getProperties() != null && contentSpec.getSchema().getProperties().containsKey(name))
+ {
+ String value = null;
+ if(ContentType.MULTIPART_FORM_DATA.getMimeType().equals(requestContentType))
+ {
+ value = context.formParam(name);
+ }
+ else if(ContentType.APPLICATION_JSON.getMimeType().equals(requestContentType))
+ {
+ /////////////////////////////////////////////////////////////////////////////
+ // avoid re-parsing the JSON object if getting multiple attributes from it //
+ // by stashing it in a (request) attribute. //
+ /////////////////////////////////////////////////////////////////////////////
+ Object jsonBodyAttribute = context.attribute("jsonBody");
+ JSONObject jsonObject = null;
+
+ if(jsonBodyAttribute instanceof JSONObject jo)
+ {
+ jsonObject = jo;
+ }
+
+ if(jsonObject == null)
+ {
+ jsonObject = new JSONObject(context.body());
+ context.attribute("jsonBody", jsonObject);
+ }
+
+ if(jsonObject.has(name))
+ {
+ value = jsonObject.getString(name);
+ }
+ }
+ else
+ {
+ LOG.warn("Unhandled content type: " + requestContentType);
+ }
+
+ return (value);
+ }
+ }
+ }
+
+ return (null);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ protected Map getRequestParamMap(Context context, String name)
+ {
+ String requestParam = getRequestParam(context, name);
+ if(requestParam == null)
+ {
+ return (null);
+ }
+
+ JSONObject jsonObject = new JSONObject(requestParam);
+ Map map = new LinkedHashMap<>();
+ for(String key : jsonObject.keySet())
+ {
+ Object value = jsonObject.get(key);
+ if(value instanceof Serializable s)
+ {
+ map.put(key, s);
+ }
+ else
+ {
+ throw (new QRuntimeException("Non-serializable value in param map under key [" + name + "][" + key + "]: " + value.getClass().getSimpleName()));
+ }
+ }
+ return (map);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ protected Integer getRequestParamInteger(Context context, String name)
+ {
+ String requestParam = getRequestParam(context, name);
+ return ValueUtils.getValueAsInteger(requestParam);
+ }
+
+
+
+ /***************************************************************************
+ ** For initial setup when server boots, set the qInstance - but also,
+ ** e.g., for development, to do a hot-swap.
+ ***************************************************************************/
+ public void setQInstance(QInstance qInstance)
+ {
+ this.qInstance = qInstance;
+
+ //////////////////////////////////////////////////////////////////
+ // if we did a hot swap, we should clear these memoizations too //
+ //////////////////////////////////////////////////////////////////
+ memoizedRequestParameters = null;
+ memoizedRequestBody = null;
+ }
+
+
+
+ /***************************************************************************
+ ** An original implementation here was prone to race-condition-based errors:
+ *
+ ** if(memoizedRequestParameters == null)
+ ** {
+ ** memoizedRequestParameters = CollectionUtils.nonNullList(defineRequestParameters());
+ ** }
+ ** return (memoizedRequestParameters);
+ **
+ ** where between the defineX call and the return, if another thread cleared the
+ ** memoizedX field, then a null would be returned, which isn't supposed to happen.
+ ** Thus, this implementation which looks a bit more convoluted, but should
+ ** be safe(r).
+ ***************************************************************************/
+ private List getMemoizedRequestParameters()
+ {
+ List rs = memoizedRequestParameters;
+ if(rs == null)
+ {
+ rs = CollectionUtils.nonNullList(defineRequestParameters());
+ memoizedRequestParameters = rs;
+ }
+
+ return (rs);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ private RequestBody getMemoizedRequestBody()
+ {
+ RequestBody rs = memoizedRequestBody;
+ if(rs == null)
+ {
+ rs = defineRequestBody();
+ memoizedRequestBody = rs;
+ }
+
+ return (rs);
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/AbstractMiddlewareVersion.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/AbstractMiddlewareVersion.java
new file mode 100644
index 00000000..41a7fc0b
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/AbstractMiddlewareVersion.java
@@ -0,0 +1,360 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs;
+
+
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.logging.QLogger;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.utils.ClassPathUtils;
+import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
+import com.kingsrook.qqq.middleware.javalin.schemabuilder.SchemaBuilder;
+import com.kingsrook.qqq.middleware.javalin.schemabuilder.ToSchema;
+import com.kingsrook.qqq.openapi.model.Components;
+import com.kingsrook.qqq.openapi.model.Contact;
+import com.kingsrook.qqq.openapi.model.Content;
+import com.kingsrook.qqq.openapi.model.Example;
+import com.kingsrook.qqq.openapi.model.Info;
+import com.kingsrook.qqq.openapi.model.Method;
+import com.kingsrook.qqq.openapi.model.OpenAPI;
+import com.kingsrook.qqq.openapi.model.Path;
+import com.kingsrook.qqq.openapi.model.Response;
+import com.kingsrook.qqq.openapi.model.Schema;
+import com.kingsrook.qqq.openapi.model.SecurityScheme;
+import com.kingsrook.qqq.openapi.model.SecuritySchemeType;
+import com.kingsrook.qqq.openapi.model.Type;
+import io.javalin.apibuilder.EndpointGroup;
+
+
+/*******************************************************************************
+ ** Baseclass that combines multiple specs together into a single "version" of
+ ** the full qqq middleware.
+ *******************************************************************************/
+public abstract class AbstractMiddlewareVersion
+{
+ public static final QLogger LOG = QLogger.getLogger(AbstractMiddlewareVersion.class);
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public abstract String getVersion();
+
+ /***************************************************************************
+ ** hey - don't re-construct the endpoint-spec objects inside this method...
+ ***************************************************************************/
+ public abstract List> getEndpointSpecs();
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public EndpointGroup getJavalinEndpointGroup(QInstance qInstance)
+ {
+ return (() ->
+ {
+ for(AbstractEndpointSpec, ?, ?> spec : CollectionUtils.nonNullList(getEndpointSpecs()))
+ {
+ spec.defineRoute("/" + getVersion() + "/");
+ }
+ });
+ }
+
+
+
+ /***************************************************************************
+ ** For initial setup when server boots, set the qInstance - but also,
+ ** e.g., for development, to do a hot-swap.
+ ***************************************************************************/
+ public void setQInstance(QInstance qInstance)
+ {
+ getEndpointSpecs().forEach(spec -> spec.setQInstance(qInstance));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public OpenAPI generateOpenAPIModel(String basePath) throws QException
+ {
+ List> list = getEndpointSpecs();
+
+ Map paths = new LinkedHashMap<>();
+ Map componentExamples = new LinkedHashMap<>();
+
+ Set> componentClasses = new HashSet<>();
+ Map componentSchemas = new TreeMap<>();
+ buildComponentSchemasFromComponentsPackage(componentSchemas, componentClasses);
+
+ String sessionUuidCookieSchemeName = "sessionUuidCookie";
+ SecurityScheme sessionUuidCookieScheme = new SecurityScheme()
+ .withType(SecuritySchemeType.API_KEY)
+ .withName("sessionUUID")
+ .withIn("cookie");
+
+ for(AbstractEndpointSpec, ?, ?> spec : list)
+ {
+ CompleteOperation completeOperation = spec.defineCompleteOperation();
+ String fullPath = ("/" + basePath + "/" + getVersion() + "/" + completeOperation.getPath()).replaceAll("/+", "/");
+ Path path = paths.computeIfAbsent(fullPath, (k) -> new Path());
+ Method method = completeOperation.getMethod();
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // if this spec is supposed to be secured, but no security has been applied, then add our default security //
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ if(spec.isSecured() && method.getSecurity() == null)
+ {
+ //////////////////////////////////////////////////////////////////////////////
+ // the N/A here refers to the lack of a 'scope' for this kind of permission //
+ //////////////////////////////////////////////////////////////////////////////
+ method.withSecurity(List.of(Map.of(sessionUuidCookieSchemeName, List.of("N/A"))));
+ }
+
+ convertMethodSchemasToRefs(method, componentClasses);
+
+ switch(completeOperation.getHttpMethod())
+ {
+ case GET ->
+ {
+ warnIfPathMethodAlreadyUsed(path.getGet(), completeOperation, spec);
+ path.withGet(method);
+ }
+ case POST ->
+ {
+ warnIfPathMethodAlreadyUsed(path.getPost(), completeOperation, spec);
+ path.withPost(method);
+ }
+ case PUT ->
+ {
+ warnIfPathMethodAlreadyUsed(path.getPut(), completeOperation, spec);
+ path.withPut(method);
+ }
+ case PATCH ->
+ {
+ warnIfPathMethodAlreadyUsed(path.getPatch(), completeOperation, spec);
+ path.withPatch(method);
+ }
+ case DELETE ->
+ {
+ warnIfPathMethodAlreadyUsed(path.getDelete(), completeOperation, spec);
+ path.withDelete(method);
+ }
+ }
+
+ for(Map.Entry entry : CollectionUtils.nonNullMap(spec.defineComponentSchemas()).entrySet())
+ {
+ if(componentSchemas.containsKey(entry.getKey()))
+ {
+ LOG.warn("More than one endpoint spec defined a componentSchema named: " + entry.getKey() + ". The last one encountered (from " + spec.getClass().getSimpleName() + ") will be used.");
+ }
+
+ componentSchemas.put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ OpenAPI openAPI = new OpenAPI();
+ openAPI.withInfo(new Info()
+ .withVersion(getVersion())
+ .withTitle("QQQ Middleware API")
+ .withDescription(getDescription())
+ .withContact(new Contact().withEmail("contact@kingsrook.com"))
+ );
+
+ openAPI.withPaths(paths);
+
+ openAPI.withComponents(new Components()
+ .withSchemas(componentSchemas)
+ .withExamples(componentExamples)
+ .withSecuritySchemes(Map.of(sessionUuidCookieSchemeName, sessionUuidCookieScheme))
+ );
+
+ return openAPI;
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ private void buildComponentSchemasFromComponentsPackage(Map componentSchemas, Set> componentClasses) throws QException
+ {
+ try
+ {
+ ////////////////////////////////////////////////////
+ // find all classes in the components sub-package //
+ ////////////////////////////////////////////////////
+ String packageName = getClass().getPackageName();
+ List> classesInPackage = ClassPathUtils.getClassesInPackage(packageName);
+ for(Class> c : classesInPackage)
+ {
+ if(c.getPackageName().matches(".*\\bcomponents\\b.*"))
+ {
+ componentClasses.add(c);
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////
+ // now that we know that full set, make any references to others schemas in those objects be via Ref //
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////
+ for(Class> c : componentClasses)
+ {
+ Object o = null;
+ try
+ {
+ o = c.getConstructor().newInstance();
+ }
+ catch(Exception nsme)
+ {
+ ///////////////////////////////////////
+ // fine, assume we can't do toSchema //
+ ///////////////////////////////////////
+ }
+
+ Schema schema = null;
+ if(o instanceof ToSchema toSchema)
+ {
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // just in case a custom implementation of toSchema is provided (e.g., to go around a wrapped object or some-such) //
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ schema = toSchema.toSchema();
+ }
+ else
+ {
+ schema = new SchemaBuilder().classToSchema(c);
+ }
+
+ convertSchemaToRefs(schema, componentClasses);
+
+ componentSchemas.put(c.getSimpleName(), schema);
+ }
+ }
+ catch(Exception e)
+ {
+ throw (new QException("Error building component schemas from components package", e));
+ }
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ private void convertMethodSchemasToRefs(Method method, Set> componentClasses)
+ {
+ for(Response response : method.getResponses().values())
+ {
+ for(Content content : response.getContent().values())
+ {
+ Schema schema = content.getSchema();
+ convertSchemaToRefs(schema, componentClasses);
+ }
+ }
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ private void convertSchemaToRefs(Schema schema, Set> componentClasses)
+ {
+ if(schema.getItems() instanceof SchemaBuilder.SchemaFromBuilder itemSchemaFromBuilder && componentClasses.contains(itemSchemaFromBuilder.getOriginalClass()))
+ {
+ schema.getItems().withRefToSchema(itemSchemaFromBuilder.getOriginalClass().getSimpleName());
+ schema.getItems().setProperties(null);
+ schema.getItems().setType((Type) null);
+ }
+ else if(schema.getItems() != null)
+ {
+ convertSchemaToRefs(schema.getItems(), componentClasses);
+ }
+
+ if(schema.getProperties() != null)
+ {
+ for(Schema propertySchema : schema.getProperties().values())
+ {
+ if(propertySchema instanceof SchemaBuilder.SchemaFromBuilder propertySchemaFromBuilder && componentClasses.contains(propertySchemaFromBuilder.getOriginalClass()))
+ {
+ propertySchema.withRefToSchema(propertySchemaFromBuilder.getOriginalClass().getSimpleName());
+ propertySchema.setProperties(null);
+ propertySchema.setType((Type) null);
+ }
+ else
+ {
+ convertSchemaToRefs(propertySchema, componentClasses);
+ }
+ }
+ }
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ private static String getDescription()
+ {
+ return """
+ ## Intro
+ This is the definition of the standard API implemented by QQQ Middleware.
+
+ Developers of QQQ Frontends (e.g., javascript libraries, or native applications) use this API to access
+ a QQQ Backend server.
+
+ As such, this API itself is not concerned with any of the application-level details of any particular
+ application built using QQQ. Instead, this API is all about the generic endpoints used for any application
+ built on QQQ. For example, many endpoints work with a `${table}` path parameter - whose possible values
+ are defined by the application - but which are not known to this API.
+
+ ## Flow
+ The typical flow of a user (as implemented in a frontend that utilizes this API) looks like:
+ 1. Frontend calls `.../metaData/authentication`, to know what type of authentication provider is used by the backend, and display an appropriate UI to the user for authenticating.
+ 2. User authenticates in frontend, as required for the authentication provider.
+ 3. Frontend calls `.../manageSession`, providing authentication details (e.g., an accessToken or other credentials) to the backend.
+ 4. The response from the `manageSession` call (assuming success), sets the `sessionUUID` Cookie, which should be included in all subsequent requests for authentication.
+ 5. After the user is authenticated, the frontend calls `.../metaData`, to discover the apps, tables, processes, etc, that the application is made up of (and that the authenticated user has permission to access).
+ 6. As the user interacts with apps, tables, process, etc, the frontend utilizes the appropriate endpoints as required.
+ """;
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public void warnIfPathMethodAlreadyUsed(Method existing, CompleteOperation completeOperation, AbstractEndpointSpec, ?, ?> spec)
+ {
+ if(existing != null)
+ {
+ LOG.warn("More than one endpoint spec for version " + getVersion() + " defined a " + completeOperation.getHttpMethod() + " at path: " + completeOperation.getPath() + ". The last one encountered (from " + spec.getClass().getSimpleName() + ") will be used.");
+ }
+ }
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/BasicOperation.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/BasicOperation.java
new file mode 100644
index 00000000..35b745c4
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/BasicOperation.java
@@ -0,0 +1,194 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs;
+
+
+import com.kingsrook.qqq.openapi.model.HttpMethod;
+
+
+/*******************************************************************************
+ ** Basic definition of an operation (e.g., an endpoint exposed in the API).
+ *******************************************************************************/
+public class BasicOperation
+{
+ private String path;
+ private HttpMethod httpMethod;
+ private TagsInterface tag;
+ private String shortSummary;
+ private String longDescription;
+
+
+
+ /*******************************************************************************
+ ** Getter for path
+ *******************************************************************************/
+ public String getPath()
+ {
+ return (this.path);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for path
+ *******************************************************************************/
+ public void setPath(String path)
+ {
+ this.path = path;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for path
+ *******************************************************************************/
+ public BasicOperation withPath(String path)
+ {
+ this.path = path;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for httpMethod
+ *******************************************************************************/
+ public HttpMethod getHttpMethod()
+ {
+ return (this.httpMethod);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for httpMethod
+ *******************************************************************************/
+ public void setHttpMethod(HttpMethod httpMethod)
+ {
+ this.httpMethod = httpMethod;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for httpMethod
+ *******************************************************************************/
+ public BasicOperation withHttpMethod(HttpMethod httpMethod)
+ {
+ this.httpMethod = httpMethod;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for tag
+ *******************************************************************************/
+ public TagsInterface getTag()
+ {
+ return (this.tag);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for tag
+ *******************************************************************************/
+ public void setTag(TagsInterface tag)
+ {
+ this.tag = tag;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for tag
+ *******************************************************************************/
+ public BasicOperation withTag(TagsInterface tag)
+ {
+ this.tag = tag;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for shortSummary
+ *******************************************************************************/
+ public String getShortSummary()
+ {
+ return (this.shortSummary);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for shortSummary
+ *******************************************************************************/
+ public void setShortSummary(String shortSummary)
+ {
+ this.shortSummary = shortSummary;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for shortSummary
+ *******************************************************************************/
+ public BasicOperation withShortSummary(String shortSummary)
+ {
+ this.shortSummary = shortSummary;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for longDescription
+ *******************************************************************************/
+ public String getLongDescription()
+ {
+ return (this.longDescription);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for longDescription
+ *******************************************************************************/
+ public void setLongDescription(String longDescription)
+ {
+ this.longDescription = longDescription;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for longDescription
+ *******************************************************************************/
+ public BasicOperation withLongDescription(String longDescription)
+ {
+ this.longDescription = longDescription;
+ return (this);
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/BasicResponse.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/BasicResponse.java
new file mode 100644
index 00000000..7447ab02
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/BasicResponse.java
@@ -0,0 +1,79 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs;
+
+
+import java.util.Map;
+import com.kingsrook.qqq.openapi.model.Example;
+import io.javalin.http.ContentType;
+import io.javalin.http.HttpStatus;
+
+
+/***************************************************************************
+ ** Basic version of a response from a spec/endpoint.
+ ***************************************************************************/
+public record BasicResponse(String contentType, HttpStatus status, String description, String schemaRefName, Map examples)
+{
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public BasicResponse(String description, String schemaRefName)
+ {
+ this(ContentType.APPLICATION_JSON.getMimeType(), HttpStatus.OK, description, schemaRefName, null);
+ }
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public BasicResponse(String description, String schemaRefName, Map examples)
+ {
+ this(ContentType.APPLICATION_JSON.getMimeType(), HttpStatus.OK, description, schemaRefName, examples);
+ }
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public BasicResponse(HttpStatus status, String description, String schemaRefName)
+ {
+ this(ContentType.APPLICATION_JSON.getMimeType(), status, description, schemaRefName, null);
+ }
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public BasicResponse(HttpStatus status, String description, String schemaRefName, Map examples)
+ {
+ this(ContentType.APPLICATION_JSON.getMimeType(), status, description, schemaRefName, examples);
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/CompleteOperation.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/CompleteOperation.java
new file mode 100644
index 00000000..02c11853
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/CompleteOperation.java
@@ -0,0 +1,80 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs;
+
+
+import com.kingsrook.qqq.openapi.model.Method;
+
+
+/*******************************************************************************
+ ** Extension of a BasicOperation that adds the full openAPI Method object.
+ *******************************************************************************/
+public class CompleteOperation extends BasicOperation
+{
+ private Method method;
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public CompleteOperation(BasicOperation basicOperation)
+ {
+ setPath(basicOperation.getPath());
+ setHttpMethod(basicOperation.getHttpMethod());
+ setTag(basicOperation.getTag());
+ setLongDescription(basicOperation.getLongDescription());
+ setShortSummary(basicOperation.getShortSummary());
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for method
+ *******************************************************************************/
+ public Method getMethod()
+ {
+ return (this.method);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for method
+ *******************************************************************************/
+ public void setMethod(Method method)
+ {
+ this.method = method;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for method
+ *******************************************************************************/
+ public CompleteOperation withMethod(Method method)
+ {
+ this.method = method;
+ return (this);
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/TagsInterface.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/TagsInterface.java
new file mode 100644
index 00000000..23d2b4a4
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/TagsInterface.java
@@ -0,0 +1,34 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public interface TagsInterface
+{
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ String getText();
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/AuthenticationMetaDataSpecV1.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/AuthenticationMetaDataSpecV1.java
new file mode 100644
index 00000000..5dce2f06
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/AuthenticationMetaDataSpecV1.java
@@ -0,0 +1,137 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1;
+
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
+import com.kingsrook.qqq.backend.core.utils.JsonUtils;
+import com.kingsrook.qqq.middleware.javalin.executors.AuthenticationMetaDataExecutor;
+import com.kingsrook.qqq.middleware.javalin.executors.io.AuthenticationMetaDataInput;
+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.AuthenticationMetaDataResponseV1;
+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.Schema;
+import io.javalin.http.Context;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class AuthenticationMetaDataSpecV1 extends AbstractEndpointSpec
+{
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public BasicOperation defineBasicOperation()
+ {
+ return new BasicOperation()
+ .withPath("/metaData/authentication")
+ .withHttpMethod(HttpMethod.GET)
+ .withTag(TagsV1.AUTHENTICATION)
+ .withShortSummary("Get authentication metaData")
+ .withLongDescription("""
+ For a frontend to determine which authentication provider or mechanism to use, it should begin its lifecycle
+ by requesting this metaData object, and inspecting the `type` property in the response.
+
+ Note that this endpoint is not secured, as its purpose is to be called as part of the workflow that results
+ in a user being authenticated."""
+ );
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public boolean isSecured()
+ {
+ return (false);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public AuthenticationMetaDataInput buildInput(Context context) throws Exception
+ {
+ AuthenticationMetaDataInput input = new AuthenticationMetaDataInput();
+ return (input);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public Map defineComponentSchemas()
+ {
+ return Map.of(AuthenticationMetaDataResponseV1.class.getSimpleName(), new AuthenticationMetaDataResponseV1().toSchema());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public BasicResponse defineBasicSuccessResponse()
+ {
+ Map examples = new LinkedHashMap<>();
+ examples.put("For FULLY_ANONYMOUS type", new Example()
+ .withValue(new AuthenticationMetaDataResponseV1()
+ .withType(QAuthenticationType.FULLY_ANONYMOUS.name())
+ .withName("anonymous")));
+
+ examples.put("For AUTH_0 type", new Example()
+ .withValue(new AuthenticationMetaDataResponseV1()
+ .withType(QAuthenticationType.AUTH_0.name())
+ .withName("auth0")
+ .withValues(new AuthenticationMetaDataResponseV1.Auth0Values()
+ .withClientId("abcdefg1234567")
+ .withBaseUrl("https://myapp.auth0.com/")
+ .withAudience("myapp.mydomain.com"))));
+
+ return new BasicResponse("Successful Response", AuthenticationMetaDataResponseV1.class.getSimpleName(), examples);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void handleOutput(Context context, AuthenticationMetaDataResponseV1 output) throws Exception
+ {
+ context.result(JsonUtils.toJson(output));
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ManageSessionSpecV1.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ManageSessionSpecV1.java
new file mode 100644
index 00000000..c15ac971
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ManageSessionSpecV1.java
@@ -0,0 +1,213 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1;
+
+
+import java.io.Serializable;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import com.kingsrook.qqq.backend.core.utils.JsonUtils;
+import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
+import com.kingsrook.qqq.backend.javalin.QJavalinImplementation;
+import com.kingsrook.qqq.middleware.javalin.executors.ManageSessionExecutor;
+import com.kingsrook.qqq.middleware.javalin.executors.io.ManageSessionInput;
+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.BasicErrorResponseV1;
+import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.ManageSessionResponseV1;
+import com.kingsrook.qqq.middleware.javalin.specs.v1.utils.ProcessSpecUtilsV1;
+import com.kingsrook.qqq.middleware.javalin.specs.v1.utils.TagsV1;
+import com.kingsrook.qqq.openapi.model.Content;
+import com.kingsrook.qqq.openapi.model.Example;
+import com.kingsrook.qqq.openapi.model.HttpMethod;
+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 io.javalin.http.Context;
+import io.javalin.http.HttpStatus;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class ManageSessionSpecV1 extends AbstractEndpointSpec
+{
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public BasicOperation defineBasicOperation()
+ {
+ return new BasicOperation()
+ .withPath("/manageSession")
+ .withHttpMethod(HttpMethod.POST)
+ .withTag(TagsV1.AUTHENTICATION)
+ .withShortSummary("Create a session")
+ .withLongDescription("""
+ After a frontend authenticates the user as per the requirements of the authentication provider specified by the
+ `type` field in the `metaData/authentication` response, data from that authentication provider should be posted
+ to this endpoint, to create a session within the QQQ application.
+
+ The response object will include a session identifier (`uuid`) to authenticate the user in subsequent API calls.""");
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public boolean isSecured()
+ {
+ return (false);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public ManageSessionResponseV1 serveRequest(Context context) throws Exception
+ {
+ ManageSessionResponseV1 result = super.serveRequest(context);
+ if(result != null)
+ {
+ String sessionUuid = result.getUuid();
+ context.cookie(QJavalinImplementation.SESSION_UUID_COOKIE_NAME, sessionUuid, QJavalinImplementation.SESSION_COOKIE_AGE);
+ }
+ return (result);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public RequestBody defineRequestBody()
+ {
+ return new RequestBody()
+ .withRequired(true)
+ .withContent(MapBuilder.of(ContentType.JSON, new Content()
+ .withSchema(new Schema()
+ .withDescription("Data required to create the session. Specific needs may vary based on the AuthenticationModule type in the QQQ Backend.")
+ .withType(Type.OBJECT)
+ .withProperty("accessToken", new Schema()
+ .withType(Type.STRING)
+ .withDescription("An access token from a downstream authentication provider (e.g., Auth0), to use as the basis for authentication and authorization.")
+ )
+ )
+ ));
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public ManageSessionInput buildInput(Context context) throws Exception
+ {
+ ManageSessionInput manageSessionInput = new ManageSessionInput();
+ manageSessionInput.setAccessToken(getRequestParam(context, "accessToken"));
+ return (manageSessionInput);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public BasicResponse defineBasicSuccessResponse()
+ {
+ Map examples = new LinkedHashMap<>();
+
+ examples.put("With no custom values", new Example().withValue(new ManageSessionResponseV1()
+ .withUuid(ProcessSpecUtilsV1.EXAMPLE_PROCESS_UUID)
+ ));
+
+ examples.put("With custom values", new Example().withValue(new ManageSessionResponseV1()
+ .withUuid(ProcessSpecUtilsV1.EXAMPLE_JOB_UUID)
+ .withValues(MapBuilder.of(LinkedHashMap::new)
+ .with("region", "US")
+ .with("userCategoryId", 47)
+ .build()
+ )
+ ));
+
+ return new BasicResponse("Successful response - session has been created",
+ ManageSessionResponseV1.class.getSimpleName(),
+ examples);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public Map defineComponentSchemas()
+ {
+ return Map.of(
+ ManageSessionResponseV1.class.getSimpleName(), new ManageSessionResponseV1().toSchema(),
+ BasicErrorResponseV1.class.getSimpleName(), new BasicErrorResponseV1().toSchema()
+ );
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public List defineAdditionalBasicResponses()
+ {
+ Map examples = new LinkedHashMap<>();
+ examples.put("Invalid token", new Example().withValue(new BasicErrorResponseV1().withError("Unable to decode access token.")));
+
+ return List.of(
+ new BasicResponse(HttpStatus.UNAUTHORIZED,
+ "Authentication error - session was not created",
+ BasicErrorResponseV1.class.getSimpleName(),
+ examples
+ )
+ );
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void handleOutput(Context context, ManageSessionResponseV1 output) throws Exception
+ {
+ context.result(JsonUtils.toJson(output));
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/MetaDataSpecV1.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/MetaDataSpecV1.java
new file mode 100644
index 00000000..2191bbfe
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/MetaDataSpecV1.java
@@ -0,0 +1,214 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1;
+
+
+import java.util.HashMap;
+import java.util.Map;
+import com.kingsrook.qqq.backend.core.actions.metadata.MetaDataAction;
+import com.kingsrook.qqq.backend.core.context.CapturedContext;
+import com.kingsrook.qqq.backend.core.context.QContext;
+import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
+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;
+import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData;
+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.layout.QAppMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
+import com.kingsrook.qqq.backend.core.model.metadata.permissions.PermissionLevel;
+import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
+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.model.session.QSystemUserSession;
+import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule;
+import com.kingsrook.qqq.backend.core.utils.JsonUtils;
+import com.kingsrook.qqq.middleware.javalin.executors.MetaDataExecutor;
+import com.kingsrook.qqq.middleware.javalin.executors.io.MetaDataInput;
+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.MetaDataResponseV1;
+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.Schema;
+import io.javalin.http.Context;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class MetaDataSpecV1 extends AbstractEndpointSpec
+{
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public BasicOperation defineBasicOperation()
+ {
+ return new BasicOperation()
+ .withPath("/metaData")
+ .withHttpMethod(HttpMethod.GET)
+ .withTag(TagsV1.GENERAL)
+ .withShortSummary("Get instance metaData")
+ .withLongDescription("""
+ Load the overall metadata, as is relevant to a frontend, for the entire application, with permissions applied, as per the
+ authenticated user.
+
+ This includes:
+ - Apps (both as a map of name to AppMetaData (`apps`), but also as a tree (`appTree`), for presenting
+ hierarchical navigation),
+ - Tables (but without all details, e.g., fields),
+ - Processes (also without full details, e.g., screens),
+ - Reports
+ - Widgets
+ - Branding
+ - Help Contents
+ - Environment values
+ """
+ );
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public MetaDataInput buildInput(Context context) throws Exception
+ {
+ MetaDataInput input = new MetaDataInput();
+ return (input);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public Map defineComponentSchemas()
+ {
+ return Map.of(MetaDataResponseV1.class.getSimpleName(), new MetaDataResponseV1().toSchema());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public BasicResponse defineBasicSuccessResponse()
+ {
+ Map examples = new HashMap<>();
+
+ QInstance exampleInstance = new QInstance();
+
+ exampleInstance.setAuthentication(new QAuthenticationMetaData().withName("anonymous").withType(QAuthenticationType.FULLY_ANONYMOUS));
+
+ QBackendMetaData exampleBackend = new QBackendMetaData()
+ .withName("example")
+ .withBackendType(MemoryBackendModule.class);
+ exampleInstance.addBackend(exampleBackend);
+
+ QTableMetaData exampleTable = new QTableMetaData()
+ .withName("person")
+ .withLabel("Person")
+ .withBackendName("example")
+ .withPrimaryKeyField("id")
+ .withIsHidden(false)
+ .withIcon(new QIcon().withName("person_outline"))
+ .withCapabilities(Capability.allReadCapabilities())
+ .withCapabilities(Capability.allWriteCapabilities())
+ .withPermissionRules(new QPermissionRules().withLevel(PermissionLevel.NOT_PROTECTED))
+ .withField(new QFieldMetaData("id", QFieldType.INTEGER));
+ exampleInstance.addTable(exampleTable);
+
+ QProcessMetaData exampleProcess = new QProcessMetaData()
+ .withName("samplePersonProcess")
+ .withLabel("Sample Person Process")
+ .withTableName("person")
+ .withIsHidden(false)
+ .withIcon(new QIcon().withName("person_add"))
+ .withPermissionRules(new QPermissionRules().withLevel(PermissionLevel.NOT_PROTECTED))
+ .withStep(new QFrontendStepMetaData().withName("example"));
+ exampleInstance.addProcess(exampleProcess);
+
+ QAppMetaData childApp = new QAppMetaData()
+ .withName("childApp")
+ .withLabel("Child App")
+ .withIcon(new QIcon().withName("child_friendly"))
+ .withPermissionRules(new QPermissionRules().withLevel(PermissionLevel.NOT_PROTECTED))
+ .withChild(exampleProcess);
+ exampleInstance.addApp(childApp);
+
+ QAppMetaData exampleApp = new QAppMetaData()
+ .withName("homeApp")
+ .withLabel("Home App")
+ .withIcon(new QIcon().withName("home"))
+ .withPermissionRules(new QPermissionRules().withLevel(PermissionLevel.NOT_PROTECTED))
+ .withChild(childApp)
+ .withChild(exampleTable);
+ exampleInstance.addApp(exampleApp);
+
+ QContext.withTemporaryContext(new CapturedContext(exampleInstance, new QSystemUserSession()), () ->
+ {
+ try
+ {
+ MetaDataAction metaDataAction = new MetaDataAction();
+ MetaDataOutput output = metaDataAction.execute(new com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput());
+ examples.put("Example", new Example()
+ .withValue(new MetaDataResponseV1()
+ .withMetaDataOutput(output)
+ )
+ );
+ }
+ catch(Exception e)
+ {
+ examples.put("Example", new Example().withValue("Error building example: " + e.getMessage())
+ );
+ }
+ });
+
+ return new BasicResponse("""
+ Overall metadata for the application.""",
+ MetaDataResponseV1.class.getSimpleName(),
+ examples
+ );
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void handleOutput(Context context, MetaDataResponseV1 output) throws Exception
+ {
+ context.result(JsonUtils.toJson(output));
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/MiddlewareVersionV1.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/MiddlewareVersionV1.java
new file mode 100644
index 00000000..ec907d4f
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/MiddlewareVersionV1.java
@@ -0,0 +1,70 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1;
+
+
+import java.util.ArrayList;
+import java.util.List;
+import com.kingsrook.qqq.middleware.javalin.specs.AbstractEndpointSpec;
+import com.kingsrook.qqq.middleware.javalin.specs.AbstractMiddlewareVersion;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class MiddlewareVersionV1 extends AbstractMiddlewareVersion
+{
+ private static List> list = new ArrayList<>();
+
+ static
+ {
+ list.add(new AuthenticationMetaDataSpecV1());
+ list.add(new ManageSessionSpecV1());
+
+ list.add(new MetaDataSpecV1());
+
+ list.add(new ProcessMetaDataSpecV1());
+ list.add(new ProcessInitSpecV1());
+ list.add(new ProcessStepSpecV1());
+ list.add(new ProcessStatusSpecV1());
+ }
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public String getVersion()
+ {
+ return "v1";
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public List> getEndpointSpecs()
+ {
+ return (list);
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ProcessInitSpecV1.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ProcessInitSpecV1.java
new file mode 100644
index 00000000..62377711
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ProcessInitSpecV1.java
@@ -0,0 +1,268 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1;
+
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import com.kingsrook.qqq.backend.core.context.QContext;
+import com.kingsrook.qqq.backend.core.logging.QLogger;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
+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.metadata.processes.QProcessMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
+import com.kingsrook.qqq.backend.core.utils.JsonUtils;
+import com.kingsrook.qqq.backend.core.utils.StringUtils;
+import com.kingsrook.qqq.middleware.javalin.executors.ProcessInitOrStepExecutor;
+import com.kingsrook.qqq.middleware.javalin.executors.io.ProcessInitOrStepInput;
+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.ProcessInitOrStepOrStatusResponseV1;
+import com.kingsrook.qqq.middleware.javalin.specs.v1.utils.ProcessSpecUtilsV1;
+import com.kingsrook.qqq.middleware.javalin.specs.v1.utils.TagsV1;
+import com.kingsrook.qqq.openapi.model.Content;
+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.ContentType;
+import io.javalin.http.Context;
+import org.apache.commons.lang.NotImplementedException;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class ProcessInitSpecV1 extends AbstractEndpointSpec
+{
+ private static final QLogger LOG = QLogger.getLogger(ProcessInitSpecV1.class);
+
+ public static int DEFAULT_ASYNC_STEP_TIMEOUT_MILLIS = 3_000;
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public BasicOperation defineBasicOperation()
+ {
+ return new BasicOperation()
+ .withPath("/processes/{processName}/init")
+ .withHttpMethod(HttpMethod.POST)
+ .withTag(TagsV1.PROCESSES)
+ .withShortSummary("Initialize a process")
+ .withLongDescription("""
+ For a user to start running a process, this endpoint should be called, to start the process
+ and run its first step(s) (any backend steps before the first frontend step).
+
+ Additional process-specific values should posted in a form param named `values`, as JSON object
+ with keys defined by the process in question.
+
+ For a process which needs to operate on a set of records that a user selected, see
+ `recordsParam`, and `recordIds` or `filterJSON`.
+
+ The response will include a `processUUID`, to be included in all subsequent requests relevant
+ to the process.
+
+ Note that this request, if it takes longer than a given threshold* to complete, will return a
+ a `jobUUID`, which should be sent to the `/processes/{processName}/{processUUID}/status/{jobUUID}`
+ endpoint, to poll for a status update.
+
+ *This threshold has a default value of 3,000 ms., but can be set per-request via the form
+ parameter `stepTimeoutMillis`.
+ """);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public List defineRequestParameters()
+ {
+ return List.of(
+ new Parameter()
+ .withName("processName")
+ .withDescription("Name of the process to initialize")
+ .withRequired(true)
+ .withSchema(new Schema().withType(Type.STRING))
+ .withExample("samplePersonProcess")
+ .withIn(In.PATH)
+ );
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public RequestBody defineRequestBody()
+ {
+ return new RequestBody()
+ .withContent(
+ ContentType.MULTIPART_FORM_DATA.getMimeType(), new Content()
+ .withSchema(new Schema()
+ .withType(Type.OBJECT)
+ .withProperty("values", new Schema()
+ .withType(Type.OBJECT)
+ .withDescription("Process-specific field names and values."))
+
+ .withProperty("recordsParam", new Schema()
+ .withDescription("Specifies which other query-param will contain the indicator of initial records to pass in to the process.")
+ .withType(Type.STRING)
+ .withExample("recordIds", new Example().withValue("recordIds"))
+ .withExample("filterJSON", new Example().withValue("recordIds")))
+
+ .withProperty("recordIds", new Schema()
+ .withDescription("Comma-separated list of ids from the table this process is based on, to use as input records for the process. Needs `recordsParam=recordIds` value to be given as well.")
+ .withType(Type.STRING)
+ .withExample("one id", new Example().withValue("1701"))
+ .withExample("multiple ids", new Example().withValue("42,47")))
+
+ .withProperty("filterJSON", new Schema()
+ .withDescription("JSON encoded QQueryFilter object, to execute against the table this process is based on, to find input records for the process. Needs `recordsParam=filterJSON` value to be given as well.")
+ .withType(Type.STRING)
+ .withExample("empty filter (all records)", new Example().withValue("{}"))
+ .withExample("filter by a condition", new Example().withValue(
+ JsonUtils.toJson(new QQueryFilter().withCriteria(new QFilterCriteria("id", QCriteriaOperator.LESS_THAN, 10))))
+ ))
+
+ .withProperty("stepTimeoutMillis", new Schema()
+ .withDescription("Optionally change the time that the server will wait for the job before letting it go asynchronous. Default value is 3000.")
+ .withType(Type.INTEGER)
+ .withExample("shorter timeout", new Example().withValue("500"))
+ .withExample("longer timeout", new Example().withValue("60000")))
+
+ .withProperty("file", new Schema()
+ .withType(Type.STRING)
+ .withFormat("binary")
+ .withDescription("A file upload, for processes which expect to be initialized with an uploaded file.")
+ )
+ )
+ );
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public ProcessInitOrStepInput buildInput(Context context) throws Exception
+ {
+ ProcessInitOrStepInput processInitOrStepInput = new ProcessInitOrStepInput();
+ processInitOrStepInput.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.BREAK);
+
+ processInitOrStepInput.setProcessName(getRequestParam(context, "processName"));
+ processInitOrStepInput.setStepTimeoutMillis(Objects.requireNonNullElse(getRequestParamInteger(context, "stepTimeoutMillis"), DEFAULT_ASYNC_STEP_TIMEOUT_MILLIS));
+ processInitOrStepInput.setValues(getRequestParamMap(context, "values"));
+
+ String recordsParam = getRequestParam(context, "recordsParam");
+ String recordIds = getRequestParam(context, "recordIds");
+ String filterJSON = getRequestParam(context, "filterJSON");
+ QQueryFilter initialRecordsFilter = buildProcessInitRecordsFilter(recordsParam, recordIds, filterJSON, processInitOrStepInput);
+ processInitOrStepInput.setRecordsFilter(initialRecordsFilter);
+
+ // todo - uploaded files
+ // todo - archive uploaded files?
+
+ return (processInitOrStepInput);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private static QQueryFilter buildProcessInitRecordsFilter(String recordsParam, String recordIds, String filterJSON, ProcessInitOrStepInput processInitOrStepInput) throws IOException
+ {
+ QProcessMetaData process = QContext.getQInstance().getProcess(processInitOrStepInput.getProcessName());
+ QTableMetaData table = QContext.getQInstance().getTable(process.getTableName());
+
+ if(table == null)
+ {
+ LOG.info("No table found in process - so not building an init records filter.");
+ return (null);
+ }
+ String primaryKeyField = table.getPrimaryKeyField();
+
+ if(StringUtils.hasContent(recordsParam))
+ {
+ return switch(recordsParam)
+ {
+ case "recordIds" ->
+ {
+ Serializable[] idStrings = recordIds.split(",");
+ yield (new QQueryFilter().withCriteria(new QFilterCriteria()
+ .withFieldName(primaryKeyField)
+ .withOperator(QCriteriaOperator.IN)
+ .withValues(Arrays.stream(idStrings).toList())));
+ }
+ case "filterJSON" -> (JsonUtils.toObject(filterJSON, QQueryFilter.class));
+ case "filterId" -> throw (new NotImplementedException("Saved filters are not yet implemented."));
+ default -> throw (new IllegalArgumentException("Unrecognized value [" + recordsParam + "] for query parameter: recordsParam"));
+ };
+ }
+
+ return (null);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public BasicResponse defineBasicSuccessResponse()
+ {
+ return new BasicResponse("""
+ State of the initialization of the job, with different fields set, based on the
+ status of the task.""",
+
+ ProcessSpecUtilsV1.getResponseSchemaRefName(),
+ ProcessSpecUtilsV1.buildResponseExample()
+ );
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void handleOutput(Context context, ProcessInitOrStepOrStatusResponseV1 output) throws Exception
+ {
+ ProcessSpecUtilsV1.handleOutput(context, output);
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ProcessMetaDataSpecV1.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ProcessMetaDataSpecV1.java
new file mode 100644
index 00000000..d311dd8c
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ProcessMetaDataSpecV1.java
@@ -0,0 +1,140 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1;
+
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import com.kingsrook.qqq.backend.core.utils.JsonUtils;
+import com.kingsrook.qqq.middleware.javalin.executors.ProcessMetaDataExecutor;
+import com.kingsrook.qqq.middleware.javalin.executors.io.ProcessMetaDataInput;
+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.ProcessMetaDataResponseV1;
+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 ProcessMetaDataSpecV1 extends AbstractEndpointSpec
+{
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public BasicOperation defineBasicOperation()
+ {
+ return new BasicOperation()
+ .withPath("/metaData/process/{processName}")
+ .withHttpMethod(HttpMethod.GET)
+ .withTag(TagsV1.PROCESSES)
+ .withShortSummary("Get process metaData")
+ .withLongDescription("""
+ Load the full metadata for a single process, including all screens (aka, frontend steps), which a frontend
+ needs to display to users."""
+ );
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public List defineRequestParameters()
+ {
+ return List.of(
+ new Parameter()
+ .withName("processName")
+ .withDescription("Name of the process to load.")
+ .withRequired(true)
+ .withSchema(new Schema().withType(Type.STRING))
+ .withExample("samplePersonProcess")
+ .withIn(In.PATH)
+ );
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public ProcessMetaDataInput buildInput(Context context) throws Exception
+ {
+ ProcessMetaDataInput input = new ProcessMetaDataInput();
+ input.setProcessName(getRequestParam(context, "processName"));
+ return (input);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public Map defineComponentSchemas()
+ {
+ return Map.of(ProcessMetaDataResponseV1.class.getSimpleName(), new ProcessMetaDataResponseV1().toSchema());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public BasicResponse defineBasicSuccessResponse()
+ {
+ Map examples = new LinkedHashMap<>();
+ examples.put("TODO", new Example()
+ .withValue(new ProcessMetaDataResponseV1())); // todo do
+
+ return new BasicResponse("""
+ The full process metadata""",
+ ProcessMetaDataResponseV1.class.getSimpleName(),
+ examples
+ );
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void handleOutput(Context context, ProcessMetaDataResponseV1 output) throws Exception
+ {
+ context.result(JsonUtils.toJson(output.getProcessMetaData()));
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ProcessStatusSpecV1.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ProcessStatusSpecV1.java
new file mode 100644
index 00000000..d7625157
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ProcessStatusSpecV1.java
@@ -0,0 +1,164 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1;
+
+
+import java.util.List;
+import java.util.Map;
+import com.kingsrook.qqq.middleware.javalin.executors.ProcessStatusExecutor;
+import com.kingsrook.qqq.middleware.javalin.executors.io.ProcessStatusInput;
+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.ProcessInitOrStepOrStatusResponseV1;
+import com.kingsrook.qqq.middleware.javalin.specs.v1.utils.ProcessSpecUtilsV1;
+import com.kingsrook.qqq.middleware.javalin.specs.v1.utils.TagsV1;
+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 ProcessStatusSpecV1 extends AbstractEndpointSpec
+{
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public BasicOperation defineBasicOperation()
+ {
+ return new BasicOperation()
+ .withPath("/processes/{processName}/{processUUID}/status/{jobUUID}")
+ .withHttpMethod(HttpMethod.GET)
+ .withTag(TagsV1.PROCESSES)
+ .withShortSummary("Get job status")
+ .withLongDescription("""
+ Get the status of a running job for a process.
+
+ Response is the same format as for an init or step call that completed synchronously.
+ """
+ );
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public List defineRequestParameters()
+ {
+ return List.of(
+ new Parameter()
+ .withName("processName")
+ .withDescription("Name of the process that is being ran")
+ .withRequired(true)
+ .withSchema(new Schema().withType(Type.STRING))
+ .withExample("samplePersonProcess")
+ .withIn(In.PATH),
+
+ new Parameter()
+ .withName("processUUID")
+ .withDescription("Unique identifier for this run of the process - as was returned by the `init` call.")
+ .withRequired(true)
+ .withSchema(new Schema().withType(Type.STRING).withFormat("uuid"))
+ .withExample(ProcessSpecUtilsV1.EXAMPLE_PROCESS_UUID)
+ .withIn(In.PATH),
+
+ new Parameter()
+ .withName("jobUUID")
+ .withDescription("Unique identifier for the asynchronous job being executed, as returned by an `init` or `step` call that went asynch.")
+ .withRequired(true)
+ .withSchema(new Schema().withType(Type.STRING).withFormat("uuid"))
+ .withExample(ProcessSpecUtilsV1.EXAMPLE_JOB_UUID)
+ .withIn(In.PATH)
+ );
+ }
+
+
+
+ /***************************************************************************
+ ** These aren't in the components sub-package, so they don't get auto-found.
+ ***************************************************************************/
+ @Override
+ public Map defineComponentSchemas()
+ {
+ return Map.of(
+ ProcessSpecUtilsV1.getResponseSchemaRefName(), new ProcessInitOrStepOrStatusResponseV1().toSchema(),
+ "ProcessStepComplete", new ProcessInitOrStepOrStatusResponseV1.ProcessStepComplete().toSchema(),
+ "ProcessStepJobStarted", new ProcessInitOrStepOrStatusResponseV1.ProcessStepJobStarted().toSchema(),
+ "ProcessStepRunning", new ProcessInitOrStepOrStatusResponseV1.ProcessStepRunning().toSchema(),
+ "ProcessStepError", new ProcessInitOrStepOrStatusResponseV1.ProcessStepError().toSchema()
+ );
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public ProcessStatusInput buildInput(Context context) throws Exception
+ {
+ ProcessStatusInput input = new ProcessStatusInput();
+ input.setProcessName(getRequestParam(context, "processName"));
+ input.setProcessUUID(getRequestParam(context, "processUUID"));
+ input.setJobUUID(getRequestParam(context, "jobUUID"));
+ return (input);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public BasicResponse defineBasicSuccessResponse()
+ {
+ return new BasicResponse("""
+ State of the backend's running of the specified job, with different fields set,
+ based on the status of the job.""",
+ // new ProcessInitOrStepOrStatusResponseV1().toSchema(),
+
+ ProcessSpecUtilsV1.getResponseSchemaRefName(),
+ ProcessSpecUtilsV1.buildResponseExample()
+ );
+ }
+
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void handleOutput(Context context, ProcessInitOrStepOrStatusResponseV1 output) throws Exception
+ {
+ ProcessSpecUtilsV1.handleOutput(context, output);
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ProcessStepSpecV1.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ProcessStepSpecV1.java
new file mode 100644
index 00000000..156bf7ce
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ProcessStepSpecV1.java
@@ -0,0 +1,201 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1;
+
+
+import java.util.List;
+import java.util.Objects;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
+import com.kingsrook.qqq.middleware.javalin.executors.ProcessInitOrStepExecutor;
+import com.kingsrook.qqq.middleware.javalin.executors.io.ProcessInitOrStepInput;
+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.ProcessInitOrStepOrStatusResponseV1;
+import com.kingsrook.qqq.middleware.javalin.specs.v1.utils.ProcessSpecUtilsV1;
+import com.kingsrook.qqq.middleware.javalin.specs.v1.utils.TagsV1;
+import com.kingsrook.qqq.openapi.model.Content;
+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.ContentType;
+import io.javalin.http.Context;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class ProcessStepSpecV1 extends AbstractEndpointSpec
+{
+ public static int DEFAULT_ASYNC_STEP_TIMEOUT_MILLIS = 3_000;
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public BasicOperation defineBasicOperation()
+ {
+ return new BasicOperation()
+ .withPath("/processes/{processName}/{processUUID}/step/{stepName}")
+ .withHttpMethod(HttpMethod.POST)
+ .withTag(TagsV1.PROCESSES)
+ .withShortSummary("Run a step in a process")
+ .withLongDescription("""
+ To run the next step in a process, this endpoint should be called, with the `processName`
+ and existing `processUUID`, as well as the step that was just completed in the frontend,
+ given as `stepName`.
+
+ Additional process-specific values should posted in a form param named `values`, as JSON object
+ with keys defined by the process in question.
+
+ Note that this request, if it takes longer than a given threshold* to complete, will return a
+ a `jobUUID`, which should be sent to the `/processes/{processName}/{processUUID}/status/{jobUUID}`
+ endpoint, to poll for a status update.
+
+ *This threshold has a default value of 3,000 ms., but can be set per-request via the form
+ parameter `stepTimeoutMillis`.
+ """);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public List defineRequestParameters()
+ {
+ return List.of(
+
+ new Parameter()
+ .withName("processName")
+ .withDescription("Name of the process to perform the step in.")
+ .withRequired(true)
+ .withExample("samplePersonProcess")
+ .withSchema(new Schema().withType(Type.STRING))
+ .withIn(In.PATH),
+
+ new Parameter()
+ .withName("processUUID")
+ .withDescription("Unique identifier for this run of the process - as was returned by the `init` call.")
+ .withRequired(true)
+ .withSchema(new Schema().withType(Type.STRING))
+ .withExample(ProcessSpecUtilsV1.EXAMPLE_PROCESS_UUID)
+ .withIn(In.PATH),
+
+ new Parameter()
+ .withName("stepName")
+ .withDescription("Name of the frontend step that the user has just completed.")
+ .withRequired(true)
+ .withSchema(new Schema().withType(Type.STRING))
+ .withExample("inputForm")
+ .withIn(In.PATH)
+ );
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public RequestBody defineRequestBody()
+ {
+ return new RequestBody()
+ .withContent(ContentType.MULTIPART_FORM_DATA.getMimeType(), new Content()
+ .withSchema(new Schema()
+ .withType(Type.OBJECT)
+ .withProperty("values", new Schema()
+ .withType(Type.OBJECT)
+ .withDescription("Process-specific field names and values."))
+
+ .withProperty("stepTimeoutMillis", new Schema()
+ .withDescription("Optionally change the time that the server will wait for the job before letting it go asynchronous. Default value is 3000.")
+ .withType(Type.INTEGER)
+ .withExample("shorter timeout", new Example().withValue("500"))
+ .withExample("longer timeout", new Example().withValue("60000")))
+
+ .withProperty("file", new Schema()
+ .withType(Type.STRING)
+ .withFormat("binary")
+ .withDescription("A file upload, for process steps which expect an uploaded file."))
+ )
+ );
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public ProcessInitOrStepInput buildInput(Context context) throws Exception
+ {
+ ProcessInitOrStepInput processInitOrStepInput = new ProcessInitOrStepInput();
+ processInitOrStepInput.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.BREAK);
+
+ processInitOrStepInput.setProcessName(getRequestParam(context, "processName"));
+ processInitOrStepInput.setProcessUUID(getRequestParam(context, "processUUID"));
+ processInitOrStepInput.setStartAfterStep(getRequestParam(context, "stepName"));
+ processInitOrStepInput.setStepTimeoutMillis(Objects.requireNonNullElse(getRequestParamInteger(context, "stepTimeoutMillis"), DEFAULT_ASYNC_STEP_TIMEOUT_MILLIS));
+ processInitOrStepInput.setValues(getRequestParamMap(context, "values"));
+
+ // todo - uploaded files
+ // todo - archive uploaded files?
+
+ return (processInitOrStepInput);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public BasicResponse defineBasicSuccessResponse()
+ {
+ return new BasicResponse("""
+ State of the backend's running of the next step(s) of the job, with different fields set,
+ based on the status of the job.""",
+
+ ProcessSpecUtilsV1.getResponseSchemaRefName(),
+ ProcessSpecUtilsV1.buildResponseExample()
+ );
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void handleOutput(Context context, ProcessInitOrStepOrStatusResponseV1 output) throws Exception
+ {
+ ProcessSpecUtilsV1.handleOutput(context, output);
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/AuthenticationMetaDataResponseV1.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/AuthenticationMetaDataResponseV1.java
new file mode 100644
index 00000000..22db2725
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/AuthenticationMetaDataResponseV1.java
@@ -0,0 +1,340 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.responses;
+
+
+import com.kingsrook.qqq.backend.core.model.metadata.authentication.Auth0AuthenticationMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData;
+import com.kingsrook.qqq.middleware.javalin.executors.io.AuthenticationMetaDataOutputInterface;
+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.OpenAPIOneOf;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class AuthenticationMetaDataResponseV1 implements AuthenticationMetaDataOutputInterface, ToSchema
+{
+ @OpenAPIDescription("""
+ Specifier for the type of authentication module being used.
+
+ Frontends should use this value to determine how to prompt the user for authentication credentials.
+ In addition, depending on this value, additional properties will be included in this object, as
+ may be needed to complete the authorization workflow with the provider (e.g., a baseUrl, clientId,
+ and audience for an OAuth type workflow).""")
+ private String type;
+
+ @OpenAPIDescription("""
+ Unique name for the authentication metaData object within the QInstance.
+ """)
+ private String name;
+
+ @OpenAPIDescription("""
+ Additional values, as determined by the type of authentication provider.
+ """)
+ @OpenAPIOneOf()
+ private Values values;
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public sealed interface Values permits EmptyValues, Auth0Values
+ {
+
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("No additional values are used for some authentication providers.")
+ public static final class EmptyValues implements Values
+ {
+
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Additional values used by the Auth0 type authentication provider.")
+ public static final class Auth0Values implements Values
+ {
+ @OpenAPIDescription("ClientId for auth0")
+ private String clientId;
+
+ @OpenAPIDescription("BaseUrl for auth0")
+ private String baseUrl;
+
+ @OpenAPIDescription("Audience for auth0")
+ private String audience;
+
+
+
+ /*******************************************************************************
+ ** Getter for clientId
+ **
+ *******************************************************************************/
+ public String getClientId()
+ {
+ return clientId;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for clientId
+ **
+ *******************************************************************************/
+ public void setClientId(String clientId)
+ {
+ this.clientId = clientId;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for clientId
+ **
+ *******************************************************************************/
+ public Auth0Values withClientId(String clientId)
+ {
+ this.clientId = clientId;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for baseUrl
+ **
+ *******************************************************************************/
+ public String getBaseUrl()
+ {
+ return baseUrl;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for baseUrl
+ **
+ *******************************************************************************/
+ public void setBaseUrl(String baseUrl)
+ {
+ this.baseUrl = baseUrl;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for baseUrl
+ **
+ *******************************************************************************/
+ public Auth0Values withBaseUrl(String baseUrl)
+ {
+ this.baseUrl = baseUrl;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for audience
+ **
+ *******************************************************************************/
+ public String getAudience()
+ {
+ return audience;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for audience
+ **
+ *******************************************************************************/
+ public void setAudience(String audience)
+ {
+ this.audience = audience;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for audience
+ **
+ *******************************************************************************/
+ public Auth0Values withAudience(String audience)
+ {
+ this.audience = audience;
+ return (this);
+ }
+
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void setAuthenticationMetaData(QAuthenticationMetaData qAuthenticationMetaData)
+ {
+ setType(qAuthenticationMetaData.getType().name());
+ setName(qAuthenticationMetaData.getName());
+
+ if(qAuthenticationMetaData instanceof Auth0AuthenticationMetaData auth0MetaData)
+ {
+ // values = new LinkedHashMap<>();
+ // values.put("clientId", auth0MetaData.getClientId());
+ // values.put("baseUrl", auth0MetaData.getBaseUrl());
+ // values.put("audience", auth0MetaData.getAudience());
+ Auth0Values auth0Values = new Auth0Values();
+ values = auth0Values;
+ auth0Values.setClientId(auth0MetaData.getClientId());
+ auth0Values.setBaseUrl(auth0MetaData.getBaseUrl());
+ auth0Values.setAudience(auth0MetaData.getAudience());
+ }
+
+ /*
+ JSONObject jsonObject = new JSONObject(JsonUtils.toJson(qAuthenticationMetaData));
+ for(String key : jsonObject.keySet())
+ {
+ if("name".equals(key) || "type".equals(key))
+ {
+ continue;
+ }
+
+ if(values == null)
+ {
+ values = new LinkedHashMap<>();
+ }
+
+ Object value = jsonObject.get(key);
+ if(value instanceof Serializable s)
+ {
+ values.put(key, s);
+ }
+ }
+ */
+ }
+
+
+
+ /*******************************************************************************
+ ** 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 AuthenticationMetaDataResponseV1 withType(String type)
+ {
+ this.type = type;
+ 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 AuthenticationMetaDataResponseV1 withName(String name)
+ {
+ this.name = name;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for values
+ *******************************************************************************/
+ public Values getValues()
+ {
+ return (this.values);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for values
+ *******************************************************************************/
+ public void setValues(Values values)
+ {
+ this.values = values;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for values
+ *******************************************************************************/
+ public AuthenticationMetaDataResponseV1 withValues(Values values)
+ {
+ this.values = values;
+ return (this);
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/BasicErrorResponseV1.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/BasicErrorResponseV1.java
new file mode 100644
index 00000000..0062e105
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/BasicErrorResponseV1.java
@@ -0,0 +1,69 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.responses;
+
+
+import com.kingsrook.qqq.middleware.javalin.schemabuilder.ToSchema;
+import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIDescription;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class BasicErrorResponseV1 implements ToSchema
+{
+ @OpenAPIDescription("Description of the error")
+ private String error;
+
+
+
+ /*******************************************************************************
+ ** Getter for error
+ *******************************************************************************/
+ public String getError()
+ {
+ return (this.error);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for error
+ *******************************************************************************/
+ public void setError(String error)
+ {
+ this.error = error;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for error
+ *******************************************************************************/
+ public BasicErrorResponseV1 withError(String error)
+ {
+ this.error = error;
+ return (this);
+ }
+
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/ManageSessionResponseV1.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/ManageSessionResponseV1.java
new file mode 100644
index 00000000..90d6810c
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/ManageSessionResponseV1.java
@@ -0,0 +1,107 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.responses;
+
+
+import java.io.Serializable;
+import java.util.Map;
+import com.kingsrook.qqq.middleware.javalin.executors.io.ManageSessionOutputInterface;
+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.OpenAPIHasAdditionalProperties;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class ManageSessionResponseV1 implements ManageSessionOutputInterface, ToSchema
+{
+ @OpenAPIDescription("Unique identifier of the session. Required to be returned on subsequent requests in the sessionUUID Cookie, to prove authentication.")
+ private String uuid;
+
+ @OpenAPIDescription("Optional object with application-defined values.")
+ @OpenAPIHasAdditionalProperties()
+ private Map values;
+
+
+
+ /*******************************************************************************
+ ** Getter for uuid
+ *******************************************************************************/
+ public String getUuid()
+ {
+ return (this.uuid);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for uuid
+ *******************************************************************************/
+ public void setUuid(String uuid)
+ {
+ this.uuid = uuid;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for uuid
+ *******************************************************************************/
+ public ManageSessionResponseV1 withUuid(String uuid)
+ {
+ this.uuid = uuid;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for values
+ *******************************************************************************/
+ public Map getValues()
+ {
+ return (this.values);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for values
+ *******************************************************************************/
+ public void setValues(Map values)
+ {
+ this.values = values;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for values
+ *******************************************************************************/
+ public ManageSessionResponseV1 withValues(Map values)
+ {
+ this.values = values;
+ return (this);
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/MetaDataResponseV1.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/MetaDataResponseV1.java
new file mode 100644
index 00000000..c6d91c15
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/MetaDataResponseV1.java
@@ -0,0 +1,155 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.responses;
+
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
+import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendAppMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendProcessMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendTableMetaData;
+import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
+import com.kingsrook.qqq.middleware.javalin.executors.io.MetaDataOutputInterface;
+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.schemabuilder.annotations.OpenAPIMapValueType;
+import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components.AppMetaData;
+import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components.AppTreeNode;
+import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components.ProcessMetaDataLight;
+import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components.TableMetaDataLight;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class MetaDataResponseV1 implements MetaDataOutputInterface, ToSchema
+{
+ @OpenAPIDescription("Map of all apps within the QQQ Instance (that the user has permission to see that they exist).")
+ @OpenAPIMapValueType(value = AppMetaData.class, useRef = true)
+ private Map apps;
+
+ @OpenAPIDescription("Tree of apps within the QQQ Instance, sorted and organized hierarchically, for presentation to a user.")
+ @OpenAPIListItems(value = AppTreeNode.class, useRef = true)
+ private List appTree;
+
+ @OpenAPIDescription("Map of all tables within the QQQ Instance (that the user has permission to see that they exist).")
+ @OpenAPIMapValueType(value = TableMetaDataLight.class, useRef = true)
+ private Map tables;
+
+ @OpenAPIDescription("Map of all processes within the QQQ Instance (that the user has permission to see that they exist).")
+ @OpenAPIMapValueType(value = ProcessMetaDataLight.class, useRef = true)
+ private Map processes;
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void setMetaDataOutput(MetaDataOutput metaDataOutput)
+ {
+ apps = new HashMap<>();
+ for(QFrontendAppMetaData app : CollectionUtils.nonNullMap(metaDataOutput.getApps()).values())
+ {
+ apps.put(app.getName(), new AppMetaData(app));
+ }
+
+ appTree = new ArrayList<>();
+ for(com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNode app : CollectionUtils.nonNullList(metaDataOutput.getAppTree()))
+ {
+ appTree.add(new AppTreeNode(app));
+ }
+
+ tables = new HashMap<>();
+ for(QFrontendTableMetaData table : CollectionUtils.nonNullMap(metaDataOutput.getTables()).values())
+ {
+ tables.put(table.getName(), new TableMetaDataLight(table));
+ }
+
+ processes = new HashMap<>();
+ for(QFrontendProcessMetaData process : CollectionUtils.nonNullMap(metaDataOutput.getProcesses()).values())
+ {
+ processes.put(process.getName(), new ProcessMetaDataLight(process));
+ }
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for MetaDataOutput
+ **
+ *******************************************************************************/
+ public MetaDataResponseV1 withMetaDataOutput(MetaDataOutput metaDataOutput)
+ {
+ setMetaDataOutput(metaDataOutput);
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for apps
+ **
+ *******************************************************************************/
+ public Map getApps()
+ {
+ return apps;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for appTree
+ **
+ *******************************************************************************/
+ public List getAppTree()
+ {
+ return appTree;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for tables
+ **
+ *******************************************************************************/
+ public Map getTables()
+ {
+ return tables;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for processes
+ **
+ *******************************************************************************/
+ public Map getProcesses()
+ {
+ return processes;
+ }
+
+}
\ No newline at end of file
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/ProcessInitOrStepOrStatusResponseV1.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/ProcessInitOrStepOrStatusResponseV1.java
new file mode 100644
index 00000000..5723c3c2
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/ProcessInitOrStepOrStatusResponseV1.java
@@ -0,0 +1,444 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.responses;
+
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import com.kingsrook.qqq.middleware.javalin.executors.io.ProcessInitOrStepOrStatusOutputInterface;
+import com.kingsrook.qqq.middleware.javalin.schemabuilder.SchemaBuilder;
+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.OpenAPIOneOf;
+import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components.FieldMetaData;
+import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components.FrontendStep;
+import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components.ProcessMetaDataAdjustment;
+import com.kingsrook.qqq.openapi.model.Schema;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class ProcessInitOrStepOrStatusResponseV1 implements ProcessInitOrStepOrStatusOutputInterface, ToSchema
+{
+ private TypedResponse typedResponse;
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIOneOf()
+ public static sealed class TypedResponse implements ToSchema permits ProcessStepComplete, ProcessStepJobStarted, ProcessStepRunning, ProcessStepError
+ {
+ @OpenAPIDescription("What kind of response has been received. Determines what additional fields will be set.")
+ private String type;
+
+ @OpenAPIDescription("Unique identifier for a running instance the process.")
+ private String processUUID;
+
+
+
+ /*******************************************************************************
+ ** Getter for type
+ **
+ *******************************************************************************/
+ public String getType()
+ {
+ return type;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for processUUID
+ **
+ *******************************************************************************/
+ public String getProcessUUID()
+ {
+ return processUUID;
+ }
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIIncludeProperties(ancestorClasses = { TypedResponse.class })
+ @OpenAPIDescription("Data returned after the job is complete (whether it was synchronous, or asynchronous)")
+ public static final class ProcessStepComplete extends TypedResponse
+ {
+ @OpenAPIDescription("Name of the next process step that needs to run (a frontend step). If there are no more steps in the process, this field will not be included. ")
+ private String nextStep;
+
+ @OpenAPIDescription("Current values for fields used by the process.Keys are Strings, values can be any type, as determined by the application & process.")
+ private Map values;
+
+ @OpenAPIDescription("Changes to be made to the process's metaData.")
+ private ProcessMetaDataAdjustment processMetaDataAdjustment;
+
+
+
+ /*******************************************************************************
+ ** Getter for nextStep
+ **
+ *******************************************************************************/
+ public String getNextStep()
+ {
+ return nextStep;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for values
+ **
+ *******************************************************************************/
+ public Map getValues()
+ {
+ return values;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for processMetaDataAdjustment
+ **
+ *******************************************************************************/
+ public ProcessMetaDataAdjustment getProcessMetaDataAdjustment()
+ {
+ return processMetaDataAdjustment;
+ }
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIIncludeProperties(ancestorClasses = { TypedResponse.class })
+ @OpenAPIDescription("In case the backend needs more time, this is a UUID of the background job that has been started.")
+ public static final class ProcessStepJobStarted extends TypedResponse
+ {
+ @OpenAPIDescription("Unique identifier for a running step of the process. Must be passed into `status` check calls.")
+ private String jobUUID;
+
+
+
+ /*******************************************************************************
+ ** Getter for jobUUID
+ **
+ *******************************************************************************/
+ public String getJobUUID()
+ {
+ return jobUUID;
+ }
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIIncludeProperties(ancestorClasses = { TypedResponse.class })
+ @OpenAPIDescription("Response to a status check for a backgrounded job.")
+ public static final class ProcessStepRunning extends TypedResponse
+ {
+ @OpenAPIDescription("Status message regarding the running process step.")
+ private String message;
+
+ @OpenAPIDescription("Optional indicator of progress (e.g., `current` of `total`, as in (`1 of 10`).")
+ private Integer current;
+
+ @OpenAPIDescription("Optional indicator of progress (e.g., `current` of `total`, as in (`1 of 10`).")
+ private Integer total;
+
+
+
+ /*******************************************************************************
+ ** Getter for message
+ **
+ *******************************************************************************/
+ public String getMessage()
+ {
+ return message;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for current
+ **
+ *******************************************************************************/
+ public Integer getCurrent()
+ {
+ return current;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for total
+ **
+ *******************************************************************************/
+ public Integer getTotal()
+ {
+ return total;
+ }
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIIncludeProperties(ancestorClasses = { TypedResponse.class })
+ @OpenAPIDescription("In case an error is thrown in the backend job.")
+ public static final class ProcessStepError extends TypedResponse
+ {
+ @OpenAPIDescription("Exception message, in case the process step threw an error.")
+ private String error;
+
+ @OpenAPIDescription("Optional user-facing exception message, in case the process step threw a user-facing error.")
+ private String userFacingError;
+
+
+
+ /*******************************************************************************
+ ** Getter for error
+ **
+ *******************************************************************************/
+ public String getError()
+ {
+ return error;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for userFacingError
+ **
+ *******************************************************************************/
+ public String getUserFacingError()
+ {
+ return userFacingError;
+ }
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void setType(Type type)
+ {
+ this.typedResponse = switch(type)
+ {
+ case COMPLETE -> new ProcessStepComplete();
+ case JOB_STARTED -> new ProcessStepJobStarted();
+ case RUNNING -> new ProcessStepRunning();
+ case ERROR -> new ProcessStepError();
+ };
+
+ this.typedResponse.type = type.toString();
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void setProcessUUID(String processUUID)
+ {
+ this.typedResponse.processUUID = processUUID;
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void setNextStep(String nextStep)
+ {
+ if(this.typedResponse instanceof ProcessStepComplete complete)
+ {
+ complete.nextStep = nextStep;
+ }
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void setValues(Map values)
+ {
+ if(this.typedResponse instanceof ProcessStepComplete complete)
+ {
+ complete.values = values;
+ }
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void setProcessMetaDataAdjustment(com.kingsrook.qqq.backend.core.model.actions.processes.ProcessMetaDataAdjustment processMetaDataAdjustment)
+ {
+ if(this.typedResponse instanceof ProcessStepComplete complete)
+ {
+ if(processMetaDataAdjustment == null)
+ {
+ complete.processMetaDataAdjustment = null;
+ }
+ else
+ {
+ complete.processMetaDataAdjustment = new ProcessMetaDataAdjustment();
+
+ Map updatedFields = processMetaDataAdjustment.getUpdatedFields().entrySet()
+ .stream().collect(Collectors.toMap(e -> e.getKey(), f -> new FieldMetaData(f.getValue())));
+ complete.processMetaDataAdjustment.setUpdatedFields(updatedFields);
+
+ List updatedFrontendSteps = processMetaDataAdjustment.getUpdatedFrontendStepList()
+ .stream().map(f -> new FrontendStep(f)).toList();
+ complete.processMetaDataAdjustment.setUpdatedFrontendStepList(updatedFrontendSteps);
+ }
+ }
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void setJobUUID(String jobUUID)
+ {
+ if(this.typedResponse instanceof ProcessStepJobStarted jobStarted)
+ {
+ jobStarted.jobUUID = jobUUID;
+ }
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void setMessage(String message)
+ {
+ if(this.typedResponse instanceof ProcessStepRunning running)
+ {
+ running.message = message;
+ }
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void setCurrent(Integer current)
+ {
+ if(this.typedResponse instanceof ProcessStepRunning running)
+ {
+ running.current = current;
+ }
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void setTotal(Integer total)
+ {
+ if(this.typedResponse instanceof ProcessStepRunning running)
+ {
+ running.total = total;
+ }
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void setError(String errorString)
+ {
+ if(this.typedResponse instanceof ProcessStepError error)
+ {
+ error.error = errorString;
+ }
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void setUserFacingError(String userFacingError)
+ {
+ if(this.typedResponse instanceof ProcessStepError error)
+ {
+ error.userFacingError = userFacingError;
+ }
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public Schema toSchema()
+ {
+ return new SchemaBuilder().classToSchema(TypedResponse.class);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for typedResponse
+ **
+ *******************************************************************************/
+ public TypedResponse getTypedResponse()
+ {
+ return typedResponse;
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/ProcessMetaDataResponseV1.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/ProcessMetaDataResponseV1.java
new file mode 100644
index 00000000..2a4d45b5
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/ProcessMetaDataResponseV1.java
@@ -0,0 +1,72 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.responses;
+
+
+import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendProcessMetaData;
+import com.kingsrook.qqq.middleware.javalin.executors.io.ProcessMetaDataOutputInterface;
+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.ProcessMetaData;
+import com.kingsrook.qqq.openapi.model.Schema;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class ProcessMetaDataResponseV1 implements ProcessMetaDataOutputInterface, ToSchema
+{
+ private ProcessMetaData processMetaData;
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void setProcessMetaData(QFrontendProcessMetaData frontendProcessMetaData)
+ {
+ this.processMetaData = new ProcessMetaData(frontendProcessMetaData);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public Schema toSchema()
+ {
+ return new SchemaBuilder().classToSchema(ProcessMetaData.class);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for processMetaData
+ **
+ *******************************************************************************/
+ public ProcessMetaData getProcessMetaData()
+ {
+ return processMetaData;
+ }
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/AppMetaData.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/AppMetaData.java
new file mode 100644
index 00000000..0dd4d914
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/AppMetaData.java
@@ -0,0 +1,159 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components;
+
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendAppMetaData;
+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;
+import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIMapValueType;
+
+
+/***************************************************************************
+ **
+ ***************************************************************************/
+public class AppMetaData implements ToSchema
+{
+ @OpenAPIExclude()
+ private QFrontendAppMetaData wrapped;
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public AppMetaData(QFrontendAppMetaData wrapped)
+ {
+ this.wrapped = wrapped;
+ }
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public AppMetaData()
+ {
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Unique name for this app within the QQQ Instance")
+ public String getName()
+ {
+ return (this.wrapped.getName());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("User-facing name for this app")
+ public String getLabel()
+ {
+ return (this.wrapped.getLabel());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Name of an icon for the app, from the material UI icon set")
+ public String getIconName()
+ {
+ return (this.wrapped.getIconName());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("List of widgets names that are part of this app. These strings should be keys to the widgets map in the QQQ Instance.")
+ @OpenAPIListItems(value = String.class)
+ public List getWidgets()
+ {
+ return (this.wrapped.getWidgets());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("List of other apps, tables, process, and reports, which are contained within this app.")
+ @OpenAPIListItems(value = AppTreeNode.class, useRef = true)
+ public List getChildren()
+ {
+ return (CollectionUtils.nonNullList(this.wrapped.getChildren()).stream().map(a -> new AppTreeNode(a)).toList());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Map of other apps, tables, process, and reports, which are contained within this app. Same contents as the children list, just structured as a map.")
+ @OpenAPIMapValueType(value = AppTreeNode.class, useRef = true)
+ public Map getChildMap()
+ {
+ return (CollectionUtils.nonNullMap(this.wrapped.getChildMap()).entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> new AppTreeNode(e.getValue()))));
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("List of sections - sub-divisions of the app, to further organize its children.")
+ @OpenAPIListItems(value = AppSection.class, useRef = true) // todo local type
+ public List getSections()
+ {
+ return (CollectionUtils.nonNullList(this.wrapped.getSections()).stream().map(s -> new AppSection(s)).toList());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Additional meta-data describing the app, which may not be known to the QQQ backend core module.")
+ public Map getSupplementalAppMetaData()
+ {
+ return (new LinkedHashMap<>(CollectionUtils.nonNullMap(this.wrapped.getSupplementalAppMetaData())));
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/AppSection.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/AppSection.java
new file mode 100644
index 00000000..236cf0df
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/AppSection.java
@@ -0,0 +1,133 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components;
+
+
+import java.util.List;
+import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection;
+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;
+import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIMapKnownEntries;
+
+
+/***************************************************************************
+ **
+ ***************************************************************************/
+public class AppSection implements ToSchema
+{
+ @OpenAPIExclude()
+ private QAppSection wrapped;
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public AppSection(QAppSection wrapped)
+ {
+ this.wrapped = wrapped;
+ }
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public AppSection()
+ {
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Unique (within the app) name for this section.")
+ public String getName()
+ {
+ return (this.wrapped.getName());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("User-facing name of the section.")
+ public String getLabel()
+ {
+ return (this.wrapped.getLabel());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Icon to display for the section.")
+ @OpenAPIMapKnownEntries(value = Icon.class, useRef = true)
+ public Icon getIcon()
+ {
+ return (this.wrapped.getIcon() == null ? null : new Icon(this.wrapped.getIcon()));
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("List of table names for the section")
+ @OpenAPIListItems(value = String.class)
+ public List getTables()
+ {
+ return (this.wrapped.getTables());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("List of process names for the section")
+ @OpenAPIListItems(value = String.class)
+ public List getProcesses()
+ {
+ return (this.wrapped.getProcesses());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("List of report names for the section")
+ @OpenAPIListItems(value = String.class)
+ public List getReports()
+ {
+ return (this.wrapped.getReports());
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/AppTreeNode.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/AppTreeNode.java
new file mode 100644
index 00000000..85a63b4c
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/AppTreeNode.java
@@ -0,0 +1,105 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components;
+
+
+import java.util.List;
+import com.kingsrook.qqq.backend.core.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 AppTreeNode implements ToSchema
+{
+ @OpenAPIExclude()
+ private com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNode wrapped;
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public AppTreeNode(com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNode wrapped)
+ {
+ this.wrapped = wrapped;
+ }
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public AppTreeNode()
+ {
+ }
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("The type of node (table, process, report, app)")
+ public String getType()
+ {
+ return (this.wrapped.getType() == null ? null : this.wrapped.getType().name());
+ }
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Unique (within its type) name for this element. e.g., for type = 'table', the table's name.")
+ public String getName()
+ {
+ return (this.wrapped.getName());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("User-facing name of the element.")
+ public String getLabel()
+ {
+ return (this.wrapped.getLabel());
+ }
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Child elements. Only applies for type='app', which contains additional apps under it")
+ @OpenAPIListItems(value = AppTreeNode.class, useRef = true)
+ public List getChildren()
+ {
+ return (CollectionUtils.nonNullList(this.wrapped.getChildren()).stream().map(a -> new AppTreeNode(a)).toList());
+ }
+
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/FieldMetaData.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/FieldMetaData.java
new file mode 100644
index 00000000..5c4505ac
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/FieldMetaData.java
@@ -0,0 +1,194 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components;
+
+
+import java.util.List;
+import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
+import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
+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 FieldMetaData implements ToSchema
+{
+ @OpenAPIExclude()
+ private QFieldMetaData wrapped;
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public FieldMetaData(QFieldMetaData wrapped)
+ {
+ this.wrapped = wrapped;
+ }
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public FieldMetaData()
+ {
+ }
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Unique name for this field within its container (table or process)")
+ public String getName()
+ {
+ return (this.wrapped.getName());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("User-facing name for this field")
+ public String getLabel()
+ {
+ return (this.wrapped.getLabel());
+ }
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Data-type for this field") // todo enum
+ public String getType()
+ {
+ return (this.wrapped.getType() == null ? null : this.wrapped.getType().name());
+ }
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Indicate if a value in this field is required.")
+ public Boolean getIsRequired()
+ {
+ return (this.wrapped.getIsRequired());
+ }
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Indicate if user may edit the value in this field.")
+ public Boolean getIsEditable()
+ {
+ return (this.wrapped.getIsEditable());
+ }
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Indicate if this field should be hidden from users")
+ public Boolean getIsHidden()
+ {
+ return (this.wrapped.getIsHidden());
+ }
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Indicator of 'heavy' fields, which are not loaded by default. e.g., some blobs or long-texts")
+ public Boolean getIsHeavy()
+ {
+ return (this.wrapped.getIsHeavy());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("C-style format specifier for displaying values in this field.")
+ public String getDisplayFormat()
+ {
+ return (this.wrapped.getDisplayFormat());
+ }
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Default value to use in this field.")
+ public String getDefaultValue()
+ {
+ return (this.wrapped.getDefaultValue() == null ? null : String.valueOf(this.wrapped.getDefaultValue()));
+ }
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("If this field's values should come from a possible value source, then that PVS is named here.")
+ public String getPossibleValueSourceName()
+ {
+ return (this.wrapped.getPossibleValueSourceName());
+ }
+
+ // todo - PVS filter!!
+
+ // todo - inline PVS
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("For String fields, the max length the field supports.")
+ public Integer getMaxLength()
+ {
+ return (this.wrapped.getMaxLength());
+ }
+
+ // todo behaviors?
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Special UI dressings to add to the field.")
+ @OpenAPIListItems(value = FieldAdornment.class) // todo!
+ public List getAdornments()
+ {
+ return (this.wrapped.getAdornments());
+ }
+
+ // todo help content
+
+ // todo supplemental...
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/FrontendComponent.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/FrontendComponent.java
new file mode 100644
index 00000000..e28af1ce
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/FrontendComponent.java
@@ -0,0 +1,88 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components;
+
+
+import java.io.Serializable;
+import java.util.Map;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QComponentType;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData;
+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.OpenAPIMapKnownEntries;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class FrontendComponent implements ToSchema
+{
+ @OpenAPIExclude()
+ private QFrontendComponentMetaData wrapped;
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public FrontendComponent(QFrontendComponentMetaData wrapped)
+ {
+ this.wrapped = wrapped;
+ }
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public FrontendComponent()
+ {
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("The type of this component. e.g., what kind of UI element(s) should be presented to the user.")
+ public QComponentType getType()
+ {
+ return (this.wrapped.getType());
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for values
+ **
+ *******************************************************************************/
+ @OpenAPIDescription("Name-value pairs specific to the type of component.")
+ @OpenAPIMapKnownEntries(value = FrontendComponentValues.class, useRef = true)
+ public Map getValues()
+ {
+ return (this.wrapped.getValues() == null ? null : new FrontendComponentValues(this.wrapped.getValues()).toMap());
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/FrontendComponentValues.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/FrontendComponentValues.java
new file mode 100644
index 00000000..48bd7eac
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/FrontendComponentValues.java
@@ -0,0 +1,145 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components;
+
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.AbstractBlockWidgetData;
+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.OpenAPIHasAdditionalProperties;
+import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIListItems;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+@OpenAPIDescription("""
+ These are the known values that can appear in the values map under a FrontendComponent, to control
+ how that component should be presented to the user.
+
+ Note that additional properties may appear as well.
+
+ In addition, components are expected to use values from an active process's `values` map (e.g., as included in
+ a `ProcessStepComplete` object), with the following contract between component-types and expected values:
+
+ - For component type=`HTML`, there will be a process value with key=`${stepName}.html` (e.g., `resultScreen.html`),
+ whose value is the HTML to display on that screen.
+ - For component type=`HELP_TEXT`: There will be a process value with key=`text`, whose value is the text to display on that screen.
+ There may also be a process value with key=`previewText`, which, if present, can be shown before the full text is shown,
+ e.g., with a toggle control to hide/show the `text` value.
+ """)
+@OpenAPIHasAdditionalProperties()
+public class FrontendComponentValues implements ToSchema
+{
+ @OpenAPIExclude()
+ private Map wrapped;
+
+ @OpenAPIDescription("""
+ Components of type=`WIDGET`, which do not reference a widget defined in the QQQ Instance, but instead,
+ are defined as a list of blocks within a frontend step component, will have a this value set to true.""")
+ private Boolean isAdHocWidget;
+
+ @OpenAPIDescription("""
+ Components of type=`WIDGET`, which are set as `isAdHocWidget=true`, should include a list of WidgetBlocks in this value.""")
+ @OpenAPIListItems(value = WidgetBlock.class, useRef = true)
+ private List blocks;
+
+ @OpenAPIDescription("""
+ Components of type=`WIDGET`, which should render a widget defined in the QQQ instance, this value specifies
+ the name of that widget. Contrast with ad-hoc widgets.
+ """)
+ private String widgetName;
+
+ @OpenAPIDescription("""
+ Components of type=`EDIT_FORM` can specify a subset of field names to include. This can be used to break a form up into
+ sections, by including multiple EDIT_FORM components, with different lists of `includeFieldNames`.
+ """)
+ @OpenAPIListItems(String.class)
+ private List includeFieldNames;
+
+ @OpenAPIDescription("""
+ Components of type=`EDIT_FORM` can specify a user-facing text label to show on screen.
+ """)
+ private String sectionLabel;
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public FrontendComponentValues(Map values)
+ {
+ this.wrapped = values;
+ }
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public FrontendComponentValues()
+ {
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public Map toMap()
+ {
+ if(wrapped == null)
+ {
+ return (null);
+ }
+
+ Map rs = new HashMap<>();
+ for(Map.Entry entry : wrapped.entrySet())
+ {
+ String key = entry.getKey();
+ Serializable value = entry.getValue();
+
+ if(key.equals("blocks"))
+ {
+ ArrayList resultList = new ArrayList<>();
+
+ List> sourceList = (List>) value;
+ for(AbstractBlockWidgetData, ?, ?, ?> abstractBlockWidgetData : sourceList)
+ {
+ resultList.add(new WidgetBlock(abstractBlockWidgetData));
+ }
+
+ value = resultList;
+ }
+
+ rs.put(key, value);
+ }
+
+ return (rs);
+ }
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/FrontendStep.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/FrontendStep.java
new file mode 100644
index 00000000..d8c36433
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/FrontendStep.java
@@ -0,0 +1,133 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components;
+
+
+import java.util.List;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
+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 FrontendStep implements ToSchema
+{
+ @OpenAPIExclude()
+ private QFrontendStepMetaData wrapped;
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public FrontendStep(QFrontendStepMetaData wrapped)
+ {
+ this.wrapped = wrapped;
+ }
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public FrontendStep()
+ {
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("The unique name for this step within its process")
+ public String getName()
+ {
+ return (this.wrapped.getName());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("The user-facing name for this step")
+ public String getLabel()
+ {
+ return (this.wrapped.getLabel());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("The components that make up this screen")
+ @OpenAPIListItems(value = FrontendComponent.class, useRef = true)
+ public List getComponents()
+ {
+ return (CollectionUtils.nonNullList(this.wrapped.getComponents()).stream().map(f -> new FrontendComponent(f)).toList());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Fields used as form fields (inputs) on this step/screen")
+ @OpenAPIListItems(value = FieldMetaData.class, useRef = true)
+ public List getFormFields()
+ {
+ return (CollectionUtils.nonNullList(this.wrapped.getFormFields()).stream().map(f -> new FieldMetaData(f)).toList());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Fields used as view-only fields on this step/screen")
+ @OpenAPIListItems(value = FieldMetaData.class, useRef = true)
+ public List getViewFields()
+ {
+ return (CollectionUtils.nonNullList(this.wrapped.getViewFields()).stream().map(f -> new FieldMetaData(f)).toList());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Fields used in record-lists shown on the step/screen.")
+ @OpenAPIListItems(value = FieldMetaData.class, useRef = true)
+ public List getRecordListFields()
+ {
+ return (CollectionUtils.nonNullList(this.wrapped.getRecordListFields()).stream().map(f -> new FieldMetaData(f)).toList());
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/Icon.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/Icon.java
new file mode 100644
index 00000000..d1736ea1
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/Icon.java
@@ -0,0 +1,92 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components;
+
+
+import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
+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 Icon implements ToSchema
+{
+ @OpenAPIExclude()
+ private QIcon wrapped;
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public Icon(QIcon wrapped)
+ {
+ this.wrapped = wrapped;
+ }
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public Icon()
+ {
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("A material UI icon name.")
+ public String getName()
+ {
+ return (this.wrapped.getName());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("A color code to use for displaying the icon")
+ public String getColor()
+ {
+ return (this.wrapped.getColor());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("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.")
+ public String getPath()
+ {
+ return (this.wrapped.getPath());
+ }
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/ProcessMetaData.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/ProcessMetaData.java
new file mode 100644
index 00000000..455f7705
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/ProcessMetaData.java
@@ -0,0 +1,74 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components;
+
+
+import java.util.List;
+import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendProcessMetaData;
+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;
+
+
+/***************************************************************************
+ **
+ ***************************************************************************/
+@OpenAPIIncludeProperties(ancestorClasses = ProcessMetaDataLight.class)
+public class ProcessMetaData extends ProcessMetaDataLight implements ToSchema
+{
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public ProcessMetaData(QFrontendProcessMetaData wrapped)
+ {
+ super(wrapped);
+ }
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public ProcessMetaData()
+ {
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Frontend steps (aka, Screens) for this process.")
+ @OpenAPIListItems(value = FrontendStep.class, useRef = true)
+ public List getFrontendSteps()
+ {
+ return (CollectionUtils.nonNullList(this.wrapped.getFrontendSteps()).stream().map(f -> new FrontendStep(f)).toList());
+ }
+
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/ProcessMetaDataAdjustment.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/ProcessMetaDataAdjustment.java
new file mode 100644
index 00000000..ea63437b
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/ProcessMetaDataAdjustment.java
@@ -0,0 +1,112 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components;
+
+
+import java.util.List;
+import java.util.Map;
+import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIDescription;
+import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIListItems;
+import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIMapValueType;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class ProcessMetaDataAdjustment
+{
+ @OpenAPIDescription("""
+ In case the backend has changed the list of frontend steps, it will be set in this field.""")
+ @OpenAPIListItems(FrontendStep.class)
+ private List updatedFrontendStepList = null;
+
+ @OpenAPIDescription("""
+ Fields whose meta-data has changed. e.g., changing a label, or required status, or inline-possible-values.""")
+ @OpenAPIMapValueType(value = FieldMetaData.class, useRef = true)
+ private Map updatedFields = null;
+
+
+
+ /*******************************************************************************
+ ** Getter for updatedFrontendStepList
+ **
+ *******************************************************************************/
+ public List getUpdatedFrontendStepList()
+ {
+ return updatedFrontendStepList;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for updatedFrontendStepList
+ **
+ *******************************************************************************/
+ public void setUpdatedFrontendStepList(List updatedFrontendStepList)
+ {
+ this.updatedFrontendStepList = updatedFrontendStepList;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for updatedFrontendStepList
+ **
+ *******************************************************************************/
+ public ProcessMetaDataAdjustment withUpdatedFrontendStepList(List updatedFrontendStepList)
+ {
+ this.updatedFrontendStepList = updatedFrontendStepList;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for updatedFields
+ *******************************************************************************/
+ public Map getUpdatedFields()
+ {
+ return (this.updatedFields);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for updatedFields
+ *******************************************************************************/
+ public void setUpdatedFields(Map updatedFields)
+ {
+ this.updatedFields = updatedFields;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for updatedFields
+ *******************************************************************************/
+ public ProcessMetaDataAdjustment withUpdatedFields(Map updatedFields)
+ {
+ this.updatedFields = updatedFields;
+ return (this);
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/ProcessMetaDataLight.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/ProcessMetaDataLight.java
new file mode 100644
index 00000000..6615b416
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/ProcessMetaDataLight.java
@@ -0,0 +1,137 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components;
+
+
+import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendProcessMetaData;
+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 ProcessMetaDataLight implements ToSchema
+{
+ @OpenAPIExclude()
+ protected QFrontendProcessMetaData wrapped;
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public ProcessMetaDataLight(QFrontendProcessMetaData wrapped)
+ {
+ this.wrapped = wrapped;
+ }
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public ProcessMetaDataLight()
+ {
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Unique name for this process within the QQQ Instance")
+ public String getName()
+ {
+ return (this.wrapped.getName());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("User-facing name for this process")
+ public String getLabel()
+ {
+ return (this.wrapped.getLabel());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("If this process is associated with a table, the table name is given here")
+ public String getTableName()
+ {
+ return (this.wrapped.getTableName());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Boolean indicator of whether the process should be shown to users or not")
+ public Boolean getIsHidden()
+ {
+ return (this.wrapped.getIsHidden());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Indicator of the Step Flow used by the process. Possible values are: LINEAR, STATE_MACHINE.")
+ public String getStepFlow()
+ {
+ return (this.wrapped.getStepFlow());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Name of an icon for the process, from the material UI icon set")
+ public String getIconName()
+ {
+ return (this.wrapped.getIconName());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Boolean to indicate if the user has permission for the process.")
+ public Boolean getHasPermission()
+ {
+ return (this.wrapped.getHasPermission());
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/TableMetaDataLight.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/TableMetaDataLight.java
new file mode 100644
index 00000000..4eb804cf
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/TableMetaDataLight.java
@@ -0,0 +1,187 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components;
+
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendTableMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
+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 TableMetaDataLight implements ToSchema
+{
+ @OpenAPIExclude()
+ private QFrontendTableMetaData wrapped;
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public TableMetaDataLight(QFrontendTableMetaData wrapped)
+ {
+ this.wrapped = wrapped;
+ }
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public TableMetaDataLight()
+ {
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Unique name for this table within the QQQ Instance")
+ public String getName()
+ {
+ return (this.wrapped.getName());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("User-facing name for this table")
+ public String getLabel()
+ {
+ return (this.wrapped.getLabel());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Boolean indicator of whether the table should be shown to users or not")
+ public Boolean getIsHidden()
+ {
+ return (this.wrapped.getIsHidden());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Name of an icon for the table, from the material UI icon set")
+ public String getIconName()
+ {
+ return (this.wrapped.getIconName());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("List of strings describing actions that are supported by the backend application for the table.")
+ @OpenAPIListItems(value = String.class) // todo - better, enum
+ public List getCapabilities()
+ {
+ return (new ArrayList<>(this.wrapped.getCapabilities()));
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Boolean to indicate if the user has read permission for the table.")
+ public Boolean getReadPermission()
+ {
+ return (this.wrapped.getReadPermission());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Boolean to indicate if the user has insert permission for the table.")
+ public Boolean getInsertPermission()
+ {
+ return (this.wrapped.getInsertPermission());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Boolean to indicate if the user has edit permission for the table.")
+ public Boolean getEditPermission()
+ {
+ return (this.wrapped.getEditPermission());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Boolean to indicate if the user has delete permission for the table.")
+ public Boolean getDeletePermission()
+ {
+ return (this.wrapped.getDeletePermission());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("If the table uses variants, this is the user-facing label for the table that supplies variants for this table.")
+ public String getVariantTableLabel()
+ {
+ return (this.wrapped.getVariantTableLabel());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Help Contents for this table.") // todo describe more
+ public Map> getHelpContents()
+ {
+ return (this.wrapped.getHelpContents());
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/WidgetBlock.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/WidgetBlock.java
new file mode 100644
index 00000000..0ed526b0
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/WidgetBlock.java
@@ -0,0 +1,216 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components;
+
+
+import java.io.Serializable;
+import java.util.EnumSet;
+import java.util.List;
+import com.kingsrook.qqq.backend.core.logging.QLogger;
+import com.kingsrook.qqq.backend.core.model.dashboard.widgets.CompositeWidgetData;
+import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.AbstractBlockWidgetData;
+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.OpenAPIEnumSubSet;
+import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIExclude;
+import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIListItems;
+import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIOneOf;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class WidgetBlock implements Serializable, ToSchema
+{
+ @OpenAPIExclude()
+ private static final QLogger LOG = QLogger.getLogger(WidgetBlock.class);
+
+ @OpenAPIExclude()
+ private final AbstractBlockWidgetData, ?, ?, ?> wrapped;
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public WidgetBlock(AbstractBlockWidgetData, ?, ?, ?> abstractBlockWidgetData)
+ {
+ this.wrapped = abstractBlockWidgetData;
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Unique identifier for this block within it widget. Used as a key for helpContents.")
+ public String getBlockId()
+ {
+ return (this.wrapped.getBlockId());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public enum BlockType
+ {
+ ACTION_BUTTON,
+ AUDIO,
+ BIG_NUMBER,
+ COMPOSITE,
+ DIVIDER,
+ IMAGE,
+ INPUT_FIELD,
+ NUMBER_ICON_BADGE,
+ PROGRESS_BAR,
+ // todo? REVEAL,
+ TABLE_SUB_ROW_DETAIL_ROW,
+ TEXT,
+ UP_OR_DOWN_NUMBER
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("What type of block to render.")
+ public BlockType getBlockType()
+ {
+ return (BlockType.valueOf(this.wrapped.getBlockTypeName()));
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Values to show in the block, or otherwise control its behavior. Different fields based on blockType.")
+ @OpenAPIOneOf()
+ public WidgetBlockValues getValues()
+ {
+ return (WidgetBlockValues.of(this.wrapped.getValues()));
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Styles to apply to the block. Different fields based on blockType.")
+ @OpenAPIOneOf()
+ public WidgetBlockStyles getStyles()
+ {
+ return (WidgetBlockStyles.of(this.wrapped.getStyles()));
+ }
+
+ // todo link, links
+
+ // todo tooltip, tooltips
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Optional field name (e.g,. from a process's set of fields) to act as a 'guard' for the block - e.g., only include it in the UI if the value for this field is true")
+ public String getConditional()
+ {
+ return (this.wrapped.getConditional());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("For COMPOSITE type blocks, a list of sub-blocks.")
+ @OpenAPIListItems(value = WidgetBlock.class, useRef = true)
+ public List getSubBlocks()
+ {
+ if(this.wrapped instanceof CompositeWidgetData compositeWidgetData)
+ {
+ return (compositeWidgetData.getBlocks() == null ? null : compositeWidgetData.getBlocks().stream().map(b -> new WidgetBlock(b)).toList());
+ }
+
+ return (null);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public static class LayoutSubSet implements OpenAPIEnumSubSet.EnumSubSet
+ {
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public EnumSet getSubSet()
+ {
+ return EnumSet.of(
+ CompositeWidgetData.Layout.FLEX_COLUMN,
+ CompositeWidgetData.Layout.FLEX_ROW_WRAPPED,
+ CompositeWidgetData.Layout.FLEX_ROW_SPACE_BETWEEN,
+ CompositeWidgetData.Layout.FLEX_ROW_CENTER,
+ CompositeWidgetData.Layout.TABLE_SUB_ROW_DETAILS,
+ CompositeWidgetData.Layout.BADGES_WRAPPER
+ );
+ }
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("For COMPOSITE type blocks, an indicator of how the sub-blocks should be laid out")
+ @OpenAPIEnumSubSet(LayoutSubSet.class)
+ public CompositeWidgetData.Layout getLayout()
+ {
+ if(this.wrapped instanceof CompositeWidgetData compositeWidgetData && compositeWidgetData.getLayout() != null)
+ {
+ CompositeWidgetData.Layout layout = compositeWidgetData.getLayout();
+ if(new LayoutSubSet().getSubSet().contains(layout))
+ {
+ return (layout);
+ }
+ else
+ {
+ LOG.info("Layout [" + layout + "] is not in the subset used by this version. It will not be returned.");
+ }
+ }
+
+ return (null);
+ }
+
+
+ /* todo
+ private Map styleOverrides = new HashMap<>();
+ private String overlayHtml;
+ private Map overlayStyleOverrides = new HashMap<>();
+ */
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/WidgetBlockActionButtonValues.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/WidgetBlockActionButtonValues.java
new file mode 100644
index 00000000..52311cbb
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/WidgetBlockActionButtonValues.java
@@ -0,0 +1,82 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components;
+
+
+import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.actionbutton.ActionButtonValues;
+import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIDescription;
+import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIExclude;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+@OpenAPIDescription("Values used for an ACTION_BUTTON type widget block")
+public final class WidgetBlockActionButtonValues implements WidgetBlockValues
+{
+ @OpenAPIExclude()
+ private ActionButtonValues wrapped;
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public WidgetBlockActionButtonValues(ActionButtonValues actionButtonValues)
+ {
+ this.wrapped = actionButtonValues;
+ }
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public WidgetBlockActionButtonValues()
+ {
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("User-facing label to display in the button")
+ public String getLabel()
+ {
+ return (this.wrapped.getLabel());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Code used within the app as the value submitted when the button is clicked")
+ public String getActionCode()
+ {
+ return (this.wrapped.getActionCode());
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/WidgetBlockInputFieldValues.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/WidgetBlockInputFieldValues.java
new file mode 100644
index 00000000..31186e94
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/WidgetBlockInputFieldValues.java
@@ -0,0 +1,93 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components;
+
+
+import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.inputfield.InputFieldValues;
+import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIDescription;
+import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIExclude;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+@OpenAPIDescription("Values used for an INPUT_FIELD type widget block")
+public final class WidgetBlockInputFieldValues implements WidgetBlockValues
+{
+ @OpenAPIExclude()
+ private InputFieldValues wrapped;
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public WidgetBlockInputFieldValues(InputFieldValues textValues)
+ {
+ this.wrapped = textValues;
+ }
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public WidgetBlockInputFieldValues()
+ {
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Metadata to define the field that this block controls")
+ public FieldMetaData getFieldMetaData()
+ {
+ return (new FieldMetaData(this.wrapped.getFieldMetaData()));
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Indicate whether this field should auto-focus when it is rendered")
+ public Boolean getAutoFocus()
+ {
+ return (this.wrapped.getAutoFocus());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Indicate whether the form that this field is on should be submitted when Enter is pressed")
+ public Boolean getSubmitOnEnter()
+ {
+ return (this.wrapped.getSubmitOnEnter());
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/WidgetBlockStyles.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/WidgetBlockStyles.java
new file mode 100644
index 00000000..07cdcd42
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/WidgetBlockStyles.java
@@ -0,0 +1,61 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components;
+
+
+import com.kingsrook.qqq.backend.core.logging.QLogger;
+import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockStylesInterface;
+import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.text.TextStyles;
+import com.kingsrook.qqq.middleware.javalin.schemabuilder.ToSchema;
+import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIExclude;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public sealed interface WidgetBlockStyles extends ToSchema permits
+ WidgetBlockTextStyles
+{
+ @OpenAPIExclude
+ QLogger LOG = QLogger.getLogger(WidgetBlockStyles.class);
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ static WidgetBlockStyles of(BlockStylesInterface blockStyles)
+ {
+ if(blockStyles == null)
+ {
+ return (null);
+ }
+
+ if(blockStyles instanceof TextStyles s)
+ {
+ return (new WidgetBlockTextStyles(s));
+ }
+
+ LOG.warn("Unrecognized block value type: " + blockStyles.getClass().getName());
+ return (null);
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/WidgetBlockTextStyles.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/WidgetBlockTextStyles.java
new file mode 100644
index 00000000..3d8c5d78
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/WidgetBlockTextStyles.java
@@ -0,0 +1,76 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components;
+
+
+import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.text.TextStyles;
+import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIDescription;
+import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIExclude;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public final class WidgetBlockTextStyles implements WidgetBlockStyles
+{
+ @OpenAPIExclude()
+ private TextStyles wrapped;
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public WidgetBlockTextStyles(TextStyles textStyles)
+ {
+ this.wrapped = textStyles;
+ }
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public WidgetBlockTextStyles()
+ {
+ }
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("Indicate if the text should be displayed as an alert (e.g., modal popup)")
+ public Boolean getIsAlert()
+ {
+ return (this.wrapped.getIsAlert());
+ }
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("A Standard Color to display the text in (e.g., not a hex or RGB code).")
+ public TextStyles.StandardColor getStandardColor()
+ {
+ return (this.wrapped.getStandardColor());
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/WidgetBlockTextValues.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/WidgetBlockTextValues.java
new file mode 100644
index 00000000..49977873
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/WidgetBlockTextValues.java
@@ -0,0 +1,71 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components;
+
+
+import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.text.TextValues;
+import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIDescription;
+import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIExclude;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+@OpenAPIDescription("Values used for a TEXT type widget block")
+public final class WidgetBlockTextValues implements WidgetBlockValues
+{
+ @OpenAPIExclude()
+ private TextValues wrapped;
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public WidgetBlockTextValues(TextValues textValues)
+ {
+ this.wrapped = textValues;
+ }
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public WidgetBlockTextValues()
+ {
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @OpenAPIDescription("The text to display in the block")
+ public String getText()
+ {
+ return (this.wrapped.getText());
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/WidgetBlockValues.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/WidgetBlockValues.java
new file mode 100644
index 00000000..bdac09ae
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/components/WidgetBlockValues.java
@@ -0,0 +1,73 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components;
+
+
+import com.kingsrook.qqq.backend.core.logging.QLogger;
+import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockValuesInterface;
+import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.actionbutton.ActionButtonValues;
+import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.inputfield.InputFieldValues;
+import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.text.TextValues;
+import com.kingsrook.qqq.middleware.javalin.schemabuilder.ToSchema;
+import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIExclude;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public sealed interface WidgetBlockValues extends ToSchema permits
+ WidgetBlockActionButtonValues,
+ WidgetBlockTextValues,
+ WidgetBlockInputFieldValues
+{
+ @OpenAPIExclude
+ QLogger LOG = QLogger.getLogger(WidgetBlockValues.class);
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ static WidgetBlockValues of(BlockValuesInterface blockValues)
+ {
+ if(blockValues == null)
+ {
+ return (null);
+ }
+
+ if(blockValues instanceof TextValues v)
+ {
+ return (new WidgetBlockTextValues(v));
+ }
+ else if(blockValues instanceof InputFieldValues v)
+ {
+ return (new WidgetBlockInputFieldValues(v));
+ }
+ else if(blockValues instanceof ActionButtonValues v)
+ {
+ return (new WidgetBlockActionButtonValues(v));
+ }
+
+ LOG.warn("Unrecognized block value type: " + blockValues.getClass().getName());
+ return (null);
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/utils/ProcessSpecUtilsV1.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/utils/ProcessSpecUtilsV1.java
new file mode 100644
index 00000000..b98dffe5
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/utils/ProcessSpecUtilsV1.java
@@ -0,0 +1,205 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.utils;
+
+
+import java.io.File;
+import java.io.Serializable;
+import java.time.LocalDate;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
+import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
+import com.kingsrook.qqq.backend.core.logging.QLogger;
+import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessMetaDataAdjustment;
+import com.kingsrook.qqq.backend.core.model.actions.processes.QUploadedFile;
+import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
+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.fields.QFieldType;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QComponentType;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
+import com.kingsrook.qqq.backend.core.utils.JsonUtils;
+import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
+import com.kingsrook.qqq.backend.javalin.QJavalinImplementation;
+import com.kingsrook.qqq.middleware.javalin.executors.io.ProcessInitOrStepOrStatusOutputInterface;
+import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.ProcessInitOrStepOrStatusResponseV1;
+import com.kingsrook.qqq.openapi.model.Example;
+import com.kingsrook.qqq.openapi.model.Schema;
+import io.javalin.http.Context;
+import org.json.JSONObject;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class ProcessSpecUtilsV1
+{
+ private static final QLogger LOG = QLogger.getLogger(ProcessSpecUtilsV1.class);
+
+ public static final String EXAMPLE_PROCESS_UUID = "01234567-89AB-CDEF-0123-456789ABCDEF";
+ public static final String EXAMPLE_JOB_UUID = "98765432-10FE-DCBA-9876-543210FEDCBA";
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public static String getResponseSchemaRefName()
+ {
+ return ("ProcessStepResponseV1");
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public static Schema buildResponseSchema()
+ {
+ return new Schema().withOneOf(List.of(
+ new Schema().withRef("#/components/schemas/ProcessStepComplete"),
+ new Schema().withRef("#/components/schemas/ProcessStepJobStarted"),
+ new Schema().withRef("#/components/schemas/ProcessStepRunning"),
+ new Schema().withRef("#/components/schemas/ProcessStepError")
+ ));
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public static LinkedHashMap buildResponseExample()
+ {
+ ProcessInitOrStepOrStatusResponseV1 completeResponse = new ProcessInitOrStepOrStatusResponseV1();
+ completeResponse.setType(ProcessInitOrStepOrStatusOutputInterface.Type.COMPLETE);
+ completeResponse.setProcessUUID(EXAMPLE_PROCESS_UUID);
+ Map values = new LinkedHashMap<>();
+ values.put("totalAge", 32768);
+ values.put("firstLastName", "Aabramson");
+ completeResponse.setValues(values);
+ completeResponse.setNextStep("reviewScreen");
+
+ ProcessInitOrStepOrStatusResponseV1 completeResponseWithMetaDataAdjustment = new ProcessInitOrStepOrStatusResponseV1();
+ completeResponseWithMetaDataAdjustment.setType(ProcessInitOrStepOrStatusOutputInterface.Type.COMPLETE);
+ completeResponseWithMetaDataAdjustment.setProcessUUID(EXAMPLE_PROCESS_UUID);
+ completeResponseWithMetaDataAdjustment.setValues(values);
+ completeResponseWithMetaDataAdjustment.setNextStep("inputScreen");
+ completeResponseWithMetaDataAdjustment.setProcessMetaDataAdjustment(new ProcessMetaDataAdjustment()
+ .withUpdatedField(new QFieldMetaData("someField", QFieldType.STRING).withIsRequired(true))
+ .withUpdatedFrontendStepList(List.of(
+ new QFrontendStepMetaData()
+ .withName("inputScreen")
+ .withComponent(new QFrontendComponentMetaData().withType(QComponentType.EDIT_FORM))
+ .withFormField(new QFieldMetaData("someField", QFieldType.STRING)),
+ new QFrontendStepMetaData()
+ .withName("resultScreen")
+ .withComponent(new QFrontendComponentMetaData().withType(QComponentType.PROCESS_SUMMARY_RESULTS))
+ )));
+
+ ProcessInitOrStepOrStatusResponseV1 jobStartedResponse = new ProcessInitOrStepOrStatusResponseV1();
+ jobStartedResponse.setType(ProcessInitOrStepOrStatusOutputInterface.Type.JOB_STARTED);
+ jobStartedResponse.setProcessUUID(EXAMPLE_PROCESS_UUID);
+ jobStartedResponse.setJobUUID(EXAMPLE_JOB_UUID);
+
+ ProcessInitOrStepOrStatusResponseV1 runningResponse = new ProcessInitOrStepOrStatusResponseV1();
+ runningResponse.setType(ProcessInitOrStepOrStatusOutputInterface.Type.RUNNING);
+ runningResponse.setProcessUUID(EXAMPLE_PROCESS_UUID);
+ runningResponse.setMessage("Processing person records");
+ runningResponse.setCurrent(47);
+ runningResponse.setTotal(1701);
+
+ ProcessInitOrStepOrStatusResponseV1 errorResponse = new ProcessInitOrStepOrStatusResponseV1();
+ errorResponse.setType(ProcessInitOrStepOrStatusOutputInterface.Type.RUNNING);
+ errorResponse.setProcessUUID(EXAMPLE_PROCESS_UUID);
+ errorResponse.setError("Illegal Argument Exception: NaN");
+ errorResponse.setUserFacingError("The process could not be completed due to invalid input.");
+
+ return MapBuilder.of(() -> new LinkedHashMap())
+ .with("COMPLETE", new Example().withValue(completeResponse))
+ .with("COMPLETE with metaDataAdjustment", new Example().withValue(completeResponseWithMetaDataAdjustment))
+ .with("JOB_STARTED", new Example().withValue(jobStartedResponse))
+ .with("RUNNING", new Example().withValue(runningResponse))
+ .with("ERROR", new Example().withValue(errorResponse))
+ .build();
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public static void handleOutput(Context context, ProcessInitOrStepOrStatusResponseV1 output)
+ {
+ ////////////////////////////////////////////////////////////////////////////////
+ // normally, we like the JsonUtils behavior of excluding null/empty elements. //
+ // but it turns out we want those in the values sub-map. //
+ // so, go through a loop of object → JSON String → JSONObject → String... //
+ // also - work with the TypedResponse sub-object within this response class //
+ ////////////////////////////////////////////////////////////////////////////////
+ ProcessInitOrStepOrStatusResponseV1.TypedResponse typedOutput = output.getTypedResponse();
+
+ String outputJson = JsonUtils.toJson(typedOutput);
+ JSONObject outputJsonObject = new JSONObject(outputJson);
+
+ if(typedOutput instanceof ProcessInitOrStepOrStatusResponseV1.ProcessStepComplete complete)
+ {
+ Map values = complete.getValues();
+ if(values != null)
+ {
+ String serializedValues = JsonUtils.toJson(values, mapper ->
+ {
+ mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
+ });
+
+ outputJsonObject.put("values", new JSONObject(serializedValues));
+ }
+ }
+
+ context.result(outputJsonObject.toString());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private static void archiveUploadedFile(String processName, QUploadedFile qUploadedFile)
+ {
+ String fileName = QValueFormatter.formatDate(LocalDate.now())
+ + File.separator + processName
+ + File.separator + qUploadedFile.getFilename();
+
+ InsertInput insertInput = new InsertInput();
+ insertInput.setTableName(QJavalinImplementation.getJavalinMetaData().getUploadedFileArchiveTableName());
+ insertInput.setRecords(List.of(new QRecord()
+ .withValue("fileName", fileName)
+ .withValue("contents", qUploadedFile.getBytes())
+ ));
+
+ new InsertAction().executeAsync(insertInput);
+ }
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/utils/QuerySpecUtils.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/utils/QuerySpecUtils.java
new file mode 100644
index 00000000..3406c899
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/utils/QuerySpecUtils.java
@@ -0,0 +1,208 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.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.middleware.javalin.executors.io.QueryMiddlewareInput;
+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.utils.JsonUtils;
+import com.kingsrook.qqq.backend.core.utils.StringUtils;
+import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
+import com.kingsrook.qqq.openapi.model.Schema;
+import com.kingsrook.qqq.openapi.model.Type;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class QuerySpecUtils
+{
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public static Schema defineQueryJoinsSchema()
+ {
+ 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()
+ .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)
+ ))
+ )
+ ));
+ return schema;
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public static QueryMiddlewareInput buildInput(Map paramMap) throws IOException
+ {
+
+ QQueryFilter filter = null;
+ String filterParam = paramMap.get("filter");
+ if(StringUtils.hasContent(filterParam))
+ {
+ filter = JsonUtils.toObject(filterParam, QQueryFilter.class);
+ }
+
+ List queryJoins = null;
+ String queryJoinsParam = paramMap.get("queryJoins");
+ if(StringUtils.hasContent(queryJoinsParam))
+ {
+ queryJoins = new ArrayList<>();
+
+ JSONArray queryJoinsJSON = new JSONArray(queryJoinsParam);
+ for(int i = 0; i < queryJoinsJSON.length(); i++)
+ {
+ 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 new QueryMiddlewareInput()
+ .withTable(paramMap.get("table"))
+ .withFilter(filter)
+ .withQueryJoins(queryJoins);
+ }
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/utils/TagsV1.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/utils/TagsV1.java
new file mode 100644
index 00000000..93fbdb20
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/utils/TagsV1.java
@@ -0,0 +1,66 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1.utils;
+
+
+import com.kingsrook.qqq.middleware.javalin.specs.TagsInterface;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public enum TagsV1 implements TagsInterface
+{
+ AUTHENTICATION("Authentication"),
+ GENERAL("General"),
+ QUERY("Query"),
+ INSERT("Insert"),
+ UPDATE("Update"),
+ DELETE("Delete"),
+ PROCESSES("Processes"),
+ REPORTS("Reports"),
+ WIDGETS("Widgets");
+
+
+ private final String text;
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ TagsV1(String text)
+ {
+ this.text = text;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for text
+ **
+ *******************************************************************************/
+ public String getText()
+ {
+ return text;
+ }
+}
diff --git a/qqq-middleware-javalin/src/main/resources/openapi/v1/openapi.yaml b/qqq-middleware-javalin/src/main/resources/openapi/v1/openapi.yaml
new file mode 100644
index 00000000..55d91ab0
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/resources/openapi/v1/openapi.yaml
@@ -0,0 +1,1614 @@
+components:
+ schemas:
+ AppMetaData:
+ properties:
+ childMap:
+ additionalProperties:
+ $ref: "#/components/schemas/AppTreeNode"
+ description: "Map of other apps, tables, process, and reports, which are\
+ \ contained within this app. Same contents as the children list, just\
+ \ structured as a map."
+ type: "object"
+ children:
+ description: "List of other apps, tables, process, and reports, which are\
+ \ contained within this app."
+ items:
+ $ref: "#/components/schemas/AppTreeNode"
+ type: "array"
+ iconName:
+ description: "Name of an icon for the app, from the material UI icon set"
+ type: "string"
+ label:
+ description: "User-facing name for this app"
+ type: "string"
+ name:
+ description: "Unique name for this app within the QQQ Instance"
+ type: "string"
+ sections:
+ description: "List of sections - sub-divisions of the app, to further organize\
+ \ its children."
+ items:
+ $ref: "#/components/schemas/AppSection"
+ type: "array"
+ supplementalAppMetaData:
+ description: "Additional meta-data describing the app, which may not be\
+ \ known to the QQQ backend core module."
+ type: "object"
+ widgets:
+ description: "List of widgets names that are part of this app. These strings\
+ \ should be keys to the widgets map in the QQQ Instance."
+ items:
+ type: "string"
+ type: "array"
+ type: "object"
+ AppSection:
+ properties:
+ icon:
+ $ref: "#/components/schemas/Icon"
+ description: "Icon to display for the section."
+ label:
+ description: "User-facing name of the section."
+ type: "string"
+ name:
+ description: "Unique (within the app) name for this section."
+ type: "string"
+ processes:
+ description: "List of process names for the section"
+ items:
+ type: "string"
+ type: "array"
+ reports:
+ description: "List of report names for the section"
+ items:
+ type: "string"
+ type: "array"
+ tables:
+ description: "List of table names for the section"
+ items:
+ type: "string"
+ type: "array"
+ type: "object"
+ AppTreeNode:
+ properties:
+ children:
+ description: "Child elements. Only applies for type='app', which contains\
+ \ additional apps under it"
+ items:
+ $ref: "#/components/schemas/AppTreeNode"
+ type: "array"
+ label:
+ description: "User-facing name of the element."
+ type: "string"
+ name:
+ description: "Unique (within its type) name for this element. e.g., for\
+ \ type = 'table', the table's name."
+ type: "string"
+ type:
+ description: "The type of node (table, process, report, app)"
+ type: "string"
+ type: "object"
+ AuthenticationMetaDataResponseV1:
+ properties:
+ name:
+ description: "Unique name for the authentication metaData object within\
+ \ the QInstance.\n"
+ type: "string"
+ type:
+ description: "Specifier for the type of authentication module being used.\n\
+ \nFrontends should use this value to determine how to prompt the user\
+ \ for authentication credentials.\nIn addition, depending on this value,\
+ \ additional properties will be included in this object, as\nmay be needed\
+ \ to complete the authorization workflow with the provider (e.g., a baseUrl,\
+ \ clientId,\nand audience for an OAuth type workflow)."
+ type: "string"
+ values:
+ description: "Additional values, as determined by the type of authentication\
+ \ provider.\n"
+ oneOf:
+ - description: "No additional values are used for some authentication providers."
+ type: "object"
+ - description: "Additional values used by the Auth0 type authentication\
+ \ provider."
+ properties:
+ audience:
+ description: "Audience for auth0"
+ type: "string"
+ baseUrl:
+ description: "BaseUrl for auth0"
+ type: "string"
+ clientId:
+ description: "ClientId for auth0"
+ type: "string"
+ type: "object"
+ type: "object"
+ BasicErrorResponseV1:
+ properties:
+ error:
+ description: "Description of the error"
+ type: "string"
+ type: "object"
+ FieldMetaData:
+ properties:
+ adornments:
+ description: "Special UI dressings to add to the field."
+ items:
+ properties:
+ type:
+ enum:
+ - "LINK"
+ - "CHIP"
+ - "SIZE"
+ - "CODE_EDITOR"
+ - "RENDER_HTML"
+ - "REVEAL"
+ - "FILE_DOWNLOAD"
+ - "ERROR"
+ type: "string"
+ values:
+ type: "object"
+ type: "object"
+ type: "array"
+ defaultValue:
+ description: "Default value to use in this field."
+ type: "string"
+ displayFormat:
+ description: "C-style format specifier for displaying values in this field."
+ type: "string"
+ isEditable:
+ description: "Indicate if user may edit the value in this field."
+ type: "boolean"
+ isHeavy:
+ description: "Indicator of 'heavy' fields, which are not loaded by default.\
+ \ e.g., some blobs or long-texts"
+ type: "boolean"
+ isHidden:
+ description: "Indicate if this field should be hidden from users"
+ type: "boolean"
+ isRequired:
+ description: "Indicate if a value in this field is required."
+ type: "boolean"
+ label:
+ description: "User-facing name for this field"
+ type: "string"
+ maxLength:
+ description: "For String fields, the max length the field supports."
+ type: "number"
+ name:
+ description: "Unique name for this field within its container (table or\
+ \ process)"
+ type: "string"
+ possibleValueSourceName:
+ description: "If this field's values should come from a possible value source,\
+ \ then that PVS is named here."
+ type: "string"
+ type:
+ description: "Data-type for this field"
+ type: "string"
+ type: "object"
+ FrontendComponent:
+ properties:
+ type:
+ description: "The type of this component. e.g., what kind of UI element(s)\
+ \ should be presented to the user."
+ enum:
+ - "HELP_TEXT"
+ - "BULK_EDIT_FORM"
+ - "BULK_LOAD_MAPPING"
+ - "VALIDATION_REVIEW_SCREEN"
+ - "EDIT_FORM"
+ - "VIEW_FORM"
+ - "DOWNLOAD_FORM"
+ - "RECORD_LIST"
+ - "PROCESS_SUMMARY_RESULTS"
+ - "GOOGLE_DRIVE_SELECT_FOLDER"
+ - "WIDGET"
+ - "HTML"
+ type: "string"
+ values:
+ $ref: "#/components/schemas/FrontendComponentValues"
+ description: "Name-value pairs specific to the type of component."
+ type: "object"
+ type: "object"
+ FrontendComponentValues:
+ additionalProperties: true
+ description: "These are the known values that can appear in the values map under\
+ \ a FrontendComponent, to control\nhow that component should be presented\
+ \ to the user.\n\nNote that additional properties may appear as well.\n\n\
+ In addition, components are expected to use values from an active process's\
+ \ `values` map (e.g., as included in\na `ProcessStepComplete` object), with\
+ \ the following contract between component-types and expected values:\n\n\
+ - For component type=`HTML`, there will be a process value with key=`${stepName}.html`\
+ \ (e.g., `resultScreen.html`),\nwhose value is the HTML to display on that\
+ \ screen.\n- For component type=`HELP_TEXT`: There will be a process value\
+ \ with key=`text`, whose value is the text to display on that screen.\nThere\
+ \ may also be a process value with key=`previewText`, which, if present, can\
+ \ be shown before the full text is shown,\ne.g., with a toggle control to\
+ \ hide/show the `text` value.\n"
+ properties:
+ blocks:
+ description: "Components of type=`WIDGET`, which are set as `isAdHocWidget=true`,\
+ \ should include a list of WidgetBlocks in this value."
+ items:
+ $ref: "#/components/schemas/WidgetBlock"
+ type: "array"
+ includeFieldNames:
+ description: "Components of type=`EDIT_FORM` can specify a subset of field\
+ \ names to include. This can be used to break a form up into\nsections,\
+ \ by including multiple EDIT_FORM components, with different lists of\
+ \ `includeFieldNames`.\n"
+ items:
+ type: "string"
+ type: "array"
+ isAdHocWidget:
+ description: "Components of type=`WIDGET`, which do not reference a widget\
+ \ defined in the QQQ Instance, but instead,\nare defined as a list of\
+ \ blocks within a frontend step component, will have a this value set\
+ \ to true."
+ type: "boolean"
+ sectionLabel:
+ description: "Components of type=`EDIT_FORM` can specify a user-facing text\
+ \ label to show on screen.\n"
+ type: "string"
+ widgetName:
+ description: "Components of type=`WIDGET`, which should render a widget\
+ \ defined in the QQQ instance, this value specifies\nthe name of that\
+ \ widget. Contrast with ad-hoc widgets.\n"
+ type: "string"
+ type: "object"
+ FrontendStep:
+ properties:
+ components:
+ description: "The components that make up this screen"
+ items:
+ $ref: "#/components/schemas/FrontendComponent"
+ type: "array"
+ formFields:
+ description: "Fields used as form fields (inputs) on this step/screen"
+ items:
+ $ref: "#/components/schemas/FieldMetaData"
+ type: "array"
+ label:
+ description: "The user-facing name for this step"
+ type: "string"
+ name:
+ description: "The unique name for this step within its process"
+ type: "string"
+ recordListFields:
+ description: "Fields used in record-lists shown on the step/screen."
+ items:
+ $ref: "#/components/schemas/FieldMetaData"
+ type: "array"
+ viewFields:
+ description: "Fields used as view-only fields on this step/screen"
+ items:
+ $ref: "#/components/schemas/FieldMetaData"
+ type: "array"
+ type: "object"
+ Icon:
+ 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"
+ ManageSessionResponseV1:
+ properties:
+ uuid:
+ description: "Unique identifier of the session. Required to be returned\
+ \ on subsequent requests in the sessionUUID Cookie, to prove authentication."
+ type: "string"
+ values:
+ additionalProperties: true
+ description: "Optional object with application-defined values."
+ type: "object"
+ type: "object"
+ MetaDataResponseV1:
+ properties:
+ appTree:
+ description: "Tree of apps within the QQQ Instance, sorted and organized\
+ \ hierarchically, for presentation to a user."
+ items:
+ $ref: "#/components/schemas/AppTreeNode"
+ type: "array"
+ apps:
+ additionalProperties:
+ $ref: "#/components/schemas/AppMetaData"
+ description: "Map of all apps within the QQQ Instance (that the user has\
+ \ permission to see that they exist)."
+ type: "object"
+ processes:
+ additionalProperties:
+ $ref: "#/components/schemas/ProcessMetaDataLight"
+ description: "Map of all processes within the QQQ Instance (that the user\
+ \ has permission to see that they exist)."
+ type: "object"
+ tables:
+ additionalProperties:
+ $ref: "#/components/schemas/TableMetaDataLight"
+ description: "Map of all tables within the QQQ Instance (that the user has\
+ \ permission to see that they exist)."
+ type: "object"
+ type: "object"
+ ProcessMetaData:
+ properties:
+ frontendSteps:
+ description: "Frontend steps (aka, Screens) for this process."
+ items:
+ $ref: "#/components/schemas/FrontendStep"
+ type: "array"
+ hasPermission:
+ description: "Boolean to indicate if the user has permission for the process."
+ type: "boolean"
+ iconName:
+ description: "Name of an icon for the process, from the material UI icon\
+ \ set"
+ type: "string"
+ isHidden:
+ description: "Boolean indicator of whether the process should be shown to\
+ \ users or not"
+ type: "boolean"
+ label:
+ description: "User-facing name for this process"
+ type: "string"
+ name:
+ description: "Unique name for this process within the QQQ Instance"
+ type: "string"
+ stepFlow:
+ description: "Indicator of the Step Flow used by the process. Possible\
+ \ values are: LINEAR, STATE_MACHINE."
+ type: "string"
+ tableName:
+ description: "If this process is associated with a table, the table name\
+ \ is given here"
+ type: "string"
+ type: "object"
+ ProcessMetaDataAdjustment:
+ properties:
+ updatedFields:
+ additionalProperties:
+ $ref: "#/components/schemas/FieldMetaData"
+ description: "Fields whose meta-data has changed. e.g., changing a label,\
+ \ or required status, or inline-possible-values."
+ type: "object"
+ updatedFrontendStepList:
+ description: "In case the backend has changed the list of frontend steps,\
+ \ it will be set in this field."
+ items:
+ $ref: "#/components/schemas/FrontendStep"
+ type: "array"
+ type: "object"
+ ProcessMetaDataLight:
+ properties:
+ hasPermission:
+ description: "Boolean to indicate if the user has permission for the process."
+ type: "boolean"
+ iconName:
+ description: "Name of an icon for the process, from the material UI icon\
+ \ set"
+ type: "string"
+ isHidden:
+ description: "Boolean indicator of whether the process should be shown to\
+ \ users or not"
+ type: "boolean"
+ label:
+ description: "User-facing name for this process"
+ type: "string"
+ name:
+ description: "Unique name for this process within the QQQ Instance"
+ type: "string"
+ stepFlow:
+ description: "Indicator of the Step Flow used by the process. Possible\
+ \ values are: LINEAR, STATE_MACHINE."
+ type: "string"
+ tableName:
+ description: "If this process is associated with a table, the table name\
+ \ is given here"
+ type: "string"
+ type: "object"
+ ProcessMetaDataResponseV1:
+ properties:
+ frontendSteps:
+ description: "Frontend steps (aka, Screens) for this process."
+ items:
+ $ref: "#/components/schemas/FrontendStep"
+ type: "array"
+ hasPermission:
+ description: "Boolean to indicate if the user has permission for the process."
+ type: "boolean"
+ iconName:
+ description: "Name of an icon for the process, from the material UI icon\
+ \ set"
+ type: "string"
+ isHidden:
+ description: "Boolean indicator of whether the process should be shown to\
+ \ users or not"
+ type: "boolean"
+ label:
+ description: "User-facing name for this process"
+ type: "string"
+ name:
+ description: "Unique name for this process within the QQQ Instance"
+ type: "string"
+ stepFlow:
+ description: "Indicator of the Step Flow used by the process. Possible\
+ \ values are: LINEAR, STATE_MACHINE."
+ type: "string"
+ tableName:
+ description: "If this process is associated with a table, the table name\
+ \ is given here"
+ type: "string"
+ type: "object"
+ ProcessStepComplete:
+ description: "Data returned after the job is complete (whether it was synchronous,\
+ \ or asynchronous)"
+ properties:
+ nextStep:
+ description: "Name of the next process step that needs to run (a frontend\
+ \ step). If there are no more steps in the process, this field will not\
+ \ be included. "
+ type: "string"
+ processMetaDataAdjustment:
+ description: "Changes to be made to the process's metaData."
+ properties:
+ updatedFields:
+ additionalProperties:
+ $ref: "#/components/schemas/FieldMetaData"
+ description: "Fields whose meta-data has changed. e.g., changing a\
+ \ label, or required status, or inline-possible-values."
+ type: "object"
+ updatedFrontendStepList:
+ description: "In case the backend has changed the list of frontend steps,\
+ \ it will be set in this field."
+ items:
+ properties:
+ components:
+ description: "The components that make up this screen"
+ items:
+ $ref: "#/components/schemas/FrontendComponent"
+ type: "array"
+ formFields:
+ description: "Fields used as form fields (inputs) on this step/screen"
+ items:
+ $ref: "#/components/schemas/FieldMetaData"
+ type: "array"
+ label:
+ description: "The user-facing name for this step"
+ type: "string"
+ name:
+ description: "The unique name for this step within its process"
+ type: "string"
+ recordListFields:
+ description: "Fields used in record-lists shown on the step/screen."
+ items:
+ $ref: "#/components/schemas/FieldMetaData"
+ type: "array"
+ viewFields:
+ description: "Fields used as view-only fields on this step/screen"
+ items:
+ $ref: "#/components/schemas/FieldMetaData"
+ type: "array"
+ type: "object"
+ type: "array"
+ type: "object"
+ processUUID:
+ description: "Unique identifier for a running instance the process."
+ type: "string"
+ type:
+ description: "What kind of response has been received. Determines what\
+ \ additional fields will be set."
+ type: "string"
+ values:
+ description: "Current values for fields used by the process.Keys are Strings,\
+ \ values can be any type, as determined by the application & process."
+ type: "object"
+ type: "object"
+ ProcessStepError:
+ description: "In case an error is thrown in the backend job."
+ properties:
+ error:
+ description: "Exception message, in case the process step threw an error."
+ type: "string"
+ processUUID:
+ description: "Unique identifier for a running instance the process."
+ type: "string"
+ type:
+ description: "What kind of response has been received. Determines what\
+ \ additional fields will be set."
+ type: "string"
+ userFacingError:
+ description: "Optional user-facing exception message, in case the process\
+ \ step threw a user-facing error."
+ type: "string"
+ type: "object"
+ ProcessStepJobStarted:
+ description: "In case the backend needs more time, this is a UUID of the background\
+ \ job that has been started."
+ properties:
+ jobUUID:
+ description: "Unique identifier for a running step of the process. Must\
+ \ be passed into `status` check calls."
+ type: "string"
+ processUUID:
+ description: "Unique identifier for a running instance the process."
+ type: "string"
+ type:
+ description: "What kind of response has been received. Determines what\
+ \ additional fields will be set."
+ type: "string"
+ type: "object"
+ ProcessStepResponseV1:
+ oneOf:
+ - description: "Data returned after the job is complete (whether it was synchronous,\
+ \ or asynchronous)"
+ properties:
+ nextStep:
+ description: "Name of the next process step that needs to run (a frontend\
+ \ step). If there are no more steps in the process, this field will\
+ \ not be included. "
+ type: "string"
+ processMetaDataAdjustment:
+ description: "Changes to be made to the process's metaData."
+ properties:
+ updatedFields:
+ additionalProperties:
+ $ref: "#/components/schemas/FieldMetaData"
+ description: "Fields whose meta-data has changed. e.g., changing\
+ \ a label, or required status, or inline-possible-values."
+ type: "object"
+ updatedFrontendStepList:
+ description: "In case the backend has changed the list of frontend\
+ \ steps, it will be set in this field."
+ items:
+ properties:
+ components:
+ description: "The components that make up this screen"
+ items:
+ $ref: "#/components/schemas/FrontendComponent"
+ type: "array"
+ formFields:
+ description: "Fields used as form fields (inputs) on this step/screen"
+ items:
+ $ref: "#/components/schemas/FieldMetaData"
+ type: "array"
+ label:
+ description: "The user-facing name for this step"
+ type: "string"
+ name:
+ description: "The unique name for this step within its process"
+ type: "string"
+ recordListFields:
+ description: "Fields used in record-lists shown on the step/screen."
+ items:
+ $ref: "#/components/schemas/FieldMetaData"
+ type: "array"
+ viewFields:
+ description: "Fields used as view-only fields on this step/screen"
+ items:
+ $ref: "#/components/schemas/FieldMetaData"
+ type: "array"
+ type: "object"
+ type: "array"
+ type: "object"
+ processUUID:
+ description: "Unique identifier for a running instance the process."
+ type: "string"
+ type:
+ description: "What kind of response has been received. Determines what\
+ \ additional fields will be set."
+ type: "string"
+ values:
+ description: "Current values for fields used by the process.Keys are Strings,\
+ \ values can be any type, as determined by the application & process."
+ type: "object"
+ type: "object"
+ - description: "In case the backend needs more time, this is a UUID of the background\
+ \ job that has been started."
+ properties:
+ jobUUID:
+ description: "Unique identifier for a running step of the process. Must\
+ \ be passed into `status` check calls."
+ type: "string"
+ processUUID:
+ description: "Unique identifier for a running instance the process."
+ type: "string"
+ type:
+ description: "What kind of response has been received. Determines what\
+ \ additional fields will be set."
+ type: "string"
+ type: "object"
+ - description: "Response to a status check for a backgrounded job."
+ properties:
+ current:
+ description: "Optional indicator of progress (e.g., `current` of `total`,\
+ \ as in (`1 of 10`)."
+ type: "number"
+ message:
+ description: "Status message regarding the running process step."
+ type: "string"
+ processUUID:
+ description: "Unique identifier for a running instance the process."
+ type: "string"
+ total:
+ description: "Optional indicator of progress (e.g., `current` of `total`,\
+ \ as in (`1 of 10`)."
+ type: "number"
+ type:
+ description: "What kind of response has been received. Determines what\
+ \ additional fields will be set."
+ type: "string"
+ type: "object"
+ - description: "In case an error is thrown in the backend job."
+ properties:
+ error:
+ description: "Exception message, in case the process step threw an error."
+ type: "string"
+ processUUID:
+ description: "Unique identifier for a running instance the process."
+ type: "string"
+ type:
+ description: "What kind of response has been received. Determines what\
+ \ additional fields will be set."
+ type: "string"
+ userFacingError:
+ description: "Optional user-facing exception message, in case the process\
+ \ step threw a user-facing error."
+ type: "string"
+ type: "object"
+ ProcessStepRunning:
+ description: "Response to a status check for a backgrounded job."
+ properties:
+ current:
+ description: "Optional indicator of progress (e.g., `current` of `total`,\
+ \ as in (`1 of 10`)."
+ type: "number"
+ message:
+ description: "Status message regarding the running process step."
+ type: "string"
+ processUUID:
+ description: "Unique identifier for a running instance the process."
+ type: "string"
+ total:
+ description: "Optional indicator of progress (e.g., `current` of `total`,\
+ \ as in (`1 of 10`)."
+ type: "number"
+ type:
+ description: "What kind of response has been received. Determines what\
+ \ additional fields will be set."
+ type: "string"
+ type: "object"
+ TableMetaDataLight:
+ 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"
+ helpContents:
+ description: "Help Contents for this table."
+ type: "object"
+ iconName:
+ description: "Name of an icon for the table, from the material UI icon set"
+ type: "string"
+ 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"
+ readPermission:
+ description: "Boolean to indicate if the user has read permission for the\
+ \ table."
+ 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"
+ WidgetBlock:
+ properties:
+ blockId:
+ description: "Unique identifier for this block within it widget. Used as\
+ \ a key for helpContents."
+ type: "string"
+ blockType:
+ description: "What type of block to render."
+ enum:
+ - "ACTION_BUTTON"
+ - "AUDIO"
+ - "BIG_NUMBER"
+ - "COMPOSITE"
+ - "DIVIDER"
+ - "IMAGE"
+ - "INPUT_FIELD"
+ - "NUMBER_ICON_BADGE"
+ - "PROGRESS_BAR"
+ - "TABLE_SUB_ROW_DETAIL_ROW"
+ - "TEXT"
+ - "UP_OR_DOWN_NUMBER"
+ type: "string"
+ conditional:
+ description: "Optional field name (e.g,. from a process's set of fields)\
+ \ to act as a 'guard' for the block - e.g., only include it in the UI\
+ \ if the value for this field is true"
+ type: "string"
+ layout:
+ description: "For COMPOSITE type blocks, an indicator of how the sub-blocks\
+ \ should be laid out"
+ enum:
+ - "FLEX_COLUMN"
+ - "FLEX_ROW_WRAPPED"
+ - "FLEX_ROW_SPACE_BETWEEN"
+ - "FLEX_ROW_CENTER"
+ - "TABLE_SUB_ROW_DETAILS"
+ - "BADGES_WRAPPER"
+ type: "string"
+ styles:
+ $ref: "#/components/schemas/WidgetBlockStyles"
+ description: "Styles to apply to the block. Different fields based on blockType."
+ oneOf:
+ - properties:
+ isAlert:
+ description: "Indicate if the text should be displayed as an alert\
+ \ (e.g., modal popup)"
+ type: "boolean"
+ standardColor:
+ description: "A Standard Color to display the text in (e.g., not a\
+ \ hex or RGB code)."
+ enum:
+ - "SUCCESS"
+ - "WARNING"
+ - "ERROR"
+ - "INFO"
+ - "MUTED"
+ type: "string"
+ type: "object"
+ subBlocks:
+ description: "For COMPOSITE type blocks, a list of sub-blocks."
+ items:
+ $ref: "#/components/schemas/WidgetBlock"
+ type: "array"
+ values:
+ $ref: "#/components/schemas/WidgetBlockValues"
+ description: "Values to show in the block, or otherwise control its behavior.\
+ \ Different fields based on blockType."
+ oneOf:
+ - description: "Values used for an ACTION_BUTTON type widget block"
+ properties:
+ actionCode:
+ description: "Code used within the app as the value submitted when\
+ \ the button is clicked"
+ type: "string"
+ label:
+ description: "User-facing label to display in the button"
+ type: "string"
+ type: "object"
+ - description: "Values used for a TEXT type widget block"
+ properties:
+ text:
+ description: "The text to display in the block"
+ type: "string"
+ type: "object"
+ - description: "Values used for an INPUT_FIELD type widget block"
+ properties:
+ autoFocus:
+ description: "Indicate whether this field should auto-focus when it\
+ \ is rendered"
+ type: "boolean"
+ fieldMetaData:
+ description: "Metadata to define the field that this block controls"
+ properties:
+ adornments:
+ description: "Special UI dressings to add to the field."
+ items:
+ properties:
+ type:
+ enum:
+ - "LINK"
+ - "CHIP"
+ - "SIZE"
+ - "CODE_EDITOR"
+ - "RENDER_HTML"
+ - "REVEAL"
+ - "FILE_DOWNLOAD"
+ - "ERROR"
+ type: "string"
+ values:
+ type: "object"
+ type: "object"
+ type: "array"
+ defaultValue:
+ description: "Default value to use in this field."
+ type: "string"
+ displayFormat:
+ description: "C-style format specifier for displaying values in\
+ \ this field."
+ type: "string"
+ isEditable:
+ description: "Indicate if user may edit the value in this field."
+ type: "boolean"
+ isHeavy:
+ description: "Indicator of 'heavy' fields, which are not loaded\
+ \ by default. e.g., some blobs or long-texts"
+ type: "boolean"
+ isHidden:
+ description: "Indicate if this field should be hidden from users"
+ type: "boolean"
+ isRequired:
+ description: "Indicate if a value in this field is required."
+ type: "boolean"
+ label:
+ description: "User-facing name for this field"
+ type: "string"
+ maxLength:
+ description: "For String fields, the max length the field supports."
+ type: "number"
+ name:
+ description: "Unique name for this field within its container\
+ \ (table or process)"
+ type: "string"
+ possibleValueSourceName:
+ description: "If this field's values should come from a possible\
+ \ value source, then that PVS is named here."
+ type: "string"
+ type:
+ description: "Data-type for this field"
+ type: "string"
+ type: "object"
+ submitOnEnter:
+ description: "Indicate whether the form that this field is on should\
+ \ be submitted when Enter is pressed"
+ type: "boolean"
+ type: "object"
+ type: "object"
+ WidgetBlockActionButtonValues:
+ description: "Values used for an ACTION_BUTTON type widget block"
+ properties:
+ actionCode:
+ description: "Code used within the app as the value submitted when the button\
+ \ is clicked"
+ type: "string"
+ label:
+ description: "User-facing label to display in the button"
+ type: "string"
+ type: "object"
+ WidgetBlockInputFieldValues:
+ description: "Values used for an INPUT_FIELD type widget block"
+ properties:
+ autoFocus:
+ description: "Indicate whether this field should auto-focus when it is rendered"
+ type: "boolean"
+ fieldMetaData:
+ $ref: "#/components/schemas/FieldMetaData"
+ description: "Metadata to define the field that this block controls"
+ submitOnEnter:
+ description: "Indicate whether the form that this field is on should be\
+ \ submitted when Enter is pressed"
+ type: "boolean"
+ type: "object"
+ WidgetBlockStyles:
+ type: "object"
+ WidgetBlockTextStyles:
+ properties:
+ isAlert:
+ description: "Indicate if the text should be displayed as an alert (e.g.,\
+ \ modal popup)"
+ type: "boolean"
+ standardColor:
+ description: "A Standard Color to display the text in (e.g., not a hex or\
+ \ RGB code)."
+ enum:
+ - "SUCCESS"
+ - "WARNING"
+ - "ERROR"
+ - "INFO"
+ - "MUTED"
+ type: "string"
+ type: "object"
+ WidgetBlockTextValues:
+ description: "Values used for a TEXT type widget block"
+ properties:
+ text:
+ description: "The text to display in the block"
+ type: "string"
+ type: "object"
+ WidgetBlockValues:
+ type: "object"
+ securitySchemes:
+ sessionUuidCookie:
+ in: "cookie"
+ name: "sessionUUID"
+ type: "apiKey"
+info:
+ contact:
+ email: "contact@kingsrook.com"
+ description: "## Intro\nThis is the definition of the standard API implemented by\
+ \ QQQ Middleware.\n\nDevelopers of QQQ Frontends (e.g., javascript libraries,\
+ \ or native applications) use this API to access\na QQQ Backend server.\n\nAs\
+ \ such, this API itself is not concerned with any of the application-level details\
+ \ of any particular\napplication built using QQQ. Instead, this API is all about\
+ \ the generic endpoints used for any application\nbuilt on QQQ. For example,\
+ \ many endpoints work with a `${table}` path parameter - whose possible values\n\
+ are defined by the application - but which are not known to this API.\n\n## Flow\n\
+ The typical flow of a user (as implemented in a frontend that utilizes this API)\
+ \ looks like:\n1. Frontend calls `.../metaData/authentication`, to know what type\
+ \ of authentication provider is used by the backend, and display an appropriate\
+ \ UI to the user for authenticating.\n2. User authenticates in frontend, as required\
+ \ for the authentication provider.\n3. Frontend calls `.../manageSession`, providing\
+ \ authentication details (e.g., an accessToken or other credentials) to the backend.\n\
+ 4. The response from the `manageSession` call (assuming success), sets the `sessionUUID`\
+ \ Cookie, which should be included in all subsequent requests for authentication.\n\
+ 5. After the user is authenticated, the frontend calls `.../metaData`, to discover\
+ \ the apps, tables, processes, etc, that the application is made up of (and that\
+ \ the authenticated user has permission to access).\n6. As the user interacts\
+ \ with apps, tables, process, etc, the frontend utilizes the appropriate endpoints\
+ \ as required.\n"
+ title: "QQQ Middleware API"
+ version: "v1"
+openapi: "3.0.3"
+paths:
+ /qqq/v1/metaData/authentication:
+ get:
+ description: "For a frontend to determine which authentication provider or mechanism\
+ \ to use, it should begin its lifecycle\nby requesting this metaData object,\
+ \ and inspecting the `type` property in the response.\n\nNote that this endpoint\
+ \ is not secured, as its purpose is to be called as part of the workflow that\
+ \ results\nin a user being authenticated."
+ responses:
+ 200:
+ content:
+ application/json:
+ examples:
+ For FULLY_ANONYMOUS type:
+ value:
+ name: "anonymous"
+ type: "FULLY_ANONYMOUS"
+ For AUTH_0 type:
+ value:
+ name: "auth0"
+ type: "AUTH_0"
+ values:
+ audience: "myapp.mydomain.com"
+ baseUrl: "https://myapp.auth0.com/"
+ clientId: "abcdefg1234567"
+ schema:
+ $ref: "#/components/schemas/AuthenticationMetaDataResponseV1"
+ description: "Successful Response"
+ summary: "Get authentication metaData"
+ tags:
+ - "Authentication"
+ /qqq/v1/manageSession:
+ post:
+ description: "After a frontend authenticates the user as per the requirements\
+ \ of the authentication provider specified by the\n`type` field in the `metaData/authentication`\
+ \ response, data from that authentication provider should be posted\nto this\
+ \ endpoint, to create a session within the QQQ application.\n\nThe response\
+ \ object will include a session identifier (`uuid`) to authenticate the user\
+ \ in subsequent API calls."
+ requestBody:
+ content:
+ application/json:
+ schema:
+ description: "Data required to create the session. Specific needs may\
+ \ vary based on the AuthenticationModule type in the QQQ Backend."
+ properties:
+ accessToken:
+ description: "An access token from a downstream authentication provider\
+ \ (e.g., Auth0), to use as the basis for authentication and authorization."
+ type: "string"
+ type: "object"
+ required: true
+ responses:
+ 401:
+ content:
+ application/json:
+ examples:
+ Invalid token:
+ value:
+ error: "Unable to decode access token."
+ schema:
+ $ref: "#/components/schemas/BasicErrorResponseV1"
+ description: "Authentication error - session was not created"
+ 200:
+ content:
+ application/json:
+ examples:
+ With no custom values:
+ value:
+ uuid: "01234567-89AB-CDEF-0123-456789ABCDEF"
+ With custom values:
+ value:
+ uuid: "98765432-10FE-DCBA-9876-543210FEDCBA"
+ values:
+ region: "US"
+ userCategoryId: 47
+ schema:
+ $ref: "#/components/schemas/ManageSessionResponseV1"
+ description: "Successful response - session has been created"
+ summary: "Create a session"
+ tags:
+ - "Authentication"
+ /qqq/v1/metaData:
+ get:
+ description: "Load the overall metadata, as is relevant to a frontend, for the\
+ \ entire application, with permissions applied, as per the\nauthenticated\
+ \ user.\n\nThis includes:\n- Apps (both as a map of name to AppMetaData (`apps`),\
+ \ but also as a tree (`appTree`), for presenting\nhierarchical navigation),\n\
+ - Tables (but without all details, e.g., fields),\n- Processes (also without\
+ \ full details, e.g., screens),\n- Reports\n- Widgets\n- Branding\n- Help\
+ \ Contents\n- Environment values\n"
+ responses:
+ 200:
+ content:
+ application/json:
+ examples:
+ Example:
+ value:
+ appTree:
+ - children:
+ - children:
+ - label: "Sample Person Process"
+ name: "samplePersonProcess"
+ type: "PROCESS"
+ label: "Child App"
+ name: "childApp"
+ type: "APP"
+ - label: "Person"
+ name: "person"
+ type: "TABLE"
+ label: "Home App"
+ name: "homeApp"
+ type: "APP"
+ apps:
+ homeApp:
+ childMap:
+ person:
+ label: "Person"
+ name: "person"
+ type: "TABLE"
+ childApp:
+ label: "Child App"
+ name: "childApp"
+ type: "APP"
+ children:
+ - label: "Child App"
+ name: "childApp"
+ type: "APP"
+ - label: "Person"
+ name: "person"
+ type: "TABLE"
+ iconName: "home"
+ label: "Home App"
+ name: "homeApp"
+ sections:
+ - icon:
+ name: "badge"
+ label: "Home App"
+ name: "homeApp"
+ tables:
+ - "person"
+ childApp:
+ childMap:
+ samplePersonProcess:
+ label: "Sample Person Process"
+ name: "samplePersonProcess"
+ type: "PROCESS"
+ children:
+ - label: "Sample Person Process"
+ name: "samplePersonProcess"
+ type: "PROCESS"
+ iconName: "child_friendly"
+ label: "Child App"
+ name: "childApp"
+ sections:
+ - icon:
+ name: "badge"
+ label: "Child App"
+ name: "childApp"
+ processes:
+ - "samplePersonProcess"
+ processes:
+ person.bulkInsert:
+ hasPermission: true
+ isHidden: true
+ label: "Person Bulk Insert"
+ name: "person.bulkInsert"
+ stepFlow: "LINEAR"
+ tableName: "person"
+ person.bulkEdit:
+ hasPermission: true
+ isHidden: true
+ label: "Person Bulk Edit"
+ name: "person.bulkEdit"
+ stepFlow: "LINEAR"
+ tableName: "person"
+ samplePersonProcess:
+ hasPermission: true
+ iconName: "person_add"
+ isHidden: false
+ label: "Sample Person Process"
+ name: "samplePersonProcess"
+ stepFlow: "LINEAR"
+ tableName: "person"
+ person.bulkDelete:
+ hasPermission: true
+ isHidden: true
+ label: "Person Bulk Delete"
+ name: "person.bulkDelete"
+ stepFlow: "LINEAR"
+ tableName: "person"
+ tables:
+ person:
+ capabilities:
+ - "TABLE_COUNT"
+ - "TABLE_GET"
+ - "TABLE_QUERY"
+ - "QUERY_STATS"
+ - "TABLE_INSERT"
+ - "TABLE_DELETE"
+ - "TABLE_UPDATE"
+ deletePermission: true
+ editPermission: true
+ iconName: "person_outline"
+ insertPermission: true
+ isHidden: false
+ label: "Person"
+ name: "person"
+ readPermission: true
+ schema:
+ $ref: "#/components/schemas/MetaDataResponseV1"
+ description: "Overall metadata for the application."
+ security:
+ - sessionUuidCookie:
+ - "N/A"
+ summary: "Get instance metaData"
+ tags:
+ - "General"
+ /qqq/v1/metaData/process/{processName}:
+ get:
+ description: "Load the full metadata for a single process, including all screens\
+ \ (aka, frontend steps), which a frontend\nneeds to display to users."
+ parameters:
+ - description: "Name of the process to load."
+ example: "samplePersonProcess"
+ in: "path"
+ name: "processName"
+ required: true
+ schema:
+ type: "string"
+ responses:
+ 200:
+ content:
+ application/json:
+ examples:
+ TODO:
+ value: {}
+ schema:
+ $ref: "#/components/schemas/ProcessMetaDataResponseV1"
+ description: "The full process metadata"
+ security:
+ - sessionUuidCookie:
+ - "N/A"
+ summary: "Get process metaData"
+ tags:
+ - "Processes"
+ /qqq/v1/processes/{processName}/init:
+ post:
+ description: "For a user to start running a process, this endpoint should be\
+ \ called, to start the process\nand run its first step(s) (any backend steps\
+ \ before the first frontend step).\n\nAdditional process-specific values should\
+ \ posted in a form param named `values`, as JSON object\nwith keys defined\
+ \ by the process in question.\n\nFor a process which needs to operate on a\
+ \ set of records that a user selected, see\n`recordsParam`, and `recordIds`\
+ \ or `filterJSON`.\n\nThe response will include a `processUUID`, to be included\
+ \ in all subsequent requests relevant\nto the process.\n\nNote that this request,\
+ \ if it takes longer than a given threshold* to complete, will return a\n\
+ a `jobUUID`, which should be sent to the `/processes/{processName}/{processUUID}/status/{jobUUID}`\n\
+ endpoint, to poll for a status update.\n\n*This threshold has a default value\
+ \ of 3,000 ms., but can be set per-request via the form\nparameter `stepTimeoutMillis`.\n"
+ parameters:
+ - description: "Name of the process to initialize"
+ example: "samplePersonProcess"
+ in: "path"
+ name: "processName"
+ required: true
+ schema:
+ type: "string"
+ requestBody:
+ content:
+ multipart/form-data:
+ schema:
+ properties:
+ values:
+ description: "Process-specific field names and values."
+ type: "object"
+ recordsParam:
+ description: "Specifies which other query-param will contain the\
+ \ indicator of initial records to pass in to the process."
+ examples:
+ recordIds:
+ value: "recordIds"
+ filterJSON:
+ value: "recordIds"
+ type: "string"
+ recordIds:
+ description: "Comma-separated list of ids from the table this process\
+ \ is based on, to use as input records for the process. Needs\
+ \ `recordsParam=recordIds` value to be given as well."
+ examples:
+ one id:
+ value: "1701"
+ multiple ids:
+ value: "42,47"
+ type: "string"
+ filterJSON:
+ description: "JSON encoded QQueryFilter object, to execute against\
+ \ the table this process is based on, to find input records for\
+ \ the process. Needs `recordsParam=filterJSON` value to be given\
+ \ as well."
+ examples:
+ empty filter (all records):
+ value: "{}"
+ filter by a condition:
+ value: "{\"criteria\":[{\"fieldName\":\"id\",\"operator\":\"\
+ LESS_THAN\",\"values\":[10]}],\"booleanOperator\":\"AND\"}"
+ type: "string"
+ stepTimeoutMillis:
+ description: "Optionally change the time that the server will wait\
+ \ for the job before letting it go asynchronous. Default value\
+ \ is 3000."
+ examples:
+ shorter timeout:
+ value: "500"
+ longer timeout:
+ value: "60000"
+ type: "integer"
+ file:
+ description: "A file upload, for processes which expect to be initialized\
+ \ with an uploaded file."
+ format: "binary"
+ type: "string"
+ type: "object"
+ required: false
+ responses:
+ 200:
+ content:
+ application/json:
+ examples:
+ COMPLETE:
+ value:
+ typedResponse:
+ nextStep: "reviewScreen"
+ processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF"
+ type: "COMPLETE"
+ values:
+ totalAge: 32768
+ firstLastName: "Aabramson"
+ COMPLETE with metaDataAdjustment:
+ value:
+ typedResponse:
+ nextStep: "inputScreen"
+ processMetaDataAdjustment:
+ updatedFields:
+ someField:
+ displayFormat: "%s"
+ isEditable: true
+ isHeavy: false
+ isHidden: false
+ isRequired: true
+ name: "someField"
+ type: "STRING"
+ updatedFrontendStepList:
+ - components:
+ - type: "EDIT_FORM"
+ formFields:
+ - displayFormat: "%s"
+ isEditable: true
+ isHeavy: false
+ isHidden: false
+ isRequired: false
+ name: "someField"
+ type: "STRING"
+ name: "inputScreen"
+ - components:
+ - type: "PROCESS_SUMMARY_RESULTS"
+ name: "resultScreen"
+ processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF"
+ type: "COMPLETE"
+ values:
+ totalAge: 32768
+ firstLastName: "Aabramson"
+ JOB_STARTED:
+ value:
+ typedResponse:
+ jobUUID: "98765432-10FE-DCBA-9876-543210FEDCBA"
+ processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF"
+ type: "JOB_STARTED"
+ RUNNING:
+ value:
+ typedResponse:
+ current: 47
+ message: "Processing person records"
+ processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF"
+ total: 1701
+ type: "RUNNING"
+ ERROR:
+ value:
+ typedResponse:
+ processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF"
+ type: "RUNNING"
+ schema:
+ $ref: "#/components/schemas/ProcessStepResponseV1"
+ description: "State of the initialization of the job, with different fields\
+ \ set, based on the\nstatus of the task."
+ security:
+ - sessionUuidCookie:
+ - "N/A"
+ summary: "Initialize a process"
+ tags:
+ - "Processes"
+ /qqq/v1/processes/{processName}/{processUUID}/step/{stepName}:
+ post:
+ description: "To run the next step in a process, this endpoint should be called,\
+ \ with the `processName`\nand existing `processUUID`, as well as the step\
+ \ that was just completed in the frontend,\ngiven as `stepName`.\n\nAdditional\
+ \ process-specific values should posted in a form param named `values`, as\
+ \ JSON object\nwith keys defined by the process in question.\n\nNote that\
+ \ this request, if it takes longer than a given threshold* to complete, will\
+ \ return a\na `jobUUID`, which should be sent to the `/processes/{processName}/{processUUID}/status/{jobUUID}`\n\
+ endpoint, to poll for a status update.\n\n*This threshold has a default value\
+ \ of 3,000 ms., but can be set per-request via the form\nparameter `stepTimeoutMillis`.\n"
+ parameters:
+ - description: "Name of the process to perform the step in."
+ example: "samplePersonProcess"
+ in: "path"
+ name: "processName"
+ required: true
+ schema:
+ type: "string"
+ - description: "Unique identifier for this run of the process - as was returned\
+ \ by the `init` call."
+ example: "01234567-89AB-CDEF-0123-456789ABCDEF"
+ in: "path"
+ name: "processUUID"
+ required: true
+ schema:
+ type: "string"
+ - description: "Name of the frontend step that the user has just completed."
+ example: "inputForm"
+ in: "path"
+ name: "stepName"
+ required: true
+ schema:
+ type: "string"
+ requestBody:
+ content:
+ multipart/form-data:
+ schema:
+ properties:
+ values:
+ description: "Process-specific field names and values."
+ type: "object"
+ stepTimeoutMillis:
+ description: "Optionally change the time that the server will wait\
+ \ for the job before letting it go asynchronous. Default value\
+ \ is 3000."
+ examples:
+ shorter timeout:
+ value: "500"
+ longer timeout:
+ value: "60000"
+ type: "integer"
+ file:
+ description: "A file upload, for process steps which expect an uploaded\
+ \ file."
+ format: "binary"
+ type: "string"
+ type: "object"
+ required: false
+ responses:
+ 200:
+ content:
+ application/json:
+ examples:
+ COMPLETE:
+ value:
+ typedResponse:
+ nextStep: "reviewScreen"
+ processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF"
+ type: "COMPLETE"
+ values:
+ totalAge: 32768
+ firstLastName: "Aabramson"
+ COMPLETE with metaDataAdjustment:
+ value:
+ typedResponse:
+ nextStep: "inputScreen"
+ processMetaDataAdjustment:
+ updatedFields:
+ someField:
+ displayFormat: "%s"
+ isEditable: true
+ isHeavy: false
+ isHidden: false
+ isRequired: true
+ name: "someField"
+ type: "STRING"
+ updatedFrontendStepList:
+ - components:
+ - type: "EDIT_FORM"
+ formFields:
+ - displayFormat: "%s"
+ isEditable: true
+ isHeavy: false
+ isHidden: false
+ isRequired: false
+ name: "someField"
+ type: "STRING"
+ name: "inputScreen"
+ - components:
+ - type: "PROCESS_SUMMARY_RESULTS"
+ name: "resultScreen"
+ processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF"
+ type: "COMPLETE"
+ values:
+ totalAge: 32768
+ firstLastName: "Aabramson"
+ JOB_STARTED:
+ value:
+ typedResponse:
+ jobUUID: "98765432-10FE-DCBA-9876-543210FEDCBA"
+ processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF"
+ type: "JOB_STARTED"
+ RUNNING:
+ value:
+ typedResponse:
+ current: 47
+ message: "Processing person records"
+ processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF"
+ total: 1701
+ type: "RUNNING"
+ ERROR:
+ value:
+ typedResponse:
+ processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF"
+ type: "RUNNING"
+ schema:
+ $ref: "#/components/schemas/ProcessStepResponseV1"
+ description: "State of the backend's running of the next step(s) of the\
+ \ job, with different fields set,\nbased on the status of the job."
+ security:
+ - sessionUuidCookie:
+ - "N/A"
+ summary: "Run a step in a process"
+ tags:
+ - "Processes"
+ /qqq/v1/processes/{processName}/{processUUID}/status/{jobUUID}:
+ get:
+ description: "Get the status of a running job for a process.\n\nResponse is\
+ \ the same format as for an init or step call that completed synchronously.\n"
+ parameters:
+ - description: "Name of the process that is being ran"
+ example: "samplePersonProcess"
+ in: "path"
+ name: "processName"
+ required: true
+ schema:
+ type: "string"
+ - description: "Unique identifier for this run of the process - as was returned\
+ \ by the `init` call."
+ example: "01234567-89AB-CDEF-0123-456789ABCDEF"
+ in: "path"
+ name: "processUUID"
+ required: true
+ schema:
+ format: "uuid"
+ type: "string"
+ - description: "Unique identifier for the asynchronous job being executed, as\
+ \ returned by an `init` or `step` call that went asynch."
+ example: "98765432-10FE-DCBA-9876-543210FEDCBA"
+ in: "path"
+ name: "jobUUID"
+ required: true
+ schema:
+ format: "uuid"
+ type: "string"
+ responses:
+ 200:
+ content:
+ application/json:
+ examples:
+ COMPLETE:
+ value:
+ typedResponse:
+ nextStep: "reviewScreen"
+ processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF"
+ type: "COMPLETE"
+ values:
+ totalAge: 32768
+ firstLastName: "Aabramson"
+ COMPLETE with metaDataAdjustment:
+ value:
+ typedResponse:
+ nextStep: "inputScreen"
+ processMetaDataAdjustment:
+ updatedFields:
+ someField:
+ displayFormat: "%s"
+ isEditable: true
+ isHeavy: false
+ isHidden: false
+ isRequired: true
+ name: "someField"
+ type: "STRING"
+ updatedFrontendStepList:
+ - components:
+ - type: "EDIT_FORM"
+ formFields:
+ - displayFormat: "%s"
+ isEditable: true
+ isHeavy: false
+ isHidden: false
+ isRequired: false
+ name: "someField"
+ type: "STRING"
+ name: "inputScreen"
+ - components:
+ - type: "PROCESS_SUMMARY_RESULTS"
+ name: "resultScreen"
+ processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF"
+ type: "COMPLETE"
+ values:
+ totalAge: 32768
+ firstLastName: "Aabramson"
+ JOB_STARTED:
+ value:
+ typedResponse:
+ jobUUID: "98765432-10FE-DCBA-9876-543210FEDCBA"
+ processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF"
+ type: "JOB_STARTED"
+ RUNNING:
+ value:
+ typedResponse:
+ current: 47
+ message: "Processing person records"
+ processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF"
+ total: 1701
+ type: "RUNNING"
+ ERROR:
+ value:
+ typedResponse:
+ processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF"
+ type: "RUNNING"
+ schema:
+ $ref: "#/components/schemas/ProcessStepResponseV1"
+ description: "State of the backend's running of the specified job, with\
+ \ different fields set,\nbased on the status of the job."
+ security:
+ - sessionUuidCookie:
+ - "N/A"
+ summary: "Get job status"
+ tags:
+ - "Processes"
diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/api/wip/TestContext.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/api/wip/TestContext.java
new file mode 100644
index 00000000..916c9a1a
--- /dev/null
+++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/api/wip/TestContext.java
@@ -0,0 +1,356 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.api.wip;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+import io.javalin.config.Key;
+import io.javalin.http.Context;
+import io.javalin.http.HandlerType;
+import io.javalin.http.HttpStatus;
+import io.javalin.json.JsonMapper;
+import io.javalin.plugin.ContextPlugin;
+import io.javalin.security.RouteRole;
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.commons.io.IOUtils;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class TestContext implements Context
+{
+ private Map queryParams = new LinkedHashMap<>();
+ private Map pathParams = new LinkedHashMap<>();
+ private Map formParams = new LinkedHashMap<>();
+
+ private InputStream result;
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public TestContext()
+ {
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public TestContext withQueryParam(String key, String value)
+ {
+ queryParams.put(key, value);
+ return (this);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public TestContext withPathParam(String key, String value)
+ {
+ pathParams.put(key, value);
+ return (this);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public TestContext withFormParam(String key, String value)
+ {
+ formParams.put(key, value);
+ return (this);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public String queryParam(String key)
+ {
+ return queryParams.get(key);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public String formParam(String key)
+ {
+ return formParams.get(key);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public boolean strictContentTypes()
+ {
+ return false;
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public HttpServletRequest req()
+ {
+ return null;
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public HttpServletResponse res()
+ {
+ return null;
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public HandlerType handlerType()
+ {
+ return null;
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public String matchedPath()
+ {
+ return "";
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public String endpointHandlerPath()
+ {
+ return "";
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public T appData(Key key)
+ {
+ return null;
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public JsonMapper jsonMapper()
+ {
+ return null;
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public T with(Class extends ContextPlugin, T>> aClass)
+ {
+ return null;
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public String pathParam(String key)
+ {
+ return pathParams.get(key);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public Map pathParamMap()
+ {
+ return pathParams;
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public ServletOutputStream outputStream()
+ {
+ return null;
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public Context minSizeForCompression(int i)
+ {
+ return null;
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public Context result(InputStream inputStream)
+ {
+ this.result = inputStream;
+ return (this);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public InputStream resultInputStream()
+ {
+ return null;
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void future(Supplier extends CompletableFuture>> supplier)
+ {
+
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void redirect(String s, HttpStatus httpStatus)
+ {
+
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public void writeJsonStream(Stream> stream)
+ {
+
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public Context skipRemainingHandlers()
+ {
+ return null;
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public Set routeRoles()
+ {
+ return Set.of();
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for response
+ **
+ *******************************************************************************/
+ public String getResultAsString() throws IOException
+ {
+ byte[] bytes = IOUtils.readFully(result, result.available());
+ return new String(bytes, StandardCharsets.UTF_8);
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/api/wip/TestUtils.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/api/wip/TestUtils.java
new file mode 100644
index 00000000..8773812e
--- /dev/null
+++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/api/wip/TestUtils.java
@@ -0,0 +1,92 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.api.wip;
+
+
+import java.util.Map;
+import com.kingsrook.qqq.openapi.model.Schema;
+import com.kingsrook.qqq.openapi.model.Type;
+import org.assertj.core.api.AbstractStringAssert;
+import org.json.JSONArray;
+import org.json.JSONObject;
+import static org.assertj.core.api.Assertions.assertThat;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class TestUtils
+{
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ private static void assertStringVsSchema(String string, Schema schema, String path)
+ {
+ String description = "At path " + path;
+ final AbstractStringAssert> assertion = assertThat(string).describedAs(description);
+
+ Type type = Type.valueOf(schema.getType().toUpperCase());
+ switch(type)
+ {
+ case OBJECT ->
+ {
+ assertion.startsWith("{");
+ JSONObject object = new JSONObject(string);
+
+ for(Map.Entry entry : schema.getProperties().entrySet())
+ {
+ // todo deal with optional
+ Object subObject = object.get(entry.getKey());
+ assertStringVsSchema(subObject.toString(), entry.getValue(), path + "/" + entry.getKey());
+ }
+ }
+ case ARRAY ->
+ {
+ assertion.startsWith("[");
+ JSONArray array = new JSONArray(string);
+
+ for(int i = 0; i < array.length(); i++)
+ {
+ Object subObject = array.get(i);
+ assertStringVsSchema(subObject.toString(), schema.getItems(), path + "[" + i + "]");
+ }
+ }
+ case BOOLEAN ->
+ {
+ assertion.matches("(true|false)");
+ }
+ case INTEGER ->
+ {
+ assertion.matches("-?\\d+");
+ }
+ case NUMBER ->
+ {
+ assertion.matches("-?\\d+(\\.\\d+)?");
+ }
+ case STRING ->
+ {
+ assertion.matches("\".*\"");
+ }
+ }
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/api/wip/v1/QueryGetSpecV1Test.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/api/wip/v1/QueryGetSpecV1Test.java
new file mode 100644
index 00000000..61626d9a
--- /dev/null
+++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/api/wip/v1/QueryGetSpecV1Test.java
@@ -0,0 +1,75 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.api.wip.v1;
+
+
+import com.kingsrook.qqq.api.wip.TestContext;
+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.data.QRecord;
+import org.junit.jupiter.api.Test;
+
+
+/*******************************************************************************
+ ** Unit test for QueryGetSpecV1
+ *******************************************************************************/
+class QueryGetSpecV1Test
+{
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Test
+ void testBuildInput() throws Exception
+ {
+ TestContext context = new TestContext()
+ .withPathParam("table", "person")
+ .withQueryParam("filter", "{}");
+
+ // QueryMiddlewareInput queryMiddlewareInput = new QueryGetSpecV1().buildInput(context);
+ // assertEquals("person", queryMiddlewareInput.getTable());
+ // assertNotNull(queryMiddlewareInput.getFilter());
+ // assertEquals(0, queryMiddlewareInput.getFilter().getCriteria().size());
+ // assertEquals(0, queryMiddlewareInput.getFilter().getOrderBys().size());
+ // assertNull(queryMiddlewareInput.getQueryJoins());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Test
+ void testBuildOutput() throws Exception
+ {
+ TestContext context = new TestContext();
+ QueryOutput queryOutput = new QueryOutput(new QueryInput());
+ queryOutput.addRecord(new QRecord().withValue("firstName", "Darin").withDisplayValue("firstName", "Darin"));
+
+ // QueryGetSpecV1 queryGetSpecV1 = new QueryGetSpecV1();
+ // queryGetSpecV1.buildOutput(context, new QueryMiddlewareOutput(queryOutput));
+ // String resultJson = context.getResultAsString();
+ // TestUtils.assertResultJsonVsSpec(queryGetSpecV1.defineSimpleSuccessResponse(), resultJson);
+ }
+
+}
\ No newline at end of file
diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandlerTest.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandlerTest.java
index 74aa407c..be0483aa 100644
--- a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandlerTest.java
+++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandlerTest.java
@@ -57,6 +57,7 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase
/*******************************************************************************
** test running a process
**
+ ** Note: ported to v1
*******************************************************************************/
@Test
public void test_processGreetInit()
@@ -73,6 +74,7 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase
/*******************************************************************************
** test running a process that requires rows, but we didn't tell it how to get them.
**
+ ** Note: ported to v1
*******************************************************************************/
@Test
public void test_processRequiresRowsButNotSpecified()
@@ -90,6 +92,7 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase
/*******************************************************************************
** test running a process and telling it rows to load via recordIds param
**
+ ** Note: ported to v1
*******************************************************************************/
@Test
public void test_processRequiresRowsWithRecordIdParam()
@@ -108,6 +111,7 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase
/*******************************************************************************
** test running a process and telling it rows to load via filter JSON
**
+ ** Note: ported to v1
*******************************************************************************/
@Test
public void test_processRequiresRowsWithFilterJSON()
@@ -169,6 +173,7 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase
/*******************************************************************************
** test running a process with field values on the query string
**
+ ** Note: ported to v1
*******************************************************************************/
@Test
public void test_processGreetInitWithQueryValues()
@@ -185,6 +190,7 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase
/*******************************************************************************
** test init'ing a process that goes async
**
+ ** Note: ported to v1, but needs todo more - the status part too in a higher-level
*******************************************************************************/
@Test
public void test_processInitGoingAsync() throws InterruptedException
@@ -221,6 +227,7 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase
/*******************************************************************************
** test init'ing a process that does NOT goes async
**
+ ** Note: not ported to v1, but feels redundant, so, not going to.
*******************************************************************************/
@Test
public void test_processInitNotGoingAsync()
diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinUtilsTest.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinUtilsTest.java
index c8ffe0dd..7fdf17b7 100644
--- a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinUtilsTest.java
+++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinUtilsTest.java
@@ -24,11 +24,17 @@ package com.kingsrook.qqq.backend.javalin;
import java.io.InputStream;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
+import java.util.stream.Stream;
+import io.javalin.config.Key;
import io.javalin.http.Context;
import io.javalin.http.HandlerType;
import io.javalin.http.HttpStatus;
+import io.javalin.json.JsonMapper;
+import io.javalin.plugin.ContextPlugin;
+import io.javalin.security.RouteRole;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@@ -133,6 +139,14 @@ class QJavalinUtilsTest
+ @Override
+ public boolean strictContentTypes()
+ {
+ return false;
+ }
+
+
+
/***************************************************************************
**
***************************************************************************/
@@ -157,17 +171,6 @@ class QJavalinUtilsTest
- /***************************************************************************
- **
- ***************************************************************************/
- @Override
- public T appAttribute(@NotNull String s)
- {
- return null;
- }
-
-
-
/***************************************************************************
**
***************************************************************************/
@@ -204,6 +207,30 @@ class QJavalinUtilsTest
+ @Override
+ public T appData(@NotNull Key key)
+ {
+ return null;
+ }
+
+
+
+ @Override
+ public @NotNull JsonMapper jsonMapper()
+ {
+ return null;
+ }
+
+
+
+ @Override
+ public T with(@NotNull Class extends ContextPlugin, T>> aClass)
+ {
+ return null;
+ }
+
+
+
/***************************************************************************
**
***************************************************************************/
@@ -240,6 +267,14 @@ class QJavalinUtilsTest
+ @Override
+ public @NotNull Context minSizeForCompression(int i)
+ {
+ return null;
+ }
+
+
+
/***************************************************************************
**
***************************************************************************/
@@ -283,5 +318,29 @@ class QJavalinUtilsTest
{
}
+
+
+
+ @Override
+ public void writeJsonStream(@NotNull Stream> stream)
+ {
+
+ }
+
+
+
+ @Override
+ public @NotNull Context skipRemainingHandlers()
+ {
+ return null;
+ }
+
+
+
+ @Override
+ public @NotNull Set routeRoles()
+ {
+ return Set.of();
+ }
}
}
\ No newline at end of file
diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/TestUtils.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/TestUtils.java
index c08773fa..1367cfa5 100644
--- a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/TestUtils.java
+++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/TestUtils.java
@@ -62,6 +62,10 @@ 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.layout.QAppMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
+import com.kingsrook.qqq.backend.core.model.metadata.permissions.PermissionLevel;
+import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PVSValueFormatAndFields;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
@@ -189,11 +193,35 @@ public class TestUtils
throw new IllegalStateException("Error adding script tables to instance");
}
+ defineApps(qInstance);
+
return (qInstance);
}
+ private static void defineApps(QInstance qInstance)
+ {
+ QAppMetaData childApp = new QAppMetaData()
+ .withName("childApp")
+ .withLabel("Child App")
+ .withIcon(new QIcon().withName("child_friendly"))
+ .withPermissionRules(new QPermissionRules().withLevel(PermissionLevel.NOT_PROTECTED))
+ .withChild(qInstance.getProcess(PROCESS_NAME_GREET_PEOPLE_INTERACTIVE));
+ qInstance.addApp(childApp);
+
+ QAppMetaData exampleApp = new QAppMetaData()
+ .withName("homeApp")
+ .withLabel("Home App")
+ .withIcon(new QIcon().withName("home"))
+ .withPermissionRules(new QPermissionRules().withLevel(PermissionLevel.NOT_PROTECTED))
+ .withChild(childApp)
+ .withChild(qInstance.getTable(TABLE_NAME_PERSON));
+ qInstance.addApp(exampleApp);
+ }
+
+
+
/*******************************************************************************
**
*******************************************************************************/
@@ -690,7 +718,7 @@ public class TestUtils
{
return (new RenderWidgetOutput(new RawHTML("title",
QContext.getQSession().getValue(QSession.VALUE_KEY_USER_TIMEZONE_OFFSET_MINUTES)
- + "|" + QContext.getQSession().getValue(QSession.VALUE_KEY_USER_TIMEZONE)
+ + "|" + QContext.getQSession().getValue(QSession.VALUE_KEY_USER_TIMEZONE)
)));
}
}
diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/schemabuilder/SchemaBuilderTest.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/schemabuilder/SchemaBuilderTest.java
new file mode 100644
index 00000000..71594ce4
--- /dev/null
+++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/schemabuilder/SchemaBuilderTest.java
@@ -0,0 +1,101 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.schemabuilder;
+
+
+import java.util.List;
+import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.AuthenticationMetaDataResponseV1;
+import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.MetaDataResponseV1;
+import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.ProcessInitOrStepOrStatusResponseV1;
+import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components.AppTreeNode;
+import com.kingsrook.qqq.openapi.model.Schema;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+
+/*******************************************************************************
+ ** Unit test for SchemaBuilder
+ *******************************************************************************/
+class SchemaBuilderTest
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testIncludesAOneOf()
+ {
+ Schema schema = new SchemaBuilder().classToSchema(AuthenticationMetaDataResponseV1.class);
+ System.out.println(schema);
+
+ Schema valuesSchema = schema.getProperties().get("values");
+ List oneOf = valuesSchema.getOneOf();
+ assertEquals(2, oneOf.size());
+ }
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testUsesIncludeProperties()
+ {
+ Schema schema = new SchemaBuilder().classToSchema(ProcessInitOrStepOrStatusResponseV1.TypedResponse.class);
+ for(Schema oneOf : schema.getOneOf())
+ {
+ /////////////////////////////////////////////////////////////////////////////////////////
+ // all of the wrapped one-of schemas should contain these fields from the parent class //
+ /////////////////////////////////////////////////////////////////////////////////////////
+ assertTrue(oneOf.getProperties().containsKey("type"));
+ assertTrue(oneOf.getProperties().containsKey("processUUID"));
+ }
+ }
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testDescriptionOnGetters()
+ {
+ Schema schema = new SchemaBuilder().classToSchema(MetaDataResponseV1.class);
+ assertTrue(schema.getProperties().containsKey("apps"));
+ assertNotNull(schema.getProperties().get("apps").getDescription());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testRecursive()
+ {
+ Schema schema = new SchemaBuilder().classToSchema(AppTreeNode.class);
+ Schema childrenSchema = schema.getProperties().get("children");
+ assertNotNull(childrenSchema.getItems());
+ System.out.println(schema);
+ }
+
+}
\ No newline at end of file
diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/SpecTestBase.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/SpecTestBase.java
new file mode 100644
index 00000000..072814e0
--- /dev/null
+++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/SpecTestBase.java
@@ -0,0 +1,124 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs;
+
+
+import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryRecordStore;
+import com.kingsrook.qqq.backend.javalin.TestUtils;
+import io.javalin.Javalin;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public abstract class SpecTestBase
+{
+ private static int PORT = 6263;
+
+ protected static Javalin service;
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ protected abstract AbstractEndpointSpec, ?, ?> getSpec();
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ protected abstract String getVersion();
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ protected String getBaseUrlAndPath()
+ {
+ return "http://localhost:" + PORT + "/qqq/" + getVersion();
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @BeforeEach
+ @AfterEach
+ void beforeAndAfterEach()
+ {
+ MemoryRecordStore.fullReset();
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @BeforeEach
+ void beforeEach() throws Exception
+ {
+ //////////////////////////////////////////////////////////////////////////////////////
+ // during initial dev here, we were having issues running multiple tests together, //
+ // where the second (but not first, and not any after second) would fail w/ javalin //
+ // not responding... so, this "works" - to constantly change our port, and stop //
+ // and restart aggresively... could be optimized, but it works. //
+ //////////////////////////////////////////////////////////////////////////////////////
+ PORT++;
+ if(service != null)
+ {
+ service.stop();
+ service = null;
+ }
+
+ if(service == null)
+ {
+ service = Javalin.create(config ->
+ {
+ config.router.apiBuilder(() -> getSpec().defineRoute(getVersion()));
+ }
+ ).start(PORT);
+ }
+
+ TestUtils.primeTestDatabase();
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @AfterAll
+ static void afterAll()
+ {
+ if(service != null)
+ {
+ service.stop();
+ service = null;
+ }
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/AuthenticationMetaDataSpecV1Test.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/AuthenticationMetaDataSpecV1Test.java
new file mode 100644
index 00000000..d5e0ab84
--- /dev/null
+++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/AuthenticationMetaDataSpecV1Test.java
@@ -0,0 +1,80 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1;
+
+
+import com.kingsrook.qqq.backend.core.utils.JsonUtils;
+import com.kingsrook.qqq.middleware.javalin.specs.AbstractEndpointSpec;
+import com.kingsrook.qqq.middleware.javalin.specs.SpecTestBase;
+import kong.unirest.HttpResponse;
+import kong.unirest.Unirest;
+import org.json.JSONObject;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+
+/*******************************************************************************
+ ** Unit test for AuthenticationMetaDataSpecV1
+ *******************************************************************************/
+class AuthenticationMetaDataSpecV1Test extends SpecTestBase
+{
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ protected AbstractEndpointSpec, ?, ?> getSpec()
+ {
+ return new AuthenticationMetaDataSpecV1();
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ protected String getVersion()
+ {
+ return "v1";
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void test()
+ {
+ HttpResponse response = Unirest.get(getBaseUrlAndPath() + "/metaData/authentication").asString();
+ assertEquals(200, response.getStatus());
+ JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
+ assertNotNull(jsonObject);
+ assertTrue(jsonObject.has("name"));
+ assertTrue(jsonObject.has("type"));
+ }
+
+}
\ No newline at end of file
diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ManageSessionSpecV1Test.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ManageSessionSpecV1Test.java
new file mode 100644
index 00000000..6fecdc9f
--- /dev/null
+++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ManageSessionSpecV1Test.java
@@ -0,0 +1,90 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1;
+
+
+import com.kingsrook.qqq.backend.core.utils.JsonUtils;
+import com.kingsrook.qqq.middleware.javalin.specs.AbstractEndpointSpec;
+import com.kingsrook.qqq.middleware.javalin.specs.SpecTestBase;
+import kong.unirest.HttpResponse;
+import kong.unirest.Unirest;
+import org.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.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+
+/*******************************************************************************
+ ** Unit test for ManageSessionV1
+ *******************************************************************************/
+class ManageSessionSpecV1Test extends SpecTestBase
+{
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ protected AbstractEndpointSpec, ?, ?> getSpec()
+ {
+ return new ManageSessionSpecV1();
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ protected String getVersion()
+ {
+ return "v1";
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void test()
+ {
+ String body = """
+ {
+ "accessToken": "abcdefg"
+ }
+ """;
+
+ HttpResponse response = Unirest.post(getBaseUrlAndPath() + "/manageSession")
+ .header("Content-Type", "application/json")
+ .body(body)
+ .asString();
+
+ assertEquals(200, response.getStatus());
+ JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
+ assertNotNull(jsonObject);
+ assertTrue(jsonObject.has("uuid"));
+ assertThat(response.getHeaders().get("Set-Cookie")).anyMatch(s -> s.contains("sessionUUID"));
+ }
+
+}
\ No newline at end of file
diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/MetaDataSpecV1Test.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/MetaDataSpecV1Test.java
new file mode 100644
index 00000000..0bc86b39
--- /dev/null
+++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/MetaDataSpecV1Test.java
@@ -0,0 +1,79 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1;
+
+
+import com.kingsrook.qqq.backend.core.utils.JsonUtils;
+import com.kingsrook.qqq.middleware.javalin.specs.AbstractEndpointSpec;
+import com.kingsrook.qqq.middleware.javalin.specs.SpecTestBase;
+import kong.unirest.HttpResponse;
+import kong.unirest.Unirest;
+import org.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 MetaDataSpecV1
+ *******************************************************************************/
+class MetaDataSpecV1Test extends SpecTestBase
+{
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ protected AbstractEndpointSpec, ?, ?> getSpec()
+ {
+ return new MetaDataSpecV1();
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ protected String getVersion()
+ {
+ return "v1";
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void test()
+ {
+ HttpResponse response = Unirest.get(getBaseUrlAndPath() + "/metaData").asString();
+ assertEquals(200, response.getStatus());
+ JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
+ assertThat(jsonObject.getJSONObject("tables").length()).isGreaterThanOrEqualTo(1);
+ assertThat(jsonObject.getJSONObject("processes").length()).isGreaterThanOrEqualTo(1);
+ assertThat(jsonObject.getJSONObject("apps").length()).isGreaterThanOrEqualTo(1);
+ assertThat(jsonObject.getJSONArray("appTree").length()).isGreaterThanOrEqualTo(1);
+ }
+
+}
\ No newline at end of file
diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ProcessInitSpecV1Test.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ProcessInitSpecV1Test.java
new file mode 100644
index 00000000..36d2fa8c
--- /dev/null
+++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ProcessInitSpecV1Test.java
@@ -0,0 +1,219 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1;
+
+
+import java.util.List;
+import com.kingsrook.qqq.backend.core.logging.QCollectingLogger;
+import com.kingsrook.qqq.backend.core.logging.QLogger;
+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.processes.implementations.mock.MockBackendStep;
+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 kong.unirest.HttpResponse;
+import kong.unirest.Unirest;
+import org.json.JSONObject;
+import org.junit.jupiter.api.AfterEach;
+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.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+
+/*******************************************************************************
+ ** Unit test for ProcessInitSpecV1
+ *******************************************************************************/
+class ProcessInitSpecV1Test extends SpecTestBase
+{
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ protected AbstractEndpointSpec, ?, ?> getSpec()
+ {
+ return new ProcessInitSpecV1();
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ protected String getVersion()
+ {
+ return "v1";
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @AfterEach
+ void afterEach()
+ {
+ QLogger.deactivateCollectingLoggerForClass(MockBackendStep.class);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testGetInitialRecordsFromRecordIdsParam()
+ {
+ QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(MockBackendStep.class);
+
+ HttpResponse response = Unirest.post(getBaseUrlAndPath() + "/processes/greet/init")
+ .multiPartContent()
+ .field("recordsParam", "recordIds")
+ .field("recordIds", "2,3")
+ .asString();
+
+ assertEquals(200, response.getStatus());
+ JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
+ assertNotNull(jsonObject);
+ assertEquals("COMPLETE", jsonObject.getString("type"));
+ assertEquals("null X null", jsonObject.getJSONObject("values").getString("outputMessage")); // these nulls are because we didn't pass values for some fields.
+
+ assertThat(collectingLogger.getCollectedMessages())
+ .filteredOn(clm -> clm.getMessage().contains("We are mocking"))
+ .hasSize(2);
+ // todo - also request records
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testGetInitialRecordsFromFilterParam()
+ {
+ QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(MockBackendStep.class);
+
+ QQueryFilter queryFilter = new QQueryFilter()
+ .withCriteria(new QFilterCriteria()
+ .withFieldName("id")
+ .withOperator(QCriteriaOperator.IN)
+ .withValues(List.of(3, 4, 5)));
+ String filterJSON = JsonUtils.toJson(queryFilter);
+
+ HttpResponse response = Unirest.post(getBaseUrlAndPath() + "/processes/greet/init")
+ .multiPartContent()
+ .field("recordsParam", "filterJSON")
+ .field("filterJSON", filterJSON)
+ .asString();
+
+ assertEquals(200, response.getStatus());
+ JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
+ assertNotNull(jsonObject);
+ assertEquals("COMPLETE", jsonObject.getString("type"));
+ assertEquals("null X null", jsonObject.getJSONObject("values").getString("outputMessage"));
+
+ assertThat(collectingLogger.getCollectedMessages())
+ .filteredOn(clm -> clm.getMessage().contains("We are mocking"))
+ .hasSize(3);
+ // todo - also request records
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testRequiresRowsButNotSpecified()
+ {
+ HttpResponse response = Unirest.post(getBaseUrlAndPath() + "/processes/greet/init")
+ .multiPartContent()
+ .asString();
+
+ assertEquals(200, response.getStatus());
+ JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
+ assertNotNull(jsonObject);
+ assertEquals("ERROR", jsonObject.getString("type"));
+ assertTrue(jsonObject.has("error"));
+ assertTrue(jsonObject.getString("error").contains("Missing input records"));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testFieldValues()
+ {
+ HttpResponse response = Unirest.post(getBaseUrlAndPath() + "/processes/greet/init")
+ .multiPartContent()
+ .field("recordsParam", "recordIds")
+ .field("recordIds", "2,3")
+ .field("values", new JSONObject()
+ .put("greetingPrefix", "Hey")
+ .put("greetingSuffix", "Jude")
+ .toString()
+ )
+ .asString();
+
+ assertEquals(200, response.getStatus());
+ JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
+ assertNotNull(jsonObject);
+ assertEquals("COMPLETE", jsonObject.getString("type"));
+ assertEquals("Hey X Jude", jsonObject.getJSONObject("values").getString("outputMessage"));
+ }
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testInitGoingAsync()
+ {
+ HttpResponse response = Unirest.post(getBaseUrlAndPath() + "/processes/" + TestUtils.PROCESS_NAME_SIMPLE_SLEEP + "/init")
+ .multiPartContent()
+ .field("stepTimeoutMillis", "50")
+ .field("values", new JSONObject()
+ .put(TestUtils.SleeperStep.FIELD_SLEEP_MILLIS, 500)
+ .toString()
+ )
+ .asString();
+
+ assertEquals(200, response.getStatus());
+ JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
+ String processUUID = jsonObject.getString("processUUID");
+ String jobUUID = jsonObject.getString("jobUUID");
+ assertNotNull(processUUID, "Process UUID should not be null.");
+ assertNotNull(jobUUID, "Job UUID should not be null");
+
+ // todo - in a higher-level test, resume test_processInitGoingAsync at the // request job status before sleep is done // line
+ }
+
+}
\ No newline at end of file
diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ProcessMetaDataSpecV1Test.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ProcessMetaDataSpecV1Test.java
new file mode 100644
index 00000000..e447482d
--- /dev/null
+++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ProcessMetaDataSpecV1Test.java
@@ -0,0 +1,108 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.middleware.javalin.specs.v1;
+
+
+import java.util.Map;
+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.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;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+
+/*******************************************************************************
+ ** Unit test for ProcessMetaDataSpecV1
+ *******************************************************************************/
+class ProcessMetaDataSpecV1Test extends SpecTestBase
+{
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ protected AbstractEndpointSpec, ?, ?> getSpec()
+ {
+ return new ProcessMetaDataSpecV1();
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ protected String getVersion()
+ {
+ return "v1";
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void test()
+ {
+ HttpResponse response = Unirest.get(getBaseUrlAndPath() + "/metaData/process/greetInteractive").asString();
+
+ assertEquals(200, response.getStatus());
+ JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
+ assertEquals("greetInteractive", jsonObject.getString("name"));
+ assertEquals("Greet Interactive", jsonObject.getString("label"));
+ assertEquals("person", jsonObject.getString("tableName"));
+
+ JSONArray frontendSteps = jsonObject.getJSONArray("frontendSteps");
+ JSONObject setupStep = frontendSteps.getJSONObject(0);
+ assertEquals("Setup", setupStep.getString("label"));
+ JSONArray setupFields = setupStep.getJSONArray("formFields");
+ assertEquals(2, setupFields.length());
+ assertTrue(setupFields.toList().stream().anyMatch(field -> "greetingPrefix".equals(((Map, ?>) field).get("name"))));
+ }
+
+
+
+ /*******************************************************************************
+ ** test the process-level meta-data endpoint for a non-real name
+ **
+ *******************************************************************************/
+ @Test
+ public void testNotFound()
+ {
+ HttpResponse response = Unirest.get(getBaseUrlAndPath() + "/metaData/process/notAnActualProcess").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("Process").contains("notAnActualProcess").contains("not found");
+ }
+
+}
\ No newline at end of file