Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modify GET package endpoint to follow creates.io API specification + add DB #1

Merged
merged 15 commits into from
Jan 2, 2025
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -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
8 changes: 1 addition & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
# Use Node.js base image
FROM node:22.8.0-alpine

# Set the working directory in the container
WORKDIR /app

# Copy package.json and package-lock.json
COPY package*.json ./
COPY yarn.lock ./

# Install dependencies
RUN yarn install

# Copy the rest of the application code
COPY src ./src
COPY tsconfig.json tsconfig.build.json nest-cli.json ./

# Build the Nest.js app
RUN yarn build

# Expose the application port
EXPOSE 3000

# Start the application
CMD ["yarn", "start:prod"]
CMD ["sh", "-c", "npx mikro-orm schema:update --run && npm start prod"]
Binary file added blobs/aztec-0.67.0
Binary file not shown.
Binary file added blobs/aztec-easy-private-state-0.67.0
Binary file not shown.
Binary file added blobs/aztec-protocol-types-0.66.0
Binary file not shown.
Binary file added blobs/aztec-value-note-0.67.0
Binary file not shown.
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -30,9 +32,10 @@
"dotenv": "^16.4.7",
"multer": "^1.4.5-lts.1",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1"
"semver": "^7.6.3"
},
"devDependencies": {
"@mikro-orm/cli": "^6.4.1",
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
"@nestjs/testing": "^10.0.0",
Expand Down
22 changes: 0 additions & 22 deletions src/app.controller.spec.ts

This file was deleted.

12 changes: 0 additions & 12 deletions src/app.controller.ts

This file was deleted.

18 changes: 5 additions & 13 deletions src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
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 { PackagesModule } from './packages/packages.module';
import MikroOrmConfig from './mikro-orm.config';

@Module({
imports: [UploadModule],
controllers: [AppController],
providers: [AppService],
})
import { AztecPackagesInitService } from './services/aztec-packages-init.service';

@Module({
imports: [
Expand All @@ -23,9 +16,8 @@ import MikroOrmConfig from './mikro-orm.config';
useFactory: async () => MikroOrmConfig,
inject: [ConfigService],
}),
UploadModule
PackagesModule
],
controllers: [AppController],
providers: [AppService],
providers: [AztecPackagesInitService],
})
export class AppModule {}
export class AppModule {}
8 changes: 0 additions & 8 deletions src/app.service.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
4 changes: 2 additions & 2 deletions src/mikro-orm.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import * as dotenv from 'dotenv';

dotenv.config();
const config: any = {
entities: ['./dist/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,
Expand Down
18 changes: 18 additions & 0 deletions src/model/download.entity.ts
Original file line number Diff line number Diff line change
@@ -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();
}
24 changes: 24 additions & 0 deletions src/model/package.entity.ts
Original file line number Diff line number Diff line change
@@ -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 {
mobycrypt marked this conversation as resolved.
Show resolved Hide resolved
@PrimaryKey()
name!: string;
mobycrypt marked this conversation as resolved.
Show resolved Hide resolved

@Property()
tags?: string;

@Property({ type: 'text' })
readme?: string;

@Property({ type: 'text' })
description?: string;

@OneToMany(() => Version, version => version.package)
versions = new Collection<Version>(this);

@OneToMany(() => Download, download => download.package)
downloads = new Collection<Download>(this);
}
20 changes: 20 additions & 0 deletions src/model/version.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Entity, PrimaryKey, Property, ManyToOne } from '@mikro-orm/core';
import { Package } from './package.entity';

@Entity()
export class Version {
@ManyToOne(() => Package, { primary: true })
package!: Package;

@PrimaryKey()
version!: string;

@Property({ type: 'blob' }) // Maps to BLOB in the database
data!: Buffer;

@Property()
sizeKb!: number;

@Property()
createdAt: Date = new Date();
}
135 changes: 135 additions & 0 deletions src/packages/packages.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import {
Controller,
Get,
Param,
StreamableFile,
NotFoundException, Res,
BadRequestException,
Query
} from '@nestjs/common';
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';
import * as semver from 'semver';

@Controller('api/v1/packages')
export class PackagesController {

constructor(private readonly em: EntityManager) {}

@Get()
mobycrypt marked this conversation as resolved.
Show resolved Hide resolved
async getAllPackages(@Query('limit') limit: string = '10') {
const limitNumber = Math.min(parseInt(limit) || 10, 100);
const packages = await this.em.findAll(Package, {
populate: ['versions'],
limit: limitNumber
});

return packages.map(pkg => ({
name: pkg.name,
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) {
if (!semver.valid(version)) {
throw new BadRequestException('Invalid version format');
}

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: verObj.package.name,
version: verObj.version,
size_kb: verObj.sizeKb,
readme: verObj.package.readme,
description: verObj.package.description,
created_at: verObj.createdAt,
tags: verObj.package.tags,
};
}

@Get(':name/:version/download')
async downloadPackage(@Param('name') name: string, @Param('version') version: string, @Res() res: Response) {
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}"`);
}

const download = new Download();
download.package = verObj.package;
download.version = verObj;
await this.em.persistAndFlush(download);

const fileStream = new stream.PassThrough();
fileStream.end(verObj.data);

const fileName = `${name}-${version}`;
res.set({
'Content-Type': 'application/octet-stream',
'Content-Disposition': `attachment; filename="${fileName}"`,
'Content-Length': verObj.data.length,
});

fileStream.pipe(res);
}

@Get(':name/:version/downloads')
async getDownloadsHistory(@Param('name') name: string, @Param('version') version: string) {
const downloads = await this.em.find(Download, {
package: { name: name.trim() },
version: { version: version.trim() }
}, {
orderBy: { downloadDate: 'DESC' }
});

return {
package: name,
version: version,
downloads: downloads.map(download => ({
downloadDate: download.downloadDate
}))
};
}

@Get('downloads')
async getAllDownloads(@Query('sortBy') sortBy: 'asc' | 'desc' = 'desc') {
const [downloads, total] = await this.em.findAndCount(Download, {}, {
populate: ['package', 'version'],
orderBy: { downloadDate: sortBy }
});

return {
data: downloads.map(download => ({
package: download.package.name,
version: download.version.version,
downloadDate: download.downloadDate
})),
total
};
}
}
14 changes: 14 additions & 0 deletions src/packages/packages.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { PackagesController } from './packages.controller';
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]
})
export class PackagesModule {}
Loading