refactoring: media system.

This commit is contained in:
Ahmed Bouhuolia
2020-10-07 21:30:16 +02:00
parent 681fa0560e
commit 15dca0a7d2
14 changed files with 572 additions and 173 deletions

View File

@@ -7,6 +7,7 @@ import ExpensesService from "services/Expenses/ExpensesService";
import { IExpenseDTO } from 'interfaces';
import { ServiceError } from "exceptions";
import DynamicListingService from 'services/DynamicListing/DynamicListService';
import { takeWhile } from "lodash";
@Service()
export default class ExpensesController extends BaseController {
@@ -73,10 +74,19 @@ export default class ExpensesController extends BaseController {
'/', [
...this.expensesListSchema,
],
this.validationResult,
asyncMiddleware(this.getExpensesList.bind(this)),
this.dynamicListService.handlerErrorsToResponse,
this.catchServiceErrors,
);
router.get(
'/:id', [
this.expenseParamSchema,
],
this.validationResult,
asyncMiddleware(this.getExpense.bind(this)),
this.catchServiceErrors,
);
return router;
}
@@ -280,6 +290,24 @@ export default class ExpensesController extends BaseController {
}
}
/**
* Retrieve expense details.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
async getExpense(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { id: expenseId } = req.params;
try {
const expense = await this.expensesService.getExpense(tenantId, expenseId);
return res.status(200).send({ expense });
} catch (error) {
next(error);
}
}
/**
* Transform service errors to api response errors.
* @param {Response} res

View File

@@ -1,163 +0,0 @@
import express from 'express';
import {
param,
query,
validationResult,
} from 'express-validator';
import Container from 'typedi';
import fs from 'fs';
import { difference } from 'lodash';
import asyncMiddleware from 'api/middleware/asyncMiddleware';
const fsPromises = fs.promises;
export default {
/**
* Router constructor.
*/
router() {
const router = express.Router();
router.post('/upload',
this.upload.validation,
asyncMiddleware(this.upload.handler));
router.delete('/',
this.delete.validation,
asyncMiddleware(this.delete.handler));
router.get('/',
this.get.validation,
asyncMiddleware(this.get.handler));
return router;
},
/**
* Retrieve all or the given attachment ids.
*/
get: {
validation: [
query('ids'),
],
async handler(req, res) {
const validationErrors = validationResult(req);
if (!validationErrors.isEmpty()) {
return res.boom.badData(null, {
code: 'validation_error', ...validationErrors,
});
}
const { Media } = req.models;
const media = await Media.query().onBuild((builder) => {
if (req.query.ids) {
const ids = Array.isArray(req.query.ids) ? req.query.ids : [req.query.ids];
builder.whereIn('id', ids);
}
});
return res.status(200).send({ media });
},
},
/**
* Uploads the given attachment file.
*/
upload: {
validation: [
// check('attachment').exists(),
],
async handler(req, res) {
const Logger = Container.get('logger');
if (!req.files.attachment) {
return res.status(400).send({
errors: [{ type: 'ATTACHMENT.NOT.FOUND', code: 200 }],
});
}
const publicPath = 'storage/app/public/';
const attachmentsMimes = ['image/png', 'image/jpeg'];
const { attachment } = req.files;
const { Media } = req.models;
const errorReasons = [];
// Validate the attachment.
if (attachment && attachmentsMimes.indexOf(attachment.mimetype) === -1) {
errorReasons.push({ type: 'ATTACHMENT.MINETYPE.NOT.SUPPORTED', code: 160 });
}
// Catch all error reasons to response 400.
if (errorReasons.length > 0) {
return res.status(400).send({ errors: errorReasons });
}
try {
await attachment.mv(`${publicPath}${req.organizationId}/${attachment.md5}.png`);
Logger.info('[attachment] uploaded successfully');
} catch (error) {
Logger.info('[attachment] uploading failed.', { error });
}
const media = await Media.query().insert({
attachment_file: `${attachment.md5}.png`,
});
return res.status(200).send({ media });
},
},
/**
* Deletes the given attachment ids from file system and database.
*/
delete: {
validation: [
query('ids').exists().isArray(),
query('ids.*').exists().isNumeric().toInt(),
],
async handler(req, res) {
const Logger = Container.get('logger');
const validationErrors = validationResult(req);
if (!validationErrors.isEmpty()) {
return res.boom.badData(null, {
code: 'validation_error', ...validationErrors,
});
}
const { Media, MediaLink } = req.models;
const ids = Array.isArray(req.query.ids) ? req.query.ids : [req.query.ids];
const media = await Media.query().whereIn('id', ids);
const mediaIds = media.map((m) => m.id);
const notFoundMedia = difference(ids, mediaIds);
if (notFoundMedia.length) {
return res.status(400).send({
errors: [{ type: 'MEDIA.IDS.NOT.FOUND', code: 200, ids: notFoundMedia }],
});
}
const publicPath = 'storage/app/public/';
const tenantPath = `${publicPath}${req.organizationId}`;
const unlinkOpers = [];
media.forEach((mediaModel) => {
const oper = fsPromises.unlink(`${tenantPath}/${mediaModel.attachmentFile}`);
unlinkOpers.push(oper);
});
await Promise.all(unlinkOpers).then((resolved) => {
resolved.forEach(() => {
Logger.info('[attachment] file has been deleted.');
});
})
.catch((errors) => {
errors.forEach((error) => {
Logger.info('[attachment] Delete item attachment file delete failed.', { error });
})
});
await MediaLink.query().whereIn('media_id', mediaIds).delete();
await Media.query().whereIn('id', mediaIds).delete();
return res.status(200).send();
},
},
};

