Skip to content

Commit

Permalink
feat(api): upload files directly via alexandria API
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Requires alexandria backend v3.0.0-beta.3
  • Loading branch information
czosel authored and anehx committed Jan 18, 2024
1 parent 81f755c commit f934fc4
Show file tree
Hide file tree
Showing 10 changed files with 106 additions and 41 deletions.
21 changes: 21 additions & 0 deletions addon/adapters/file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import ApplicationAdapter from "./application";

export default class FileAdapter extends ApplicationAdapter {
ajaxOptions(url, type, options) {
const ajaxOptions = super.ajaxOptions(url, type, options);

if (type === "PUT") {
// Use PATCH instead of PUT for updating records
ajaxOptions.type = "PATCH";
ajaxOptions.method = "PATCH";
}

if (type === "PUT" || type === "POST") {
// Remove content type for updating and creating records so the content
// type will be defined by the passed form data
delete ajaxOptions.headers["content-type"];
}

return ajaxOptions;
}
}
3 changes: 1 addition & 2 deletions addon/models/file.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ import Model, { attr, belongsTo, hasMany } from "@ember-data/model";
export default class FileModel extends Model {
@attr variant;
@attr name;
@attr uploadUrl;
@attr downloadUrl;
@attr objectName;
@attr metainfo;
@attr content;
@attr checksum;

@attr createdAt;
Expand Down
31 changes: 31 additions & 0 deletions addon/serializers/file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import JSONSerializer from "@ember-data/serializer/json-api";

/*
* If pagination is enabled in the backend, the response format will be changed.
* The response data will be wrapped in a `results` object.
* This would need some configurable normalizer functionality to work.
*/
export default class FileSerializer extends JSONSerializer {
// If we don't do this, Ember will interpret the `meta` property in the single
// response as meta object and omit it from the attributes.
extractMeta() {}

// Disable root key serialization since we want to send plain form data
serializeIntoHash = null;

serialize(snapshot) {
const { name, variant, content } = snapshot.attributes();

const formData = new FormData();

formData.append("name", name);
formData.append("variant", variant);
formData.append("document", snapshot.belongsTo("document")?.id);

if (content instanceof File) {
formData.append("content", content);
}

return formData;
}
}
26 changes: 2 additions & 24 deletions addon/services/alexandria-documents.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { action } from "@ember/object";
import Service, { inject as service } from "@ember/service";
import { tracked } from "@glimmer/tracking";
import fetch from "fetch";

export default class AlexandriaDocumentsService extends Service {
@service store;
Expand Down Expand Up @@ -68,19 +67,10 @@ export default class AlexandriaDocumentsService extends Service {
document: documentModel,
createdByGroup: this.config.activeGroup,
modifiedByGroup: this.config.activeGroup,
content: file,
});
await fileModel.save();

const response = await fetch(fileModel.uploadUrl, {
method: "PUT",
body: file,
headers: { "content-type": "application/octet-stream" },
});

if (!response.ok) {
throw new Error(response.statusText, response.status);
}

return documentModel;
}),
);
Expand All @@ -99,21 +89,9 @@ export default class AlexandriaDocumentsService extends Service {
document,
createdByGroup: this.config.activeGroup,
modifiedByGroup: this.config.activeGroup,
content: file,
});

await fileModel.save();

const response = await fetch(fileModel.uploadUrl, {
method: "PUT",
body: file,
headers: { "content-type": "application/octet-stream" },
});

if (!response.ok) {
throw new Error(response.statusText, response.status);
}

await document.reload();
}

