diff --git a/docker-compose.yaml b/docker-compose.yaml index 7fa4f0a4..23acc367 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -7,7 +7,8 @@ services: ports: - '5432:5432' environment: - POSTGRES_USER: vlingo_schemata + POSTGRES_DB: vlingo_schemata + POSTGRES_USER: vlingo_test POSTGRES_PASSWORD: vlingo123 volumes: diff --git a/src/main/java/io/vlingo/schemata/infra/persistence/PostgresSchemataObjectStore.java b/src/main/java/io/vlingo/schemata/infra/persistence/PostgresSchemataObjectStore.java index 68e3f21d..1c9f8cf2 100644 --- a/src/main/java/io/vlingo/schemata/infra/persistence/PostgresSchemataObjectStore.java +++ b/src/main/java/io/vlingo/schemata/infra/persistence/PostgresSchemataObjectStore.java @@ -20,10 +20,17 @@ protected void createOrganizationStateTable() { "name VARCHAR(128) NOT NULL, " + "description VARCHAR(8000) " + - "UNIQUE (name) " + ")"); jdbi.handle().execute("CREATE UNIQUE INDEX IF NOT EXISTS ORG_ALL_INDEX ON TBL_ORGANIZATIONS (organizationId)"); + + /* + Dropping the constraint and recreating it afterwards is not an optimal solution once we have actual data. + + TODO: Refactor to find out if constraint already exists or switch to a schema migration tool like flyway or liquibase + */ + jdbi.handle().execute("ALTER TABLE TBL_ORGANIZATIONS DROP CONSTRAINT IF EXISTS ORGANIZATION_UNIQUE"); + jdbi.handle().execute("ALTER TABLE TBL_ORGANIZATIONS ADD CONSTRAINT ORGANIZATION_UNIQUE UNIQUE (name)"); } @Override @@ -36,11 +43,13 @@ protected void createUnitStateTable() { "name VARCHAR(128) NOT NULL, " + "description VARCHAR(8000) " + - "UNIQUE (organizationId, name) " + ")"); jdbi.handle().execute("CREATE UNIQUE INDEX IF NOT EXISTS UNIT_PARENT_INDEX ON TBL_UNITS (organizationId)"); jdbi.handle().execute("CREATE UNIQUE INDEX IF NOT EXISTS UNIT_ALL_INDEX ON TBL_UNITS (organizationId, unitId)"); + + jdbi.handle().execute("ALTER TABLE TBL_UNITS DROP CONSTRAINT IF EXISTS UNIT_ALL_UNIQUE"); + jdbi.handle().execute("ALTER TABLE TBL_UNITS ADD CONSTRAINT UNIT_ALL_UNIQUE UNIQUE (organizationId, name)"); } @Override @@ -54,11 +63,13 @@ protected void createContextStateTable() { "namespace VARCHAR(256) NOT NULL, " + "description VARCHAR(8000) " + - "UNIQUE (unitId, namespace) " + ")"); jdbi.handle().execute("CREATE UNIQUE INDEX IF NOT EXISTS CONTEXT_PARENT_INDEX ON TBL_CONTEXTS (organizationId, unitId)"); jdbi.handle().execute("CREATE UNIQUE INDEX IF NOT EXISTS CONTEXT_ALL_INDEX ON TBL_CONTEXTS (organizationId, unitId, contextId)"); + + jdbi.handle().execute("ALTER TABLE TBL_CONTEXTS DROP CONSTRAINT IF EXISTS CONTEXT_ALL_UNIQUE"); + jdbi.handle().execute("ALTER TABLE TBL_CONTEXTS ADD CONSTRAINT CONTEXT_ALL_UNIQUE UNIQUE (organizationId, unitId, namespace)"); } @Override @@ -75,11 +86,14 @@ protected void createSchemaStateTable() { "name VARCHAR(128) NOT NULL, " + "description VARCHAR(8000) " + - "UNIQUE (contextId, category, name) " + ")"); jdbi.handle().execute("CREATE UNIQUE INDEX IF NOT EXISTS SCHEMA_PARENT_INDEX ON TBL_SCHEMAS (organizationId, unitId, contextId)"); jdbi.handle().execute("CREATE UNIQUE INDEX IF NOT EXISTS SCHEMA_ALL_INDEX ON TBL_SCHEMAS (organizationId, unitId, contextId, schemaId)"); + + jdbi.handle().execute("ALTER TABLE TBL_SCHEMAS DROP CONSTRAINT IF EXISTS SCHEMA_ALL_UNIQUE"); + jdbi.handle().execute("ALTER TABLE TBL_SCHEMAS ADD CONSTRAINT SCHEMA_ALL_UNIQUE UNIQUE (organizationId, unitId, contextId, name)"); + } @Override @@ -112,11 +126,13 @@ protected void createSchemaVersionStateTable() { "previousVersion VARCHAR(20) NOT NULL, " + "currentVersion VARCHAR(20) NOT NULL " + - "UNIQUE (schemaId, currentVersion) " + ")"); jdbi.handle().execute("CREATE UNIQUE INDEX IF NOT EXISTS SCHEMAVERSION_PARENT_INDEX ON TBL_SCHEMAVERSIONS (organizationId, unitId, contextId, schemaId)"); jdbi.handle().execute("CREATE UNIQUE INDEX IF NOT EXISTS SCHEMAVERSION_ALL_INDEX ON TBL_SCHEMAVERSIONS (organizationId, unitId, contextId, schemaId, schemaVersionId)"); + + jdbi.handle().execute("ALTER TABLE TBL_SCHEMAVERSIONS DROP CONSTRAINT IF EXISTS SCHEMAVERSIION_ALL_UNIQUE"); + jdbi.handle().execute("ALTER TABLE TBL_SCHEMAVERSIONS ADD CONSTRAINT SCHEMAVERSIION_ALL_UNIQUE UNIQUE (organizationId, unitId, contextId, schemaId, currentVersion)"); } @Override diff --git a/src/main/java/io/vlingo/schemata/resource/CodeResource.java b/src/main/java/io/vlingo/schemata/resource/CodeResource.java index af2da656..74e3e4d2 100644 --- a/src/main/java/io/vlingo/schemata/resource/CodeResource.java +++ b/src/main/java/io/vlingo/schemata/resource/CodeResource.java @@ -7,18 +7,6 @@ package io.vlingo.schemata.resource; -import static io.vlingo.http.RequestHeader.Authorization; -import static io.vlingo.http.Response.Status.BadRequest; -import static io.vlingo.http.Response.Status.InternalServerError; -import static io.vlingo.http.Response.Status.Ok; -import static io.vlingo.http.resource.ResourceBuilder.get; -import static io.vlingo.http.resource.ResourceBuilder.resource; -import static io.vlingo.schemata.Schemata.StageName; -import static io.vlingo.schemata.codegen.TypeDefinitionCompiler.compilerFor; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; - import io.vlingo.actors.Logger; import io.vlingo.actors.Stage; import io.vlingo.actors.World; @@ -28,15 +16,20 @@ import io.vlingo.http.Response; import io.vlingo.http.resource.Resource; import io.vlingo.http.resource.ResourceHandler; +import io.vlingo.schemata.Schemata; import io.vlingo.schemata.query.Queries; import io.vlingo.schemata.query.QueryResultsCollector; -import io.vlingo.schemata.resource.data.AuthorizationData; -import io.vlingo.schemata.resource.data.ContextData; -import io.vlingo.schemata.resource.data.OrganizationData; -import io.vlingo.schemata.resource.data.PathData; -import io.vlingo.schemata.resource.data.SchemaData; -import io.vlingo.schemata.resource.data.SchemaVersionData; -import io.vlingo.schemata.resource.data.UnitData; +import io.vlingo.schemata.resource.data.*; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import static io.vlingo.http.RequestHeader.Authorization; +import static io.vlingo.http.Response.Status.*; +import static io.vlingo.http.resource.ResourceBuilder.get; +import static io.vlingo.http.resource.ResourceBuilder.resource; +import static io.vlingo.schemata.Schemata.StageName; +import static io.vlingo.schemata.codegen.TypeDefinitionCompiler.compilerFor; // // like this: @@ -58,7 +51,9 @@ public CodeResource(final World world) { } public Completes queryCodeForLanguage(final String reference, final String language) { - final Collector collector = given(context().request, reference); + // FIXME: temporary workaround for missing context in handler, see #55 + final Request request = context() == null ? null : context().request; + final Collector collector = given(request, reference); return Queries.forCode() .schemaVersionFor(collector.authorization, collector.path, collector) @@ -74,11 +69,11 @@ public Completes queryCodeForLanguage(final String reference, final St logger.debug("COMPILING: " + collector.schemaVersion().specification); return compile(collector.path.reference, collector.schemaVersion(), language); }) - .andThenTo(code -> { + .andThenTo(code -> { logger.debug("CODE: \n" + code); return recordDependency(code, collector); }) - .andThenTo(code -> { + .andThenTo(code -> { logger.debug("SUCCESS: \n" + code); return Completes.withSuccess(Response.of(Ok, code)); }) @@ -95,10 +90,10 @@ public Completes queryCodeForLanguage(final String reference, final St @Override public Resource routes() { return resource("Code Resource", 1, - get("/code/{reference}/{language}") - .param(String.class) - .param(String.class) - .handle(this::queryCodeForLanguage)); + get("/code/{reference}/{language}") + .param(String.class) + .param(String.class) + .handle(this::queryCodeForLanguage)); } ////////////////////////////////// @@ -113,12 +108,15 @@ private Completes compile(final String reference, final SchemaVersionDat private Collector given(final Request request, final String reference) { AuthorizationData auth = null; PathData path = null; - final String header = request.headerValueOr(Authorization, ""); + + // FIXME: temporary workaround for missing context in handler, see #55 + String mockAuth = mockAuth(reference); + final String header = request == null ? mockAuth : request.headerValueOr(Authorization, mockAuth); try { auth = AuthorizationData.with(header); path = PathData.withVersion(reference); - final Tuple3 ids = auth.dependentAsIds(); + final Tuple3 ids = auth.dependentAsIds(); return Collector.startingWith(auth, path, ContextData.identity(ids._1, ids._2, ids._3)); } catch (Exception e) { if (auth == null) { @@ -128,6 +126,22 @@ private Collector given(final Request request, final String reference) { } } + // FIXME: temporary workaround for missing context in handler, see #55 + private String mockAuth(String reference) { + final String[] parts = reference.split(Schemata.ReferenceSeparator); + + OrganizationData organization = Queries.forOrganizations().organizationNamed(parts[0]).await(); + UnitData unit = Queries.forUnits().unitNamed(organization.organizationId, parts[1]).await(); + ContextData context = Queries.forContexts().contextOfNamespace(organization.organizationId, unit.unitId, parts[2]).await(); + + return AuthorizationData.AuthorizationType + + " source = " + organization.organizationId + + " dependent = " + + organization.organizationId + Schemata.ReferenceSeparator + + unit.unitId + Schemata.ReferenceSeparator + + context.contextId; + } + private Completes queryContextWith(final ContextData contextIds, final Collector collector) { final Completes context = Queries.forContexts().context( diff --git a/src/test/resources/rest-api-calls.http b/src/test/resources/rest-api-calls.http index 7b0fc2bb..3e474f66 100644 --- a/src/test/resources/rest-api-calls.http +++ b/src/test/resources/rest-api-calls.http @@ -108,4 +108,87 @@ Accept: application/json GET http://localhost:9019/schema/categories Accept: application/json +### + + +// Reproduce https://github.com/vlingo/vlingo-schemata/issues/55 +POST http://localhost:9019/organizations +Content-Type: application/json + +{ + "organizationId": "", + "name": "org", + "description": "My organization." +} +> {% client.global.set('orgId', response.body.organizationId) %} + + +### Create Unit +POST http://localhost:9019/organizations/{{orgId}}/units +Content-Type: application/json + +{ + "organizationId": "{{orgId}}", + "unitId": "", + "name": "unit", + "description": "My unit." +} + +> {% client.global.set('unitId', response.body.unitId) %} + +### Create Context +POST http://localhost:9019/organizations/{{orgId}}/units/{{unitId}}/contexts +Content-Type: application/json + +{ + "organizationId": "{{orgId}}", + "unitId": "{{unitId}}", + "contextId": "", + "namespace": "context", + "description": "Schemata Context." +} + +> {% client.global.set('contextId', response.body.contextId) %} + +### Create Schema +POST http://localhost:9019/organizations/{{orgId}}/units/{{unitId}}/contexts/{{contextId}}/schemas +Content-Type: application/json + +{ + "organizationId": "{{orgId}}", + "unitId": "{{unitId}}", + "contextId": "{{contextId}}", + "schemaId": "", + "category": "Event", + "name": "schema", + "scope": "Private", + "description": "Schemata was defined event." +} + +> {% client.global.set('schemaId', response.body.schemaId) %} + + +### Create Schema Version +POST http://localhost:9019/organizations/{{orgId}}/units/{{unitId}}/contexts/{{contextId}}/schemas/{{schemaId}}/versions +Content-Type: application/json + +{ + "organizationId": "{{orgId}}", + "unitId": "{{unitId}}", + "contextId": "{{contextId}}", + "schemaId": "{{schemaId}}", + "schemaVersionId": "", + "description": "Initial revision.", + "specification": "event SchemaDefined { type eventType }", + "status": "Published", + "previousVersion": "0.0.0", + "currentVersion": "1.0.0" +} +> {% client.global.set('schemaVersionId', response.body.schemaVersionId) %} + + +### this crashes the compiler +GET http://localhost:9019/code/org:unit:context:schema:1.0.0/java +Accept: application/json + ### \ No newline at end of file