View File

@@ -0,0 +1,212 @@
import { Router, Request, Response, NextFunction } from 'express';
import {
param,
query,
check,
} from 'express-validator';
import { camelCase, upperFirst } from 'lodash';
import { Inject, Service } from 'typedi';
import { IMediaLinkDTO } from 'interfaces';
import fs from 'fs';
import asyncMiddleware from 'api/middleware/asyncMiddleware';
import BaseController from './BaseController';
import MediaService from 'services/Media/MediaService';
import { ServiceError } from 'exceptions';
const fsPromises = fs.promises;
@Service()
export default class MediaController extends BaseController {
@Inject()
mediaService: MediaService;
/**
* Router constructor.
*/
router() {
const router = Router();
router.post('/upload', [
...this.uploadValidationSchema,
],
this.validationResult,
asyncMiddleware(this.uploadMedia.bind(this)),
this.handlerServiceErrors,
);
router.post('/:id/link', [
...this.mediaIdParamSchema,
...this.linkValidationSchema,
],
this.validationResult,
asyncMiddleware(this.linkMedia.bind(this)),
this.handlerServiceErrors,
);
router.delete('/', [
...this.deleteValidationSchema,
],
this.validationResult,
asyncMiddleware(this.deleteMedia.bind(this)),
this.handlerServiceErrors,
);
router.get('/:id', [
...this.mediaIdParamSchema,
],
this.validationResult,
asyncMiddleware(this.getMedia.bind(this)),
this.handlerServiceErrors,
);
return router;
}
get uploadValidationSchema() {
return [
// check('attachment'),
check('model_name').optional().trim().escape(),
check('model_id').optional().isNumeric().toInt(),
];
}
get linkValidationSchema() {
return [
check('model_name').exists().trim().escape(),
check('model_id').exists().isNumeric().toInt(),
]
}
get deleteValidationSchema() {
return [
query('ids').exists().isArray(),
query('ids.*').exists().isNumeric().toInt(),
];
}
get mediaIdParamSchema() {
return [
param('id').exists().isNumeric().toInt(),
];
}
/**
* Retrieve all or the given attachment ids.
* @param {Request} req -
* @param {Response} req -
* @param {NextFunction} req -
*/
async getMedia(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { id: mediaId } = req.params;
try {
const media = await this.mediaService.getMedia(tenantId, mediaId);
return res.status(200).send({ media });
} catch (error) {
next(error);
}
}
/**
* Uploads media.
* @param {Request} req -
* @param {Response} req -
* @param {NextFunction} req -
*/
async uploadMedia(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { attachment } = req.files
const linkMediaDTO: IMediaLinkDTO = this.matchedBodyData(req);
const modelName = linkMediaDTO.modelName
? upperFirst(camelCase(linkMediaDTO.modelName)) : '';
try {
const media = await this.mediaService.upload(tenantId, attachment, modelName, linkMediaDTO.modelId);
return res.status(200).send({ media_id: media.id });
} catch (error) {
next(error);
}
}
/**
* Deletes the given attachment ids from file system and database.
* @param {Request} req -
* @param {Response} req -
* @param {NextFunction} req -
*/
async deleteMedia(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { ids: mediaIds } = req.query;
try {
await this.mediaService.deleteMedia(tenantId, mediaIds);
return res.status(200).send({
media_ids: mediaIds
});
} catch (error) {
next(error);
}
}
/**
* Links the given media to the specific resource model.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
async linkMedia(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { id: mediaId } = req.params;
const linkMediaDTO: IMediaLinkDTO = this.matchedBodyData(req);
const modelName = upperFirst(camelCase(linkMediaDTO.modelName));
try {
await this.mediaService.linkMedia(tenantId, mediaId, linkMediaDTO.modelId, modelName);
return res.status(200).send({ media_id: mediaId });
} catch (error) {
next(error);
}
}
/**
* Handler service errors.
* @param {Error} error
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
handlerServiceErrors(error, req: Request, res: Response, next: NextFunction) {
if (error instanceof ServiceError) {
if (error.errorType === 'MINETYPE_NOT_SUPPORTED') {
return res.boom.badRequest(null, {
errors: [{ type: 'MINETYPE_NOT_SUPPORTED', code: 100, }]
});
}
if (error.errorType === 'MEDIA_NOT_FOUND') {
return res.boom.badRequest(null, {
errors: [{ type: 'MEDIA_NOT_FOUND', code: 200 }]
});
}
if (error.errorType === 'MODEL_NAME_HAS_NO_MEDIA') {
return res.boom.badRequest(null, {
errors: [{ type: 'MODEL_NAME_HAS_NO_MEDIA', code: 300 }]
});
}
if (error.errorType === 'MODEL_ID_NOT_FOUND') {
return res.boom.badRequest(null, {
errors: [{ type: 'MODEL_ID_NOT_FOUND', code: 400 }]
});
}
if (error.errorType === 'MEDIA_IDS_NOT_FOUND') {
return res.boom.badRequest(null, {
errors: [{ type: 'MEDIA_IDS_NOT_FOUND', code: 500 }],
});
}
if (error.errorType === 'MEDIA_LINK_EXISTS') {
return res.boom.badRequest(null, {
errors: [{ type: 'MEDIA_LINK_EXISTS', code: 600 }],
});
}
}
next(error);
}
};

View File

@@ -98,7 +98,7 @@ export default () => {
// dashboard.use('/purchases', Container.get(Purchases).router());
dashboard.use('/resources', Container.get(Resources).router());
dashboard.use('/exchange_rates', Container.get(ExchangeRates).router());
dashboard.use('/media', Media.router());
dashboard.use('/media', Container.get(Media).router());
app.use('/', dashboard);

View File

@@ -66,4 +66,5 @@ export interface IExpensesService {
publishBulkExpenses(tenantId: number, expensesIds: number[], authorizedUser: ISystemUser): Promise<void>;
getExpensesList(tenantId: number, expensesFilter: IExpensesFilter): Promise<{ expenses: IExpense[], pagination: IPaginationMeta, filterMeta: IFilterMeta }>;
getExpense(tenantId: number, expenseId: number): Promise<IExpense>;
}

View File

@@ -0,0 +1,25 @@
export interface IMedia {
id?: number,
attachmentFile: string,
createdAt?: Date,
};
export interface IMediaLink {
mediaId: number,
modelName: string,
modelId: number,
};
export interface IMediaLinkDTO {
modelName: string,
modelId: number,
};
export interface IMediaService {
linkMedia(tenantId: number, mediaId: number, modelId?: number, modelName?: string): Promise<void>;
getMedia(tenantId: number, mediaId: number): Promise<IMedia>;
deleteMedia(tenantId: number, mediaId: number | number[]): Promise<void>;
upload(tenantId: number, attachment: any, modelName?: string, modelId?: number): Promise<IMedia>;
}

View File

@@ -24,4 +24,5 @@ export * from './Tenancy';
export * from './View';
export * from './ManualJournal';
export * from './Currency';
export * from './ExchangeRate';
export * from './ExchangeRate';
export * from './Media';

View File

@@ -1,6 +1,7 @@
import { Model } from 'objection';
import TenantModel from 'models/TenantModel';
import { viewRolesBuilder } from 'lib/ViewRolesBuilder';
import Media from './Media';
export default class Expense extends TenantModel {
/**
@@ -24,6 +25,11 @@ export default class Expense extends TenantModel {
return ['createdAt', 'updatedAt'];
}
static get media () {
return true;
}
/**
* Model modifiers.
*/
@@ -55,7 +61,6 @@ export default class Expense extends TenantModel {
query.where('payment_account_id', accountId);
}
},
viewRolesBuilder(query, conditionals, expression) {
viewRolesBuilder(conditionals, expression)(query);
},
@@ -68,6 +73,7 @@ export default class Expense extends TenantModel {
static get relationMappings() {
const Account = require('models/Account');
const ExpenseCategory = require('models/ExpenseCategory');
const Media = require('models/Media');
return {
paymentAccount: {
@@ -86,6 +92,18 @@ export default class Expense extends TenantModel {
to: 'expense_transaction_categories.expenseId',
},
},
media: {
relation: Model.ManyToManyRelation,
modelClass: Media.default,
join: {
from: 'expenses_transactions.id',
through: {
from: 'media_links.model_id',
to: 'media_links.media_id',
},
to: 'media.id',
},
},
};
}

View File

@@ -1,3 +1,4 @@
import { Model } from 'objection';
import TenantModel from 'models/TenantModel';
export default class Media extends TenantModel {
@@ -7,4 +8,29 @@ export default class Media extends TenantModel {
static get tableName() {
return 'media';
}
/**
* Model timestamps.
*/
get timestamps() {
return ['createdAt', 'updatedAt'];
}
/**
* Relationship mapping.
*/
static get relationMappings() {
const MediaLink = require('models/MediaLink');
return {
links: {
relation: Model.HasManyRelation,
modelClass: MediaLink.default,
join: {
from: 'media.id',
to: 'media_links.media_id',
},
},
};
}
}

View File

@@ -0,0 +1,8 @@
export default class ResourceableModel {
static get resourceable() {
return true;
}
}

View File

@@ -48,7 +48,7 @@ export default class ExpensesService implements IExpensesService {
this.logger.info('[expenses] trying to get the given payment account.', { tenantId, paymentAccountId });
const { accountRepository } = this.tenancy.repositories(tenantId);
const paymentAccount = await accountRepository.getById(paymentAccountId)
const paymentAccount = await accountRepository.findById(paymentAccountId)
if (!paymentAccount) {
this.logger.info('[expenses] the given payment account not found.', { tenantId, paymentAccountId });
@@ -460,4 +460,24 @@ export default class ExpensesService implements IExpensesService {
dynamicFilter.getResponseMeta(),
};
}
/**
* Retrieve expense details.
* @param {number} tenantId
* @param {number} expenseId
* @return {Promise<IExpense>}
*/
public async getExpense(tenantId: number, expenseId: number): Promise<IExpense> {
const { Expense } = this.tenancy.models(tenantId);
const expense = await Expense.query().findById(expenseId)
.withGraphFetched('paymentAccount')
.withGraphFetched('media')
.withGraphFetched('categories');
if (!expense) {
throw new ServiceError(ERRORS.EXPENSE_NOT_FOUND);
}
return expense;
}
}

View File

@@ -108,7 +108,7 @@ export default class ItemCategoriesService implements IItemCategoriesService {
this.logger.info('[items] validate sell account existance.', { tenantId, sellAccountId });
const incomeType = await accountTypeRepository.getByKey('income');
const foundAccount = await accountRepository.getById(sellAccountId);
const foundAccount = await accountRepository.findById(sellAccountId);
if (!foundAccount) {
this.logger.info('[items] sell account not found.', { tenantId, sellAccountId });
@@ -130,7 +130,7 @@ export default class ItemCategoriesService implements IItemCategoriesService {
this.logger.info('[items] validate cost account existance.', { tenantId, costAccountId });
const COGSType = await accountTypeRepository.getByKey('cost_of_goods_sold');
const foundAccount = await accountRepository.getById(costAccountId)
const foundAccount = await accountRepository.findById(costAccountId)
if (!foundAccount) {
this.logger.info('[items] cost account not found.', { tenantId, costAccountId });
@@ -152,7 +152,7 @@ export default class ItemCategoriesService implements IItemCategoriesService {
this.logger.info('[items] validate inventory account existance.', { tenantId, inventoryAccountId });
const otherAsset = await accountTypeRepository.getByKey('other_asset');
const foundAccount = await accountRepository.getById(inventoryAccountId);
const foundAccount = await accountRepository.findById(inventoryAccountId);
if (!foundAccount) {
this.logger.info('[items] inventory account not found.', { tenantId, inventoryAccountId });

View File

@@ -85,7 +85,7 @@ export default class ItemsService implements IItemsService {
this.logger.info('[items] validate cost account existance.', { tenantId, costAccountId });
const COGSType = await accountTypeRepository.getByKey('cost_of_goods_sold');
const foundAccount = await accountRepository.getById(costAccountId)
const foundAccount = await accountRepository.findById(costAccountId)
if (!foundAccount) {
this.logger.info('[items] cost account not found.', { tenantId, costAccountId });
@@ -106,7 +106,7 @@ export default class ItemsService implements IItemsService {
this.logger.info('[items] validate sell account existance.', { tenantId, sellAccountId });
const incomeType = await accountTypeRepository.getByKey('income');
const foundAccount = await accountRepository.getById(sellAccountId);
const foundAccount = await accountRepository.findById(sellAccountId);
if (!foundAccount) {
this.logger.info('[items] sell account not found.', { tenantId, sellAccountId });
@@ -127,7 +127,7 @@ export default class ItemsService implements IItemsService {
this.logger.info('[items] validate inventory account existance.', { tenantId, inventoryAccountId });
const otherAsset = await accountTypeRepository.getByKey('other_asset');
const foundAccount = await accountRepository.getById(inventoryAccountId);
const foundAccount = await accountRepository.findById(inventoryAccountId);
if (!foundAccount) {
this.logger.info('[items] inventory account not found.', { tenantId, inventoryAccountId });

View File

@@ -0,0 +1,223 @@
import fs from 'fs';
import { Service, Inject } from 'typedi';
import TenancyService from 'services/Tenancy/TenancyService';
import { ServiceError } from "exceptions";
import { IMedia, IMediaService } from 'interfaces';
import { difference } from 'lodash';
const fsPromises = fs.promises;
const ERRORS = {
MINETYPE_NOT_SUPPORTED: 'MINETYPE_NOT_SUPPORTED',
MEDIA_NOT_FOUND: 'MEDIA_NOT_FOUND',
MODEL_NAME_HAS_NO_MEDIA: 'MODEL_NAME_HAS_NO_MEDIA',
MODEL_ID_NOT_FOUND: 'MODEL_ID_NOT_FOUND',
MEDIA_IDS_NOT_FOUND: 'MEDIA_IDS_NOT_FOUND',
MEDIA_LINK_EXISTS: 'MEDIA_LINK_EXISTS'
}
const publicPath = 'storage/app/public/';
const attachmentsMimes = ['image/png', 'image/jpeg'];
@Service()
export default class MediaService implements IMediaService {
@Inject('logger')
logger: any;
@Inject()
tenancy: TenancyService;
@Inject('repositories')
sysRepositories: any;
/**
* Retrieve media model or throw not found error
* @param tenantId
* @param mediaId
*/
async getMediaOrThrowError(tenantId: number, mediaId: number) {
const { Media } = this.tenancy.models(tenantId);
const foundMedia = await Media.query().findById(mediaId);
if (!foundMedia) {
throw new ServiceError(ERRORS.MEDIA_NOT_FOUND);
}
return foundMedia;
}
/**
* Retreive media models by the given ids or throw not found error.
* @param {number} tenantId
* @param {number[]} mediaIds
*/
async getMediaByIdsOrThrowError(tenantId: number, mediaIds: number[]) {
const { Media } = this.tenancy.models(tenantId);
const foundMedia = await Media.query().whereIn('id', mediaIds);
const storedMediaIds = foundMedia.map((m) => m.id);
const notFoundMedia = difference(mediaIds, storedMediaIds);
if (notFoundMedia.length > 0) {
throw new ServiceError(ERRORS.MEDIA_IDS_NOT_FOUND);
}
return foundMedia;
}
/**
* Validates the model name and id.
* @param {number} tenantId
* @param {string} modelName
* @param {number} modelId
*/
async validateModelNameAndIdExistance(tenantId: number, modelName: string, modelId: number) {
const models = this.tenancy.models(tenantId);
this.logger.info('[media] trying to validate model name and id.', { tenantId, modelName, modelId });
if (!models[modelName]) {
this.logger.info('[media] model name not found.', { tenantId, modelName, modelId });
throw new ServiceError(ERRORS.MODEL_NAME_HAS_NO_MEDIA);
}
if (!models[modelName].media) {
this.logger.info('[media] model is not media-able.', { tenantId, modelName, modelId });
throw new ServiceError(ERRORS.MODEL_NAME_HAS_NO_MEDIA);
}
const foundModel = await models[modelName].query().findById(modelId);
if (!foundModel) {
this.logger.info('[media] model is not found.', { tenantId, modelName, modelId });
throw new ServiceError(ERRORS.MODEL_ID_NOT_FOUND);
}
}
/**
* Validates the media existance.
* @param {number} tenantId
* @param {number} mediaId
* @param {number} modelId
* @param {string} modelName
*/
async validateMediaLinkExistance(
tenantId: number,
mediaId: number,
modelId: number,
modelName: string
) {
const { MediaLink } = this.tenancy.models(tenantId);
const foundMediaLinks = await MediaLink.query()
.where('media_id', mediaId)
.where('model_id', modelId)
.where('model_name', modelName);
if (foundMediaLinks.length > 0) {
throw new ServiceError(ERRORS.MEDIA_LINK_EXISTS);
}
}
/**
* Links the given media to the specific media-able model resource.
* @param {number} tenantId
* @param {number} mediaId
* @param {number} modelId
* @param {string} modelType
*/
async linkMedia(tenantId: number, mediaId: number, modelId: number, modelName: string) {
this.logger.info('[media] trying to link media.', { tenantId, mediaId, modelId, modelName });
const { MediaLink } = this.tenancy.models(tenantId);
await this.validateMediaLinkExistance(tenantId, mediaId, modelId, modelName);
const media = await this.getMediaOrThrowError(tenantId, mediaId);
await this.validateModelNameAndIdExistance(tenantId, modelName, modelId);
await MediaLink.query().insert({ mediaId, modelId, modelName });
}
/**
* Retrieve media metadata.
* @param {number} tenantId - Tenant id.
* @param {number} mediaId - Media id.
* @return {Promise<IMedia>}
*/
public async getMedia(tenantId: number, mediaId: number): Promise<IMedia> {
this.logger.info('[media] try to get media.', { tenantId, mediaId });
return this.getMediaOrThrowError(tenantId, mediaId);
}
/**
* Deletes the given media.
* @param {number} tenantId
* @param {number} mediaId
* @return {Promise<void>}
*/
public async deleteMedia(tenantId: number, mediaId: number|number[]): Promise<void> {
const { Media, MediaLink } = this.tenancy.models(tenantId);
const { tenantRepository } = this.sysRepositories;
this.logger.info('[media] trying to delete media.', { tenantId, mediaId });
const mediaIds = Array.isArray(mediaId) ? mediaId : [mediaId];
const tenant = await tenantRepository.getById(tenantId);
const media = await this.getMediaByIdsOrThrowError(tenantId, mediaIds);
const tenantPath = `${publicPath}${tenant.organizationId}`;
const unlinkOpers = [];
media.forEach((mediaModel) => {
const oper = fsPromises.unlink(`${tenantPath}/${mediaModel.attachmentFile}`);
unlinkOpers.push(oper);
});
await Promise.all(unlinkOpers)
.then((resolved) => {
resolved.forEach(() => {
this.logger.info('[attachment] file has been deleted.');
});
})
.catch((errors) => {
this.logger.info('[attachment] Delete item attachment file delete failed.', { errors });
});
await MediaLink.query().whereIn('media_id', mediaIds).delete();
await Media.query().whereIn('id', mediaIds).delete();
}
/**
* Uploads the given attachment.
* @param {number} tenantId -
* @param {any} attachment -
* @return {Promise<IMedia>}
*/
public async upload(tenantId: number, attachment: any, modelName?: string, modelId?: number): Promise<IMedia> {
const { tenantRepository } = this.sysRepositories;
const { Media } = this.tenancy.models(tenantId);
this.logger.info('[media] trying to upload media.', { tenantId });
const tenant = await tenantRepository.getById(tenantId);
const fileName = `${attachment.md5}.png`;
// Validate the attachment.
if (attachment && attachmentsMimes.indexOf(attachment.mimetype) === -1) {
throw new ServiceError(ERRORS.MINETYPE_NOT_SUPPORTED);
}
if (modelName && modelId) {
await this.validateModelNameAndIdExistance(tenantId, modelName, modelId);
}
try {
await attachment.mv(`${publicPath}${tenant.organizationId}/${fileName}`);
this.logger.info('[attachment] uploaded successfully');
} catch (error) {
this.logger.info('[attachment] uploading failed.', { error });
}
const media = await Media.query().insertGraph({
attachmentFile: `${fileName}`,
...(modelName && modelId) ? {
links: [{
modelName,
modelId,
}]
} : {},
});
this.logger.info('[media] uploaded successfully.', { tenantId, fileName, modelName, modelId });
return media;
}
}