/**
Expand Down
7 changes: 4 additions & 3 deletions tests/acceptance/documents-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,10 @@ module("Acceptance | documents", function (hooks) {
assert.dom("[data-test-file]").doesNotExist();

this.assertRequest("POST", "/api/v1/files", (request) => {
const { attributes } = JSON.parse(request.requestBody).data;
assert.strictEqual(attributes.name, "test-file.txt");
assert.strictEqual(attributes.variant, "original");
const name = request.requestBody.get("name");
const variant = request.requestBody.get("variant");
assert.strictEqual(name, "test-file.txt");
assert.strictEqual(variant, "original");
});
await triggerEvent("[data-test-replace]", "change", {
files: [new File(["Ember Rules!"], "test-file.txt")],
Expand Down
21 changes: 21 additions & 0 deletions tests/dummy/app/adapters/file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import ApplicationAdapter from "./application";

export default class FileAdapter extends ApplicationAdapter {
ajaxOptions(url, type, options) {
const ajaxOptions = super.ajaxOptions(url, type, options);

if (type === "PUT") {
// Use PATCH instead of PUT for updating records
ajaxOptions.type = "PATCH";
ajaxOptions.method = "PATCH";
}

if (type === "PUT" || type === "POST") {
// Remove content type for updating and creating records so the content
// type will be defined by the passed form data
delete ajaxOptions.headers["content-type"];
}

return ajaxOptions;
}
}
6 changes: 3 additions & 3 deletions tests/dummy/mirage/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ export default function makeServer(config) {
this.resource("tags", { except: ["delete"] });
this.resource("marks", { only: ["index"] });

this.post("/files", function (schema) {
const attrs = this.normalizedRequestAttrs();
this.post("/files", function (schema, request) {
const attrs = Object.fromEntries(request.requestBody.entries());
return schema.files.create({
...attrs,
uploadUrl: "/api/v1/file-upload",
document: schema.documents.find(attrs.document),
});
});

Expand Down
12 changes: 12 additions & 0 deletions tests/unit/adapters/file-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { setupTest } from "dummy/tests/helpers";
import { module, test } from "qunit";

module("Unit | Adapter | file", function (hooks) {
setupTest(hooks);

// Replace this with your real tests.
test("it exists", function (assert) {
const adapter = this.owner.lookup("adapter:file");
assert.ok(adapter);
});
});
11 changes: 9 additions & 2 deletions tests/unit/serializers/file-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,17 @@ module("Unit | Serializer | file", function (hooks) {

test("it serializes records", function (assert) {
const store = this.owner.lookup("service:store");
const record = store.createRecord("file", {});
const file = {
name: "foo",
variant: "original",
};
const record = store.createRecord("file", file);

const serializedRecord = record.serialize();

assert.ok(serializedRecord);
assert.deepEqual(Object.fromEntries(serializedRecord.entries()), {
...file,
document: "undefined",
});
});
});
9 changes: 2 additions & 7 deletions tests/unit/services/alexandria-documents-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ module("Unit | Service | alexandria-documents", function (hooks) {
);

// Each file generates three requests.
assert.strictEqual(requests.length, files.length * 3);
assert.strictEqual(requests.length, files.length * 2);

// Files will be uploaded in parallel. So, we cannot know the order.
const documentRequests = requests.filter((request) =>
Expand All @@ -41,13 +41,9 @@ module("Unit | Service | alexandria-documents", function (hooks) {
const fileRequests = requests.filter((request) =>
request.url.endsWith("files"),
);
const uploadRequests = requests.filter((request) =>
request.url.endsWith("file-upload"),
);

assert.strictEqual(documentRequests.length, files.length);
assert.strictEqual(fileRequests.length, files.length);
assert.strictEqual(uploadRequests.length, files.length);
});

test("it replaces documents", async function (assert) {
Expand All @@ -66,8 +62,7 @@ module("Unit | Service | alexandria-documents", function (hooks) {
(request) => !request.url.includes("documents"),
);

assert.strictEqual(requests.length, 2);
assert.strictEqual(requests.length, 1);
assert.ok(requests[0].url.endsWith("files"));
assert.ok(requests[1].url.endsWith("file-upload"));
});
});

0 comments on commit f934fc4

Please sign in to comment.