Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 0 additions & 37 deletions apps/api/__tests__/apikey/handlers.test.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,7 @@
import test, { afterEach, describe, mock } from "node:test";
import { createApikey } from "../../src/apikey/handlers";
import assert from "node:assert";
import queries from "../../src/apikey/queries";

describe("API key test suite", () => {
afterEach(() => {
mock.restoreAll();
});

test("Create API key throws an error if name is empty", async (t) => {
const req = {
body: {},
};
const res = {
status: () => ({
json: (data: any) => data,
}),
};
const response = await createApikey(req, res, () => {});
assert.strictEqual(response.error, "Name is required");
});

test("Create API succeeds if name is provided.", async (t) => {
const req = {
body: {
name: "Test API",
},
user: {
id: "123",
},
};
const res = {
status: () => ({
json: (data: any) => data,
}),
};
mock.method(queries, "createApiKey").mock.mockImplementation(
async () => ({ key: "123" }),
);
const response = await createApikey(req, res, () => {});
assert.strictEqual(response.key, "123");
});
});
102 changes: 102 additions & 0 deletions apps/api/__tests__/media/handlers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { Constants } from "@medialit/models";
import test, { afterEach, describe, mock } from "node:test";
import { uploadMedia } from "../../src/media/handlers";
import assert from "node:assert";
import { FILE_SIZE_EXCEEDED } from "../../src/config/strings";
import mediaService from "../../src/media/service";

describe("Media handlers", () => {
afterEach(() => {
mock.restoreAll();
});

test("should reject upload if file size exceeds limit for non-subscribed user", async () => {
const req = {
files: {
file: {
size: 100000000, // 100MB
},
},
user: {
id: "123",
subscriptionStatus: Constants.SubscriptionStatus.NOT_SUBSCRIBED,
},
socket: {
setTimeout: () => {},
},
};

const res = {
status: (code: number) => ({
json: (data: any) => ({ code, data }),
}),
};

const response = await uploadMedia(req, res, () => {});
assert.strictEqual(response.code, 400);
assert.ok(response.data.error.includes(FILE_SIZE_EXCEEDED));
});

test("should reject upload if file size exceeds limit for subscribed user", async () => {
const req = {
files: {
file: {
size: 2147483648 + 1, // 2GB + 1 byte
},
},
user: {
id: "123",
subscriptionStatus: Constants.SubscriptionStatus.SUBSCRIBED,
},
socket: {
setTimeout: () => {},
},
};

const res = {
status: (code: number) => ({
json: (data: any) => ({ code, data }),
}),
};

const response = await uploadMedia(req, res, () => {});
assert.strictEqual(response.code, 400);
assert.ok(response.data.error.includes(FILE_SIZE_EXCEEDED));
});

test("should allow larger file upload for subscribed user", async () => {
const req = {
files: {
file: {
size: 100000000, // 100MB - within subscribed limit
},
},
user: {
id: "123",
subscriptionStatus: Constants.SubscriptionStatus.SUBSCRIBED,
},
socket: {
setTimeout: () => {},
},
body: {},
query: {},
};

const res = {
status: (code: number) => ({
json: (data: any) => ({ code, data }),
}),
};

mock.method(mediaService, "upload").mock.mockImplementation(
async () => "test-media-id",
);

mock.method(mediaService, "getMediaDetails").mock.mockImplementation(
async () => ({ id: "test-media-id" }),
);

const response = await uploadMedia(req, res, () => {});
assert.strictEqual(response.code, 200);
});
});
20 changes: 10 additions & 10 deletions apps/api/src/apikey/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,19 @@ export async function getApiKeyByUserId(
return result;
}

export async function deleteApiKey(
userId: string,
keyId: string,
): Promise<void> {
await ApikeyModel.deleteOne({
key: keyId,
userId,
});
}
// export async function deleteApiKey(
// userId: string,
// keyId: string,
// ): Promise<void> {
// await ApikeyModel.deleteOne({
// key: keyId,
// userId,
// });
// }

