From 631cc38ddb52a91004e139ec5470d13d9a666caa Mon Sep 17 00:00:00 2001 From: Melnik George Date: Thu, 19 Dec 2024 22:37:11 +0300 Subject: [PATCH 1/6] Added endpoints --- .env.example | 6 + migrations/.snapshot-noir_libs.json | 288 ++++++++++++++++++++++++++ migrations/Migration20241219143807.ts | 33 +++ migrations/Migration20241219155508.ts | 13 ++ package.json | 5 +- src/app.module.ts | 17 +- src/mikro-orm.config.ts | 16 +- src/model/download.entity.ts | 18 ++ src/model/package.entity.ts | 24 +++ src/model/version.entity.ts | 35 ++++ src/packages/packages.controller.ts | 81 ++++++++ src/packages/packages.module.ts | 17 ++ src/packages/packages.service.ts | 54 +++++ src/upload/upload.controller.ts | 121 ++++++----- src/upload/upload.module.ts | 2 + yarn.lock | 189 ++++++++++++++++- 16 files changed, 843 insertions(+), 76 deletions(-) create mode 100644 .env.example create mode 100644 migrations/.snapshot-noir_libs.json create mode 100644 migrations/Migration20241219143807.ts create mode 100644 migrations/Migration20241219155508.ts create mode 100644 src/model/download.entity.ts create mode 100644 src/model/package.entity.ts create mode 100644 src/model/version.entity.ts create mode 100644 src/packages/packages.controller.ts create mode 100644 src/packages/packages.module.ts create mode 100644 src/packages/packages.service.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..79e7680 --- /dev/null +++ b/.env.example @@ -0,0 +1,6 @@ +DB_NAME=noir_libs +DB_USER=your_username +DB_PASSWORD=your_password +DB_HOST=localhost +DB_PORT=5432 +IS_PROD=false \ No newline at end of file diff --git a/migrations/.snapshot-noir_libs.json b/migrations/.snapshot-noir_libs.json new file mode 100644 index 0000000..72dbd9f --- /dev/null +++ b/migrations/.snapshot-noir_libs.json @@ -0,0 +1,288 @@ +{ + "namespaces": [ + "public" + ], + "name": "public", + "tables": [ + { + "columns": { + "id": { + "name": "id", + "type": "serial", + "unsigned": false, + "autoincrement": true, + "primary": true, + "nullable": false, + "mappedType": "integer" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 255, + "mappedType": "string" + }, + "tag_name": { + "name": "tag_name", + "type": "varchar(255)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 255, + "mappedType": "string" + }, + "readme": { + "name": "readme", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + } + }, + "name": "package", + "schema": "public", + "indexes": [ + { + "keyName": "package_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": {}, + "nativeEnums": {} + }, + { + "columns": { + "id": { + "name": "id", + "type": "serial", + "unsigned": false, + "autoincrement": true, + "primary": true, + "nullable": false, + "mappedType": "integer" + }, + "package_id": { + "name": "package_id", + "type": "int", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "integer" + }, + "major": { + "name": "major", + "type": "int", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "integer" + }, + "minor": { + "name": "minor", + "type": "int", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "integer" + }, + "patch": { + "name": "patch", + "type": "int", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "integer" + }, + "sortable_version": { + "name": "sortable_version", + "type": "varchar(255)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 255, + "mappedType": "string" + }, + "version_number": { + "name": "version_number", + "type": "varchar(255)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 255, + "mappedType": "string" + }, + "package_blob": { + "name": "package_blob", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "size_kb": { + "name": "size_kb", + "type": "int", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "integer" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "version", + "schema": "public", + "indexes": [ + { + "columnNames": [ + "sortable_version" + ], + "composite": false, + "keyName": "version_sortable_version_unique", + "constraint": true, + "primary": false, + "unique": true + }, + { + "keyName": "version_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "version_package_id_foreign": { + "constraintName": "version_package_id_foreign", + "columnNames": [ + "package_id" + ], + "localTableName": "public.version", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.package", + "updateRule": "cascade" + } + }, + "nativeEnums": {} + }, + { + "columns": { + "id": { + "name": "id", + "type": "serial", + "unsigned": false, + "autoincrement": true, + "primary": true, + "nullable": false, + "mappedType": "integer" + }, + "package_id": { + "name": "package_id", + "type": "int", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "integer" + }, + "version_id": { + "name": "version_id", + "type": "int", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "integer" + }, + "download_date": { + "name": "download_date", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "download", + "schema": "public", + "indexes": [ + { + "keyName": "download_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "download_package_id_foreign": { + "constraintName": "download_package_id_foreign", + "columnNames": [ + "package_id" + ], + "localTableName": "public.download", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.package", + "updateRule": "cascade" + }, + "download_version_id_foreign": { + "constraintName": "download_version_id_foreign", + "columnNames": [ + "version_id" + ], + "localTableName": "public.download", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.version", + "updateRule": "cascade" + } + }, + "nativeEnums": {} + } + ], + "nativeEnums": {} +} diff --git a/migrations/Migration20241219143807.ts b/migrations/Migration20241219143807.ts new file mode 100644 index 0000000..0d4d643 --- /dev/null +++ b/migrations/Migration20241219143807.ts @@ -0,0 +1,33 @@ +import { Migration } from '@mikro-orm/migrations'; + +export class Migration20241219143807 extends Migration { + + override async up(): Promise { + this.addSql(`create table "package" ("id" serial primary key, "name" varchar(255) not null, "tag_name" varchar(255) not null, "readme" text not null);`); + + this.addSql(`create table "version" ("id" serial primary key, "package_id" int not null, "major" int not null, "minor" int not null, "patch" int not null, "sortable_version" varchar(255) not null, "version_number" varchar(255) not null, "package_blob" varchar(255) not null, "size_kb" int not null, "created_at" timestamptz not null);`); + this.addSql(`alter table "version" add constraint "version_sortable_version_unique" unique ("sortable_version");`); + + this.addSql(`create table "download" ("id" serial primary key, "package_id" int not null, "version_id" int not null, "download_date" timestamptz not null);`); + + this.addSql(`alter table "version" add constraint "version_package_id_foreign" foreign key ("package_id") references "package" ("id") on update cascade;`); + + this.addSql(`alter table "download" add constraint "download_package_id_foreign" foreign key ("package_id") references "package" ("id") on update cascade;`); + this.addSql(`alter table "download" add constraint "download_version_id_foreign" foreign key ("version_id") references "version" ("id") on update cascade;`); + } + + override async down(): Promise { + this.addSql(`alter table "version" drop constraint "version_package_id_foreign";`); + + this.addSql(`alter table "download" drop constraint "download_package_id_foreign";`); + + this.addSql(`alter table "download" drop constraint "download_version_id_foreign";`); + + this.addSql(`drop table if exists "package" cascade;`); + + this.addSql(`drop table if exists "version" cascade;`); + + this.addSql(`drop table if exists "download" cascade;`); + } + +} diff --git a/migrations/Migration20241219155508.ts b/migrations/Migration20241219155508.ts new file mode 100644 index 0000000..922b8e0 --- /dev/null +++ b/migrations/Migration20241219155508.ts @@ -0,0 +1,13 @@ +import { Migration } from '@mikro-orm/migrations'; + +export class Migration20241219155508 extends Migration { + + override async up(): Promise { + this.addSql(`alter table "version" alter column "package_blob" type text using ("package_blob"::text);`); + } + + override async down(): Promise { + this.addSql(`alter table "version" alter column "package_blob" type varchar(255) using ("package_blob"::varchar(255));`); + } + +} diff --git a/package.json b/package.json index 0b72507..008d0c8 100644 --- a/package.json +++ b/package.json @@ -17,10 +17,12 @@ "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config ./test/jest-e2e.json" + "test:e2e": "jest --config ./test/jest-e2e.json", + "mikro-orm": "mikro-orm" }, "dependencies": { "@mikro-orm/core": "^6.4.1", + "@mikro-orm/migrations": "^6.4.1", "@mikro-orm/nestjs": "^6.0.2", "@mikro-orm/postgresql": "^6.4.1", "@nestjs/common": "^10.0.0", @@ -33,6 +35,7 @@ "rxjs": "^7.8.1" }, "devDependencies": { + "@mikro-orm/cli": "^6.4.1", "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", diff --git a/src/app.module.ts b/src/app.module.ts index 8e1256a..5272cbf 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,16 +1,11 @@ import { Module } from '@nestjs/common'; -import { AppController } from './app.controller'; -import { AppService } from './app.service'; -import { UploadModule } from './upload/upload.module'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { MikroOrmModule } from '@mikro-orm/nestjs'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; +import { PackagesModule } from './packages/packages.module'; import MikroOrmConfig from './mikro-orm.config'; - -@Module({ - imports: [UploadModule], - controllers: [AppController], - providers: [AppService], -}) +import { UploadModule } from './upload/upload.module'; @Module({ imports: [ @@ -23,9 +18,9 @@ import MikroOrmConfig from './mikro-orm.config'; useFactory: async () => MikroOrmConfig, inject: [ConfigService], }), - UploadModule + PackagesModule, UploadModule ], controllers: [AppController], providers: [AppService], }) -export class AppModule {} +export class AppModule {} \ No newline at end of file diff --git a/src/mikro-orm.config.ts b/src/mikro-orm.config.ts index 3eca12e..50e2fc7 100644 --- a/src/mikro-orm.config.ts +++ b/src/mikro-orm.config.ts @@ -3,7 +3,7 @@ import * as dotenv from 'dotenv'; dotenv.config(); const config: any = { - entities: ['./dist/model'], + entities: ['./dist/src/model'], entitiesTs: ['./src/model'], dbName: process.env.DB_NAME, user: process.env.DB_USER, @@ -12,6 +12,20 @@ const config: any = { port: Number(process.env.DB_PORT), driver: PostgreSqlDriver, debug: process.env.IS_PROD !== 'true', + migrations: { + tableName: 'mikro_orm_migrations', // name of database table with log of executed transactions + path: './migrations', // path to the folder with migrations + pathTs: undefined, // path to the folder with TS migrations (if used, you should put path to compiled files in `path`) + glob: '!(*.d).{js,ts}', // how to match migration files (all .js and .ts files, but not .d.ts) + transactional: true, // wrap each migration in a transaction + disableForeignKeys: true, // wrap statements with `set foreign_key_checks = 0` or equivalent + allOrNothing: true, // wrap all migrations in master transaction + dropTables: true, // allow to disable table dropping + safe: false, // allow to disable table and column dropping + snapshot: true, // save snapshot when creating new migrations + emit: 'ts', // migration generation mode + }, + }; export default config; diff --git a/src/model/download.entity.ts b/src/model/download.entity.ts new file mode 100644 index 0000000..1644352 --- /dev/null +++ b/src/model/download.entity.ts @@ -0,0 +1,18 @@ +import { Entity, PrimaryKey, Property, ManyToOne } from '@mikro-orm/core'; +import { Package } from './package.entity'; +import { Version } from './version.entity'; + +@Entity() +export class Download { + @PrimaryKey() + id!: number; + + @ManyToOne(() => Package) + package!: Package; + + @ManyToOne(() => Version) + version!: Version; + + @Property() + downloadDate: Date = new Date(); +} \ No newline at end of file diff --git a/src/model/package.entity.ts b/src/model/package.entity.ts new file mode 100644 index 0000000..d4d320f --- /dev/null +++ b/src/model/package.entity.ts @@ -0,0 +1,24 @@ +import { Entity, PrimaryKey, Property, Collection, OneToMany } from '@mikro-orm/core'; +import { Version } from './version.entity'; +import { Download } from './download.entity'; + +@Entity() +export class Package { + @PrimaryKey() + id!: number; + + @Property() + name!: string; + + @Property() + tagName!: string; + + @Property({ type: 'text' }) + readme?: string; + + @OneToMany(() => Version, version => version.package) + versions = new Collection(this); + + @OneToMany(() => Download, download => download.package) + downloads = new Collection(this); +} \ No newline at end of file diff --git a/src/model/version.entity.ts b/src/model/version.entity.ts new file mode 100644 index 0000000..8e0a745 --- /dev/null +++ b/src/model/version.entity.ts @@ -0,0 +1,35 @@ +import { Entity, PrimaryKey, Property, ManyToOne } from '@mikro-orm/core'; +import { Package } from './package.entity'; + +@Entity() +export class Version { + @PrimaryKey() + id!: number; + + @ManyToOne(() => Package) + package!: Package; + + @Property() + major!: number; + + @Property() + minor!: number; + + @Property() + patch!: number; + + @Property({ unique: true }) + sortableVersion!: string; + + @Property() + versionNumber!: string; + + @Property({ columnType: 'text' }) + packageBlob!: string; + + @Property() + sizeKb!: number; + + @Property() + createdAt: Date = new Date(); +} diff --git a/src/packages/packages.controller.ts b/src/packages/packages.controller.ts new file mode 100644 index 0000000..6d5c5dc --- /dev/null +++ b/src/packages/packages.controller.ts @@ -0,0 +1,81 @@ +import { + Controller, + Get, + Param, + StreamableFile, + NotFoundException, Res +} from '@nestjs/common'; +import { EntityManager } from '@mikro-orm/postgresql'; +import { Package } from '../model/package.entity'; +import { Response } from 'express'; +import * as stream from 'stream'; + +@Controller('api/v1/packages') +export class PackagesController { + constructor(private readonly em: EntityManager) {} + + @Get(':name/:version') + async getPackage(@Param('name') name: string, @Param('version') version: string) { + const [major, minor, patch] = version.split('.').map(Number); + + const pkg = await this.em.findOne(Package, { name }, { populate: ['versions'] }); + if (!pkg) { + throw new NotFoundException(`Package "${name}" not found`); + } + + const ver = pkg.versions.getItems().find( + v => v.major === major && v.minor === minor && v.patch === patch, + ); + + if (!ver) { + throw new NotFoundException(`Version "${version}" not found for package "${name}"`); + } + + return { + name: pkg.name, + version: ver.versionNumber, + size_kb: ver.sizeKb, + readme: pkg.readme, + created_at: ver.createdAt, + tag_name: pkg.tagName, + }; + } + + @Get(':name/:version/download') + async downloadPackage( + @Param('name') name: string, + @Param('version') version: string, + @Res() res: Response, + ) { + const [major, minor, patch] = version.split('.').map(Number); + + const pkg = await this.em.findOne(Package, { name }, { populate: ['versions'] }); + if (!pkg) { + throw new NotFoundException(`Package "${name}" not found`); + } + + const ver = pkg.versions.getItems().find( + v => v.major === major && v.minor === minor && v.patch === patch, + ); + + if (!ver) { + throw new NotFoundException(`Version "${version}" not found for package "${name}"`); + } + + const buffer = Buffer.from(ver.packageBlob, 'base64'); + + const fileStream = new stream.PassThrough(); + fileStream.end(buffer); + + const fileName = `${name}-${version}.tar.gz`; + res.set({ + 'Content-Type': 'application/octet-stream', + 'Content-Disposition': `attachment; filename="${fileName}"`, + 'Content-Length': buffer.length, + }); + + fileStream.pipe(res); + } + + +} diff --git a/src/packages/packages.module.ts b/src/packages/packages.module.ts new file mode 100644 index 0000000..9eb079c --- /dev/null +++ b/src/packages/packages.module.ts @@ -0,0 +1,17 @@ +import { Module } from '@nestjs/common'; +import { MikroOrmModule } from '@mikro-orm/nestjs'; +import { PackagesController } from './packages.controller'; +import { PackagesService } from './packages.service'; +import { Package } from '../model/package.entity'; +import { Version } from '../model/version.entity'; +import { Download } from '../model/download.entity'; + +@Module({ + imports: [ + MikroOrmModule.forFeature([Package, Version, Download]) + ], + controllers: [PackagesController], + providers: [PackagesService], + exports: [PackagesService], +}) +export class PackagesModule {} \ No newline at end of file diff --git a/src/packages/packages.service.ts b/src/packages/packages.service.ts new file mode 100644 index 0000000..95fb24d --- /dev/null +++ b/src/packages/packages.service.ts @@ -0,0 +1,54 @@ +import { Injectable } from '@nestjs/common'; +import { EntityManager } from '@mikro-orm/postgresql'; +import { Package } from '../model/package.entity'; +import { Version } from '../model/version.entity'; + +@Injectable() +export class PackagesService { + constructor(private readonly em: EntityManager) {} + + async findOrCreatePackage(name: string): Promise { + let pkg = await this.em.findOne(Package, { name }); + + if (!pkg) { + pkg = this.em.create(Package, { name, tagName: '', readme: '' }); + await this.em.persistAndFlush(pkg); + } + + return pkg; + } + + async addVersion( + packageId: number, + versionData: { + major: number; + minor: number; + patch: number; + sortableVersion: string; + versionNumber: string; + packageBlob: string; + sizeKb: number; + }, + ): Promise { + const pkg = await this.em.findOneOrFail(Package, packageId, { populate: ['versions'] }); + + const existingVersion = await this.em.findOne(Version, { + package: pkg, + sortableVersion: versionData.sortableVersion, + }); + + if (existingVersion) { + throw new Error('Version already exists'); + } + + const version = this.em.create(Version, { + ...versionData, + package: pkg, + }); + + pkg.versions.add(version); + await this.em.persistAndFlush(version); + + return version; + } +} diff --git a/src/upload/upload.controller.ts b/src/upload/upload.controller.ts index 8fd94c8..237536c 100644 --- a/src/upload/upload.controller.ts +++ b/src/upload/upload.controller.ts @@ -1,69 +1,78 @@ -import { Controller, Post, UseInterceptors, UploadedFile, Res, Get, Param } from '@nestjs/common'; +import { Controller, Post, UseInterceptors, UploadedFile, Res, HttpStatus, Get } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import * as fs from 'fs'; import * as path from 'path'; +import { PackagesService } from '../packages/packages.service'; -@Controller('api/v1') +@Controller('api/v1/upload') export class UploadController { + private readonly uploadPath = process.env.REGISTRY_PATH ?? path.join(__dirname, '..', 'noir_registry'); - private readonly uploadPath = process.env.REGISTRY_PATH ?? path.join(__dirname, '..', `noir_registry`); - @Post('') - @UseInterceptors(FileInterceptor('file')) - async uploadFile(@UploadedFile() file: Express.Multer.File, @Res() res) { - try { - // Ensure the directory exists - if (!fs.existsSync(this.uploadPath)) { - fs.mkdirSync(this.uploadPath, { recursive: true }); - } + constructor(private readonly packagesService: PackagesService) {} - // Save the file using a writable stream - const filePath = path.join(this.uploadPath, `${file.originalname}`); - const writeStream = fs.createWriteStream(filePath); - - // Write the file in chunks to avoid issues with large files - writeStream.write(file.buffer); - writeStream.end(); // Ensure stream is closed after writing is complete - - writeStream.on('finish', () => { - console.log('File successfully saved:', filePath); - }); - - writeStream.on('error', (error) => { - console.error('Error writing file:', error); - return res.status(500).json({ - message: 'Error saving file', - error: error.message, - }); - }); - - return res.json({ - message: 'File uploaded successfully', - file: file.originalname, - }); - - } catch (error) { - console.error('Error uploading file:', error); - return res.status(500).json({ - message: 'Error uploading file', - error: error.message, - }); - } + @Post('') + @UseInterceptors(FileInterceptor('file')) + async uploadFile(@UploadedFile() file: Express.Multer.File, @Res() res) { + if (!file) { + return res.status(HttpStatus.BAD_REQUEST).json({ message: 'File is required.' }); } - @Get('/:name/:version') - getFile(@Param('name') packageName: string, @Param('version') version: string, @Res() res) { - const filePath = path.join(this.uploadPath, `${packageName}_${version}`); - - // Check if the file exists - if (fs.existsSync(filePath)) { - console.log(`File found: ${filePath}`); - res.sendFile(filePath, { headers: { 'Content-Disposition': `attachment; filename="${packageName}"` } }); - } else { - console.log(`File not found: ${filePath}`); - return res.status(404).json({ message: 'File not found' }); - } + const filePath = path.join(this.uploadPath, file.originalname); + + try { + console.log('Checking upload directory:', this.uploadPath); + if (!fs.existsSync(this.uploadPath)) { + console.log('Directory does not exist. Creating...'); + fs.mkdirSync(this.uploadPath, { recursive: true }); + } + + + console.log('Saving file to:', filePath); + fs.writeFileSync(filePath, file.buffer); + + + const [name, version] = file.originalname.split('_'); + if (!name || !version) { + throw new Error('Invalid file name format. Use "name_major.minor.patch.ext".'); + } + + const versionMatch = version.match(/^(\d+)\.(\d+)\.(\d+)/); + if (!versionMatch) { + throw new Error('Invalid version format. Use "major.minor.patch".'); + } + + const [major, minor, patch] = versionMatch.slice(1).map(Number); + const sortableVersion = `${major}.${minor}.${patch}`.padStart(12, '0'); + + + const packageBlob = file.buffer.toString('base64'); + const sizeKb = Math.round(file.buffer.length / 1024); + + console.log('Saving package and version to the database...'); + const pkg = await this.packagesService.findOrCreatePackage(name); + const newVersion = await this.packagesService.addVersion(pkg.id, { + major, + minor, + patch, + sortableVersion, + versionNumber: `${major}.${minor}.${patch}`, + packageBlob, + sizeKb, + }); + console.log('File uploaded and processed successfully.'); + return res.status(HttpStatus.OK).json({ + message: 'File uploaded successfully', + package: pkg.name, + version: newVersion.versionNumber, + }); + } catch (error) { + console.error('Error uploading file:', error); + return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ + message: 'Error uploading file', + error: error.message, + }); } - + } @Get() getAllPackages(@Res() res) { const dirPath = path.join(this.uploadPath); diff --git a/src/upload/upload.module.ts b/src/upload/upload.module.ts index e73481d..3b865a0 100644 --- a/src/upload/upload.module.ts +++ b/src/upload/upload.module.ts @@ -1,8 +1,10 @@ import { Module } from '@nestjs/common'; import { UploadController } from './upload.controller'; import { UploadService } from './upload.service'; +import { PackagesModule } from 'src/packages/packages.module'; @Module({ + imports: [PackagesModule], controllers: [UploadController], providers: [UploadService] }) diff --git a/yarn.lock b/yarn.lock index a5d87bf..854d463 100644 --- a/yarn.lock +++ b/yarn.lock @@ -436,6 +436,15 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== +"@jercle/yargonaut@1.1.5": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@jercle/yargonaut/-/yargonaut-1.1.5.tgz#b640a73a2e82d6f9b636e93f310c9bb4947f5754" + integrity sha512-zBp2myVvBHp1UaJsNTyS6q4UDKT7eRiqTS4oNTS6VQMd6mpxYOdbeK4pY279cDCdakGy6hG0J3ejoXZVsPwHqw== + dependencies: + chalk "^4.1.2" + figlet "^1.5.2" + parent-require "^1.0.0" + "@jest/console@^29.7.0": version "29.7.0" resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" @@ -688,7 +697,19 @@ resolved "https://registry.yarnpkg.com/@lukeed/csprng/-/csprng-1.1.0.tgz#1e3e4bd05c1cc7a0b2ddbd8a03f39f6e4b5e6cfe" integrity sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA== -"@mikro-orm/core@^6.4.1": +"@mikro-orm/cli@^6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@mikro-orm/cli/-/cli-6.4.1.tgz#1008a8705f4020d3b1b084f92fbf764f352e1f94" + integrity sha512-5gXEdG3o2zPOAjAoP7gwfEy13PIxcOAstkY28acZ0INs29ln1us6zQRV4vCYDPDPd95DBQFvpP71eASrqFePqA== + dependencies: + "@jercle/yargonaut" "1.1.5" + "@mikro-orm/core" "6.4.1" + "@mikro-orm/knex" "6.4.1" + fs-extra "11.2.0" + tsconfig-paths "4.2.0" + yargs "17.7.2" + +"@mikro-orm/core@6.4.1", "@mikro-orm/core@^6.4.1": version "6.4.1" resolved "https://registry.yarnpkg.com/@mikro-orm/core/-/core-6.4.1.tgz#aae35b0ff55267d951c8eb90fae650d11bb30e55" integrity sha512-OChUSn2Kx1cGnVo8TcB5J9+6LonVXZCuzK808oD1gnnkmQxWtqFMLO695s4m2jmBGl3LUXXvKfXndagp9v/h7g== @@ -710,6 +731,15 @@ knex "3.1.0" sqlstring "2.3.3" +"@mikro-orm/migrations@^6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@mikro-orm/migrations/-/migrations-6.4.1.tgz#817d1a1f70cea1b2b1f59352beda9b1685893bb4" + integrity sha512-fGLNnN6POW6vWncTkJKK0EOP1nVC19bK9fJwofDV1cOEVNP0NtVvcASZI3tqyhBrrALD2quTk1RM9o/nZRskzQ== + dependencies: + "@mikro-orm/knex" "6.4.1" + fs-extra "11.2.0" + umzug "3.8.2" + "@mikro-orm/nestjs@^6.0.2": version "6.0.2" resolved "https://registry.yarnpkg.com/@mikro-orm/nestjs/-/nestjs-6.0.2.tgz#0c8d57db1b958b1d7f06a501653a30d88467d72b" @@ -850,6 +880,38 @@ resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31" integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA== +"@rushstack/node-core-library@5.10.1": + version "5.10.1" + resolved "https://registry.yarnpkg.com/@rushstack/node-core-library/-/node-core-library-5.10.1.tgz#14c10c918ed12da003c21af9d5bf0e76633215d2" + integrity sha512-BSb/KcyBHmUQwINrgtzo6jiH0HlGFmrUy33vO6unmceuVKTEyL2q+P0fQq2oB5hvXVWOEUhxB2QvlkZluvUEmg== + dependencies: + ajv "~8.13.0" + ajv-draft-04 "~1.0.0" + ajv-formats "~3.0.1" + fs-extra "~7.0.1" + import-lazy "~4.0.0" + jju "~1.4.0" + resolve "~1.22.1" + semver "~7.5.4" + +"@rushstack/terminal@0.14.4": + version "0.14.4" + resolved "https://registry.yarnpkg.com/@rushstack/terminal/-/terminal-0.14.4.tgz#37e160b0878a324cf3e0fecab25fe48a030e29ed" + integrity sha512-NxACqERW0PHq8Rpq1V6v5iTHEwkRGxenjEW+VWqRYQ8T9puUzgmGHmEZUaUEDHAe9Qyvp0/Ew04sAiQw9XjhJg== + dependencies: + "@rushstack/node-core-library" "5.10.1" + supports-color "~8.1.1" + +"@rushstack/ts-command-line@^4.12.2": + version "4.23.2" + resolved "https://registry.yarnpkg.com/@rushstack/ts-command-line/-/ts-command-line-4.23.2.tgz#37b28a418db84d04f6a1c787390dd02ad8dfadf0" + integrity sha512-JJ7XZX5K3ThBBva38aomgsPv1L7FV6XmSOcR6HtM7HDFZJkepqT65imw26h9ggGqMjsY0R9jcl30tzKcVj9aOQ== + dependencies: + "@rushstack/terminal" "0.14.4" + "@types/argparse" "1.0.38" + argparse "~1.0.9" + string-argv "~0.3.1" + "@sinclair/typebox@^0.27.8": version "0.27.8" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" @@ -889,6 +951,11 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== +"@types/argparse@1.0.38": + version "1.0.38" + resolved "https://registry.yarnpkg.com/@types/argparse/-/argparse-1.0.38.tgz#a81fd8606d481f873a3800c6ebae4f1d768a56a9" + integrity sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA== + "@types/babel__core@^7.1.14": version "7.20.5" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" @@ -1357,6 +1424,11 @@ acorn@^8.11.0, acorn@^8.14.0, acorn@^8.4.1, acorn@^8.8.2: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== +ajv-draft-04@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz#3b64761b268ba0b9e668f0b41ba53fce0ad77fc8" + integrity sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw== + ajv-formats@2.1.1, ajv-formats@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" @@ -1364,6 +1436,13 @@ ajv-formats@2.1.1, ajv-formats@^2.1.1: dependencies: ajv "^8.0.0" +ajv-formats@~3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-3.0.1.tgz#3d5dc762bca17679c3c2ea7e90ad6b7532309578" + integrity sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ== + dependencies: + ajv "^8.0.0" + ajv-keywords@^3.5.2: version "3.5.2" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" @@ -1406,6 +1485,16 @@ ajv@^8.0.0, ajv@^8.9.0: json-schema-traverse "^1.0.0" require-from-string "^2.0.2" +ajv@~8.13.0: + version "8.13.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.13.0.tgz#a3939eaec9fb80d217ddf0c3376948c023f28c91" + integrity sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA== + dependencies: + fast-deep-equal "^3.1.3" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.4.1" + ansi-colors@4.1.3: version "4.1.3" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" @@ -1463,7 +1552,7 @@ arg@^4.1.0: resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== -argparse@^1.0.7: +argparse@^1.0.7, argparse@~1.0.9: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== @@ -2140,7 +2229,7 @@ electron-to-chromium@^1.5.73: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.73.tgz#f32956ce40947fa3c8606726a96cd8fb5bb5f720" integrity sha512-8wGNxG9tAG5KhGd3eeA0o6ixhiNdgr0DcHWm85XPCphwZgD1lIEoi6t3VERayWao7SF7AAZTw6oARGJeVjH8Kg== -emittery@^0.13.1: +emittery@^0.13.0, emittery@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== @@ -2496,6 +2585,11 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +figlet@^1.5.2: + version "1.8.0" + resolved "https://registry.yarnpkg.com/figlet/-/figlet-1.8.0.tgz#1b93c4f65f4c1a3b1135221987eee8cf8b9c0ac7" + integrity sha512-chzvGjd+Sp7KUvPHZv6EXV5Ir3Q7kYNpCr4aHrRW79qFtTefmQZNny+W1pW9kf5zeE6dikku2W50W/wAH2xWgw== + figures@^3.0.0, figures@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" @@ -2638,6 +2732,15 @@ fs-extra@^10.0.0: jsonfile "^6.0.1" universalify "^2.0.0" +fs-extra@~7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-monkey@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.6.tgz#8ead082953e88d992cf3ff844faa907b26756da2" @@ -2859,6 +2962,11 @@ import-fresh@^3.2.1, import-fresh@^3.3.0: parent-module "^1.0.0" resolve-from "^4.0.0" +import-lazy@~4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153" + integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw== + import-local@^3.0.2: version "3.2.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" @@ -3452,6 +3560,11 @@ jest@^29.5.0: import-local "^3.0.2" jest-cli "^29.7.0" +jju@~1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/jju/-/jju-1.4.0.tgz#a3abe2718af241a2b2904f84a625970f389ae32a" + integrity sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA== + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -3517,6 +3630,13 @@ jsonc-parser@3.3.1: resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.3.1.tgz#f2a524b4f7fd11e3d791e559977ad60b98b798b4" integrity sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ== +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== + optionalDependencies: + graceful-fs "^4.1.6" + jsonfile@^6.0.1: version "6.1.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" @@ -3630,6 +3750,13 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + magic-string@0.30.8: version "0.30.8" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.8.tgz#14e8624246d2bedba70d5462aa99ac9681844613" @@ -3986,6 +4113,11 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" +parent-require@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parent-require/-/parent-require-1.0.0.tgz#746a167638083a860b0eef6732cb27ed46c32977" + integrity sha512-2MXDNZC4aXdkkap+rBBMv0lUsfJqvX5/2FiYYnfCnorZt3Pk06/IOR5KeaoghgS2w07MLWgjbsnyaq6PdHn2LQ== + parse-json@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" @@ -4137,6 +4269,11 @@ pluralize@8.0.0: resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== +pony-cause@^2.1.4: + version "2.1.11" + resolved "https://registry.yarnpkg.com/pony-cause/-/pony-cause-2.1.11.tgz#d69a20aaccdb3bdb8f74dd59e5c68d8e6772e4bd" + integrity sha512-M7LhCsdNbNgiLYiP4WjsfLUuFmCfnjdF6jKe2R9NKl4WFN+HZPGHJZ9lnLP7f9ZnKe3U9nuWD0szirmj+migUg== + postgres-array@3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-3.0.2.tgz#68d6182cb0f7f152a7e60dc6a6889ed74b0a5f98" @@ -4355,7 +4492,7 @@ resolve.exports@^2.0.0: resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== -resolve@^1.20.0: +resolve@^1.20.0, resolve@~1.22.1: version "1.22.9" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.9.tgz#6da76e4cdc57181fa4471231400e8851d0a924f3" integrity sha512-QxrmX1DzraFIi9PxdG5VkRfRwIgjwyud+z/iBwfRRrVmHc+P9Q7u2lSSpQ6bjr2gy5lrqIiU9vb6iAeGf2400A== @@ -4445,6 +4582,13 @@ semver@^7.3.4, semver@^7.3.5, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semve resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== +semver@~7.5.4: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + send@0.19.0: version "0.19.0" resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" @@ -4628,6 +4772,11 @@ streamsearch@^1.1.0: resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== +string-argv@~0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" + integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== + string-length@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" @@ -4748,7 +4897,7 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -supports-color@^8.0.0: +supports-color@^8.0.0, supports-color@~8.1.1: version "8.1.1" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== @@ -4948,6 +5097,11 @@ type-fest@^0.21.3: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== +type-fest@^4.0.0: + version "4.30.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.30.2.tgz#d94429edde1f7deacf554741650aab394197a4cc" + integrity sha512-UJShLPYi1aWqCdq9HycOL/gwsuqda1OISdBO3t8RlXQC4QvtuIz4b5FCfe2dQIWEpmlRExKmcTBfP1r9bhY7ig== + type-is@^1.6.4, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -4973,6 +5127,17 @@ uid@2.0.2: dependencies: "@lukeed/csprng" "^1.0.0" +umzug@3.8.2: + version "3.8.2" + resolved "https://registry.yarnpkg.com/umzug/-/umzug-3.8.2.tgz#53c2189604d36956d7b75a89128108d0e3073a9f" + integrity sha512-BEWEF8OJjTYVC56GjELeHl/1XjFejrD7aHzn+HldRJTx+pL1siBrKHZC8n4K/xL3bEzVA9o++qD1tK2CpZu4KA== + dependencies: + "@rushstack/ts-command-line" "^4.12.2" + emittery "^0.13.0" + fast-glob "^3.3.2" + pony-cause "^2.1.4" + type-fest "^4.0.0" + undici-types@~6.19.2: version "6.19.8" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" @@ -4983,6 +5148,11 @@ undici-types@~6.20.0: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + universalify@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" @@ -5001,7 +5171,7 @@ update-browserslist-db@^1.1.1: escalade "^3.2.0" picocolors "^1.1.0" -uri-js@^4.2.2: +uri-js@^4.2.2, uri-js@^4.4.1: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== @@ -5187,12 +5357,17 @@ yallist@^3.0.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + yargs-parser@21.1.1, yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== -yargs@^17.3.1: +yargs@17.7.2, yargs@^17.3.1: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== From ceda13468b31392d84bd086ac9e38338da30dc4e Mon Sep 17 00:00:00 2001 From: Melnik George Date: Mon, 23 Dec 2024 07:54:14 +0300 Subject: [PATCH 2/6] Added /downloads and latest/download endpoints --- src/main.ts | 2 +- src/packages/packages.controller.ts | 133 ++++++++++++++++++++++++++-- 2 files changed, 126 insertions(+), 9 deletions(-) diff --git a/src/main.ts b/src/main.ts index 8bb164e..f1645af 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,6 +9,6 @@ async function bootstrap() { methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', credentials: true, // Enable if you need to send cookies or authentication data }); - await app.listen(process.env.PORT ?? 3000); + await app.listen(process.env.PORT ?? 3001); } bootstrap(); diff --git a/src/packages/packages.controller.ts b/src/packages/packages.controller.ts index 6d5c5dc..1838f73 100644 --- a/src/packages/packages.controller.ts +++ b/src/packages/packages.controller.ts @@ -9,11 +9,35 @@ import { EntityManager } from '@mikro-orm/postgresql'; import { Package } from '../model/package.entity'; import { Response } from 'express'; import * as stream from 'stream'; +import { Download } from 'src/model/download.entity'; +import { Version } from 'src/model/version.entity'; @Controller('api/v1/packages') export class PackagesController { constructor(private readonly em: EntityManager) {} + @Get(':name') + async getPackageName(@Param('name') name: string, @Param('version') version: string) { + + const pkg = await this.em.findOne(Package, { name }, { populate: ['versions'] }); + if (!pkg) { + throw new NotFoundException(`Package "${name}" not found`); + } + + const ver = pkg.versions.getItems(); + + if (!ver) { + throw new NotFoundException(`Version "${version}" not found for package "${name}"`); + } + + return { + name: pkg.name, + versions: ver, + readme: pkg.readme, + tag_name: pkg.tagName, + }; + } + @Get(':name/:version') async getPackage(@Param('name') name: string, @Param('version') version: string) { const [major, minor, patch] = version.split('.').map(Number); @@ -41,6 +65,56 @@ export class PackagesController { }; } + + + @Get(':name/latest/download') + async downloadLatestPackage( + @Param('name') name: string, + @Res() res: Response, + ) { + const pkg = await this.em.findOne(Package, { name }, { populate: ['versions'] }); + if (!pkg) { + throw new NotFoundException(`Package "${name}" not found`); + } + + const versions = pkg.versions.getItems(); + if (versions.length === 0) { + throw new NotFoundException(`No versions found for package "${name}"`); + } + + const latestVersion = versions.reduce((latest, current) => { + if (!latest) return current; + + if (current.major > latest.major) return current; + if (current.major < latest.major) return latest; + + if (current.minor > latest.minor) return current; + if (current.minor < latest.minor) return latest; + + if (current.patch > latest.patch) return current; + if (current.patch < latest.patch) return latest; + + return latest; + }); + const download = new Download(); + download.package = pkg; + download.version = latestVersion; + await this.em.persistAndFlush(download); + + const buffer = Buffer.from(latestVersion.packageBlob, 'base64'); + const fileStream = new stream.PassThrough(); + fileStream.end(buffer); + + const fileName = `${name}-${latestVersion.versionNumber}.tar.gz`; + res.set({ + 'Content-Type': 'application/octet-stream', + 'Content-Disposition': `attachment; filename="${fileName}"`, + 'Content-Length': buffer.length, + }); + + fileStream.pipe(res); + } + @Get(':name/:version/download') async downloadPackage( @Param('name') name: string, @@ -48,34 +122,77 @@ export class PackagesController { @Res() res: Response, ) { const [major, minor, patch] = version.split('.').map(Number); - + const pkg = await this.em.findOne(Package, { name }, { populate: ['versions'] }); if (!pkg) { throw new NotFoundException(`Package "${name}" not found`); } - + const ver = pkg.versions.getItems().find( v => v.major === major && v.minor === minor && v.patch === patch, ); - + if (!ver) { throw new NotFoundException(`Version "${version}" not found for package "${name}"`); } - + + const download = new Download(); + download.package = pkg; + download.version = ver; + await this.em.persistAndFlush(download); + const buffer = Buffer.from(ver.packageBlob, 'base64'); - const fileStream = new stream.PassThrough(); fileStream.end(buffer); - + const fileName = `${name}-${version}.tar.gz`; res.set({ 'Content-Type': 'application/octet-stream', 'Content-Disposition': `attachment; filename="${fileName}"`, 'Content-Length': buffer.length, }); - + fileStream.pipe(res); } - + @Get(':name/:version/downloads') + async getDownloadsHistory( + @Param('name') name: string, + @Param('version') version: string, + ) { + const pkg = await this.em.findOne(Package, { name }); + if (!pkg) { + throw new NotFoundException(`Package "${name}" not found`); + } + + const [major, minor, patch] = version.split('.').map(Number); + + const ver = await this.em.findOne(Version, { + package: pkg, + major, + minor, + patch + }); + + if (!ver) { + throw new NotFoundException(`Version "${version}" not found for package "${name}"`); + } + + const downloads = await this.em.find(Download, { + package: pkg, + version: ver + }, { + orderBy: { downloadDate: 'DESC' } + }); + + return { + package: name, + version: version, + totalDownloads: downloads.length, + downloads: downloads.map(download => ({ + id: download.id, + downloadDate: download.downloadDate + })) + }; + } } From 06b03337e11e0491b379e424be864b948eb25ffd Mon Sep 17 00:00:00 2001 From: Melnik George Date: Tue, 24 Dec 2024 00:51:35 +0300 Subject: [PATCH 3/6] Requested changes --- migrations/.snapshot-noir_libs.json | 288 -------------------------- migrations/Migration20241219143807.ts | 33 --- migrations/Migration20241219155508.ts | 13 -- package.json | 3 +- src/mikro-orm.config.ts | 18 +- src/model/package.entity.ts | 5 +- src/model/version.entity.ts | 14 +- src/packages/packages.controller.ts | 108 +++------- src/packages/packages.service.ts | 4 +- src/upload/upload.controller.spec.ts | 18 -- src/upload/upload.controller.ts | 109 ---------- src/upload/upload.module.ts | 11 - src/upload/upload.service.spec.ts | 18 -- src/upload/upload.service.ts | 4 - 14 files changed, 35 insertions(+), 611 deletions(-) delete mode 100644 migrations/.snapshot-noir_libs.json delete mode 100644 migrations/Migration20241219143807.ts delete mode 100644 migrations/Migration20241219155508.ts delete mode 100644 src/upload/upload.controller.spec.ts delete mode 100644 src/upload/upload.controller.ts delete mode 100644 src/upload/upload.module.ts delete mode 100644 src/upload/upload.service.spec.ts delete mode 100644 src/upload/upload.service.ts diff --git a/migrations/.snapshot-noir_libs.json b/migrations/.snapshot-noir_libs.json deleted file mode 100644 index 72dbd9f..0000000 --- a/migrations/.snapshot-noir_libs.json +++ /dev/null @@ -1,288 +0,0 @@ -{ - "namespaces": [ - "public" - ], - "name": "public", - "tables": [ - { - "columns": { - "id": { - "name": "id", - "type": "serial", - "unsigned": false, - "autoincrement": true, - "primary": true, - "nullable": false, - "mappedType": "integer" - }, - "name": { - "name": "name", - "type": "varchar(255)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 255, - "mappedType": "string" - }, - "tag_name": { - "name": "tag_name", - "type": "varchar(255)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 255, - "mappedType": "string" - }, - "readme": { - "name": "readme", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - } - }, - "name": "package", - "schema": "public", - "indexes": [ - { - "keyName": "package_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": {}, - "nativeEnums": {} - }, - { - "columns": { - "id": { - "name": "id", - "type": "serial", - "unsigned": false, - "autoincrement": true, - "primary": true, - "nullable": false, - "mappedType": "integer" - }, - "package_id": { - "name": "package_id", - "type": "int", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "integer" - }, - "major": { - "name": "major", - "type": "int", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "integer" - }, - "minor": { - "name": "minor", - "type": "int", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "integer" - }, - "patch": { - "name": "patch", - "type": "int", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "integer" - }, - "sortable_version": { - "name": "sortable_version", - "type": "varchar(255)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 255, - "mappedType": "string" - }, - "version_number": { - "name": "version_number", - "type": "varchar(255)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 255, - "mappedType": "string" - }, - "package_blob": { - "name": "package_blob", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "size_kb": { - "name": "size_kb", - "type": "int", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "integer" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "version", - "schema": "public", - "indexes": [ - { - "columnNames": [ - "sortable_version" - ], - "composite": false, - "keyName": "version_sortable_version_unique", - "constraint": true, - "primary": false, - "unique": true - }, - { - "keyName": "version_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "version_package_id_foreign": { - "constraintName": "version_package_id_foreign", - "columnNames": [ - "package_id" - ], - "localTableName": "public.version", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.package", - "updateRule": "cascade" - } - }, - "nativeEnums": {} - }, - { - "columns": { - "id": { - "name": "id", - "type": "serial", - "unsigned": false, - "autoincrement": true, - "primary": true, - "nullable": false, - "mappedType": "integer" - }, - "package_id": { - "name": "package_id", - "type": "int", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "integer" - }, - "version_id": { - "name": "version_id", - "type": "int", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "integer" - }, - "download_date": { - "name": "download_date", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "download", - "schema": "public", - "indexes": [ - { - "keyName": "download_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "download_package_id_foreign": { - "constraintName": "download_package_id_foreign", - "columnNames": [ - "package_id" - ], - "localTableName": "public.download", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.package", - "updateRule": "cascade" - }, - "download_version_id_foreign": { - "constraintName": "download_version_id_foreign", - "columnNames": [ - "version_id" - ], - "localTableName": "public.download", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.version", - "updateRule": "cascade" - } - }, - "nativeEnums": {} - } - ], - "nativeEnums": {} -} diff --git a/migrations/Migration20241219143807.ts b/migrations/Migration20241219143807.ts deleted file mode 100644 index 0d4d643..0000000 --- a/migrations/Migration20241219143807.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Migration } from '@mikro-orm/migrations'; - -export class Migration20241219143807 extends Migration { - - override async up(): Promise { - this.addSql(`create table "package" ("id" serial primary key, "name" varchar(255) not null, "tag_name" varchar(255) not null, "readme" text not null);`); - - this.addSql(`create table "version" ("id" serial primary key, "package_id" int not null, "major" int not null, "minor" int not null, "patch" int not null, "sortable_version" varchar(255) not null, "version_number" varchar(255) not null, "package_blob" varchar(255) not null, "size_kb" int not null, "created_at" timestamptz not null);`); - this.addSql(`alter table "version" add constraint "version_sortable_version_unique" unique ("sortable_version");`); - - this.addSql(`create table "download" ("id" serial primary key, "package_id" int not null, "version_id" int not null, "download_date" timestamptz not null);`); - - this.addSql(`alter table "version" add constraint "version_package_id_foreign" foreign key ("package_id") references "package" ("id") on update cascade;`); - - this.addSql(`alter table "download" add constraint "download_package_id_foreign" foreign key ("package_id") references "package" ("id") on update cascade;`); - this.addSql(`alter table "download" add constraint "download_version_id_foreign" foreign key ("version_id") references "version" ("id") on update cascade;`); - } - - override async down(): Promise { - this.addSql(`alter table "version" drop constraint "version_package_id_foreign";`); - - this.addSql(`alter table "download" drop constraint "download_package_id_foreign";`); - - this.addSql(`alter table "download" drop constraint "download_version_id_foreign";`); - - this.addSql(`drop table if exists "package" cascade;`); - - this.addSql(`drop table if exists "version" cascade;`); - - this.addSql(`drop table if exists "download" cascade;`); - } - -} diff --git a/migrations/Migration20241219155508.ts b/migrations/Migration20241219155508.ts deleted file mode 100644 index 922b8e0..0000000 --- a/migrations/Migration20241219155508.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Migration } from '@mikro-orm/migrations'; - -export class Migration20241219155508 extends Migration { - - override async up(): Promise { - this.addSql(`alter table "version" alter column "package_blob" type text using ("package_blob"::text);`); - } - - override async down(): Promise { - this.addSql(`alter table "version" alter column "package_blob" type varchar(255) using ("package_blob"::varchar(255));`); - } - -} diff --git a/package.json b/package.json index 008d0c8..4079af8 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "dotenv": "^16.4.7", "multer": "^1.4.5-lts.1", "reflect-metadata": "^0.2.0", - "rxjs": "^7.8.1" + "rxjs": "^7.8.1", + "semver": "^7.6.3" }, "devDependencies": { "@mikro-orm/cli": "^6.4.1", diff --git a/src/mikro-orm.config.ts b/src/mikro-orm.config.ts index 50e2fc7..028bfad 100644 --- a/src/mikro-orm.config.ts +++ b/src/mikro-orm.config.ts @@ -3,8 +3,8 @@ import * as dotenv from 'dotenv'; dotenv.config(); const config: any = { - entities: ['./dist/src/model'], - entitiesTs: ['./src/model'], + entities: ['./dist/**/*.entity.js'], + entitiesTs: ['./src/**/*.entity.ts'], dbName: process.env.DB_NAME, user: process.env.DB_USER, password: process.env.DB_PASSWORD, @@ -12,19 +12,7 @@ const config: any = { port: Number(process.env.DB_PORT), driver: PostgreSqlDriver, debug: process.env.IS_PROD !== 'true', - migrations: { - tableName: 'mikro_orm_migrations', // name of database table with log of executed transactions - path: './migrations', // path to the folder with migrations - pathTs: undefined, // path to the folder with TS migrations (if used, you should put path to compiled files in `path`) - glob: '!(*.d).{js,ts}', // how to match migration files (all .js and .ts files, but not .d.ts) - transactional: true, // wrap each migration in a transaction - disableForeignKeys: true, // wrap statements with `set foreign_key_checks = 0` or equivalent - allOrNothing: true, // wrap all migrations in master transaction - dropTables: true, // allow to disable table dropping - safe: false, // allow to disable table and column dropping - snapshot: true, // save snapshot when creating new migrations - emit: 'ts', // migration generation mode - }, + }; diff --git a/src/model/package.entity.ts b/src/model/package.entity.ts index d4d320f..e0012d0 100644 --- a/src/model/package.entity.ts +++ b/src/model/package.entity.ts @@ -5,13 +5,10 @@ import { Download } from './download.entity'; @Entity() export class Package { @PrimaryKey() - id!: number; - - @Property() name!: string; @Property() - tagName!: string; + tags!: string; @Property({ type: 'text' }) readme?: string; diff --git a/src/model/version.entity.ts b/src/model/version.entity.ts index 8e0a745..cd485b2 100644 --- a/src/model/version.entity.ts +++ b/src/model/version.entity.ts @@ -10,19 +10,7 @@ export class Version { package!: Package; @Property() - major!: number; - - @Property() - minor!: number; - - @Property() - patch!: number; - - @Property({ unique: true }) - sortableVersion!: string; - - @Property() - versionNumber!: string; + version!: string; @Property({ columnType: 'text' }) packageBlob!: string; diff --git a/src/packages/packages.controller.ts b/src/packages/packages.controller.ts index 1838f73..b333f99 100644 --- a/src/packages/packages.controller.ts +++ b/src/packages/packages.controller.ts @@ -3,7 +3,8 @@ import { Get, Param, StreamableFile, - NotFoundException, Res + NotFoundException, Res, + BadRequestException } from '@nestjs/common'; import { EntityManager } from '@mikro-orm/postgresql'; import { Package } from '../model/package.entity'; @@ -15,40 +16,39 @@ import { Version } from 'src/model/version.entity'; @Controller('api/v1/packages') export class PackagesController { constructor(private readonly em: EntityManager) {} + @Get() + async getAllPackages() { + const packages = await this.em.find(Package, {}, { + populate: ['versions'] + }); - @Get(':name') - async getPackageName(@Param('name') name: string, @Param('version') version: string) { - - const pkg = await this.em.findOne(Package, { name }, { populate: ['versions'] }); - if (!pkg) { - throw new NotFoundException(`Package "${name}" not found`); - } - - const ver = pkg.versions.getItems(); - - if (!ver) { - throw new NotFoundException(`Version "${version}" not found for package "${name}"`); - } - - return { + return packages.map(pkg => ({ name: pkg.name, - versions: ver, - readme: pkg.readme, - tag_name: pkg.tagName, - }; + tags: pkg.tags, + versions: pkg.versions.getItems().map(ver => ({ + version: ver.version, + createdAt: ver.createdAt, + sizeKb: ver.sizeKb + })) + })); } @Get(':name/:version') async getPackage(@Param('name') name: string, @Param('version') version: string) { - const [major, minor, patch] = version.split('.').map(Number); + // const parsedVersion = semver.parse(version); const pkg = await this.em.findOne(Package, { name }, { populate: ['versions'] }); if (!pkg) { throw new NotFoundException(`Package "${name}" not found`); } + // if (!parsedVersion) { + // throw new BadRequestException('Invalid version format'); + // } + + // const { major, minor, patch } = parsedVersion; const ver = pkg.versions.getItems().find( - v => v.major === major && v.minor === minor && v.patch === patch, + v => v.version === version, ); if (!ver) { @@ -57,64 +57,15 @@ export class PackagesController { return { name: pkg.name, - version: ver.versionNumber, + version: ver.version, size_kb: ver.sizeKb, readme: pkg.readme, created_at: ver.createdAt, - tag_name: pkg.tagName, + tag_name: pkg.tags, }; } - - @Get(':name/latest/download') - async downloadLatestPackage( - @Param('name') name: string, - @Res() res: Response, - ) { - const pkg = await this.em.findOne(Package, { name }, { populate: ['versions'] }); - if (!pkg) { - throw new NotFoundException(`Package "${name}" not found`); - } - - const versions = pkg.versions.getItems(); - if (versions.length === 0) { - throw new NotFoundException(`No versions found for package "${name}"`); - } - - const latestVersion = versions.reduce((latest, current) => { - if (!latest) return current; - - if (current.major > latest.major) return current; - if (current.major < latest.major) return latest; - - if (current.minor > latest.minor) return current; - if (current.minor < latest.minor) return latest; - - if (current.patch > latest.patch) return current; - if (current.patch < latest.patch) return latest; - - return latest; - }); - const download = new Download(); - download.package = pkg; - download.version = latestVersion; - await this.em.persistAndFlush(download); - - const buffer = Buffer.from(latestVersion.packageBlob, 'base64'); - const fileStream = new stream.PassThrough(); - fileStream.end(buffer); - - const fileName = `${name}-${latestVersion.versionNumber}.tar.gz`; - res.set({ - 'Content-Type': 'application/octet-stream', - 'Content-Disposition': `attachment; filename="${fileName}"`, - 'Content-Length': buffer.length, - }); - - fileStream.pipe(res); - } - @Get(':name/:version/download') async downloadPackage( @Param('name') name: string, @@ -129,7 +80,7 @@ export class PackagesController { } const ver = pkg.versions.getItems().find( - v => v.major === major && v.minor === minor && v.patch === patch, + v => v.version === version, ); if (!ver) { @@ -165,13 +116,10 @@ export class PackagesController { throw new NotFoundException(`Package "${name}" not found`); } - const [major, minor, patch] = version.split('.').map(Number); const ver = await this.em.findOne(Version, { - package: pkg, - major, - minor, - patch + package: {name: name.trim()}, + version }); if (!ver) { @@ -188,9 +136,7 @@ export class PackagesController { return { package: name, version: version, - totalDownloads: downloads.length, downloads: downloads.map(download => ({ - id: download.id, downloadDate: download.downloadDate })) }; diff --git a/src/packages/packages.service.ts b/src/packages/packages.service.ts index 95fb24d..f8d543b 100644 --- a/src/packages/packages.service.ts +++ b/src/packages/packages.service.ts @@ -11,7 +11,7 @@ export class PackagesService { let pkg = await this.em.findOne(Package, { name }); if (!pkg) { - pkg = this.em.create(Package, { name, tagName: '', readme: '' }); + pkg = this.em.create(Package, { name, tags: '', readme: '' }); await this.em.persistAndFlush(pkg); } @@ -24,7 +24,6 @@ export class PackagesService { major: number; minor: number; patch: number; - sortableVersion: string; versionNumber: string; packageBlob: string; sizeKb: number; @@ -34,7 +33,6 @@ export class PackagesService { const existingVersion = await this.em.findOne(Version, { package: pkg, - sortableVersion: versionData.sortableVersion, }); if (existingVersion) { diff --git a/src/upload/upload.controller.spec.ts b/src/upload/upload.controller.spec.ts deleted file mode 100644 index cd3d2d2..0000000 --- a/src/upload/upload.controller.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { UploadController } from './upload.controller'; - -describe('UploadController', () => { - let controller: UploadController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [UploadController], - }).compile(); - - controller = module.get(UploadController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/src/upload/upload.controller.ts b/src/upload/upload.controller.ts deleted file mode 100644 index 237536c..0000000 --- a/src/upload/upload.controller.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { Controller, Post, UseInterceptors, UploadedFile, Res, HttpStatus, Get } from '@nestjs/common'; -import { FileInterceptor } from '@nestjs/platform-express'; -import * as fs from 'fs'; -import * as path from 'path'; -import { PackagesService } from '../packages/packages.service'; - -@Controller('api/v1/upload') -export class UploadController { - private readonly uploadPath = process.env.REGISTRY_PATH ?? path.join(__dirname, '..', 'noir_registry'); - - constructor(private readonly packagesService: PackagesService) {} - - @Post('') - @UseInterceptors(FileInterceptor('file')) - async uploadFile(@UploadedFile() file: Express.Multer.File, @Res() res) { - if (!file) { - return res.status(HttpStatus.BAD_REQUEST).json({ message: 'File is required.' }); - } - - const filePath = path.join(this.uploadPath, file.originalname); - - try { - console.log('Checking upload directory:', this.uploadPath); - if (!fs.existsSync(this.uploadPath)) { - console.log('Directory does not exist. Creating...'); - fs.mkdirSync(this.uploadPath, { recursive: true }); - } - - - console.log('Saving file to:', filePath); - fs.writeFileSync(filePath, file.buffer); - - - const [name, version] = file.originalname.split('_'); - if (!name || !version) { - throw new Error('Invalid file name format. Use "name_major.minor.patch.ext".'); - } - - const versionMatch = version.match(/^(\d+)\.(\d+)\.(\d+)/); - if (!versionMatch) { - throw new Error('Invalid version format. Use "major.minor.patch".'); - } - - const [major, minor, patch] = versionMatch.slice(1).map(Number); - const sortableVersion = `${major}.${minor}.${patch}`.padStart(12, '0'); - - - const packageBlob = file.buffer.toString('base64'); - const sizeKb = Math.round(file.buffer.length / 1024); - - console.log('Saving package and version to the database...'); - const pkg = await this.packagesService.findOrCreatePackage(name); - const newVersion = await this.packagesService.addVersion(pkg.id, { - major, - minor, - patch, - sortableVersion, - versionNumber: `${major}.${minor}.${patch}`, - packageBlob, - sizeKb, - }); - console.log('File uploaded and processed successfully.'); - return res.status(HttpStatus.OK).json({ - message: 'File uploaded successfully', - package: pkg.name, - version: newVersion.versionNumber, - }); - } catch (error) { - console.error('Error uploading file:', error); - return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ - message: 'Error uploading file', - error: error.message, - }); - } - } - @Get() - getAllPackages(@Res() res) { - const dirPath = path.join(this.uploadPath); - - // Check if the directory exists - if (!fs.existsSync(dirPath)) { - return res.status(404).json({ message: 'No packages found' }); - } - - // Read all files in the directory - const files = fs.readdirSync(dirPath); - const packages = files.map((file) => { - // Extract package name and version - const match = file.match(/^(.*)_(\d+\.\d+\.\d+)/); - if (match) { - const name = match[1]; // Package name - const version = match[2]; // Package version - - // Get the file size in KB - const fileSizeInBytes = fs.statSync(path.join(dirPath, file)).size; - const sizeInKB = (fileSizeInBytes / 1024).toFixed(2); // Convert to KB - - return { - name, - version, - size: `${sizeInKB} KB`, - }; - } - return null; // Return null if the file doesn't match the expected pattern - }).filter(pkg => pkg !== null); // Filter out any null entries - - return res.json(packages); - } -} diff --git a/src/upload/upload.module.ts b/src/upload/upload.module.ts deleted file mode 100644 index 3b865a0..0000000 --- a/src/upload/upload.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Module } from '@nestjs/common'; -import { UploadController } from './upload.controller'; -import { UploadService } from './upload.service'; -import { PackagesModule } from 'src/packages/packages.module'; - -@Module({ - imports: [PackagesModule], - controllers: [UploadController], - providers: [UploadService] -}) -export class UploadModule {} diff --git a/src/upload/upload.service.spec.ts b/src/upload/upload.service.spec.ts deleted file mode 100644 index 7b83db6..0000000 --- a/src/upload/upload.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { UploadService } from './upload.service'; - -describe('UploadService', () => { - let service: UploadService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [UploadService], - }).compile(); - - service = module.get(UploadService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/src/upload/upload.service.ts b/src/upload/upload.service.ts deleted file mode 100644 index 53913c9..0000000 --- a/src/upload/upload.service.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -@Injectable() -export class UploadService {} From abab7d4f74068235480afc4f05f66b386a766ccf Mon Sep 17 00:00:00 2001 From: Melnik George Date: Tue, 24 Dec 2024 16:55:32 +0300 Subject: [PATCH 4/6] Fixes --- src/app.module.ts | 3 +- src/model/package.entity.ts | 3 ++ src/model/version.entity.ts | 2 +- src/packages/packages.controller.ts | 56 ++++++++++++++--------------- src/packages/packages.module.ts | 5 +-- src/packages/packages.service.ts | 52 --------------------------- 6 files changed, 33 insertions(+), 88 deletions(-) delete mode 100644 src/packages/packages.service.ts diff --git a/src/app.module.ts b/src/app.module.ts index 5272cbf..e56bcba 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -5,7 +5,6 @@ import { AppController } from './app.controller'; import { AppService } from './app.service'; import { PackagesModule } from './packages/packages.module'; import MikroOrmConfig from './mikro-orm.config'; -import { UploadModule } from './upload/upload.module'; @Module({ imports: [ @@ -18,7 +17,7 @@ import { UploadModule } from './upload/upload.module'; useFactory: async () => MikroOrmConfig, inject: [ConfigService], }), - PackagesModule, UploadModule + PackagesModule ], controllers: [AppController], providers: [AppService], diff --git a/src/model/package.entity.ts b/src/model/package.entity.ts index e0012d0..68ea5c3 100644 --- a/src/model/package.entity.ts +++ b/src/model/package.entity.ts @@ -13,6 +13,9 @@ export class Package { @Property({ type: 'text' }) readme?: string; + @Property({ type: 'text' }) + description?: string; + @OneToMany(() => Version, version => version.package) versions = new Collection(this); diff --git a/src/model/version.entity.ts b/src/model/version.entity.ts index cd485b2..d8dc7d8 100644 --- a/src/model/version.entity.ts +++ b/src/model/version.entity.ts @@ -9,7 +9,7 @@ export class Version { @ManyToOne(() => Package) package!: Package; - @Property() + @PrimaryKey() version!: string; @Property({ columnType: 'text' }) diff --git a/src/packages/packages.controller.ts b/src/packages/packages.controller.ts index b333f99..8717c78 100644 --- a/src/packages/packages.controller.ts +++ b/src/packages/packages.controller.ts @@ -4,7 +4,8 @@ import { Param, StreamableFile, NotFoundException, Res, - BadRequestException + BadRequestException, + Query } from '@nestjs/common'; import { EntityManager } from '@mikro-orm/postgresql'; import { Package } from '../model/package.entity'; @@ -12,14 +13,17 @@ import { Response } from 'express'; import * as stream from 'stream'; import { Download } from 'src/model/download.entity'; import { Version } from 'src/model/version.entity'; +import * as semver from 'semver'; @Controller('api/v1/packages') export class PackagesController { constructor(private readonly em: EntityManager) {} @Get() - async getAllPackages() { + async getAllPackages(@Query('limit') limit: string = '10') { + const limitNumber = Math.min(parseInt(limit) || 10, 100); const packages = await this.em.find(Package, {}, { - populate: ['versions'] + populate: ['versions'], + limit: limitNumber }); return packages.map(pkg => ({ @@ -35,33 +39,29 @@ export class PackagesController { @Get(':name/:version') async getPackage(@Param('name') name: string, @Param('version') version: string) { - // const parsedVersion = semver.parse(version); - - const pkg = await this.em.findOne(Package, { name }, { populate: ['versions'] }); - if (!pkg) { - throw new NotFoundException(`Package "${name}" not found`); + if (!semver.valid(version)) { + throw new BadRequestException('Invalid version format'); } - - // if (!parsedVersion) { - // throw new BadRequestException('Invalid version format'); - // } - // const { major, minor, patch } = parsedVersion; - const ver = pkg.versions.getItems().find( - v => v.version === version, - ); - - if (!ver) { - throw new NotFoundException(`Version "${version}" not found for package "${name}"`); + const verObj = await this.em.findOne(Version, { + package: { name: name.trim() }, + version: version + }, { + populate: ['package'] + }); + + if (!verObj) { + throw new NotFoundException(`Version ${version} not found for package "${name}"`); } return { - name: pkg.name, - version: ver.version, - size_kb: ver.sizeKb, - readme: pkg.readme, - created_at: ver.createdAt, - tag_name: pkg.tags, + name: verObj.package.name, + version: verObj.version, + size_kb: verObj.sizeKb, + readme: verObj.package.readme, + description: verObj.package.description, + created_at: verObj.createdAt, + tag_name: verObj.package.tags, }; } @@ -72,7 +72,6 @@ export class PackagesController { @Param('version') version: string, @Res() res: Response, ) { - const [major, minor, patch] = version.split('.').map(Number); const pkg = await this.em.findOne(Package, { name }, { populate: ['versions'] }); if (!pkg) { @@ -92,15 +91,14 @@ export class PackagesController { download.version = ver; await this.em.persistAndFlush(download); - const buffer = Buffer.from(ver.packageBlob, 'base64'); const fileStream = new stream.PassThrough(); - fileStream.end(buffer); + fileStream.end(ver.data); const fileName = `${name}-${version}.tar.gz`; res.set({ 'Content-Type': 'application/octet-stream', 'Content-Disposition': `attachment; filename="${fileName}"`, - 'Content-Length': buffer.length, + 'Content-Length': ver.data.length, }); fileStream.pipe(res); diff --git a/src/packages/packages.module.ts b/src/packages/packages.module.ts index 9eb079c..943e077 100644 --- a/src/packages/packages.module.ts +++ b/src/packages/packages.module.ts @@ -1,7 +1,6 @@ import { Module } from '@nestjs/common'; import { MikroOrmModule } from '@mikro-orm/nestjs'; import { PackagesController } from './packages.controller'; -import { PackagesService } from './packages.service'; import { Package } from '../model/package.entity'; import { Version } from '../model/version.entity'; import { Download } from '../model/download.entity'; @@ -10,8 +9,6 @@ import { Download } from '../model/download.entity'; imports: [ MikroOrmModule.forFeature([Package, Version, Download]) ], - controllers: [PackagesController], - providers: [PackagesService], - exports: [PackagesService], + controllers: [PackagesController] }) export class PackagesModule {} \ No newline at end of file diff --git a/src/packages/packages.service.ts b/src/packages/packages.service.ts deleted file mode 100644 index f8d543b..0000000 --- a/src/packages/packages.service.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { EntityManager } from '@mikro-orm/postgresql'; -import { Package } from '../model/package.entity'; -import { Version } from '../model/version.entity'; - -@Injectable() -export class PackagesService { - constructor(private readonly em: EntityManager) {} - - async findOrCreatePackage(name: string): Promise { - let pkg = await this.em.findOne(Package, { name }); - - if (!pkg) { - pkg = this.em.create(Package, { name, tags: '', readme: '' }); - await this.em.persistAndFlush(pkg); - } - - return pkg; - } - - async addVersion( - packageId: number, - versionData: { - major: number; - minor: number; - patch: number; - versionNumber: string; - packageBlob: string; - sizeKb: number; - }, - ): Promise { - const pkg = await this.em.findOneOrFail(Package, packageId, { populate: ['versions'] }); - - const existingVersion = await this.em.findOne(Version, { - package: pkg, - }); - - if (existingVersion) { - throw new Error('Version already exists'); - } - - const version = this.em.create(Version, { - ...versionData, - package: pkg, - }); - - pkg.versions.add(version); - await this.em.persistAndFlush(version); - - return version; - } -} From 136115e364c4d4ce92b2331eaab20d25d6b6abf2 Mon Sep 17 00:00:00 2001 From: Melnik George Date: Tue, 24 Dec 2024 17:01:22 +0300 Subject: [PATCH 5/6] Update version.entity.ts --- src/model/version.entity.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/model/version.entity.ts b/src/model/version.entity.ts index d8dc7d8..fa2fcca 100644 --- a/src/model/version.entity.ts +++ b/src/model/version.entity.ts @@ -12,8 +12,8 @@ export class Version { @PrimaryKey() version!: string; - @Property({ columnType: 'text' }) - packageBlob!: string; + @Property({ type: 'blob' }) // Maps to BLOB in the database + data!: Buffer; @Property() sizeKb!: number; From 8a67f3b0b6fbcffd235b52183fe8d114480ba044 Mon Sep 17 00:00:00 2001 From: Melnik George Date: Tue, 24 Dec 2024 17:22:35 +0300 Subject: [PATCH 6/6] Update version.entity.ts --- src/model/version.entity.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/model/version.entity.ts b/src/model/version.entity.ts index fa2fcca..dc017b1 100644 --- a/src/model/version.entity.ts +++ b/src/model/version.entity.ts @@ -3,10 +3,7 @@ import { Package } from './package.entity'; @Entity() export class Version { - @PrimaryKey() - id!: number; - - @ManyToOne(() => Package) + @ManyToOne(() => Package, { primary: true }) package!: Package; @PrimaryKey()