feat: Track storage usage

This commit is contained in:
Maksim Eltyshev
2025-08-23 00:03:20 +02:00
parent 2f4bcb0583
commit 4d77a1f596
89 changed files with 1052 additions and 304 deletions

View File

@@ -0,0 +1,36 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const makeWhereQueryBuilder = (Model) => (criteria) => {
if (_.isPlainObject(criteria)) {
if (Object.keys(criteria).length === 0) {
throw new Error('Empty criteria');
}
const parts = [];
const values = [];
// eslint-disable-next-line no-restricted-syntax
for (const [key, value] of Object.entries(criteria)) {
// eslint-disable-next-line no-underscore-dangle
const columnName = Model._transformer._transformations[key];
if (!columnName) {
throw new Error('Unknown column');
}
parts.push(`${columnName} = $${index + 1}`);
values.push(value);
}
return [parts.join(' AND '), values];
}
return ['id = $1', [criteria]];
};
module.exports = {
makeWhereQueryBuilder,
};

View File

@@ -13,25 +13,24 @@ const create = (arrayOfValues) => {
const arrayOfFileValues = arrayOfValues.filter(({ type }) => type === Attachment.Types.FILE);
if (arrayOfFileValues.length > 0) {
const arrayOfValuesByFileReferenceId = _.groupBy(arrayOfFileValues, 'data.fileReferenceId');
const arrayOfValuesByUploadedFileId = _.groupBy(arrayOfFileValues, 'data.uploadedFileId');
const uploadedFileIds = Object.keys(arrayOfValuesByUploadedFileId);
const fileReferenceIds = Object.keys(arrayOfValuesByFileReferenceId);
const fileReferenceIdsByTotal = Object.entries(arrayOfValuesByFileReferenceId).reduce(
(result, [fileReferenceId, arrayOfValuesItem]) => ({
const uploadedFileIdsByTotal = Object.entries(arrayOfValuesByUploadedFileId).reduce(
(result, [uploadedFileId, arrayOfValuesItem]) => ({
...result,
[arrayOfValuesItem.length]: [...(result[arrayOfValuesItem.length] || []), fileReferenceId],
[arrayOfValuesItem.length]: [...(result[arrayOfValuesItem.length] || []), uploadedFileId],
}),
{},
);
return sails.getDatastore().transaction(async (db) => {
const queryValues = [];
let query = `UPDATE file_reference SET total = total + CASE `;
let query = `UPDATE uploaded_file SET references_total = references_total + CASE `;
Object.entries(fileReferenceIdsByTotal).forEach(([total, fileReferenceIdsItem]) => {
const inValues = fileReferenceIdsItem.map((fileReferenceId) => {
queryValues.push(fileReferenceId);
Object.entries(uploadedFileIdsByTotal).forEach(([total, uploadedFileIdsItem]) => {
const inValues = uploadedFileIdsItem.map((uploadedFileId) => {
queryValues.push(uploadedFileId);
return `$${queryValues.length}`;
});
@@ -39,25 +38,25 @@ const create = (arrayOfValues) => {
query += `WHEN id IN (${inValues.join(', ')}) THEN $${queryValues.length}::int `;
});
const inValues = fileReferenceIds.map((fileReferenceId) => {
queryValues.push(fileReferenceId);
const inValues = uploadedFileIds.map((uploadedFileId) => {
queryValues.push(uploadedFileId);
return `$${queryValues.length}`;
});
queryValues.push(new Date().toISOString());
query += `END, updated_at = $${queryValues.length} WHERE id IN (${inValues.join(', ')}) AND total IS NOT NULL RETURNING id`;
query += `END, updated_at = $${queryValues.length} WHERE id IN (${inValues.join(', ')}) AND references_total IS NOT NULL RETURNING id`;
const queryResult = await sails.sendNativeQuery(query, queryValues).usingConnection(db);
const nextFileReferenceIds = sails.helpers.utils.mapRecords(queryResult.rows);
const nextUploadedFileIds = sails.helpers.utils.mapRecords(queryResult.rows);
if (nextFileReferenceIds.length < fileReferenceIds.length) {
const nextFileReferenceIdsSet = new Set(nextFileReferenceIds);
if (nextUploadedFileIds.length < uploadedFileIds.length) {
const nextUploadedFileIdsSet = new Set(nextUploadedFileIds);
// eslint-disable-next-line no-param-reassign
arrayOfValues = arrayOfValues.filter(
(values) =>
values.type !== Attachment.Types.FILE ||
nextFileReferenceIdsSet.has(values.data.fileReferenceId),
nextUploadedFileIdsSet.has(values.data.uploadedFileId),
);
}
@@ -70,8 +69,6 @@ const create = (arrayOfValues) => {
const createOne = (values) => {
if (values.type === Attachment.Types.FILE) {
const { fileReferenceId } = values.data;
return sails.getDatastore().transaction(async (db) => {
const attachment = await Attachment.create({ ...values })
.fetch()
@@ -79,13 +76,13 @@ const createOne = (values) => {
const queryResult = await sails
.sendNativeQuery(
'UPDATE file_reference SET total = total + 1, updated_at = $1 WHERE id = $2 AND total IS NOT NULL',
[new Date().toISOString(), fileReferenceId],
'UPDATE uploaded_file SET references_total = references_total + 1, updated_at = $1 WHERE id = $2 AND references_total IS NOT NULL',
[new Date().toISOString(), values.data.uploadedFileId],
)
.usingConnection(db);
if (queryResult.rowCount === 0) {
throw 'fileReferenceNotFound';
throw 'uploadedFileNotFound';
}
return attachment;
@@ -129,24 +126,24 @@ const delete_ = (criteria) =>
const attachments = await Attachment.destroy(criteria).fetch().usingConnection(db);
const fileAttachments = attachments.filter(({ type }) => type === Attachment.Types.FILE);
let fileReferences = [];
let uploadedFiles = [];
if (fileAttachments.length > 0) {
const attachmentsByFileReferenceId = _.groupBy(fileAttachments, 'data.fileReferenceId');
const attachmentsByUploadedFileId = _.groupBy(fileAttachments, 'data.uploadedFileId');
const fileReferenceIdsByTotal = Object.entries(attachmentsByFileReferenceId).reduce(
(result, [fileReferenceId, attachmentsItem]) => ({
const uploadedFileIdsByTotal = Object.entries(attachmentsByUploadedFileId).reduce(
(result, [uploadedFileId, attachmentsItem]) => ({
...result,
[attachmentsItem.length]: [...(result[attachmentsItem.length] || []), fileReferenceId],
[attachmentsItem.length]: [...(result[attachmentsItem.length] || []), uploadedFileId],
}),
{},
);
const queryValues = [];
let query = 'UPDATE file_reference SET total = CASE WHEN total = CASE ';
let query = 'UPDATE uploaded_file SET references_total = CASE WHEN references_total = CASE ';
Object.entries(fileReferenceIdsByTotal).forEach(([total, fileReferenceIds]) => {
const inValues = fileReferenceIds.map((fileReferenceId) => {
queryValues.push(fileReferenceId);
Object.entries(uploadedFileIdsByTotal).forEach(([total, uploadedFileIds]) => {
const inValues = uploadedFileIds.map((uploadedFileId) => {
queryValues.push(uploadedFileId);
return `$${queryValues.length}`;
});
@@ -154,11 +151,11 @@ const delete_ = (criteria) =>
query += `WHEN id IN (${inValues.join(', ')}) THEN $${queryValues.length}::int `;
});
query += 'END THEN NULL ELSE total - CASE ';
query += 'END THEN NULL ELSE references_total - CASE ';
Object.entries(fileReferenceIdsByTotal).forEach(([total, fileReferenceIds]) => {
const inValues = fileReferenceIds.map((fileReferenceId) => {
queryValues.push(fileReferenceId);
Object.entries(uploadedFileIdsByTotal).forEach(([total, uploadedFileIds]) => {
const inValues = uploadedFileIds.map((uploadedFileId) => {
queryValues.push(uploadedFileId);
return `$${queryValues.length}`;
});
@@ -166,56 +163,58 @@ const delete_ = (criteria) =>
query += `WHEN id IN (${inValues.join(', ')}) THEN $${queryValues.length}::int `;
});
const inValues = Object.keys(attachmentsByFileReferenceId).map((fileReferenceId) => {
queryValues.push(fileReferenceId);
const inValues = Object.keys(attachmentsByUploadedFileId).map((uploadedFileId) => {
queryValues.push(uploadedFileId);
return `$${queryValues.length}`;
});
queryValues.push(new Date().toISOString());
query += `END END, updated_at = $${queryValues.length} WHERE id IN (${inValues.join(', ')}) AND total IS NOT NULL RETURNING id, total`;
query += `END END, updated_at = $${queryValues.length} WHERE id IN (${inValues.join(', ')}) AND references_total IS NOT NULL RETURNING *`;
const queryResult = await sails.sendNativeQuery(query, queryValues).usingConnection(db);
fileReferences = queryResult.rows;
uploadedFiles = queryResult.rows.map((row) => ({
id: row.id,
type: row.type,
mimeType: row.mime_type,
size: row.size,
referencesTotal: row.references_total,
createdAt: row.created_at,
updatedAt: row.updated_at,
}));
}
return {
attachments,
fileReferences,
};
return { attachments, uploadedFiles };
});
const deleteOne = async (criteria, { isFile } = {}) => {
let fileReference = null;
const deleteOne = (criteria) =>
sails.getDatastore().transaction(async (db) => {
const attachment = await Attachment.destroyOne(criteria).usingConnection(db);
if (isFile) {
return sails.getDatastore().transaction(async (db) => {
const attachment = await Attachment.destroyOne(criteria).usingConnection(db);
let uploadedFile;
if (attachment.type === Attachment.Types.FILE) {
const queryResult = await sails
.sendNativeQuery(
'UPDATE uploaded_file SET references_total = CASE WHEN references_total > 1 THEN references_total - 1 END, updated_at = $1 WHERE id = $2 RETURNING *',
[new Date().toISOString(), attachment.data.uploadedFileId],
)
.usingConnection(db);
if (attachment.type === Attachment.Types.FILE) {
const queryResult = await sails
.sendNativeQuery(
'UPDATE file_reference SET total = CASE WHEN total > 1 THEN total - 1 END, updated_at = $1 WHERE id = $2 RETURNING id, total',
[new Date().toISOString(), attachment.data.fileReferenceId],
)
.usingConnection(db);
const [row] = queryResult.rows;
[fileReference] = queryResult.rows;
}
return {
attachment,
fileReference,
uploadedFile = {
id: row.id,
type: row.type,
mimeType: row.mime_type,
size: row.size,
referencesTotal: row.references_total,
createdAt: row.created_at,
updatedAt: row.updated_at,
};
});
}
}
const attachment = await Attachment.destroyOne(criteria);
return {
attachment,
fileReference,
};
};
return { attachment, uploadedFile };
});
module.exports = {
create,

View File

@@ -7,7 +7,25 @@ const defaultFind = (criteria) => BackgroundImage.find(criteria).sort('id');
/* Query methods */
const createOne = (values) => BackgroundImage.create({ ...values }).fetch();
const createOne = (values) =>
sails.getDatastore().transaction(async (db) => {
const backgroundImage = await BackgroundImage.create({ ...values })
.fetch()
.usingConnection(db);
const queryResult = await sails
.sendNativeQuery(
'UPDATE uploaded_file SET references_total = references_total + 1, updated_at = $1 WHERE id = $2 AND references_total IS NOT NULL',
[new Date().toISOString(), values.uploadedFileId],
)
.usingConnection(db);
if (queryResult.rowCount === 0) {
throw 'uploadedFileNotFound';
}
return backgroundImage;
});
const getByIds = (ids) => defaultFind(ids);
@@ -34,9 +52,99 @@ const getOneById = (id, { projectId } = {}) => {
};
// eslint-disable-next-line no-underscore-dangle
const delete_ = (criteria) => BackgroundImage.destroy(criteria).fetch();
const delete_ = (criteria) =>
sails.getDatastore().transaction(async (db) => {
const backgroundImages = await BackgroundImage.destroy(criteria).fetch().usingConnection(db);
const deleteOne = (criteria) => BackgroundImage.destroyOne(criteria);
let uploadedFiles = [];
if (backgroundImages.length > 0) {
const backgroundImagesByUploadedFileId = _.groupBy(backgroundImages, 'uploadedFileId');
const uploadedFileIdsByTotal = Object.entries(backgroundImagesByUploadedFileId).reduce(
(result, [uploadedFileId, backgroundImagesItem]) => ({
...result,
[backgroundImagesItem.length]: [
...(result[backgroundImagesItem.length] || []),
uploadedFileId,
],
}),
{},
);
const queryValues = [];
let query = 'UPDATE uploaded_file SET references_total = CASE WHEN references_total = CASE ';
Object.entries(uploadedFileIdsByTotal).forEach(([total, uploadedFileIds]) => {
const inValues = uploadedFileIds.map((uploadedFileId) => {
queryValues.push(uploadedFileId);
return `$${queryValues.length}`;
});
queryValues.push(total);
query += `WHEN id IN (${inValues.join(', ')}) THEN $${queryValues.length}::int `;
});
query += 'END THEN NULL ELSE references_total - CASE ';
Object.entries(uploadedFileIdsByTotal).forEach(([total, uploadedFileIds]) => {
const inValues = uploadedFileIds.map((uploadedFileId) => {
queryValues.push(uploadedFileId);
return `$${queryValues.length}`;
});
queryValues.push(total);
query += `WHEN id IN (${inValues.join(', ')}) THEN $${queryValues.length}::int `;
});
const inValues = Object.keys(backgroundImagesByUploadedFileId).map((uploadedFileId) => {
queryValues.push(uploadedFileId);
return `$${queryValues.length}`;
});
queryValues.push(new Date().toISOString());
query += `END END, updated_at = $${queryValues.length} WHERE id IN (${inValues.join(', ')}) AND references_total IS NOT NULL RETURNING *`;
const queryResult = await sails.sendNativeQuery(query, queryValues).usingConnection(db);
uploadedFiles = queryResult.rows.map((row) => ({
id: row.id,
type: row.type,
mimeType: row.mime_type,
size: row.size,
referencesTotal: row.references_total,
createdAt: row.created_at,
updatedAt: row.updated_at,
}));
}
return { backgroundImages, uploadedFiles };
});
const deleteOne = (criteria) =>
sails.getDatastore().transaction(async (db) => {
const backgroundImage = await BackgroundImage.destroyOne(criteria).usingConnection(db);
const queryResult = await sails
.sendNativeQuery(
'UPDATE uploaded_file SET references_total = CASE WHEN references_total > 1 THEN references_total - 1 END, updated_at = $1 WHERE id = $2 RETURNING *',
[new Date().toISOString(), backgroundImage.uploadedFileId],
)
.usingConnection(db);
const [row] = queryResult.rows;
uploadedFile = {
id: row.id,
type: row.type,
mimeType: row.mime_type,
size: row.size,
referencesTotal: row.references_total,
createdAt: row.created_at,
updatedAt: row.updated_at,
};
return { backgroundImage, uploadedFile };
});
module.exports = {
createOne,

View File

@@ -217,18 +217,12 @@ const update = async (criteria, values) => {
.usingConnection(db);
}
return {
cards,
tasks,
};
return { cards, tasks };
});
}
const cards = await Card.update(criteria).set(values).fetch();
return {
cards,
};
return { cards };
};
const updateOne = async (criteria, values) => {
@@ -250,18 +244,12 @@ const updateOne = async (criteria, values) => {
.usingConnection(db);
}
return {
card,
tasks,
};
return { card, tasks };
});
}
const card = await Card.updateOne(criteria).set({ ...values });
return {
card,
};
return { card };
};
// eslint-disable-next-line no-underscore-dangle

View File

@@ -26,16 +26,16 @@ const createOrUpdateOne = async (values) => {
new Date().toISOString(),
]);
const [customFieldValue] = queryResult.rows;
const [row] = queryResult.rows;
return {
id: customFieldValue.id,
cardId: customFieldValue.card_id,
customFieldGroupId: customFieldValue.custom_field_group_id,
customFieldId: customFieldValue.custom_field_id,
content: customFieldValue.content,
createdAt: customFieldValue.created_at,
updatedAt: customFieldValue.updated_at,
id: row.id,
cardId: row.card_id,
customFieldGroupId: row.custom_field_group_id,
customFieldId: row.custom_field_id,
content: row.content,
createdAt: row.created_at,
updatedAt: row.updated_at,
};
};

View File

@@ -3,6 +3,10 @@
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const { makeWhereQueryBuilder } = require('../helpers');
const buildWhereQuery = makeWhereQueryBuilder(List);
const defaultFind = (criteria, { sort = 'id' } = {}) => List.find(criteria).sort(sort);
/* Query methods */
@@ -48,8 +52,20 @@ const getOneTrashByBoardId = (boardId) =>
});
const updateOne = async (criteria, values) => {
if (values.type) {
if (!_.isUndefined(values.type)) {
return sails.getDatastore().transaction(async (db) => {
const [whereQuery, whereQueryValues] = buildWhereQuery(criteria);
const queryResult = await sails
.sendNativeQuery(`SELECT type FROM list WHERE ${whereQuery} FOR UPDATE`, whereQueryValues)
.usingConnection(db);
if (queryResult.rowCount === 0) {
return { list: null };
}
const [{ type: prevType }] = queryResult.rows;
const list = await List.updateOne(criteria)
.set({ ...values })
.usingConnection(db);
@@ -58,7 +74,7 @@ const updateOne = async (criteria, values) => {
let tasks = [];
if (list) {
const prevTypeState = List.TYPE_STATE_BY_TYPE[prevList.type];
const prevTypeState = List.TYPE_STATE_BY_TYPE[prevType];
const typeState = List.TYPE_STATE_BY_TYPE[list.type];
let isClosed;
@@ -94,19 +110,12 @@ const updateOne = async (criteria, values) => {
}
}
return {
list,
cards,
tasks,
};
return { list, cards, tasks };
});
}
const list = await List.updateOne(criteria).set({ ...values });
return {
list,
};
return { list };
};
// eslint-disable-next-line no-underscore-dangle

View File

@@ -0,0 +1,12 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
/* Query methods */
const getOneMain = () => StorageUsage.findOne(StorageUsage.MAIN_ID);
module.exports = {
getOneMain,
};

View File

@@ -0,0 +1,50 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const COLUMN_NAME_BY_TYPE = {
[UploadedFile.Types.USER_AVATAR]: 'user_avatars',
[UploadedFile.Types.BACKGROUND_IMAGE]: 'background_images',
[UploadedFile.Types.ATTACHMENT]: 'attachments',
};
/* Query methods */
const createOne = (values) =>
sails.getDatastore().transaction(async (db) => {
const uploadedFile = await UploadedFile.create({ ...values })
.fetch()
.usingConnection(db);
const columnName = COLUMN_NAME_BY_TYPE[uploadedFile.type];
await sails
.sendNativeQuery(
`UPDATE storage_usage SET total = total + $1, ${columnName} = ${columnName} + $1, updated_at = $2 WHERE id = $3`,
[uploadedFile.size, new Date().toISOString(), StorageUsage.MAIN_ID],
)
.usingConnection(db);
return uploadedFile;
});
const deleteOne = (criteria) =>
sails.getDatastore().transaction(async (db) => {
const uploadedFile = await UploadedFile.destroyOne(criteria).usingConnection(db);
const columnName = COLUMN_NAME_BY_TYPE[uploadedFile.type];
await sails
.sendNativeQuery(
`UPDATE storage_usage SET total = total - $1, ${columnName} = ${columnName} - $1, updated_at = $2 WHERE id = $3`,
[uploadedFile.size, new Date().toISOString(), StorageUsage.MAIN_ID],
)
.usingConnection(db);
return uploadedFile;
});
module.exports = {
createOne,
deleteOne,
};

View File

@@ -3,12 +3,28 @@
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const { makeWhereQueryBuilder } = require('../helpers');
const hasAvatarChanged = (avatar, prevAvatar) => {
if (!avatar && !prevAvatar) {
return false;
}
if (!avatar || !prevAvatar) {
return true;
}
return avatar.uploadedFileId !== prevAvatar.uploadedFileId;
};
const buildWhereQuery = makeWhereQueryBuilder(User);
const defaultFind = (criteria) => User.find(criteria).sort('id');
/* Query methods */
const createOne = (values) => {
if (sails.config.custom.activeUsersLimit) {
if (!_.isNil(sails.config.custom.activeUsersLimit)) {
return sails.getDatastore().transaction(async (db) => {
const queryResult = await sails
.sendNativeQuery('SELECT NULL FROM user_account WHERE is_deactivated = $1 FOR UPDATE', [
@@ -62,29 +78,119 @@ const getOneActiveByEmailOrUsername = (emailOrUsername) => {
});
};
const updateOne = (criteria, values) => {
if (values.isDeactivated === false && sails.config.custom.activeUsersLimit) {
return sails.getDatastore().transaction(async (db) => {
const queryResult = await sails
.sendNativeQuery('SELECT NULL FROM user_account WHERE is_deactivated = $1 FOR UPDATE', [
false,
])
.usingConnection(db);
const updateOne = async (criteria, values) => {
const enforceActiveLimit =
values.isDeactivated === false && !_.isNil(sails.config.custom.activeUsersLimit);
if (queryResult.rowCount >= sails.config.custom.activeUsersLimit) {
throw 'activeLimitReached';
if (!_.isUndefined(values.avatar) || enforceActiveLimit) {
return sails.getDatastore().transaction(async (db) => {
if (enforceActiveLimit) {
const queryResult = await sails
.sendNativeQuery('SELECT NULL FROM user_account WHERE is_deactivated = $1 FOR UPDATE', [
false,
])
.usingConnection(db);
if (queryResult.rowCount >= sails.config.custom.activeUsersLimit) {
throw 'activeLimitReached';
}
}
return User.updateOne(criteria)
let prevAvatar;
if (!_.isUndefined(values.avatar)) {
const [whereQuery, whereQueryValues] = buildWhereQuery(criteria);
const queryResult = await sails
.sendNativeQuery(
`SELECT avatar FROM user_account WHERE ${whereQuery} FOR UPDATE`,
whereQueryValues,
)
.usingConnection(db);
if (queryResult.rowCount === 0) {
return { user: null };
}
[{ avatar: prevAvatar }] = queryResult.rows;
}
const user = await User.updateOne(criteria)
.set({ ...values })
.usingConnection(db);
let uploadedFile;
if (hasAvatarChanged(user.avatar, prevAvatar)) {
if (prevAvatar) {
const queryResult = await sails
.sendNativeQuery(
'UPDATE uploaded_file SET references_total = CASE WHEN references_total > 1 THEN references_total - 1 END, updated_at = $1 WHERE id = $2 RETURNING *',
[new Date().toISOString(), prevAvatar.uploadedFileId],
)
.usingConnection(db);
const [row] = queryResult.rows;
uploadedFile = {
id: row.id,
type: row.type,
mimeType: row.mime_type,
size: row.size,
referencesTotal: row.references_total,
createdAt: row.created_at,
updatedAt: row.updated_at,
};
}
if (user.avatar) {
const queryResult = await sails
.sendNativeQuery(
'UPDATE uploaded_file SET references_total = references_total + 1, updated_at = $1 WHERE id = $2 AND references_total IS NOT NULL',
[new Date().toISOString(), user.avatar.uploadedFileId],
)
.usingConnection(db);
if (queryResult.rowCount === 0) {
throw 'uploadedFileNotFound';
}
}
}
return { user, uploadedFile };
});
}
return User.updateOne(criteria).set({ ...values });
const user = await User.updateOne(criteria).set({ ...values });
return { user };
};
const deleteOne = (criteria) => User.destroyOne(criteria);
const deleteOne = (criteria) =>
sails.getDatastore().transaction(async (db) => {
const user = await User.destroyOne(criteria).usingConnection(db);
let uploadedFile;
if (user.avatar) {
const queryResult = await sails
.sendNativeQuery(
'UPDATE uploaded_file SET references_total = CASE WHEN references_total > 1 THEN references_total - 1 END, updated_at = $1 WHERE id = $2 RETURNING *',
[new Date().toISOString(), user.avatar.uploadedFileId],
)
.usingConnection(db);
const [row] = queryResult.rows;
uploadedFile = {
id: row.id,
type: row.type,
mimeType: row.mime_type,
size: row.size,
referencesTotal: row.references_total,
createdAt: row.created_at,
updatedAt: row.updated_at,
};
}
return { user, uploadedFile };
});
module.exports = {
createOne,