export default {
createApiKey,
getApiKeyUsingKeyId,
getApiKeyByUserId,
deleteApiKey,
// deleteApiKey,
};
9 changes: 8 additions & 1 deletion apps/api/src/config/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ export const appName = process.env.APP_NAME || "MediaLit";
export const jwtSecret = process.env.JWT_SECRET || "r@nd0m1e";
export const jwtExpire = process.env.JWT_EXPIRES_IN || "1d";
export const tempFileDirForUploads = process.env.TEMP_FILE_DIR_FOR_UPLOADS;
export const maxFileUploadSize = process.env.MAX_UPLOAD_SIZE || 2147483648;
export const maxFileUploadSizeSubscribed = process.env
.MAX_UPLOAD_SIZE_SUBSCRIBED
? +process.env.MAX_UPLOAD_SIZE_SUBSCRIBED
: 2147483648;
export const maxFileUploadSizeNotSubscribed = process.env
.MAX_UPLOAD_SIZE_NOT_SUBSCRIBED
? +process.env.MAX_UPLOAD_SIZE_NOT_SUBSCRIBED
: 52428800;
export const PRESIGNED_URL_VALIDITY_MINUTES = 5;
export const PRESIGNED_URL_LENGTH = 100;
export const MEDIA_ID_LENGTH = 40;
Expand Down
7 changes: 7 additions & 0 deletions apps/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ app.set("trust proxy", process.env.ENABLE_TRUST_PROXY === "true");

app.use(express.json());

app.get("/health", (req, res) => {
res.status(200).json({
status: "ok",
uptime: process.uptime(),
});
});

app.use("/settings/media", mediaSettingsRoutes(passport));
app.use("/media/presigned", presignedUrlRoutes);
app.use("/media", mediaRoutes);
Expand Down
21 changes: 18 additions & 3 deletions apps/api/src/media/handlers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import Joi from "joi";
import { maxFileUploadSize } from "../config/constants";
import {
maxFileUploadSizeNotSubscribed,
maxFileUploadSizeSubscribed,
} from "../config/constants";
import {
FILE_IS_REQUIRED,
FILE_SIZE_EXCEEDED,
Expand All @@ -10,6 +13,7 @@ import logger from "../services/log";
import { Request } from "express";
import mediaService from "./service";
import { getMediaCount as getCount, getTotalSpace } from "./queries";
import { Constants } from "@medialit/models";

function validateUploadOptions(req: Request): Joi.ValidationResult {
const uploadSchema = Joi.object({
Expand All @@ -21,6 +25,14 @@ function validateUploadOptions(req: Request): Joi.ValidationResult {
return uploadSchema.validate({ caption, access, group });
}

function getMaxFileUploadSize(req: any): number {
const isSubscribed =
req.user.subscriptionStatus === Constants.SubscriptionStatus.SUBSCRIBED;
return isSubscribed
? maxFileUploadSizeSubscribed
: maxFileUploadSizeNotSubscribed;
}

export async function uploadMedia(
req: any,
res: any,
Expand All @@ -32,8 +44,11 @@ export async function uploadMedia(
return res.status(400).json({ error: FILE_IS_REQUIRED });
}

if (req.files.file.size > maxFileUploadSize) {
return res.status(400).json({ error: FILE_SIZE_EXCEEDED });
const allowedFileSize = getMaxFileUploadSize(req);
if (req.files.file.size > allowedFileSize) {
return res.status(400).json({
error: `${FILE_SIZE_EXCEEDED}. Allowed: ${allowedFileSize} bytes`,
});
}

const { error } = validateUploadOptions(req);
Expand Down
31 changes: 19 additions & 12 deletions apps/api/src/services/s3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,36 +37,43 @@ export interface PresignedURLParams {
mimetype?: string;
}

const s3Client = new S3Client({
region: cloudRegion,
endpoint: cloudEndpoint,
credentials: {
accessKeyId: cloudKey,
secretAccessKey: cloudSecret,
},
});
let s3Client: S3Client | null = null;

const getS3Client = () => {
if (!s3Client) {
s3Client = new S3Client({
region: cloudRegion,
endpoint: cloudEndpoint,
credentials: {
accessKeyId: cloudKey,
secretAccessKey: cloudSecret,
},
});
}
return s3Client;
};

export const putObject = async (params: UploadParams) => {
const command = new PutObjectCommand(
Object.assign({}, { Bucket: cloudBucket }, params),
);
const response = await s3Client.send(command);
const response = await getS3Client().send(command);
return response;
};

export const deleteObject = async (params: DeleteParams) => {
const command = new DeleteObjectCommand(
Object.assign({}, { Bucket: cloudBucket }, params),
);
const response = await s3Client.send(command);
const response = await getS3Client().send(command);
return response;
};

export const getObjectTagging = async (params: { Key: string }) => {
const command = new GetObjectTaggingCommand(
Object.assign({}, { Bucket: cloudBucket }, params),
);
const response = await s3Client.send(command);
const response = await getS3Client().send(command);
return response;
};

Expand All @@ -75,7 +82,7 @@ export const generateSignedUrl = async (key: string): Promise<string> => {
Bucket: cloudBucket,
Key: key,
});
const url = await getS3SignedUrl(s3Client, command);
const url = await getS3SignedUrl(getS3Client(), command);
return url;
};

Expand Down
Loading