From 5829f41467cbacf3b17f5c9fce9906c3b7af4177 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Sun, 19 May 2024 00:29:51 +0800 Subject: [PATCH] fix: files meta only return currrent files and sub directory closes https://github.com/cnpm/cnpmcore/issues/680 --- .../PackageVersionFileController.ts | 42 +-- .../PackageVersionFileRepository.ts | 25 +- .../listFiles.test.ts | 311 +++++++++++++++++- .../PackageVersionFileController/raw.test.ts | 17 +- 4 files changed, 352 insertions(+), 43 deletions(-) diff --git a/app/port/controller/PackageVersionFileController.ts b/app/port/controller/PackageVersionFileController.ts index 6a65c29b..39aac1f2 100644 --- a/app/port/controller/PackageVersionFileController.ts +++ b/app/port/controller/PackageVersionFileController.ts @@ -210,32 +210,24 @@ export class PackageVersionFileController extends AbstractController { } async #listFilesByDirectory(packageVersion: PackageVersion, directory: string) { - const files = await this.packageVersionFileService.listPackageVersionFiles(packageVersion, directory); - if (!files || files.length === 0) return null; - // convert files to directory and file - const directories = new Map(); + const { files, directories } = await this.packageVersionFileService.listPackageVersionFiles(packageVersion, directory); + if (files.length === 0 && directories.length === 0) return null; + + const info: DirectoryItem = { + path: directory, + type: 'directory', + files: [], + }; for (const file of files) { - // make sure parent directories exists - const splits = file.directory.split('/'); - for (const [ index, name ] of splits.entries()) { - const parentPath = index === 0 ? '' : `/${splits.slice(1, index).join('/')}`; - const directoryPath = parentPath !== '/' ? `${parentPath}/${name}` : `/${name}`; - let directoryItem = directories.get(directoryPath); - if (!directoryItem) { - directoryItem = { - path: directoryPath, - type: 'directory', - files: [], - }; - directories.set(directoryPath, directoryItem); - if (parentPath) { - // only set the first time - directories.get(parentPath!)!.files.push(directoryItem); - } - } - } - directories.get(file.directory)!.files.push(formatFileItem(file)); + info.files.push(formatFileItem(file)); + } + for (const name of directories) { + info.files.push({ + path: name, + type: 'directory', + files: [], + } as DirectoryItem); } - return directories.get(directory); + return info; } } diff --git a/app/repository/PackageVersionFileRepository.ts b/app/repository/PackageVersionFileRepository.ts index f7bfbcb2..19077b77 100644 --- a/app/repository/PackageVersionFileRepository.ts +++ b/app/repository/PackageVersionFileRepository.ts @@ -33,20 +33,39 @@ export class PackageVersionFileRepository extends AbstractRepository { } async listPackageVersionFiles(packageVersionId: string, directory: string) { - const where = directory === '/' ? { packageVersionId } : + const isRoot = directory === '/'; + const where = isRoot ? { packageVersionId } : { packageVersionId, directory: { $or: [{ $eq: directory }, { $like: `${directory}/%` }] } }; + // only return current directory's files and directories + // https://github.com/cnpm/cnpmcore/issues/680 const models = await this.PackageVersionFile.find(where); - const distIds = models.map(model => model.distId); + const distIds: string[] = []; + const prefix = isRoot ? directory : `${directory}/`; + const subDirectories = new Set(); + const needModels: PackageVersionFileModel[] = []; + for (const item of models) { + if (item.directory === directory) { + // sub file + distIds.push(item.distId); + needModels.push(item); + } else { + // only keep directory = '/' or sub directory like `/dist` but not `/dist/foo` + // sub directory + const subDirectoryName = item.directory.substring(prefix.length).split('/')[0]; + subDirectories.add(`${prefix}${subDirectoryName}`); + } + } const distModels = await this.Dist.find({ distId: distIds }); const distEntitiesMap = new Map(); for (const distModel of distModels) { const dist = ModelConvertor.convertModelToEntity(distModel, DistEntity); distEntitiesMap.set(distModel.distId, dist); } - return models.map(model => { + const files = needModels.map(model => { const dist = distEntitiesMap.get(model.distId); return ModelConvertor.convertModelToEntity(model, PackageVersionFileEntity, { dist }); }); + return { files, directories: Array.from(subDirectories) }; } async hasPackageVersionFiles(packageVersionId: string) { diff --git a/test/port/controller/PackageVersionFileController/listFiles.test.ts b/test/port/controller/PackageVersionFileController/listFiles.test.ts index 5d1365d8..f7c43e0a 100644 --- a/test/port/controller/PackageVersionFileController/listFiles.test.ts +++ b/test/port/controller/PackageVersionFileController/listFiles.test.ts @@ -151,6 +151,315 @@ describe('test/port/controller/PackageVersionFileController/listFiles.test.ts', }); }); + it('should return the current directory\'s files and directories instead all sub items', async () => { + mock(app.config.cnpmcore, 'allowPublishNonScopePackage', true); + const tarball = await TestUtil.readFixturesFile('unpkg.com/openapi-7.3.3.tgz'); + const { integrity } = await calculateIntegrity(tarball); + const pkg = await TestUtil.getFullPackage({ + name: 'openapi', + version: '1.0.0', + versionObject: { + description: 'foo latest description', + }, + attachment: { + data: tarball.toString('base64'), + length: tarball.length, + }, + dist: { + integrity, + }, + main: './lib/index.js', + }); + let res = await app.httpRequest() + .put(`/${pkg.name}`) + .set('authorization', publisher.authorization) + .set('user-agent', publisher.ua) + .send(pkg); + assert.equal(res.status, 201); + res = await app.httpRequest() + .get(`/${pkg.name}/1.0.0/files/`); + assert.equal(res.status, 200); + for (const file of res.body.files) { + if (!file.lastModified) continue; + file.lastModified = '2024-05-18T16:00:18.307Z'; + } + // console.log(JSON.stringify(res.body, null, 2)); + assert.deepEqual(res.body, { + path: '/', + type: 'directory', + files: [ + { + path: '/LICENSE', + type: 'file', + contentType: 'text/plain', + integrity: 'sha512-OJCAthMtPqrngGSNaZg5DYzHGQhWG84JV44nxUKqGp8xIuAfZAxbAb7nMATCOqTp8gZv5e4MogcsJCBXiyjXHw==', + lastModified: '2024-05-18T16:00:18.307Z', + size: 11357, + }, + { + path: '/index.html', + type: 'file', + contentType: 'text/html', + integrity: 'sha512-L4Vxx8DW1PtZfPut4uwP9DSK9+DbFbKDWWGp4KK5TRKGTHSjYoMExqY50WiTKs/bGu1Ecpneiu3dnYlRZ/sDdw==', + lastModified: '2024-05-18T16:00:18.307Z', + size: 1437, + }, + { + path: '/package.json', + type: 'file', + contentType: 'application/json', + integrity: 'sha512-ke5ybpErJgl+Mul1XCSMvly0uYAt8/5mWa5/yYykxfMCE0OBpzgWoFHC+/RM9AQfNgic3bW/ssHXDUUPZiEKkg==', + lastModified: '2024-05-18T16:00:18.307Z', + size: 2852, + }, + { + path: '/CHANGES.md', + type: 'file', + contentType: 'text/markdown', + integrity: 'sha512-xxD+0Mdep4Pprq0JsudGLCKtSfHBeIqJVoGqM0qK1b2B/0sXjSQYinxgAwjK8rKSD0jNSo3R5aK8VbgOXLtbjw==', + lastModified: '2024-05-18T16:00:18.307Z', + size: 12346, + }, + { + path: '/README.md', + type: 'file', + contentType: 'text/markdown', + integrity: 'sha512-Nnj8b9SsDDobga1LsV7FVE46YrxkdZf5MOMboVHICw56tPHnQ0v1lXvXkWz7k12kTFWbA0z42daaW7WE+AQWfw==', + lastModified: '2024-05-18T16:00:18.307Z', + size: 4409, + }, + { + path: '/.npmcheckrc.yaml', + type: 'file', + contentType: 'text/yaml', + integrity: 'sha512-EYTJJ5StGM9DUpAbF8XHV4Z02rlmzN9O6k93fu1YXpf1wDBtmFYG64xaTXk2UfB8x0BCotga+Upm1yOgJVIZTQ==', + lastModified: '2024-05-18T16:00:18.307Z', + size: 105, + }, + { + path: '/.redocly.lint-ignore.yaml', + type: 'file', + contentType: 'text/yaml', + integrity: 'sha512-tyPeiIaOGIXb3PNFb2ELAZawxGHSdPZ7IoLdl+tEcDARVFlq6B9yJVAzL5R8L26iCBbvPtlfNGnYkHj4H/5ZMA==', + lastModified: '2024-05-18T16:00:18.307Z', + size: 644, + }, + { + path: '/index.yaml', + type: 'file', + contentType: 'text/yaml', + integrity: 'sha512-KW7xaZW5F8NOGt72kc9WvLcvkFDmXbm65JdWPM2pYfy9HMX0/6obJD5jhzQSX5ZU8ww0HMlXGXkRviFnDr88ZA==', + lastModified: '2024-05-18T16:00:18.307Z', + size: 21379, + }, + { + path: '/.eslintrc.yml', + type: 'file', + contentType: 'text/yaml', + integrity: 'sha512-3q0aghG4dBd7pgE4UrbtVn52cfg3BqOPkuNcCSwHZKMSFnKZxWr+sH7/OgnBDaifVsXGK7AN8q7sX0Eds6Ditw==', + lastModified: '2024-05-18T16:00:18.307Z', + size: 149, + }, + { + path: '/oauth2', + type: 'directory', + files: [], + }, + { + path: '/_util', + type: 'directory', + files: [], + }, + { + path: '/resource', + type: 'directory', + files: [], + }, + { + path: '/parameters', + type: 'directory', + files: [], + }, + { + path: '/id', + type: 'directory', + files: [], + }, + { + path: '/location', + type: 'directory', + files: [], + }, + { + path: '/string', + type: 'directory', + files: [], + }, + { + path: '/money', + type: 'directory', + files: [], + }, + { + path: '/time', + type: 'directory', + files: [], + }, + { + path: '/human', + type: 'directory', + files: [], + }, + { + path: '/health', + type: 'directory', + files: [], + }, + ], + }); + + res = await app.httpRequest() + .get(`/${pkg.name}/1.0.0/files/id/?meta`); + assert.equal(res.status, 200); + for (const file of res.body.files) { + if (!file.lastModified) continue; + file.lastModified = '2024-05-18T16:00:18.307Z'; + } + // console.log(JSON.stringify(res.body, null, 2)); + assert.deepEqual(res.body, { + path: '/id', + type: 'directory', + files: [ + { + path: '/id/AccountId.d.ts', + type: 'file', + contentType: 'text/plain', + integrity: 'sha512-xj1/RCRAp72pukals97C98DG0b38Gl2xNrUwOi2SRj+EnJKIfQX8WisDpCOSKLFq5j++sGbL0/4wCttrPvi37w==', + lastModified: '2024-05-18T16:00:18.307Z', + size: 787, + }, + { + path: '/id/AccountId.js', + type: 'file', + contentType: 'application/javascript', + integrity: 'sha512-kFa+SXSMGbCh2DiuSGmlCS8OCBSE4VRGlq/A2IyY3QxL794soFq4zO3F+UEx4ANUG33urAa4LG4IY2OiUc2Mng==', + lastModified: '2024-05-18T16:00:18.307Z', + size: 1343, + }, + { + path: '/id/AccountId.yaml', + type: 'file', + contentType: 'text/yaml', + integrity: 'sha512-R6WB9dXEaNpvqIAH6OdRQ77gSEBlq1GeH2jv2tv1wQEVOmzQtErHlpj+ukvZUwzqf9wTXIPxKjeUhqk6VbfBkA==', + lastModified: '2024-05-18T16:00:18.307Z', + size: 571, + }, + { + path: '/id/Mode.js', + type: 'file', + contentType: 'application/javascript', + integrity: 'sha512-jfMuIff4LW/ZQ8el9iCww8c9gw+12UK7eZn+6TMDAlStfLhlu8u7jcCSWSEG1zBTty9DIHn4Nbp+dMDjRUnVWQ==', + lastModified: '2024-05-18T16:00:18.307Z', + size: 3357, + }, + { + path: '/id/mode.yaml', + type: 'file', + contentType: 'text/yaml', + integrity: 'sha512-er9S1Da52G8fxwfgxhNbcXPdYz9bzABM7VifDXhgVGX+hwtu8tve9y2aZhPAHcJOy3dClMDQ1eYLAHp7k8TMNQ==', + lastModified: '2024-05-18T16:00:18.307Z', + size: 1222, + }, + { + path: '/id/UUID.js', + type: 'file', + contentType: 'application/javascript', + integrity: 'sha512-bo/JyxOZeRRjbN0OR8vNRz3cTY2GcJfRmRnp3QTGXE5iuKYjrpjYzj+vEXopZV1QYPdZaXUK671EoysPE59pQQ==', + lastModified: '2024-05-18T16:00:18.307Z', + size: 992, + }, + { + path: '/id/UUID.yaml', + type: 'file', + contentType: 'text/yaml', + integrity: 'sha512-Gjr0LNqWQcO5/oaCyMm9oZWpc/D9K6Qe37sGuYv4kbq0I8teZL92xbR81L+2VShkhLSXdg2Qw5WRjwCkSWyfoA==', + lastModified: '2024-05-18T16:00:18.307Z', + size: 659, + }, + { + path: '/id/legalPerson', + type: 'directory', + files: [], + }, + { + path: '/id/naturalPerson', + type: 'directory', + files: [], + }, + { + path: '/id/sigedis', + type: 'directory', + files: [], + }, + ], + }); + + res = await app.httpRequest() + .get(`/${pkg.name}/1.0.0/files/id/legalPerson/?meta`); + assert.equal(res.status, 200); + // console.log(JSON.stringify(res.body, null, 2)); + assert.deepEqual(res.body, { + path: '/id/legalPerson', + type: 'directory', + files: [ + { + path: '/id/legalPerson/be', + type: 'directory', + files: [], + }, + ], + }); + + res = await app.httpRequest() + .get(`/${pkg.name}/1.0.0/files/id/legalPerson/be/?meta`); + assert.equal(res.status, 200); + for (const file of res.body.files) { + if (!file.lastModified) continue; + file.lastModified = '2024-05-18T16:00:18.307Z'; + } + // console.log(JSON.stringify(res.body, null, 2)); + assert.deepEqual(res.body, { + path: '/id/legalPerson/be', + type: 'directory', + files: [ + { + path: '/id/legalPerson/be/CRN.js', + type: 'file', + contentType: 'application/javascript', + integrity: 'sha512-K7fRjnkAkNnSYbWZW4A+xcdYbI2J1fk49AxFVut2Kk6LXOZbLH6nU9CFeo0YixDLa1Hl5sjLiUQ7Mur2HQgvNw==', + lastModified: '2024-05-18T16:00:18.307Z', + size: 3285, + }, + { + path: '/id/legalPerson/be/CRN.yaml', + type: 'file', + contentType: 'text/yaml', + integrity: 'sha512-pG12081uMexKHGfmetjZ5p6sB1z+Y/StRyRC1BOW/CGcuLW8iDdY848C6gS9qEXq0DAQwIg9jv18uf4uP1lOwg==', + lastModified: '2024-05-18T16:00:18.307Z', + size: 2793, + }, + { + path: '/id/legalPerson/be/KBO.yaml', + type: 'file', + contentType: 'text/yaml', + integrity: 'sha512-8s8lUEsYAJfPw1ar9l6fUxOapU1q5GzuhsprQrOmsGRbDNildPvzdO5KPVXQdoz4aHxMkOIxaVDDQl1NB1OPAA==', + lastModified: '2024-05-18T16:00:18.307Z', + size: 700, + }, + ], + }); + }); + it('should latest tag with scoped package', async () => { const pkg = await TestUtil.getFullPackage({ name: '@cnpm/foo-tag-latest', @@ -239,7 +548,7 @@ describe('test/port/controller/PackageVersionFileController/listFiles.test.ts', }); }); - it('should list sub dir files', async () => { + it('should list sub dir files not found', async () => { mock(app.config.cnpmcore, 'allowPublishNonScopePackage', true); const pkg = await TestUtil.getFullPackage({ name: 'foo', diff --git a/test/port/controller/PackageVersionFileController/raw.test.ts b/test/port/controller/PackageVersionFileController/raw.test.ts index e8624e4b..ad8ec173 100644 --- a/test/port/controller/PackageVersionFileController/raw.test.ts +++ b/test/port/controller/PackageVersionFileController/raw.test.ts @@ -337,12 +337,7 @@ describe('test/port/controller/PackageVersionFileController/raw.test.ts', () => assert(res.body.files.find(file => file.path === '/CONTRIBUTING.md')); let testDir = res.body.files.find(file => file.path === '/tests'); assert(testDir); - assert(testDir.files.length > 0); - let integrationDir1 = testDir.files.find(file => file.path === '/tests/integration'); - assert(integrationDir1); - assert(integrationDir1.files.length > 0); - assert.equal(integrationDir1.type, 'directory'); - assert(integrationDir1.files.find(file => file.path === '/tests/integration/test.replication.js')); + assert.equal(testDir.files.length, 0); assert.equal(res.headers['cache-control'], 'public, s-maxage=600, max-age=60'); assert.equal(res.headers.vary, 'Origin, Accept, Accept-Encoding'); assert.equal(res.body.path, '/'); @@ -369,12 +364,7 @@ describe('test/port/controller/PackageVersionFileController/raw.test.ts', () => assert(res.body.files.find(file => file.path === '/CONTRIBUTING.md')); testDir = res.body.files.find(file => file.path === '/tests'); assert(testDir); - assert(testDir.files.length > 0); - integrationDir1 = testDir.files.find(file => file.path === '/tests/integration'); - assert(integrationDir1); - assert(integrationDir1.files.length > 0); - assert.equal(integrationDir1.type, 'directory'); - assert(integrationDir1.files.find(file => file.path === '/tests/integration/test.replication.js')); + assert.equal(testDir.files.length, 0); assert.equal(res.headers['cache-control'], 'public, s-maxage=600, max-age=60'); assert.equal(res.headers.vary, 'Origin, Accept, Accept-Encoding'); assert.equal(res.body.path, '/'); @@ -436,9 +426,8 @@ describe('test/port/controller/PackageVersionFileController/raw.test.ts', () => // make sure sub dirs exists const integrationDir = res.body.files.find(file => file.path === '/tests/integration'); assert(integrationDir); - assert(integrationDir.files.length > 0); + assert.equal(integrationDir.files.length, 0); assert.equal(integrationDir.type, 'directory'); - assert(integrationDir.files.find(file => file.path === '/tests/integration/test.replication.js')); res = await app.httpRequest() .get(`/${pkg.name}/1.0.0/files/tests/integration/test.http.js`);