@drax/media-back 3.11.0 → 3.12.0

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.
@@ -1,8 +1,8 @@
1
1
  import { describe, it, beforeAll, afterAll, expect } from "vitest"
2
- import FileRoutes from "../../../../src/routes/FileRoutes.js"
3
- import FilePermissions from "../../../../src/permissions/FilePermissions.js"
4
- import TestSetup from "../../../setup/TestSetup.js"
5
- import type { IFileBase } from "../../../../src/interfaces/IFile.js"
2
+ import FileRoutes from "../../src/routes/FileRoutes.js"
3
+ import FilePermissions from "../../src/permissions/FilePermissions.js"
4
+ import TestSetup from "../setup/TestSetup.js"
5
+ import type { IFileBase } from "../../src/interfaces/IFile.js"
6
6
 
7
7
  describe("File Endpoints Test", function () {
8
8
 
@@ -0,0 +1,124 @@
1
+ import { describe, it, beforeAll, afterAll, expect } from "vitest";
2
+ // @ts-ignore
3
+ import FormData from "form-data";
4
+ import { readFileSync } from "node:fs";
5
+ import { access, rm } from "node:fs/promises";
6
+ import { join } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import MediaRoutes from "../../src/routes/MediaRoutes.js";
9
+ import MediaPermissions from "../../src/permissions/MediaPermissions.js";
10
+ import FilePermissions from "../../src/permissions/FilePermissions.js";
11
+ import { FileServiceFactory } from "../../src/factory/services/FileServiceFactory.js";
12
+ import TestSetup from "../setup/TestSetup.js";
13
+
14
+ // @ts-ignore
15
+ const __dirname = fileURLToPath(new URL(".", import.meta.url));
16
+ const fixturePath = join(__dirname, "..", "data", "test.jpg");
17
+ const storePath = join(process.cwd(), "packages/media/media-back/test/store");
18
+
19
+ describe("Media Endpoints Test", function () {
20
+ const testSetup = new TestSetup({
21
+ routes: [MediaRoutes],
22
+ permissions: [MediaPermissions, FilePermissions],
23
+ });
24
+
25
+ beforeAll(async () => {
26
+ await testSetup.setup();
27
+ });
28
+
29
+ afterAll(async () => {
30
+ await rm(storePath, {recursive: true, force: true});
31
+ await testSetup.dropAndClose();
32
+ });
33
+
34
+ async function uploadFile(accessToken: string, dir: string = "inbox") {
35
+ const form = new FormData();
36
+ form.append("file", readFileSync(fixturePath), {
37
+ filename: "test.jpg",
38
+ contentType: "image/jpeg",
39
+ });
40
+
41
+ return await testSetup.fastifyInstance.inject({
42
+ method: "POST",
43
+ url: `/api/file/${dir}`,
44
+ payload: form.getBuffer(),
45
+ headers: {
46
+ ...form.getHeaders(),
47
+ Authorization: `Bearer ${accessToken}`,
48
+ },
49
+ });
50
+ }
51
+
52
+ it("should upload a file and register metadata", async () => {
53
+ const {accessToken} = await testSetup.rootUserLogin();
54
+ await testSetup.dropCollection("File");
55
+
56
+ const resp = await uploadFile(accessToken, "emails");
57
+ const body = await resp.json();
58
+
59
+ expect(resp.statusCode).toBe(200);
60
+ expect(body.filename).toMatch(/test\.jpg$/);
61
+ expect(body.filepath).toContain("packages/media/media-back/test/store/emails/");
62
+ expect(body.url).toContain("/api/file/emails/");
63
+
64
+ const metadata = await FileServiceFactory.instance.findOneBy("relativePath", body.filepath);
65
+ expect(metadata).toBeTruthy();
66
+ expect(metadata?.filename).toBe(body.filename);
67
+
68
+ await access(join(process.cwd(), body.filepath));
69
+ });
70
+
71
+ it("should download a file and register a hit", async () => {
72
+ const {accessToken} = await testSetup.rootUserLogin();
73
+ await testSetup.dropCollection("File");
74
+
75
+ const uploadResp = await uploadFile(accessToken, "download-test");
76
+ const uploaded = await uploadResp.json();
77
+ const metadataBefore = await FileServiceFactory.instance.findOneBy("relativePath", uploaded.filepath);
78
+
79
+ const url = new URL(uploaded.url);
80
+ const downloadResp = await testSetup.fastifyInstance.inject({
81
+ method: "GET",
82
+ url: url.pathname,
83
+ });
84
+
85
+ expect(downloadResp.statusCode).toBe(200);
86
+ expect(downloadResp.headers["content-type"]).toContain("image/jpeg");
87
+ expect(downloadResp.body.length).toBeGreaterThan(0);
88
+
89
+ const metadataAfter = await FileServiceFactory.instance.findOneBy("relativePath", uploaded.filepath);
90
+ expect((metadataAfter?.hits || 0)).toBe((metadataBefore?.hits || 0) + 1);
91
+ });
92
+
93
+ it("should reject upload for invalid directory", async () => {
94
+ const {accessToken} = await testSetup.rootUserLogin();
95
+
96
+ const resp = await uploadFile(accessToken, "invalid.dir");
97
+ const body = await resp.json();
98
+
99
+ expect(resp.statusCode).toBe(400);
100
+ expect(body.message).toBe("Invalid directory name");
101
+ });
102
+
103
+ it("should return 404 when downloading a missing file", async () => {
104
+ const resp = await testSetup.fastifyInstance.inject({
105
+ method: "GET",
106
+ url: "/api/file/emails/2024/03/missing.jpg",
107
+ });
108
+
109
+ const body = await resp.json();
110
+ expect(resp.statusCode).toBe(404);
111
+ expect(body.message).toBe("File not found");
112
+ });
113
+
114
+ it("should reject download for invalid month", async () => {
115
+ const resp = await testSetup.fastifyInstance.inject({
116
+ method: "GET",
117
+ url: "/api/file/emails/2024/3/test.jpg",
118
+ });
119
+
120
+ const body = await resp.json();
121
+ expect(resp.statusCode).toBe(400);
122
+ expect(body.message).toBe("Invalid month");
123
+ });
124
+ });
@@ -0,0 +1,112 @@
1
+ import { describe, it, beforeAll, afterAll, expect } from "vitest";
2
+ import { createReadStream } from "node:fs";
3
+ import { access, rm } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { BadRequestError, NotFoundError } from "@drax/common-back";
7
+ import MediaService from "../../src/services/MediaService.js";
8
+ import { FileServiceFactory } from "../../src/factory/services/FileServiceFactory.js";
9
+ import TestSetup from "../setup/TestSetup.js";
10
+
11
+ // @ts-ignore
12
+ const __dirname = fileURLToPath(new URL(".", import.meta.url));
13
+ const fixturePath = join(__dirname, "..", "data", "test.jpg");
14
+ const storePath = join(process.cwd(), "packages/media/media-back/test/store");
15
+
16
+ describe("Media Service Test", function () {
17
+ const mediaService = new MediaService();
18
+ const testSetup = new TestSetup();
19
+
20
+ beforeAll(async () => {
21
+ await testSetup.setup();
22
+ });
23
+
24
+ afterAll(async () => {
25
+ await rm(storePath, {recursive: true, force: true});
26
+ await testSetup.dropAndClose();
27
+ });
28
+
29
+ it("should save a file and register metadata", async () => {
30
+ await testSetup.dropCollection("File");
31
+
32
+ const saved = await mediaService.saveFile({
33
+ dir: "emails",
34
+ file: {
35
+ filename: "test.jpg",
36
+ fileStream: createReadStream(fixturePath),
37
+ mimetype: "image/jpeg",
38
+ },
39
+ createdBy: {
40
+ id: "507f1f77bcf86cd799439011",
41
+ username: "email-provider",
42
+ },
43
+ date: new Date("2024-03-15T10:00:00.000Z"),
44
+ });
45
+
46
+ expect(saved.filename).toMatch(/test\.jpg$/);
47
+ expect(saved.relativePath).toBe(`packages/media/media-back/test/store/emails/2024/03/${saved.filename}`);
48
+ expect(saved.url).toBe(`http://localhost/api/file/emails/2024/03/${saved.filename}`);
49
+
50
+ await access(saved.absolutePath);
51
+
52
+ const metadata = await FileServiceFactory.instance.findOneBy("relativePath", saved.relativePath);
53
+ expect(metadata).toBeTruthy();
54
+ expect(metadata?.createdBy?.username).toBe("email-provider");
55
+ });
56
+
57
+ it("should get a file and increment metadata hits", async () => {
58
+ await testSetup.dropCollection("File");
59
+
60
+ const saved = await mediaService.saveFile({
61
+ dir: "attachments",
62
+ file: {
63
+ filename: "test.jpg",
64
+ fileStream: createReadStream(fixturePath),
65
+ mimetype: "image/jpeg",
66
+ },
67
+ createdBy: {
68
+ id: "507f1f77bcf86cd799439011",
69
+ username: "email-provider",
70
+ },
71
+ date: new Date("2024-04-20T10:00:00.000Z"),
72
+ });
73
+
74
+ const before = await FileServiceFactory.instance.findOneBy("relativePath", saved.relativePath);
75
+ const file = await mediaService.getFile({
76
+ dir: "attachments",
77
+ year: "2024",
78
+ month: "04",
79
+ filename: saved.filename,
80
+ });
81
+
82
+ expect(file.relativePath).toBe(saved.relativePath);
83
+ expect(file.absolutePath).toBe(saved.absolutePath);
84
+
85
+ const after = await FileServiceFactory.instance.findOneBy("relativePath", saved.relativePath);
86
+ expect((after?.hits || 0)).toBe((before?.hits || 0) + 1);
87
+ });
88
+
89
+ it("should reject invalid directory names", async () => {
90
+ await expect(
91
+ mediaService.saveFile({
92
+ dir: "../emails",
93
+ file: {
94
+ filename: "test.jpg",
95
+ fileStream: createReadStream(fixturePath),
96
+ mimetype: "image/jpeg",
97
+ },
98
+ })
99
+ ).rejects.toBeInstanceOf(BadRequestError);
100
+ });
101
+
102
+ it("should throw not found when the file does not exist", async () => {
103
+ await expect(
104
+ mediaService.getFile({
105
+ dir: "emails",
106
+ year: "2024",
107
+ month: "03",
108
+ filename: "missing.jpg",
109
+ })
110
+ ).rejects.toBeInstanceOf(NotFoundError);
111
+ });
112
+ });
@@ -1,5 +1,8 @@
1
1
  import Fastify, { FastifyInstance, FastifyPluginAsync } from "fastify";
2
2
  import { LoadCommonConfigFromEnv } from "@drax/common-back";
3
+ import fastifyMultipart from "@fastify/multipart";
4
+ import fastifyStatic from "@fastify/static";
5
+ import path from "node:path";
3
6
  import {
4
7
  LoadIdentityConfigFromEnv,
5
8
  CreateTenantIfNotExist,
@@ -72,6 +75,9 @@ class TestSetup {
72
75
  // Define environment variables
73
76
  process.env.DRAX_DB_ENGINE = "mongo";
74
77
  process.env.DRAX_JWT_SECRET = "xxx";
78
+ process.env.DRAX_FILE_DIR = "packages/media/media-back/test/store";
79
+ process.env.DRAX_BASE_URL = "http://localhost";
80
+ process.env.DRAX_MAX_UPLOAD_SIZE = "1000000";
75
81
  }
76
82
 
77
83
  setupConfig() {
@@ -108,6 +114,11 @@ class TestSetup {
108
114
  reply.status(500).send({ statusCode: 500, error: "InternalServerError", message: error?.message });
109
115
  });
110
116
  this._fastifyInstance.setValidatorCompiler(() => () => true)
117
+ this._fastifyInstance.register(fastifyMultipart as any)
118
+ this._fastifyInstance.register(fastifyStatic as any, {
119
+ root: path.resolve(process.cwd(), process.env.DRAX_FILE_DIR || "packages/media/media-back/test/store"),
120
+ prefix: "/",
121
+ })
111
122
  this._fastifyInstance.addHook('onRequest', jwtMiddleware)
112
123
  this._fastifyInstance.addHook('onRequest', rbacMiddleware)
113
124
  this._fastifyInstance.addHook('onRequest', apiKeyMiddleware)
package/tsconfig.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "extends": "../../../tsconfig.base.json",
3
3
  "compilerOptions": {
4
+ "module": "esnext",
4
5
  "rootDir": "src",
5
6
  "outDir": "dist",
6
7
  "declarationDir": "./types",