mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 21:20:45 +00:00
Initial code checkin
This commit is contained in:
@ -0,0 +1,272 @@
|
||||
package com.kingsrook.qqq.backend.javalin;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.actions.MetaDataAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.TableMetaDataAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.MetaDataRequest;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.MetaDataResult;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.QueryRequest;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.QueryResult;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.TableMetaDataRequest;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.TableMetaDataResult;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
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 io.javalin.Javalin;
|
||||
import io.javalin.apibuilder.EndpointGroup;
|
||||
import io.javalin.http.Context;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import static io.javalin.apibuilder.ApiBuilder.delete;
|
||||
import static io.javalin.apibuilder.ApiBuilder.get;
|
||||
import static io.javalin.apibuilder.ApiBuilder.patch;
|
||||
import static io.javalin.apibuilder.ApiBuilder.path;
|
||||
import static io.javalin.apibuilder.ApiBuilder.post;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QJavalinImplementation
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(QJavalinImplementation.class);
|
||||
|
||||
private static QInstance qInstance;
|
||||
|
||||
private static int PORT = 8001;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void main(String[] args)
|
||||
{
|
||||
QInstance qInstance = new QInstance();
|
||||
// todo - parse args to look up metaData and prime instance
|
||||
// qInstance.addBackend(QMetaDataProvider.getQBackend());
|
||||
|
||||
new QJavalinImplementation(qInstance).startJavalinServer(PORT);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QJavalinImplementation(QInstance qInstance)
|
||||
{
|
||||
QJavalinImplementation.qInstance = qInstance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for qInstance
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void setQInstance(QInstance qInstance)
|
||||
{
|
||||
QJavalinImplementation.qInstance = qInstance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
void startJavalinServer(int port)
|
||||
{
|
||||
// todo port from arg
|
||||
// todo base path from arg?
|
||||
Javalin service = Javalin.create().start(port);
|
||||
service.routes(getRoutes());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public EndpointGroup getRoutes()
|
||||
{
|
||||
return (() ->
|
||||
{
|
||||
path("/metaData", () ->
|
||||
{
|
||||
get("/", QJavalinImplementation::metaData);
|
||||
path("/:table", () ->
|
||||
{
|
||||
get("", QJavalinImplementation::tableMetaData);
|
||||
});
|
||||
});
|
||||
path("/data", () ->
|
||||
{
|
||||
path("/:table", () ->
|
||||
{
|
||||
get("/", QJavalinImplementation::dataQuery);
|
||||
post("/", QJavalinImplementation::dataInsert);
|
||||
path("/:id", () ->
|
||||
{
|
||||
get("", QJavalinImplementation::dataGet);
|
||||
patch("", QJavalinImplementation::dataUpdate);
|
||||
delete("", QJavalinImplementation::dataDelete);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void dataDelete(Context context)
|
||||
{
|
||||
context.result("{\"deleteResult\":{}}");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void dataUpdate(Context context)
|
||||
{
|
||||
context.result("{\"updateResult\":{}}");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void dataInsert(Context context)
|
||||
{
|
||||
context.result("{\"insertResult\":{}}");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
********************************************************************************/
|
||||
private static void dataGet(Context context)
|
||||
{
|
||||
context.result("{\"getResult\":{}}");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
static void dataQuery(Context context)
|
||||
{
|
||||
try
|
||||
{
|
||||
QueryRequest queryRequest = new QueryRequest(qInstance);
|
||||
queryRequest.setTableName(context.pathParam("table"));
|
||||
queryRequest.setSkip(integerQueryParam(context, "skip"));
|
||||
queryRequest.setLimit(integerQueryParam(context, "limit"));
|
||||
|
||||
QueryAction queryAction = new QueryAction();
|
||||
QueryResult queryResult = queryAction.execute(queryRequest);
|
||||
|
||||
context.result(JsonUtils.toJson(queryResult));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
handleException(context, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void metaData(Context context)
|
||||
{
|
||||
try
|
||||
{
|
||||
MetaDataRequest metaDataRequest = new MetaDataRequest(qInstance);
|
||||
MetaDataAction metaDataAction = new MetaDataAction();
|
||||
MetaDataResult metaDataResult = metaDataAction.execute(metaDataRequest);
|
||||
|
||||
context.result(JsonUtils.toJson(metaDataResult));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
handleException(context, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void tableMetaData(Context context)
|
||||
{
|
||||
try
|
||||
{
|
||||
TableMetaDataRequest tableMetaDataRequest = new TableMetaDataRequest(qInstance);
|
||||
tableMetaDataRequest.setTableName(context.pathParam("table"));
|
||||
TableMetaDataAction tableMetaDataAction = new TableMetaDataAction();
|
||||
TableMetaDataResult tableMetaDataResult = tableMetaDataAction.execute(tableMetaDataRequest);
|
||||
|
||||
context.result(JsonUtils.toJson(tableMetaDataResult));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
handleException(context, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void handleException(Context context, Exception e)
|
||||
{
|
||||
QUserFacingException userFacingException = ExceptionUtils.findClassInRootChain(e, QUserFacingException.class);
|
||||
if(userFacingException != null)
|
||||
{
|
||||
LOG.info("User-facing exception", e);
|
||||
context.status(HttpStatus.INTERNAL_SERVER_ERROR_500)
|
||||
.result("{\"error\":\"" + userFacingException.getMessage() + "\"}");
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.warn("Exception in javalin request", e);
|
||||
e.printStackTrace();
|
||||
context.status(HttpStatus.INTERNAL_SERVER_ERROR_500)
|
||||
.result("{\"error\":\"" + e.getClass().getSimpleName() + "\"}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Returns Integer if context has a valid int query parameter by the given name,
|
||||
* Returns null if no param (or empty value).
|
||||
* Throws NumberFormatException for malformed numbers.
|
||||
*******************************************************************************/
|
||||
private static Integer integerQueryParam(Context context, String name) throws NumberFormatException
|
||||
{
|
||||
String value = context.queryParam(name);
|
||||
if(StringUtils.hasContent(value))
|
||||
{
|
||||
return (Integer.parseInt(value));
|
||||
}
|
||||
|
||||
return (null);
|
||||
}
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
package com.kingsrook.qqq.backend.javalin;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
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.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** based on https://javalin.io/tutorials/testing
|
||||
**
|
||||
*******************************************************************************/
|
||||
class QJavalinImplementationTest
|
||||
{
|
||||
private static final int PORT = 6262;
|
||||
private static final String BASE_URL = "http://localhost:" + PORT;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@BeforeAll
|
||||
public static void beforeAll() throws Exception
|
||||
{
|
||||
QJavalinImplementation qJavalinImplementation = new QJavalinImplementation(TestUtils.defineInstance());
|
||||
qJavalinImplementation.startJavalinServer(PORT);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@BeforeEach
|
||||
public void beforeEach() throws Exception
|
||||
{
|
||||
TestUtils.primeTestDatabase();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_metaData()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/metaData").asString();
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
assertTrue(jsonObject.has("tables"));
|
||||
JSONObject tables = jsonObject.getJSONObject("tables");
|
||||
assertEquals(1, tables.length());
|
||||
JSONObject table0 = tables.getJSONObject("person");
|
||||
assertTrue(table0.has("name"));
|
||||
assertEquals("person", table0.getString("name"));
|
||||
assertTrue(table0.has("label"));
|
||||
assertEquals("Person", table0.getString("label"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_tableMetaData()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/metaData/person").asString();
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
assertEquals(1, jsonObject.keySet().size(), "Number of top-level keys");
|
||||
JSONObject table = jsonObject.getJSONObject("table");
|
||||
assertEquals(4, table.keySet().size(), "Number of mid-level keys");
|
||||
assertEquals("person", table.getString("name"));
|
||||
assertEquals("Person", table.getString("label"));
|
||||
assertEquals("id", table.getString("primaryKeyField"));
|
||||
JSONObject fields = table.getJSONObject("fields");
|
||||
JSONObject field0 = fields.getJSONObject("id");
|
||||
assertEquals("id", field0.getString("name"));
|
||||
assertEquals("INTEGER", field0.getString("type"));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_tableMetaData_notFound()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/metaData/notAnActualTable").asString();
|
||||
|
||||
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR_500, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
assertEquals(1, jsonObject.keySet().size(), "Number of top-level keys");
|
||||
String error = jsonObject.getString("error");
|
||||
assertTrue(error.contains("not found"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_dataQuery()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/data/person").asString();
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
assertTrue(jsonObject.has("records"));
|
||||
JSONArray records = jsonObject.getJSONArray("records");
|
||||
assertEquals(5, records.length());
|
||||
JSONObject record0 = records.getJSONObject(0);
|
||||
assertTrue(record0.has("values"));
|
||||
assertEquals("person", record0.getString("tableName"));
|
||||
assertTrue(record0.has("primaryKey"));
|
||||
JSONObject values0 = record0.getJSONObject("values");
|
||||
assertTrue(values0.has("firstName"));
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
package com.kingsrook.qqq.backend.javalin;
|
||||
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.sql.Connection;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.RDBSMBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class TestUtils
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void primeTestDatabase() throws Exception
|
||||
{
|
||||
ConnectionManager connectionManager = new ConnectionManager();
|
||||
Connection connection = connectionManager.getConnection(new RDBSMBackendMetaData(TestUtils.defineBackend()));
|
||||
InputStream primeTestDatabaseSqlStream = TestUtils.class.getResourceAsStream("/prime-test-database.sql");
|
||||
assertNotNull(primeTestDatabaseSqlStream);
|
||||
List<String> lines = (List<String>) IOUtils.readLines(primeTestDatabaseSqlStream);
|
||||
String joinedSQL = String.join("\n", lines);
|
||||
for(String sql : joinedSQL.split(";"))
|
||||
{
|
||||
QueryManager.executeUpdate(connection, sql);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QInstance defineInstance()
|
||||
{
|
||||
QInstance qInstance = new QInstance();
|
||||
qInstance.addBackend(defineBackend());
|
||||
qInstance.addTable(defineTablePerson());
|
||||
return (qInstance);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QBackendMetaData defineBackend()
|
||||
{
|
||||
return new QBackendMetaData()
|
||||
.withName("default")
|
||||
.withType("rdbms")
|
||||
.withValue("vendor", "h2")
|
||||
.withValue("hostName", "mem")
|
||||
.withValue("databaseName", "test_database")
|
||||
.withValue("username", "sa")
|
||||
.withValue("password", "");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QTableMetaData defineTablePerson()
|
||||
{
|
||||
return new QTableMetaData()
|
||||
.withName("person")
|
||||
.withLabel("Person")
|
||||
.withBackendName(defineBackend().getName())
|
||||
.withPrimaryKeyField("id")
|
||||
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
|
||||
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withBackendName("create_date"))
|
||||
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withBackendName("modify_date"))
|
||||
.withField(new QFieldMetaData("firstName", QFieldType.STRING).withBackendName("first_name"))
|
||||
.withField(new QFieldMetaData("lastName", QFieldType.STRING).withBackendName("last_name"))
|
||||
.withField(new QFieldMetaData("birthDate", QFieldType.DATE).withBackendName("birth_date"))
|
||||
.withField(new QFieldMetaData("email", QFieldType.STRING));
|
||||
}
|
||||
|
||||
}
|
18
src/test/resources/prime-test-database.sql
Normal file
18
src/test/resources/prime-test-database.sql
Normal file
@ -0,0 +1,18 @@
|
||||
DROP TABLE IF EXISTS person;
|
||||
CREATE TABLE person
|
||||
(
|
||||
id SERIAL,
|
||||
create_date TIMESTAMP DEFAULT now(),
|
||||
modify_date TIMESTAMP DEFAULT now(),
|
||||
|
||||
first_name VARCHAR(80) NOT NULL,
|
||||
last_name VARCHAR(80) NOT NULL,
|
||||
birth_date DATE,
|
||||
email VARCHAR(250) NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (1, 'Darin', 'Kelkhoff', '1980-05-31', 'darin.kelkhoff@gmail.com');
|
||||
INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (2, 'James', 'Maes', '1980-05-15', 'jmaes@mmltholdings.com');
|
||||
INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (3, 'Tim', 'Chamberlain', '1976-05-28', 'tchamberlain@mmltholdings.com');
|
||||
INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (4, 'Tyler', 'Samples', '1990-01-01', 'tsamples@mmltholdings.com');
|
||||
INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (5, 'Garret', 'Richardson', '1981-01-01', 'grichardson@mmltholdings.com');
|
Reference in New Issue
Block a user