@lobb-js/lobb-ext-storage 0.5.8 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/create_tag_on_version_change.yaml +45 -0
- package/.github/workflows/publish_npm_package.yaml +28 -0
- package/.vscode/settings.json +5 -0
- package/CHANGELOG.md +150 -29
- package/README.md +1 -35
- package/extensions/storage/adapters/index.ts +18 -0
- package/extensions/storage/adapters/localAdapter.ts +60 -0
- package/extensions/storage/adapters/storageAdapter.ts +13 -0
- package/extensions/storage/collections/fileSystem.ts +58 -0
- package/extensions/storage/collections/index.ts +12 -0
- package/extensions/storage/config/extensionConfigSchema.ts +15 -0
- package/extensions/storage/index.ts +26 -0
- package/extensions/storage/init.ts +6 -0
- package/extensions/storage/meta.ts +5 -0
- package/extensions/storage/migrations.ts +3 -0
- package/extensions/storage/openapi.ts +84 -0
- package/extensions/storage/studio/tests/fileManager.spec.ts +23 -0
- package/extensions/storage/studio/tests/package.json +1 -0
- package/extensions/storage/studio/tests/playwright.config.cjs +27 -0
- package/extensions/storage/studioExtension.json +1 -0
- package/extensions/storage/tests/configs/simple.ts +95 -0
- package/extensions/storage/tests/directories.test.ts +156 -0
- package/extensions/storage/tests/extraFormData.test.ts +47 -0
- package/extensions/storage/tests/files/rose.jpeg +0 -0
- package/extensions/storage/tests/files.test.ts +292 -0
- package/extensions/storage/tests/forceUpload.test.ts +72 -0
- package/extensions/storage/tests/massRemove.test.ts +189 -0
- package/extensions/storage/tests/meta.test.ts +26 -0
- package/extensions/storage/tests/recursiveDeleteMany.test.ts +208 -0
- package/extensions/storage/tests/recursiveDeleteOne.test.ts +206 -0
- package/extensions/storage/tests/storage/127 +0 -0
- package/extensions/storage/types.ts +11 -0
- package/extensions/storage/utils.ts +87 -0
- package/extensions/storage/workflows/controllers.ts +103 -0
- package/extensions/storage/workflows/index.ts +10 -0
- package/extensions/storage/workflows/services.ts +182 -0
- package/lobb.ts +54 -0
- package/package.json +32 -9
- package/scripts/postpublish.sh +12 -0
- package/scripts/prepublish.sh +17 -0
- package/studio/app.html +12 -0
- package/studio/routes/+layout.svelte +7 -0
- package/studio/routes/+layout.ts +1 -0
- package/studio/routes/[...path]/+page.svelte +6 -0
- package/svelte.config.js +23 -7
- package/todo.md +36 -0
- package/tsconfig.app.json +3 -3
- package/tsconfig.json +11 -5
- package/vite.config.ts +4 -10
- package/components.json +0 -16
- package/index.html +0 -13
- package/src/app.css +0 -124
- package/src/main.ts +0 -14
- /package/{src → extensions/storage/studio}/extension.types.ts +0 -0
- /package/{src → extensions/storage/studio}/index.ts +0 -0
- /package/{src → extensions/storage/studio}/lib/components/childrenFileExplorer.svelte +0 -0
- /package/{src → extensions/storage/studio}/lib/components/explorerNotSupported.svelte +0 -0
- /package/{src → extensions/storage/studio}/lib/components/fileExplorer.svelte +0 -0
- /package/{src → extensions/storage/studio}/lib/components/fileIcon.svelte +0 -0
- /package/{src → extensions/storage/studio}/lib/components/fileManagerBreadCrumbs.svelte +0 -0
- /package/{src → extensions/storage/studio}/lib/components/foreignKeyComponent.svelte +0 -0
- /package/{src → extensions/storage/studio}/lib/components/pages/fileExplorerPage.svelte +0 -0
- /package/{src → extensions/storage/studio}/lib/index.ts +0 -0
- /package/{src → extensions/storage/studio}/lib/utils.ts +0 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { test, expect } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
const FILE_MANAGER_URL = "/studio/extensions/storage/file_manager";
|
|
4
|
+
|
|
5
|
+
test.describe("File Manager page", () => {
|
|
6
|
+
test.beforeEach(async ({ page }) => {
|
|
7
|
+
await page.goto(FILE_MANAGER_URL);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("loads the file manager page", async ({ page }) => {
|
|
11
|
+
await expect(page).toHaveURL(new RegExp(FILE_MANAGER_URL));
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("shows the file manager nav link in the sidebar", async ({ page }) => {
|
|
15
|
+
await expect(page.locator(`a[href="${FILE_MANAGER_URL}"]`)).toBeVisible();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("file manager page is accessible from the nav", async ({ page }) => {
|
|
19
|
+
await page.goto("/studio");
|
|
20
|
+
await page.locator(`a[href="${FILE_MANAGER_URL}"]`).click();
|
|
21
|
+
await expect(page).toHaveURL(new RegExp(FILE_MANAGER_URL));
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type": "commonjs"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const { defineConfig, devices } = require("@playwright/test");
|
|
2
|
+
|
|
3
|
+
module.exports = defineConfig({
|
|
4
|
+
testDir: __dirname,
|
|
5
|
+
testMatch: "**/*.spec.ts",
|
|
6
|
+
fullyParallel: true,
|
|
7
|
+
retries: 0,
|
|
8
|
+
expect: { timeout: 15000 },
|
|
9
|
+
use: {
|
|
10
|
+
baseURL: "http://localhost:3000",
|
|
11
|
+
trace: "on-first-retry",
|
|
12
|
+
headless: true,
|
|
13
|
+
},
|
|
14
|
+
projects: [
|
|
15
|
+
{
|
|
16
|
+
name: "chromium",
|
|
17
|
+
use: { ...devices["Desktop Chrome"] },
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
// Automatically start the dev server before running tests
|
|
21
|
+
webServer: {
|
|
22
|
+
command: "bun run dev",
|
|
23
|
+
url: "http://localhost:3000/studio",
|
|
24
|
+
reuseExistingServer: true,
|
|
25
|
+
cwd: "../../../../", // packages/storage-ext root
|
|
26
|
+
},
|
|
27
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { Config } from "@lobb-js/core";
|
|
2
|
+
import storage from "../../index.ts";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
|
|
6
|
+
export function createSimpleConfig(storagePath?: string): Config & { storagePath: string } {
|
|
7
|
+
const resolvedStoragePath = storagePath ?? join(tmpdir(), "lobb_storage_" + crypto.randomUUID());
|
|
8
|
+
return {
|
|
9
|
+
storagePath: resolvedStoragePath,
|
|
10
|
+
project: {
|
|
11
|
+
name: "Lobb",
|
|
12
|
+
force_sync: true,
|
|
13
|
+
},
|
|
14
|
+
database: {
|
|
15
|
+
host: "localhost",
|
|
16
|
+
port: 5432,
|
|
17
|
+
username: "test",
|
|
18
|
+
password: "test",
|
|
19
|
+
database: "*",
|
|
20
|
+
},
|
|
21
|
+
web_server: {
|
|
22
|
+
host: "0.0.0.0",
|
|
23
|
+
port: 0,
|
|
24
|
+
cors: {
|
|
25
|
+
origin: "*",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
extensions: [
|
|
29
|
+
storage({
|
|
30
|
+
adapter: "local",
|
|
31
|
+
storagePath: resolvedStoragePath,
|
|
32
|
+
}),
|
|
33
|
+
],
|
|
34
|
+
collections: {
|
|
35
|
+
articles: {
|
|
36
|
+
indexes: {},
|
|
37
|
+
fields: {
|
|
38
|
+
id: {
|
|
39
|
+
type: "integer",
|
|
40
|
+
},
|
|
41
|
+
image: {
|
|
42
|
+
type: "string",
|
|
43
|
+
length: 255,
|
|
44
|
+
},
|
|
45
|
+
title: {
|
|
46
|
+
type: "string",
|
|
47
|
+
length: 255,
|
|
48
|
+
validators: {
|
|
49
|
+
required: true,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
description: {
|
|
53
|
+
type: "string",
|
|
54
|
+
length: 255,
|
|
55
|
+
},
|
|
56
|
+
body: {
|
|
57
|
+
type: "string",
|
|
58
|
+
length: 255,
|
|
59
|
+
validators: {
|
|
60
|
+
required: true,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
status: {
|
|
64
|
+
type: "string",
|
|
65
|
+
length: 255,
|
|
66
|
+
validators: {
|
|
67
|
+
enum: ["public", "private"],
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
comments: {
|
|
73
|
+
indexes: {},
|
|
74
|
+
fields: {
|
|
75
|
+
id: {
|
|
76
|
+
type: "integer",
|
|
77
|
+
},
|
|
78
|
+
body: {
|
|
79
|
+
type: "string",
|
|
80
|
+
length: 255,
|
|
81
|
+
validators: {
|
|
82
|
+
required: true,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
article_id: {
|
|
86
|
+
type: "integer",
|
|
87
|
+
validators: {
|
|
88
|
+
required: true,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { Lobb } from "@lobb-js/core";
|
|
2
|
+
import { afterAll, beforeAll, describe, expect, it } from "bun:test";
|
|
3
|
+
import { createSimpleConfig } from "./configs/simple.ts";
|
|
4
|
+
|
|
5
|
+
describe("Directories Operations", () => {
|
|
6
|
+
let lobb: Lobb;
|
|
7
|
+
let baseUrl: string;
|
|
8
|
+
let createdRecordId: number;
|
|
9
|
+
|
|
10
|
+
beforeAll(async () => {
|
|
11
|
+
const config = createSimpleConfig();
|
|
12
|
+
lobb = await Lobb.init(config);
|
|
13
|
+
baseUrl = `http://127.0.0.1:${lobb.webServer.port}`;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterAll(async () => {
|
|
17
|
+
await lobb.close();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should create a directory successfully", async () => {
|
|
21
|
+
const response = await fetch(
|
|
22
|
+
`${baseUrl}/api/collections/storage_fs`,
|
|
23
|
+
{
|
|
24
|
+
method: "POST",
|
|
25
|
+
body: JSON.stringify({
|
|
26
|
+
data: {
|
|
27
|
+
name: "test_directory",
|
|
28
|
+
path: "/",
|
|
29
|
+
},
|
|
30
|
+
}),
|
|
31
|
+
},
|
|
32
|
+
);
|
|
33
|
+
const data = await response.json();
|
|
34
|
+
|
|
35
|
+
expect(data).toMatchObject({
|
|
36
|
+
data: {
|
|
37
|
+
name: "test_directory",
|
|
38
|
+
path: "/",
|
|
39
|
+
type: "directory",
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
createdRecordId = data.data.id;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should prevent creating a directory in a non existing path", async () => {
|
|
47
|
+
const response = await fetch(
|
|
48
|
+
`${baseUrl}/api/collections/storage_fs`,
|
|
49
|
+
{
|
|
50
|
+
method: "POST",
|
|
51
|
+
body: JSON.stringify({
|
|
52
|
+
data: {
|
|
53
|
+
name: "another_dir",
|
|
54
|
+
path: "/none_existing_directory",
|
|
55
|
+
},
|
|
56
|
+
}),
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
const data = await response.json();
|
|
60
|
+
|
|
61
|
+
expect(data).toEqual({
|
|
62
|
+
code: "BAD_REQUEST",
|
|
63
|
+
status: 400,
|
|
64
|
+
message: "The specified path does not exist. Please create it first.",
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should respond with the record of the requested directory entry", async () => {
|
|
69
|
+
const response = await fetch(
|
|
70
|
+
`${baseUrl}/api/collections/storage_fs/${createdRecordId}`,
|
|
71
|
+
{
|
|
72
|
+
method: "GET",
|
|
73
|
+
},
|
|
74
|
+
);
|
|
75
|
+
const data = await response.json();
|
|
76
|
+
|
|
77
|
+
expect(data).toMatchObject({
|
|
78
|
+
data: {
|
|
79
|
+
name: "test_directory",
|
|
80
|
+
path: "/",
|
|
81
|
+
type: "directory",
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should list all file system entries", async () => {
|
|
87
|
+
const response = await fetch(
|
|
88
|
+
`${baseUrl}/api/collections/storage_fs`,
|
|
89
|
+
{
|
|
90
|
+
method: "GET",
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
const data = await response.json();
|
|
94
|
+
|
|
95
|
+
expect(data).toMatchObject({
|
|
96
|
+
data: [
|
|
97
|
+
{
|
|
98
|
+
name: "test_directory",
|
|
99
|
+
path: "/",
|
|
100
|
+
type: "directory",
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
meta: {
|
|
104
|
+
totalCount: 1,
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("should list all file system entries using a filter", async () => {
|
|
110
|
+
const response = await fetch(
|
|
111
|
+
`${baseUrl}/api/collections/storage_fs/search`,
|
|
112
|
+
{
|
|
113
|
+
method: "POST",
|
|
114
|
+
body: JSON.stringify({
|
|
115
|
+
filter: {
|
|
116
|
+
path: {
|
|
117
|
+
$starts_with: "/",
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
}),
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
const data = await response.json();
|
|
124
|
+
|
|
125
|
+
expect(data).toMatchObject({
|
|
126
|
+
data: [
|
|
127
|
+
{
|
|
128
|
+
name: "test_directory",
|
|
129
|
+
path: "/",
|
|
130
|
+
type: "directory",
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
meta: {
|
|
134
|
+
totalCount: 1,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("should delete the directory successully", async () => {
|
|
140
|
+
const response = await fetch(
|
|
141
|
+
`${baseUrl}/api/collections/storage_fs/${createdRecordId}`,
|
|
142
|
+
{
|
|
143
|
+
method: "DELETE",
|
|
144
|
+
},
|
|
145
|
+
);
|
|
146
|
+
const data = await response.json();
|
|
147
|
+
|
|
148
|
+
expect(data).toMatchObject({
|
|
149
|
+
data: {
|
|
150
|
+
name: "test_directory",
|
|
151
|
+
path: "/",
|
|
152
|
+
type: "directory",
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Lobb } from "@lobb-js/core";
|
|
2
|
+
import { afterAll, beforeAll, describe, expect, it } from "bun:test";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { createSimpleConfig } from "./configs/simple.ts";
|
|
5
|
+
|
|
6
|
+
describe("Extra Form Data", () => {
|
|
7
|
+
let lobb: Lobb;
|
|
8
|
+
let baseUrl: string;
|
|
9
|
+
|
|
10
|
+
beforeAll(async () => {
|
|
11
|
+
const config = createSimpleConfig();
|
|
12
|
+
lobb = await Lobb.init(config);
|
|
13
|
+
baseUrl = `http://127.0.0.1:${lobb.webServer.port}`;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterAll(async () => {
|
|
17
|
+
await lobb.close();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should persist extra form fields alongside the file record", async () => {
|
|
21
|
+
const image = readFileSync(import.meta.dirname + "/files/rose.jpeg");
|
|
22
|
+
|
|
23
|
+
const formData = new FormData();
|
|
24
|
+
formData.append("file", new File([image], "rose.jpeg"));
|
|
25
|
+
formData.append("icon", "Image");
|
|
26
|
+
|
|
27
|
+
const response = await fetch(
|
|
28
|
+
`${baseUrl}/api/collections/storage_fs`,
|
|
29
|
+
{
|
|
30
|
+
method: "POST",
|
|
31
|
+
body: formData,
|
|
32
|
+
},
|
|
33
|
+
);
|
|
34
|
+
const data = await response.json();
|
|
35
|
+
|
|
36
|
+
expect(data).toMatchObject({
|
|
37
|
+
data: {
|
|
38
|
+
name: "rose.jpeg",
|
|
39
|
+
path: "/",
|
|
40
|
+
type: "file",
|
|
41
|
+
icon: "Image",
|
|
42
|
+
file_mime_type: "image/jpeg",
|
|
43
|
+
file_size: "1086103",
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
});
|
|
Binary file
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { Lobb } from "@lobb-js/core";
|
|
2
|
+
import { afterAll, beforeAll, describe, expect, it } from "bun:test";
|
|
3
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
4
|
+
import { createSimpleConfig } from "./configs/simple.ts";
|
|
5
|
+
|
|
6
|
+
describe("Files Storage", () => {
|
|
7
|
+
let lobb: Lobb;
|
|
8
|
+
let baseUrl: string;
|
|
9
|
+
let storagePath: string;
|
|
10
|
+
let createdFileId: number;
|
|
11
|
+
|
|
12
|
+
beforeAll(async () => {
|
|
13
|
+
const config = createSimpleConfig();
|
|
14
|
+
storagePath = config.storagePath;
|
|
15
|
+
lobb = await Lobb.init(config);
|
|
16
|
+
baseUrl = `http://127.0.0.1:${lobb.webServer.port}`;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterAll(async () => {
|
|
20
|
+
await lobb.close();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should throw an error if no files were provided", async () => {
|
|
24
|
+
const response = await fetch(
|
|
25
|
+
`${baseUrl}/api/collections/storage_fs`,
|
|
26
|
+
{
|
|
27
|
+
method: "POST",
|
|
28
|
+
body: new FormData(),
|
|
29
|
+
},
|
|
30
|
+
);
|
|
31
|
+
const data = await response.json();
|
|
32
|
+
|
|
33
|
+
expect(data).toEqual({
|
|
34
|
+
code: "BAD_REQUEST",
|
|
35
|
+
status: 400,
|
|
36
|
+
message: "No files were provided.",
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should throw an error if more than one file is provided", async () => {
|
|
41
|
+
const image = readFileSync(import.meta.dirname + "/files/rose.jpeg");
|
|
42
|
+
const imageFileName = "rose.jpeg";
|
|
43
|
+
|
|
44
|
+
const formData = new FormData();
|
|
45
|
+
formData.append("file", new File([image], imageFileName));
|
|
46
|
+
formData.append("file", new File([image], imageFileName));
|
|
47
|
+
|
|
48
|
+
const response = await fetch(
|
|
49
|
+
`${baseUrl}/api/collections/storage_fs`,
|
|
50
|
+
{
|
|
51
|
+
method: "POST",
|
|
52
|
+
body: formData,
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
const data = await response.json();
|
|
56
|
+
|
|
57
|
+
expect(data).toEqual({
|
|
58
|
+
code: "BAD_REQUEST",
|
|
59
|
+
status: 400,
|
|
60
|
+
message: "Only one file can be uploaded at a time.",
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("should create a file record and upload it successfully", async () => {
|
|
65
|
+
const image = readFileSync(import.meta.dirname + "/files/rose.jpeg");
|
|
66
|
+
const imageFileName = "rose.jpeg";
|
|
67
|
+
|
|
68
|
+
const formData = new FormData();
|
|
69
|
+
formData.append(
|
|
70
|
+
"file",
|
|
71
|
+
new File([image], imageFileName),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const response = await fetch(
|
|
75
|
+
`${baseUrl}/api/collections/storage_fs`,
|
|
76
|
+
{
|
|
77
|
+
method: "POST",
|
|
78
|
+
body: formData,
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
const result = await response.json();
|
|
82
|
+
|
|
83
|
+
createdFileId = result.data.id;
|
|
84
|
+
|
|
85
|
+
expect(result).toMatchObject({
|
|
86
|
+
data: {
|
|
87
|
+
name: "rose.jpeg",
|
|
88
|
+
path: "/",
|
|
89
|
+
type: "file",
|
|
90
|
+
file_mime_type: "image/jpeg",
|
|
91
|
+
file_size: "1086103",
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const uploadedFileExists = existsSync(`${storagePath}/${createdFileId}`);
|
|
96
|
+
|
|
97
|
+
expect(uploadedFileExists).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("should prevent uploading a file in a non existing path", async () => {
|
|
101
|
+
const image = readFileSync(import.meta.dirname + "/files/rose.jpeg");
|
|
102
|
+
const imageFileName = "rose.jpeg";
|
|
103
|
+
|
|
104
|
+
const formData = new FormData();
|
|
105
|
+
formData.append("file", new File([image], imageFileName));
|
|
106
|
+
formData.append("path", "/some/folder/");
|
|
107
|
+
|
|
108
|
+
const response = await fetch(
|
|
109
|
+
`${baseUrl}/api/collections/storage_fs`,
|
|
110
|
+
{
|
|
111
|
+
method: "POST",
|
|
112
|
+
body: formData,
|
|
113
|
+
},
|
|
114
|
+
);
|
|
115
|
+
const data = await response.json();
|
|
116
|
+
|
|
117
|
+
expect(data).toEqual({
|
|
118
|
+
code: "BAD_REQUEST",
|
|
119
|
+
status: 400,
|
|
120
|
+
message: "The specified path does not exist. Please create it first.",
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("should create a directory successfully", async () => {
|
|
125
|
+
const response = await fetch(
|
|
126
|
+
`${baseUrl}/api/collections/storage_fs`,
|
|
127
|
+
{
|
|
128
|
+
method: "POST",
|
|
129
|
+
body: JSON.stringify({
|
|
130
|
+
data: {
|
|
131
|
+
name: "test_directory",
|
|
132
|
+
path: "/",
|
|
133
|
+
},
|
|
134
|
+
}),
|
|
135
|
+
},
|
|
136
|
+
);
|
|
137
|
+
const data = await response.json();
|
|
138
|
+
|
|
139
|
+
expect(data).toMatchObject({
|
|
140
|
+
data: {
|
|
141
|
+
name: "test_directory",
|
|
142
|
+
path: "/",
|
|
143
|
+
type: "directory",
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("should respond with the record of the requested file from the file system collection", async () => {
|
|
149
|
+
const response = await fetch(
|
|
150
|
+
`${baseUrl}/api/collections/storage_fs/${createdFileId}`,
|
|
151
|
+
{
|
|
152
|
+
method: "GET",
|
|
153
|
+
},
|
|
154
|
+
);
|
|
155
|
+
const data = await response.json();
|
|
156
|
+
|
|
157
|
+
expect(data).toMatchObject({
|
|
158
|
+
data: {
|
|
159
|
+
name: "rose.jpeg",
|
|
160
|
+
path: "/",
|
|
161
|
+
type: "file",
|
|
162
|
+
file_mime_type: "image/jpeg",
|
|
163
|
+
file_size: "1086103",
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("should respond with the image itself when you specify the action query into view", async () => {
|
|
169
|
+
const response = await fetch(
|
|
170
|
+
`${baseUrl}/api/collections/storage_fs/${createdFileId}?action=view`,
|
|
171
|
+
{
|
|
172
|
+
method: "GET",
|
|
173
|
+
},
|
|
174
|
+
);
|
|
175
|
+
const headers = response.headers;
|
|
176
|
+
await response.text();
|
|
177
|
+
|
|
178
|
+
expect(headers.get("Content-Type")).toBe("image/jpeg");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("should respond with the image itself and have the (Content-Disposition) header when you specify the action query into download to download it right away if requested from a browser", async () => {
|
|
182
|
+
const response = await fetch(
|
|
183
|
+
`${baseUrl}/api/collections/storage_fs/${createdFileId}?action=download`,
|
|
184
|
+
{
|
|
185
|
+
method: "GET",
|
|
186
|
+
},
|
|
187
|
+
);
|
|
188
|
+
const headers = response.headers;
|
|
189
|
+
await response.text();
|
|
190
|
+
|
|
191
|
+
expect(headers.get("Content-Type")).toBe("image/jpeg");
|
|
192
|
+
|
|
193
|
+
expect(
|
|
194
|
+
headers.get("Content-Disposition"),
|
|
195
|
+
).toBe(`attachment; filename="rose.jpeg"`);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("should update the icon of rose.jpeg file successfully", async () => {
|
|
199
|
+
const response = await fetch(
|
|
200
|
+
`${baseUrl}/api/collections/storage_fs/${createdFileId}`,
|
|
201
|
+
{
|
|
202
|
+
method: "PATCH",
|
|
203
|
+
body: JSON.stringify({
|
|
204
|
+
data: {
|
|
205
|
+
icon: "Image",
|
|
206
|
+
},
|
|
207
|
+
}),
|
|
208
|
+
},
|
|
209
|
+
);
|
|
210
|
+
const data = await response.json();
|
|
211
|
+
|
|
212
|
+
expect(data).toMatchObject({
|
|
213
|
+
data: {
|
|
214
|
+
name: "rose.jpeg",
|
|
215
|
+
type: "file",
|
|
216
|
+
path: "/",
|
|
217
|
+
icon: "Image",
|
|
218
|
+
file_mime_type: "image/jpeg",
|
|
219
|
+
file_size: "1086103",
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("should update the name of rose.jpeg file successfully", async () => {
|
|
225
|
+
const response = await fetch(
|
|
226
|
+
`${baseUrl}/api/collections/storage_fs/${createdFileId}`,
|
|
227
|
+
{
|
|
228
|
+
method: "PATCH",
|
|
229
|
+
body: JSON.stringify({
|
|
230
|
+
data: {
|
|
231
|
+
name: "renamed_file_name.txt",
|
|
232
|
+
},
|
|
233
|
+
}),
|
|
234
|
+
},
|
|
235
|
+
);
|
|
236
|
+
const data = await response.json();
|
|
237
|
+
|
|
238
|
+
expect(data).toMatchObject({
|
|
239
|
+
data: {
|
|
240
|
+
name: "renamed_file_name.txt",
|
|
241
|
+
type: "file",
|
|
242
|
+
path: "/",
|
|
243
|
+
icon: "Image",
|
|
244
|
+
file_mime_type: "image/jpeg",
|
|
245
|
+
file_size: "1086103",
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("should prevent updating the record if the specified path didnt exist", async () => {
|
|
251
|
+
const response = await fetch(
|
|
252
|
+
`${baseUrl}/api/collections/storage_fs/${createdFileId}`,
|
|
253
|
+
{
|
|
254
|
+
method: "PATCH",
|
|
255
|
+
body: JSON.stringify({
|
|
256
|
+
data: {
|
|
257
|
+
icon: "Image",
|
|
258
|
+
path: "/none_existing_path/bla_bla_bla",
|
|
259
|
+
},
|
|
260
|
+
}),
|
|
261
|
+
},
|
|
262
|
+
);
|
|
263
|
+
const data = await response.json();
|
|
264
|
+
|
|
265
|
+
expect(data).toEqual({
|
|
266
|
+
code: "BAD_REQUEST",
|
|
267
|
+
status: 400,
|
|
268
|
+
message: "The specified path does not exist. Please create it first.",
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("should delete the file record and delete the file itself successfully", async () => {
|
|
273
|
+
const response = await fetch(
|
|
274
|
+
`${baseUrl}/api/collections/storage_fs/${createdFileId}`,
|
|
275
|
+
{
|
|
276
|
+
method: "DELETE",
|
|
277
|
+
},
|
|
278
|
+
);
|
|
279
|
+
const data = await response.json();
|
|
280
|
+
|
|
281
|
+
expect(response.status).toBe(200);
|
|
282
|
+
expect(data).toMatchObject({
|
|
283
|
+
data: {
|
|
284
|
+
name: "renamed_file_name.txt",
|
|
285
|
+
path: "/",
|
|
286
|
+
type: "file",
|
|
287
|
+
file_mime_type: "image/jpeg",
|
|
288
|
+
file_size: "1086103",
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
});
|