change folder uid for pathId

This commit is contained in:
Pierre Noël 2022-06-03 16:21:52 +02:00
parent 66a44454f3
commit 1e66f567f8
15 changed files with 147 additions and 125 deletions

View File

@ -38,23 +38,6 @@
"localized": true
}
}
},
"description": {
"pluginOptions": {
"i18n": {
"localized": true
}
},
"type": "string",
"maxLength": 3
},
"time": {
"pluginOptions": {
"i18n": {
"localized": true
}
},
"type": "time"
}
}
}

View File

@ -33,6 +33,7 @@ const createMetadata = (models = []) => {
...model.attributes,
},
lifecycles: model.lifecycles || {},
indexes: model.indexes || [],
});
}

View File

@ -12,6 +12,7 @@ const createQueryBuilder = (uid, db) => {
type: 'select',
select: [],
count: null,
max: null,
first: false,
data: null,
where: [],
@ -77,6 +78,14 @@ const createQueryBuilder = (uid, db) => {
return this;
},
max(column) {
state.type = 'max';
state.max = column;
state.first = true;
return this;
},
where(where = {}) {
if (!_.isPlainObject(where)) {
throw new Error('Where must be an object');
@ -296,6 +305,10 @@ const createQueryBuilder = (uid, db) => {
qb.count({ count: state.count });
break;
}
case 'max': {
qb.max(helpers.toColumnName(meta, state.max), { as: state.max });
break;
}
case 'insert': {
qb.insert(state.data);

View File

@ -41,8 +41,7 @@
"react-redux": "7.2.3",
"react-router": "^5.2.0",
"react-router-dom": "5.2.0",
"sharp": "0.30.6",
"uuid": "8.3.2"
"sharp": "0.30.6"
},
"engines": {
"node": ">=12.22.0 <=16.x.x",

View File

@ -101,4 +101,12 @@ module.exports = {
private: true,
},
},
// experimental feature:
indexes: [
{
name: 'upload_files_folder_path_index',
columns: ['folder_path'],
type: null,
},
],
};

View File

@ -24,8 +24,8 @@ module.exports = {
min: 1,
required: true,
},
uid: {
type: 'string',
pathId: {
type: 'integer',
unique: true,
required: true,
},
@ -53,4 +53,17 @@ module.exports = {
required: true,
},
},
// experimental feature:
indexes: [
{
name: 'upload_folders_path_id_index',
columns: ['path_id'],
type: 'unique',
},
{
name: 'upload_folders_path_index',
columns: ['path'],
type: 'unique',
},
],
};

View File

@ -66,7 +66,7 @@ module.exports = {
// fetch folders
const existingFolders = await strapi.db
.queryBuilder(FOLDER_MODEL_UID)
.select(['id', 'uid', 'path'])
.select(['id', 'pathId', 'path'])
.where({ id: { $in: folderIds } })
.transacting(trx)
.forUpdate()
@ -121,6 +121,11 @@ module.exports = {
.execute();
for (const existingFolder of existingFolders) {
const replaceQuery =
strapi.db.dialect.client === 'sqlite'
? '? || substring(??, ?)'
: 'CONCAT(?, substring(??, ?))';
// update path for folders themselves & folders below
await strapi.db
.connection(folderTable)
@ -128,10 +133,10 @@ module.exports = {
.where(pathColName, 'like', `${existingFolder.path}%`)
.update(
pathColName,
strapi.db.connection.raw('REPLACE(??, ?, ?)', [
strapi.db.connection.raw(replaceQuery, [
joinBy('/', destinationFolderPath, existingFolder.pathId),
pathColName,
existingFolder.path,
joinBy('/', destinationFolderPath, existingFolder.uid),
existingFolder.path.length + 1,
])
);
@ -142,10 +147,10 @@ module.exports = {
.where(folderPathColName, 'like', `${existingFolder.path}%`)
.update(
folderPathColName,
strapi.db.connection.raw('REPLACE(??, ?, ?)', [
strapi.db.connection.raw(replaceQuery, [
joinBy('/', destinationFolderPath, existingFolder.pathId),
folderPathColName,
existingFolder.path,
joinBy('/', destinationFolderPath, existingFolder.uid),
existingFolder.path.length + 1,
])
);
}

View File

@ -20,6 +20,7 @@ const validateStructureMoveManyFoldersFilesSchema = yup
destinationFolderId: yup
.strapiID()
.nullable()
.defined()
.test('folder-exists', 'destination folder does not exist', folderExists),
fileIds: yup.array().of(yup.strapiID().required()),
folderIds: yup.array().of(yup.strapiID().required()),
@ -37,7 +38,7 @@ const validateDuplicatesMoveManyFoldersFilesSchema = yup
fields: ['name'],
filters: { id: { $in: folderIds } },
});
// TODO: handle when parent is null
const existingFolders = await strapi.entityService.findMany(FOLDER_MODEL_UID, {
fields: ['name'],
filters: { parent: { id: destinationFolderId } },

View File

@ -2,7 +2,7 @@
const { getFolderPath, deleteByIds } = require('../file');
const folderPath = '/9bc2352b-e29b-4ba3-810f-7b91033222de';
const folderPath = '/1';
describe('file', () => {
describe('getFolderPath', () => {

View File

@ -1,19 +1,22 @@
'use strict';
const { setPathAndUID } = require('../folder');
const { setPathIdAndPath } = require('../folder');
const folderUID = '9bc2352b-e29b-4ba3-810f-7b91033222de';
const uuidRegex = /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i;
const rootPathRegex = /^\/[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i;
const folderPathRegex = new RegExp(
'^/' + folderUID + '/[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$',
'i'
);
const folderUID = '1';
const rootPathRegex = /^\/[0-9]*$/i;
const folderPathRegex = new RegExp('^/' + folderUID + '/[0-9]*$', 'i');
describe('folder', () => {
describe('setPathAndUID', () => {
describe('setPathIdAndPath', () => {
beforeAll(() => {
global.strapi = {
db: {
queryBuilder: () => ({
max: () => ({
execute: () => ({ pathId: 2 }),
}),
}),
},
entityService: {
findOne: jest.fn(() => ({ path: `/${folderUID}` })),
},
@ -26,12 +29,12 @@ describe('folder', () => {
[{ parent: null }, rootPathRegex],
])('inputs %s', async (folder, expectedPath) => {
const clonedFolder = { ...folder };
const result = await setPathAndUID(clonedFolder);
const result = await setPathIdAndPath(clonedFolder);
expect(result).toBe(clonedFolder);
expect(result).toMatchObject({
...folder,
uid: expect.stringMatching(uuidRegex),
pathId: expect.any(Number),
path: expect.stringMatching(expectedPath),
});
});

View File

@ -1,15 +1,17 @@
'use strict';
const uuid = require('uuid').v4;
const { keys, sortBy, omit, map, isUndefined } = require('lodash/fp');
const { joinBy, setCreatorFields } = require('@strapi/utils');
const { FOLDER_MODEL_UID, FILE_MODEL_UID } = require('../constants');
const { getService } = require('../utils');
const generateUID = () => uuid();
const setPathIdAndPath = async folder => {
const { pathId: maxPathId } = await strapi.db
.queryBuilder(FOLDER_MODEL_UID)
.max('pathId')
.execute();
const setPathAndUID = async folder => {
const uid = generateUID();
const pathId = maxPathId + 1;
let parentPath = '/';
if (folder.parent) {
const parentFolder = await strapi.entityService.findOne(FOLDER_MODEL_UID, folder.parent);
@ -17,16 +19,15 @@ const setPathAndUID = async folder => {
}
return Object.assign(folder, {
uid,
path: joinBy('/', parentPath, uid),
pathId,
path: joinBy('/', parentPath, pathId),
});
};
const create = async (folderData, { user } = {}) => {
const folderService = getService('folder');
// TODO: wrap with a transaction
let enrichedFolder = await folderService.setPathAndUID(folderData);
let enrichedFolder = await folderService.setPathIdAndPath(folderData);
if (user) {
enrichedFolder = await setCreatorFields({ user })(enrichedFolder);
}
@ -95,7 +96,7 @@ const update = async (id, { name, parent }, { user }) => {
// fetch existing folder
const existingFolder = await strapi.db
.queryBuilder(FOLDER_MODEL_UID)
.select(['uid', 'path'])
.select(['pathId', 'path'])
.where({ id })
.transacting(trx)
.forUpdate()
@ -146,7 +147,7 @@ const update = async (id, { name, parent }, { user }) => {
strapi.db.connection.raw('REPLACE(??, ?, ?)', [
pathColumnName,
existingFolder.path,
joinBy('/', destinationFolderPath, existingFolder.uid),
joinBy('/', destinationFolderPath, existingFolder.pathId),
])
);
@ -160,7 +161,7 @@ const update = async (id, { name, parent }, { user }) => {
strapi.db.connection.raw('REPLACE(??, ?, ?)', [
folderPathColumnName,
existingFolder.path,
joinBy('/', destinationFolderPath, existingFolder.uid),
joinBy('/', destinationFolderPath, existingFolder.pathId),
])
);
@ -228,6 +229,6 @@ module.exports = {
exists,
deleteByIds,
update,
setPathAndUID,
setPathIdAndPath,
getStructure,
};

View File

@ -104,7 +104,7 @@ describe('Bulk actions for folders & files', () => {
id: folder1a.id,
name: 'folder-a-1a',
path: expect.anything(),
uid: expect.anything(),
pathId: expect.anything(),
createdAt: expect.anything(),
updatedAt: expect.anything(),
},
@ -187,8 +187,8 @@ describe('Bulk actions for folders & files', () => {
{
id: folder1a.id,
name: 'folder-b-1a',
path: `${folder1b.path}/${folder1a.uid}`,
uid: expect.anything(),
path: `${folder1b.path}/${folder1a.pathId}`,
pathId: expect.anything(),
createdAt: expect.anything(),
updatedAt: expect.anything(),
},
@ -210,14 +210,14 @@ describe('Bulk actions for folders & files', () => {
expect(folderResults[0]).toMatchObject({ ...folder1, parent: null });
expect(folderResults[1]).toMatchObject({
...folder1a,
path: `${folder1b.path}/${folder1a.uid}`,
path: `${folder1b.path}/${folder1a.pathId}`,
parent: { id: folder1b.id },
updatedAt: expect.anything(),
});
expect(folderResults[2]).toMatchObject({ ...folder1b, parent: { id: folder1.id } });
expect(folderResults[3]).toMatchObject({
...folder1a1,
path: `${folder1b.path}/${folder1a.uid}/${folder1a1.uid}`,
path: `${folder1b.path}/${folder1a.pathId}/${folder1a1.pathId}`,
parent: { id: folder1a.id },
});
@ -244,7 +244,7 @@ describe('Bulk actions for folders & files', () => {
expect(fileResults[2]).toMatchObject({ ...file1b, folder: { id: folder1b.id } });
expect(fileResults[3]).toMatchObject({
...file1a1,
folderPath: `${folder1b.path}/${folder1a.uid}/${folder1a1.uid}`,
folderPath: `${folder1b.path}/${folder1a.pathId}/${folder1a1.pathId}`,
folder: { id: folder1a1.id },
});
@ -296,8 +296,8 @@ describe('Bulk actions for folders & files', () => {
{
id: folder1a.id,
name: 'folder-c-1a',
path: `/${folder1a.uid}`,
uid: expect.anything(),
path: `/${folder1a.pathId}`,
pathId: expect.anything(),
createdAt: expect.anything(),
updatedAt: expect.anything(),
},
@ -319,13 +319,13 @@ describe('Bulk actions for folders & files', () => {
expect(folderResults[0]).toMatchObject({ ...folder1, parent: null });
expect(folderResults[1]).toMatchObject({
...folder1a,
path: `/${folder1a.uid}`,
path: `/${folder1a.pathId}`,
parent: null,
updatedAt: expect.anything(),
});
expect(folderResults[2]).toMatchObject({
...folder1a1,
path: `/${folder1a.uid}/${folder1a1.uid}`,
path: `/${folder1a.pathId}/${folder1a1.pathId}`,
parent: { id: folder1a.id },
});
@ -351,7 +351,7 @@ describe('Bulk actions for folders & files', () => {
});
expect(fileResults[2]).toMatchObject({
...file1a1,
folderPath: `/${folder1a.uid}/${folder1a1.uid}`,
folderPath: `/${folder1a.pathId}/${folder1a1.pathId}`,
folder: { id: folder1a1.id },
});

View File

@ -15,13 +15,8 @@ let data = {
folders: [],
};
const uuidRegex = /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i;
const rootPathRegex = /^\/[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i;
const getFolderPathRegex = uid =>
new RegExp(
'^/' + uid + '/[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$',
'i'
);
const rootPathRegex = /^\/[0-9]*$/i;
const getFolderPathRegex = pathId => new RegExp('^/' + pathId + '/[0-9]*$', 'i');
const createFolder = async (name, parent = null) => {
const res = await rq({
@ -80,12 +75,12 @@ describe('Folder', () => {
expect(res.body.data).toMatchObject({
id: expect.anything(),
name: 'folder 1',
uid: expect.stringMatching(uuidRegex),
pathId: expect.any(Number),
path: expect.stringMatching(rootPathRegex),
createdAt: expect.anything(),
updatedAt: expect.anything(),
});
expect(res.body.data.uid).toBe(res.body.data.path.split('/').pop());
expect(res.body.data.pathId.toString()).toBe(res.body.data.path.split('/').pop());
data.folders.push(res.body.data);
});
@ -103,12 +98,12 @@ describe('Folder', () => {
expect(res.body.data).toMatchObject({
id: expect.anything(),
name: 'folder-2',
uid: expect.stringMatching(uuidRegex),
path: expect.stringMatching(getFolderPathRegex(data.folders[0].uid)),
pathId: expect.any(Number),
path: expect.stringMatching(getFolderPathRegex(data.folders[0].pathId)),
createdAt: expect.anything(),
updatedAt: expect.anything(),
});
expect(res.body.data.uid).toBe(res.body.data.path.split('/').pop());
expect(res.body.data.pathId.toString()).toBe(res.body.data.path.split('/').pop());
data.folders.push(res.body.data);
});
@ -195,7 +190,7 @@ describe('Folder', () => {
});
expect(res.body.data).toMatchObject({
...pick(['id', 'name', 'uid', 'path', 'createAt', 'updatedAt'], data.folders[0]),
...pick(['id', 'name', 'pathId', 'path', 'createAt', 'updatedAt'], data.folders[0]),
children: {
count: expect.anything(),
},
@ -393,7 +388,7 @@ describe('Folder', () => {
expect(res.body.data).toMatchObject({
name: 'folder-00-new',
path: `${folder01.path}/${folder00.uid}`,
path: `${folder01.path}/${folder00.pathId}`,
});
const resFolders = await rq({
@ -408,7 +403,7 @@ describe('Folder', () => {
expect(resFolders.body.data[0]).toMatchObject({ path: folder0.path, parent: null });
expect(resFolders.body.data[1]).toMatchObject({
path: `${folder01.path}/${folder00.uid}`,
path: `${folder01.path}/${folder00.pathId}`,
parent: { id: folder01.id },
});
expect(resFolders.body.data[2]).toMatchObject({
@ -420,7 +415,7 @@ describe('Folder', () => {
parent: { id: folder0.id },
});
expect(resFolders.body.data[4]).toMatchObject({
path: `${folder01.path}/${folder00.uid}/${folder000.uid}`,
path: `${folder01.path}/${folder00.pathId}/${folder000.pathId}`,
parent: { id: folder00.id },
});
@ -434,7 +429,7 @@ describe('Folder', () => {
});
expect(resFiles.body.results[0]).toMatchObject({
folderPath: `${folder01.path}/${folder00.uid}/${folder000.uid}`,
folderPath: `${folder01.path}/${folder00.pathId}/${folder000.pathId}`,
});
expect(resFiles.body.results[1]).toMatchObject({ folderPath: file02.folderPath });
@ -461,7 +456,7 @@ describe('Folder', () => {
expect(res.body.data).toMatchObject({
name: 'folder-test-00-new',
path: `/${folder00.uid}`,
path: `/${folder00.pathId}`,
});
const resFolders = await rq({
@ -476,7 +471,7 @@ describe('Folder', () => {
expect(resFolders.body.data[0]).toMatchObject({ path: folder0.path, parent: null });
expect(resFolders.body.data[1]).toMatchObject({
path: `/${folder00.uid}`,
path: `/${folder00.pathId}`,
parent: null,
});
expect(resFolders.body.data[2]).toMatchObject({
@ -484,7 +479,7 @@ describe('Folder', () => {
parent: { id: folder0.id },
});
expect(resFolders.body.data[3]).toMatchObject({
path: `/${folder00.uid}/${folder000.uid}`,
path: `/${folder00.pathId}/${folder000.pathId}`,
parent: { id: folder00.id },
});
@ -498,7 +493,7 @@ describe('Folder', () => {
});
expect(resFiles.body.results[0]).toMatchObject({
folderPath: `/${folder00.uid}/${folder000.uid}`,
folderPath: `/${folder00.pathId}/${folder000.pathId}`,
});
expect(resFiles.body.results[1]).toMatchObject({ folderPath: file02.folderPath });

View File

@ -66,9 +66,9 @@ describe('Uploads folder (GraphQL)', () => {
expect(file).toMatchObject({
folder: {
name: 'API Uploads',
uid: expect.anything(),
pathId: expect.anything(),
},
folderPath: `/${file.folder.uid}`,
folderPath: `/${file.folder.pathId}`,
});
uploadFolder = file.folder;
@ -111,11 +111,11 @@ describe('Uploads folder (GraphQL)', () => {
expect(file).toMatchObject({
folder: {
name: 'API Uploads',
uid: expect.anything(),
pathId: expect.anything(),
},
folderPath: `/${file.folder.uid}`,
folderPath: `/${file.folder.pathId}`,
});
expect(file.folder.uid).not.toBe(uploadFolder.uid);
expect(file.folder.id).not.toBe(uploadFolder.id);
uploadFolder = file.folder;
});
@ -166,11 +166,11 @@ describe('Uploads folder (GraphQL)', () => {
expect(file).toMatchObject({
folder: {
name: 'API Uploads (1)',
uid: expect.anything(),
pathId: expect.anything(),
},
folderPath: `/${file.folder.uid}`,
folderPath: `/${file.folder.pathId}`,
});
expect(file.folder.uid).not.toBe(uploadFolder.uid);
expect(file.folder.id).not.toBe(uploadFolder.id);
uploadFolder = file.folder;
});
@ -206,9 +206,9 @@ describe('Uploads folder (GraphQL)', () => {
expect(file).toMatchObject({
folder: {
name: 'API Uploads (1)',
uid: expect.anything(),
pathId: expect.anything(),
},
folderPath: `/${file.folder.uid}`,
folderPath: `/${file.folder.pathId}`,
});
uploadFolder = file.folder;
@ -251,11 +251,11 @@ describe('Uploads folder (GraphQL)', () => {
expect(file).toMatchObject({
folder: {
name: 'API Uploads (1)',
uid: expect.anything(),
pathId: expect.anything(),
},
folderPath: `/${file.folder.uid}`,
folderPath: `/${file.folder.pathId}`,
});
expect(file.folder.uid).not.toBe(uploadFolder.uid);
expect(file.folder.id).not.toBe(uploadFolder.id);
uploadFolder = file.folder;
});
@ -306,11 +306,11 @@ describe('Uploads folder (GraphQL)', () => {
expect(file).toMatchObject({
folder: {
name: 'API Uploads (2)',
uid: expect.anything(),
pathId: expect.anything(),
},
folderPath: `/${file.folder.uid}`,
folderPath: `/${file.folder.pathId}`,
});
expect(file.folder.uid).not.toBe(uploadFolder.uid);
expect(file.folder.id).not.toBe(uploadFolder.id);
uploadFolder = file.folder;
});

View File

@ -79,9 +79,9 @@ describe('Uploads folder', () => {
expect(file).toMatchObject({
folder: {
name: 'API Uploads',
uid: expect.anything(),
pathId: expect.anything(),
},
folderPath: `/${file.folder.uid}`,
folderPath: `/${file.folder.pathId}`,
});
uploadFolder = file.folder;
@ -114,11 +114,11 @@ describe('Uploads folder', () => {
expect(file).toMatchObject({
folder: {
name: 'API Uploads',
uid: expect.anything(),
pathId: expect.anything(),
},
folderPath: `/${file.folder.uid}`,
folderPath: `/${file.folder.pathId}`,
});
expect(file.folder.uid).not.toBe(uploadFolder.uid);
expect(file.folder.id).not.toBe(uploadFolder.id);
uploadFolder = file.folder;
});
@ -159,11 +159,11 @@ describe('Uploads folder', () => {
expect(file).toMatchObject({
folder: {
name: 'API Uploads (1)',
uid: expect.anything(),
pathId: expect.anything(),
},
folderPath: `/${file.folder.uid}`,
folderPath: `/${file.folder.pathId}`,
});
expect(file.folder.uid).not.toBe(uploadFolder.uid);
expect(file.folder.id).not.toBe(uploadFolder.id);
uploadFolder = file.folder;
});
@ -190,9 +190,9 @@ describe('Uploads folder', () => {
expect(file).toMatchObject({
folder: {
name: 'API Uploads (1)',
uid: expect.anything(),
pathId: expect.anything(),
},
folderPath: `/${file.folder.uid}`,
folderPath: `/${file.folder.pathId}`,
});
uploadFolder = file.folder;
@ -226,11 +226,11 @@ describe('Uploads folder', () => {
expect(file).toMatchObject({
folder: {
name: 'API Uploads (1)',
uid: expect.anything(),
pathId: expect.anything(),
},
folderPath: `/${file.folder.uid}`,
folderPath: `/${file.folder.pathId}`,
});
expect(file.folder.uid).not.toBe(uploadFolder.uid);
expect(file.folder.id).not.toBe(uploadFolder.id);
uploadFolder = file.folder;
});
@ -272,11 +272,11 @@ describe('Uploads folder', () => {
expect(file).toMatchObject({
folder: {
name: 'API Uploads (2)',
uid: expect.anything(),
pathId: expect.anything(),
},
folderPath: `/${file.folder.uid}`,
folderPath: `/${file.folder.pathId}`,
});
expect(file.folder.uid).not.toBe(uploadFolder.uid);
expect(file.folder.id).not.toBe(uploadFolder.id);
uploadFolder = file.folder;
});
@ -317,9 +317,9 @@ describe('Uploads folder', () => {
expect(file).toMatchObject({
folder: {
name: 'API Uploads (2)',
uid: expect.anything(),
pathId: expect.anything(),
},
folderPath: `/${file.folder.uid}`,
folderPath: `/${file.folder.pathId}`,
});
uploadFolder = file.folder;
@ -356,11 +356,11 @@ describe('Uploads folder', () => {
expect(file).toMatchObject({
folder: {
name: 'API Uploads (2)',
uid: expect.anything(),
pathId: expect.anything(),
},
folderPath: `/${file.folder.uid}`,
folderPath: `/${file.folder.pathId}`,
});
expect(file.folder.uid).not.toBe(uploadFolder.uid);
expect(file.folder.id).not.toBe(uploadFolder.id);
uploadFolder = file.folder;
});
@ -404,11 +404,11 @@ describe('Uploads folder', () => {
expect(file).toMatchObject({
folder: {
name: 'API Uploads (3)',
uid: expect.anything(),
pathId: expect.anything(),
},
folderPath: `/${file.folder.uid}`,
folderPath: `/${file.folder.pathId}`,
});
expect(file.folder.uid).not.toBe(uploadFolder.uid);
expect(file.folder.id).not.toBe(uploadFolder.id);
uploadFolder = file.folder;
});