Skip to content

Commit

Permalink
feat/limit-upload-file-size
Browse files Browse the repository at this point in the history
  • Loading branch information
apsantiso committed Feb 27, 2024
1 parent e137d42 commit f791c1f
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 1 deletion.
64 changes: 64 additions & 0 deletions src/app/middleware/feature-limits.middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Request, Response, NextFunction } from 'express';
import { AuthorizedUser } from '../routes/types';

type User = AuthorizedUser['user'];
type Middleware = (req: Request & { behalfUser?: User }, res: Response, next: NextFunction) => Promise<void>;
type DataSource = { fieldName: string; sourceKey: string };
type BuilderArgs = {
limitLabel: string;
dataSources: DataSource[];
};

const LimitLabels = {
MaxFileUploadSize: 'max-file-upload-size',
};

const build = (Service: {
FeatureLimits: {
shouldLimitBeEnforced: (user: User, limitLabel: string, data: any) => Promise<boolean>;
};
}) => {
const mdBuilder = ({ limitLabel, dataSources }: BuilderArgs) =>
(async (req, res, next) => {
try {
const user = (req as any).behalfUser || (req as AuthorizedUser).user;

if (!user) {
return next();
}

const extractedData = extractDataFromRequest(req, dataSources);
const shouldLimitBeEnforced = await Service.FeatureLimits.shouldLimitBeEnforced(
user,
limitLabel,
extractedData,
);

if (shouldLimitBeEnforced) {
return res.status(402).send('You reached the limit for your tier!');
}

next();
} catch (err) {
next(err);
}
}) as Middleware;

return {
UploadFile: mdBuilder({
limitLabel: LimitLabels.MaxFileUploadSize,
dataSources: [{ sourceKey: 'body', fieldName: 'file' }],
}),
};
};

const extractDataFromRequest = (request: any, dataSources: DataSource[]) => {
const extractedData = {} as any;
for (const { sourceKey, fieldName } of dataSources) {
const value = request[sourceKey][fieldName];
extractedData[fieldName] = value;
}
return extractedData;
};

export { build, LimitLabels };
1 change: 0 additions & 1 deletion src/app/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,6 @@ export default (database: Sequelize) => {

Limit.belongsToMany(Tier, {
through: TierLimit,
as: 'tiers',
});

return {
Expand Down
3 changes: 3 additions & 0 deletions src/app/routes/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
FolderWithNameAlreadyExistsError
} from '../services/errors/FolderWithNameAlreadyExistsError';
import * as resourceSharingMiddlewareBuilder from '../middleware/resource-sharing.middleware';
import * as featureLimitsMiddlewareBuilder from '../middleware/feature-limits.middleware';
import {validate } from 'uuid';

type AuthorizedRequest = Request & { user: UserAttributes };
Expand Down Expand Up @@ -811,12 +812,14 @@ export default (router: Router, service: any) => {
const sharedAdapter = sharedMiddlewareBuilder.build(service);
const teamsAdapter = teamsMiddlewareBuilder.build(service);
const resourceSharingAdapter = resourceSharingMiddlewareBuilder.build(service);
const featureLimitsAdapter = featureLimitsMiddlewareBuilder.build(service);
const controller = new StorageController(service, Logger);

router.post('/storage/file',
passportAuth,
sharedAdapter,
resourceSharingAdapter.UploadFile,
featureLimitsAdapter.UploadFile,
controller.createFile.bind(controller)
);
router.post('/storage/file/exists',
Expand Down
7 changes: 7 additions & 0 deletions src/app/services/errors/FeatureLimitsErrors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export class NoLimitFoundForUserTierAndLabel extends Error {
constructor(message: string) {
super(message);

Object.setPrototypeOf(this, NoLimitFoundForUserTierAndLabel.prototype);
}
}
59 changes: 59 additions & 0 deletions src/app/services/featureLimit.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
const { LimitLabels } = require('../middleware/feature-limits.middleware');
const { NoLimitFoundForUserTierAndLabel } = require('./errors/FeatureLimitsErrors');

const LimitTypes = {
Counter: 'counter',
Boolean: 'boolean',
};

module.exports = (Model, App) => {

Check warning on line 9 in src/app/services/featureLimit.js

View workflow job for this annotation

GitHub Actions / build (16.x)

'App' is defined but never used

Check warning on line 9 in src/app/services/featureLimit.js

View workflow job for this annotation

GitHub Actions / run-tests (16.x)

'App' is defined but never used
const getTierByPlanId = async (planId) => {
Expand All @@ -7,8 +14,60 @@ module.exports = (Model, App) => {
});
};

const findLimitByLabelAndTier = async (tierId, label) => {
return Model.limits.findOne({
where: {
label,
},
include: [
{
model: Model.tiers,
where: {
id: tierId,
},
},
],
});
};

const isBooleanLimitNotAvailable = (limit) => {
return limit.type === LimitTypes.Boolean && limit.value !== 'true';
};

const shouldLimitBeEnforced = async (user, limitLabel, data) => {
const limit = await findLimitByLabelAndTier(user.tierId, limitLabel);

if (!limit) {
throw new NoLimitFoundForUserTierAndLabel('No limit found for this user tier and label!');
}
if (limit.type === LimitTypes.Boolean) {
return isBooleanLimitNotAvailable(limit);
}

const isLimitSuprassed = await checkCounterLimit(user, limit, data);

return isLimitSuprassed;
};

const checkCounterLimit = (user, limit, data) => {
switch (limit.label) {
case LimitLabels.MaxFileUploadSize:
return isMaxFileSizeLimitSurprassed({ limit, data });
default:
return null;
}
};

const isMaxFileSizeLimitSurprassed = async ({ limit, data }) => {
const {
file: { size },
} = data;
return Number(limit.value) < size / 1024 / 1024 / 1024;
};

return {
Name: 'FeatureLimits',
getTierByPlanId,
shouldLimitBeEnforced,
};
};

0 comments on commit f791c1f

Please sign in to comment.