feat(manager-ddl): liquibase definition for outbox and blobstore
feat(manager-dist): regenerate DDL
MODIFY
distro/data/src/main/resources/ddls/apiman_h2.ddl
distro/data/src/main/resources/ddls/apiman_h2.ddl
-- Update Database Script
-- *********************************************************************
-- Change Log: /Users/msavy/oss/apiman/apiman/distro/ddl/src/main/liquibase/master.xml
--- Ran at: 02/10/2021, 18:24
+-- Ran at: 06/10/2021, 18:44
-- Against: sa@offline:h2?version=1.4.200&caseSensitive=true&changeLogFile=/Users/msavy/oss/apiman/apiman/distro/ddl/target/changelog/h2/databasechangelog.csv
-- Liquibase version: 4.4.3
-- *********************************************************************
CREATE domain IF NOT EXISTS json AS text;
CREATE domain IF NOT EXISTS jsonb AS other;
+-- *********************************************************************
+-- Update Database Script
+-- *********************************************************************
+-- Change Log: /Users/msavy/oss/apiman/apiman/distro/ddl/src/main/liquibase/master.xml
+-- Ran at: 06/10/2021, 19:01
+-- Against: sa@offline:h2?version=1.4.200&caseSensitive=true&changeLogFile=/Users/msavy/oss/apiman/apiman/distro/ddl/target/changelog/h2/databasechangelog.csv
+-- Liquibase version: 4.4.3
+-- *********************************************************************
+
-- Changeset src/main/liquibase/current/000-apiman-manager-api.db.sequences.changelog.xml::1434723514712-2::apiman (generated)
CREATE SEQUENCE hibernate_sequence START WITH 999;
@@ -344,3 +353,20 @@ CREATE TABLE KeyValueTag (id BIGINT AUTO_INCREMENT NOT NULL, key VARCHAR(255) NO
-- Changeset src/main/liquibase/current/20211002-154432-apiman3-dev-portal-2-initial.changelog.xml::1633441143380-5::msavy (generated)
CREATE TABLE api_tag (api_id VARCHAR(255) NOT NULL, org_id VARCHAR(255) NOT NULL, tag_id BIGINT NOT NULL, CONSTRAINT PK_API_TAG PRIMARY KEY (api_id, org_id, tag_id));
+-- Changeset src/main/liquibase/current/20211002-154432-apiman3-dev-portal-2-initial.changelog.xml::1633542267834-6::msavy (generated)
+CREATE TABLE blob_store (id VARCHAR(255) NOT NULL, mrblobby BLOB NOT NULL, created_on TIMESTAMP NOT NULL, hash BIGINT NOT NULL, mime_type VARCHAR(255) NOT NULL, modified_on TIMESTAMP NOT NULL, name VARCHAR(255) NOT NULL, references INT NOT NULL, CONSTRAINT blob_storePK PRIMARY KEY (id));
+
+-- Changeset src/main/liquibase/current/20211002-154432-apiman3-dev-portal-2-initial.changelog.xml::1633542267834-7::msavy (generated)
+CREATE TABLE outbox (id BIGINT AUTO_INCREMENT NOT NULL, event_version BIGINT NOT NULL, payload BINARY NOT NULL, source VARCHAR(255) NOT NULL, subject VARCHAR(255) NOT NULL, time TIMESTAMP NOT NULL, type VARCHAR(255) NOT NULL, CONSTRAINT outboxPK PRIMARY KEY (id));
+
+-- Changeset src/main/liquibase/current/20211002-154432-apiman3-dev-portal-2-initial.changelog.xml::1633542267834-8::msavy (generated)
+ALTER TABLE blob_store ADD CONSTRAINT UC_BLOB_STOREID_COL UNIQUE (id);
+
+-- Changeset src/main/liquibase/current/20211002-154432-apiman3-dev-portal-2-initial.changelog.xml::1633542267834-10::msavy (generated)
+ALTER TABLE blob_store ADD CONSTRAINT UK_4jee67ekw7s4y8spoc58i4dsf UNIQUE (hash, mime_type, name);
+
+-- Changeset src/main/liquibase/current/20211002-154432-apiman3-dev-portal-2-initial.changelog.xml::1633542267834-15::msavy (generated)
+ALTER TABLE api_tag ADD CONSTRAINT FK2h64maqscweorti1hta9josl2 FOREIGN KEY (tag_id) REFERENCES KeyValueTag (id);
+
+-- Changeset src/main/liquibase/current/20211002-154432-apiman3-dev-portal-2-initial.changelog.xml::1633542267834-16::msavy (generated)
+ALTER TABLE api_tag ADD CONSTRAINT FKlpr8yu65omneju5297uqthb6k FOREIGN KEY (api_id, org_id) REFERENCES apis (id, organization_id);
\ No newline at end of file

MODIFY
distro/ddl/src/main/liquibase/current/20211002-154432-apiman3-dev-portal-2-initial.changelog.xml
distro/ddl/src/main/liquibase/current/20211002-154432-apiman3-dev-portal-2-initial.changelog.xml
</column>
</createTable>
</changeSet>
+ <!-- blobstore + eventstore -->
+ <changeSet author="msavy (generated)" id="1633542267834-6">
+ <createTable tableName="blob_store">
+ <column name="id" type="VARCHAR(255)">
+ <constraints nullable="false" primaryKey="true" primaryKeyName="blob_storePK"/>
+ </column>
+ <column name="mrblobby" type="BLOB">
+ <constraints nullable="false"/>
+ </column>
+ <column name="created_on" type="TIMESTAMP">
+ <constraints nullable="false"/>
+ </column>
+ <column name="hash" type="BIGINT">
+ <constraints nullable="false"/>
+ </column>
+ <column name="mime_type" type="VARCHAR(255)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="modified_on" type="TIMESTAMP">
+ <constraints nullable="false"/>
+ </column>
+ <column name="name" type="VARCHAR(255)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="references" type="INT">
+ <constraints nullable="false"/>
+ </column>
+ </createTable>
+ </changeSet>
+ <changeSet author="msavy (generated)" id="1633542267834-7">
+ <createTable tableName="outbox">
+ <column autoIncrement="true" name="id" type="BIGINT">
+ <constraints nullable="false" primaryKey="true" primaryKeyName="outboxPK"/>
+ </column>
+ <column name="event_version" type="BIGINT">
+ <constraints nullable="false"/>
+ </column>
+ <column name="payload" type="BINARY">
+ <constraints nullable="false"/>
+ </column>
+ <column name="source" type="VARCHAR(255)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="subject" type="VARCHAR(255)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="time" type="TIMESTAMP">
+ <constraints nullable="false"/>
+ </column>
+ <column name="type" type="VARCHAR(255)">
+ <constraints nullable="false"/>
+ </column>
+ </createTable>
+ </changeSet>
+ <changeSet author="msavy (generated)" id="1633542267834-8">
+ <addUniqueConstraint columnNames="id" constraintName="UC_BLOB_STOREID_COL" tableName="blob_store"/>
+ </changeSet>
+<!-- <changeSet author="msavy (generated)" id="1633542267834-9">-->
+<!-- <addUniqueConstraint columnNames="plan_id, plan_org_id, version" constraintName="UK53ss55b2hiye2e9v6lftdo5jg" tableName="plan_versions"/>-->
+<!-- </changeSet>-->
+ <changeSet author="msavy (generated)" id="1633542267834-10">
+ <addUniqueConstraint columnNames="hash, mime_type, name" constraintName="UK_4jee67ekw7s4y8spoc58i4dsf" tableName="blob_store"/>
+ </changeSet>
+<!-- <changeSet author="msavy (generated)" id="1633542267834-11">-->
+<!-- <addUniqueConstraint columnNames="key" constraintName="UK_9ag6p3dn96urpj910hbblr5d7" tableName="KeyValueTag"/>-->
+<!-- </changeSet>-->
+<!-- <changeSet author="msavy (generated)" id="1633542267834-12">-->
+<!-- <addUniqueConstraint columnNames="user_id, role_id, org_id" constraintName="UKfwy6c61bcnvshcsxubanjvi08" tableName="memberships"/>-->
+<!-- </changeSet>-->
+<!-- <changeSet author="msavy (generated)" id="1633542267834-13">-->
+<!-- <addUniqueConstraint columnNames="group_id, artifact_id" constraintName="UKofbok9ushig9vviq01dnu11x" tableName="plugins"/>-->
+<!-- </changeSet>-->
+<!-- <changeSet author="msavy (generated)" id="1633542267834-14">-->
+<!-- <addUniqueConstraint columnNames="userId, type" constraintName="UserAllowedOnlyOneOfEachNotificationType" tableName="notification_preferences"/>-->
+<!-- </changeSet>-->
+ <changeSet author="msavy (generated)" id="1633542267834-15">
+ <addForeignKeyConstraint baseColumnNames="tag_id" baseTableName="api_tag" constraintName="FK2h64maqscweorti1hta9josl2" deferrable="false" initiallyDeferred="false" referencedColumnNames="id" referencedTableName="KeyValueTag" validate="true"/>
+ </changeSet>
+ <changeSet author="msavy (generated)" id="1633542267834-16">
+ <addForeignKeyConstraint baseColumnNames="api_id,org_id" baseTableName="api_tag" constraintName="FKlpr8yu65omneju5297uqthb6k" deferrable="false" initiallyDeferred="false" referencedColumnNames="id,organization_id" referencedTableName="apis" validate="true"/>
+ </changeSet>
+<!-- <changeSet author="msavy (generated)" id="1633542267834-1">-->
+<!-- <alterSequence startValue="69" incrementBy="10" sequenceName="hibernate_sequence"/>-->
+<!-- </changeSet>-->
+<!-- <changeSet author="msavy (generated)" id="1633542267834-2">-->
+<!-- <dropPrimaryKey tableName="api_plans"/>-->
+<!-- </changeSet>-->
+<!-- <changeSet author="msavy (generated)" id="1633542267834-3">-->
+<!-- <addPrimaryKey columnNames="api_version_id, expose_in_portal, plan_id, requires_approval, version" tableName="api_plans"/>-->
+<!-- </changeSet>-->
+<!-- <changeSet author="msavy (generated)" id="1633542267834-4">-->
+<!-- <dropPrimaryKey tableName="users"/>-->
+<!-- </changeSet>-->
+<!-- <changeSet author="msavy (generated)" id="1633542267834-5">-->
+<!-- <addPrimaryKey columnNames="username" constraintName="usersPK" tableName="users"/>-->
+<!-- </changeSet>-->
</databaseChangeLog>

MODIFY
manager/api/beans/src/main/java/io/apiman/manager/api/beans/download/BlobDto.java
manager/api/beans/src/main/java/io/apiman/manager/api/beans/download/BlobDto.java
@@ -19,6 +19,9 @@ public class BlobDto {
private FileBackedOutputStream blob;
private long hash;
+ public BlobDto() {
+ }
+
public String getId() {
return id;
}

MODIFY
manager/api/beans/src/main/java/io/apiman/manager/api/beans/download/BlobRef.java
manager/api/beans/src/main/java/io/apiman/manager/api/beans/download/BlobRef.java
@@ -2,14 +2,18 @@ package io.apiman.manager.api.beans.download;
import java.time.OffsetDateTime;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
/**
* Blob summary.
*
* @author Marc Savy {@literal <marc@blackparrotlabs.io>}
*/
public class BlobRef {
- @BlobReference
private String id;
+ @BlobReference
+ @JsonProperty
+ private String location;
private String name;
private String mimeType;
private OffsetDateTime createdOn;
@@ -25,6 +29,7 @@ public class BlobRef {
public BlobRef setId(String id) {
this.id = id;
+ this.location = id;
return this;
}

MODIFY
manager/api/jpa/src/main/java/io/apiman/manager/api/jpa/blobstore/BlobEntity.java
manager/api/jpa/src/main/java/io/apiman/manager/api/jpa/blobstore/BlobEntity.java
@@ -43,13 +43,13 @@ public class BlobEntity {
// @Temporal(TemporalType.TIMESTAMP)
@CreationTimestamp
@Column(name = "created_on", nullable = false)
- @NotNull
+ //@NotNull
private OffsetDateTime createdOn;
// @Temporal(TemporalType.TIMESTAMP)
@UpdateTimestamp
@Column(name = "modified_on", nullable = false)
- @NotNull
+ //@NotNull
private OffsetDateTime modifiedOn;
@Lob

MODIFY
manager/api/rest-impl/src/main/java/io/apiman/manager/api/rest/impl/BlobResourceImpl.java
manager/api/rest-impl/src/main/java/io/apiman/manager/api/rest/impl/BlobResourceImpl.java
@@ -14,10 +14,12 @@ import io.apiman.manager.api.security.ISecurityContext;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
+import java.net.URI;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriBuilder;
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
@@ -70,11 +72,14 @@ public class BlobResourceImpl implements IBlobResource {
}
@Override
- public BlobRef uploadBlob(MultipartFormDataInput multipartInput) throws IOException {
+ public Response uploadBlob(MultipartFormDataInput multipartInput) throws IOException {
Preconditions.checkState(securityContext.getCurrentUser() != null, "Must be logged in!");
// Try to do image first as it is probably more likely to fail.
MultipartUploadHolder image = MultipartHelper.getRequiredImage(multipartInput, "image");
// Blob is unreferenced and will be deleted if nobody attaches to it.
- return blobStore.storeBlob(image.getFilename(), image.getMediaType().toString(), image.getFileBackedOutputStream(), 0);
+ BlobRef blobRef = blobStore.storeBlob(image.getFilename(), image.getMediaType().toString(), image.getFileBackedOutputStream(), 0);
+ // Where the blob can be resolved
+ URI location = UriBuilder.fromResource(IBlobResource.class).path(blobRef.getId()).build();
+ return Response.created(location).build();
}
}

MODIFY
manager/api/rest/src/main/java/io/apiman/manager/api/rest/IBlobResource.java
manager/api/rest/src/main/java/io/apiman/manager/api/rest/IBlobResource.java
@@ -32,11 +32,11 @@ public interface IBlobResource {
* @return the file in the response
*/
@GET
- @Path("{uid}")
+ @Path("{uid:.+}")
Response getBlob(@PathParam("uid") String uid);
@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.APPLICATION_JSON)
- BlobRef uploadBlob(@NotNull MultipartFormDataInput multipartInput) throws IOException;
+ Response uploadBlob(@NotNull MultipartFormDataInput multipartInput) throws IOException;
}

MODIFY
manager/ui/war/plugins/api-manager/ts/api/api-devportal.ts
manager/ui/war/plugins/api-manager/ts/api/api-devportal.ts
/// <reference path="../apimanPlugin.ts"/>
/// <reference path="../rpc.ts"/>
-import {Editor, HookCallback, HookMap} from "@toast-ui/editor/types/editor";
+//import {Editor, HookCallback, HookMap} from "@toast-ui/editor/types/editor";
module Apiman {
import ApiPlanSummaryBean = ApimanRPC.ApiPlanSummaryBean;
@@ -30,7 +30,7 @@ module Apiman {
PageLifecycle.setPageTitle('api-devportal', [$scope.api.name]);
});
- const uploadImage = (blob: Blob | File, callback: HookCallback): void => {
+ const uploadImage = (blob: Blob | File, callback): void => {
BlobService.uploadBlob(blob).then(
(blobRef: BlobRef) => {
Logger.debug("Uploaded successfully: {0}", blobRef);
@@ -43,7 +43,7 @@ module Apiman {
}
// Load Markdown Editor
- const ed: Editor = new $window.editor({
+ const ed = new $window.editor({
el: document.querySelector('#editor'),
height: '500px',
initialEditType: 'markdown',
@@ -51,9 +51,9 @@ module Apiman {
usageStatistics: false,
hooks: {
addImageBlobHook: uploadImage
- } as HookMap,
+ },
plugins: [[$window.codeSyntaxHighlightPlugin, { highlighter: $window.prism }]]
- }) as Editor;
+ });
// Get the API Version Plan summaries
DevPortalService.getApiVersionPlans(params.org, params.api, params.version).then(

MODIFY
manager/ui/war/plugins/api-manager/ts/rpc.ts
manager/ui/war/plugins/api-manager/ts/rpc.ts
@@ -161,6 +161,7 @@ module ApimanRPC {
formData.append('image', blob);
return $http({
method: 'POST',
+ headers: {"Content-Type": undefined }, // Otherwise Angular will set the content type as JSON
url: `${Configuration.api.endpoint}/blobs`,
data: formData
})

feat(manager-ddl): liquibase definition for outbox and blobstore
feat(manager-dist): regenerate DDL