Compare commits
6 Commits
3fd0398a78
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
075ce07550
|
|||
|
143e6747cc
|
|||
|
4d7049a30a
|
|||
|
4e3ac71c78
|
|||
|
6ab755d91a
|
|||
|
8b66e589d7
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "archrepo",
|
"name": "archrepo",
|
||||||
"version": "0.0.1",
|
"version": "2.0.2",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { AppController } from './app.controller';
|
import { AppController } from './app.controller';
|
||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
|
|
||||||
@@ -8,7 +9,7 @@ describe('AppController', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const app: TestingModule = await Test.createTestingModule({
|
const app: TestingModule = await Test.createTestingModule({
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [AppService],
|
providers: [ConfigService, AppService],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
appController = app.get<AppController>(AppController);
|
appController = app.get<AppController>(AppController);
|
||||||
|
|||||||
@@ -1,15 +1,25 @@
|
|||||||
|
import { Response } from 'express';
|
||||||
|
import { Readable } from 'stream';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
Get,
|
Get,
|
||||||
Header,
|
Header,
|
||||||
|
Res,
|
||||||
Param,
|
Param,
|
||||||
|
Query,
|
||||||
Headers,
|
Headers,
|
||||||
HttpException,
|
HttpException,
|
||||||
HttpStatus,
|
HttpStatus,
|
||||||
StreamableFile,
|
StreamableFile,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { GetObjectCommandInput } from '@aws-sdk/client-s3';
|
import { ApiOkResponse, ApiNotFoundResponse } from '@nestjs/swagger';
|
||||||
|
import {
|
||||||
|
GetObjectCommandInput,
|
||||||
|
ListObjectsV2CommandInput,
|
||||||
|
} from '@aws-sdk/client-s3';
|
||||||
|
|
||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
|
|
||||||
@Controller()
|
@Controller()
|
||||||
@@ -19,15 +29,19 @@ export class AppController {
|
|||||||
private readonly appService: AppService,
|
private readonly appService: AppService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
@ApiOkResponse()
|
||||||
@Get('healthz')
|
@Get('healthz')
|
||||||
getHealthz(): string {
|
getHealthz(): string {
|
||||||
return this.appService.getHealthz();
|
return this.appService.getHealthz();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(`:repo/os/:arch/:pkg`)
|
@ApiOkResponse()
|
||||||
|
@ApiNotFoundResponse()
|
||||||
|
@Get(':repo/os/:arch/:pkg')
|
||||||
@Header('Accept-Ranges', 'bytes')
|
@Header('Accept-Ranges', 'bytes')
|
||||||
async getRepo(
|
async getPackage(
|
||||||
@Headers('Range') range: string,
|
@Headers('Range') range: string,
|
||||||
|
@Res({ passthrough: true }) res: Response,
|
||||||
@Param('repo') repo: string,
|
@Param('repo') repo: string,
|
||||||
@Param('arch') arch: string,
|
@Param('arch') arch: string,
|
||||||
@Param('pkg') pkg: string,
|
@Param('pkg') pkg: string,
|
||||||
@@ -40,12 +54,54 @@ export class AppController {
|
|||||||
HttpStatus.NOT_FOUND,
|
HttpStatus.NOT_FOUND,
|
||||||
);
|
);
|
||||||
|
|
||||||
const stream = await this.appService.getObject({
|
const output = await this.appService.getObject({
|
||||||
Bucket: this.configService.get<string>('MINIO_BUCKET'),
|
Bucket: this.configService.get<string>('MINIO_BUCKET'),
|
||||||
Key: pkg,
|
Key: pkg,
|
||||||
Range: range,
|
Range: range,
|
||||||
} as GetObjectCommandInput);
|
} as GetObjectCommandInput);
|
||||||
|
if (!output || !output.Body) {
|
||||||
|
console.log(`No such file '${pkg}'`);
|
||||||
|
throw new HttpException(`No such file '${pkg}'`, HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
return new StreamableFile(stream);
|
if (range !== undefined) {
|
||||||
|
res.set('Content-Length', `${output.ContentLength}`);
|
||||||
|
res.set('Content-Range', `${output.ContentRange}`);
|
||||||
|
}
|
||||||
|
return new StreamableFile(output.Body as Readable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse()
|
||||||
|
@ApiNotFoundResponse()
|
||||||
|
@Get(':repo/os/:arch')
|
||||||
|
async getPackageList(
|
||||||
|
@Res({ passthrough: true }) res: Response,
|
||||||
|
@Param('repo') repo: string,
|
||||||
|
@Param('arch') arch: string,
|
||||||
|
@Query('format') format: string,
|
||||||
|
): Promise<string> {
|
||||||
|
if (repo !== this.configService.get<string>('REPO_NAME'))
|
||||||
|
throw new HttpException(`Repo '${repo}' not exist`, HttpStatus.NOT_FOUND)
|
||||||
|
if (arch !== this.configService.get<string>('ARCH_NAME'))
|
||||||
|
throw new HttpException(
|
||||||
|
`Architecture '${arch}' not exist`,
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
);
|
||||||
|
|
||||||
|
const output = await this.appService.getObjectList({
|
||||||
|
Bucket: this.configService.get<string>('MINIO_BUCKET')
|
||||||
|
} as ListObjectsV2CommandInput);
|
||||||
|
if (!output) {
|
||||||
|
console.log('Cannot list files');
|
||||||
|
throw new HttpException(`Cannot list files`, HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format === 'json')
|
||||||
|
return JSON.stringify(output.Contents?.reduce((acc, cur) => {
|
||||||
|
acc.push(cur.Key ?? '');
|
||||||
|
return acc;
|
||||||
|
}, Array<string>()), null, 2);
|
||||||
|
return output.Contents?.reduce(
|
||||||
|
(acc, cur) => `${acc}\n${cur.Key ?? ''}`, '') ?? '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import { Readable } from 'stream';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
|
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import {
|
import {
|
||||||
S3Client,
|
S3Client,
|
||||||
S3ClientConfig,
|
S3ClientConfig,
|
||||||
GetObjectCommand,
|
GetObjectCommand,
|
||||||
GetObjectCommandInput,
|
GetObjectCommandInput,
|
||||||
|
GetObjectCommandOutput,
|
||||||
|
ListObjectsV2Command,
|
||||||
|
ListObjectsV2CommandInput,
|
||||||
|
ListObjectsV2CommandOutput,
|
||||||
|
NoSuchKey,
|
||||||
} from '@aws-sdk/client-s3';
|
} from '@aws-sdk/client-s3';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -28,14 +32,28 @@ export class AppService {
|
|||||||
return 'OK';
|
return 'OK';
|
||||||
}
|
}
|
||||||
|
|
||||||
async getObject(getObjectConfig: GetObjectCommandInput): Promise<Readable> {
|
async getObject(
|
||||||
const command = new GetObjectCommand(getObjectConfig);
|
config: GetObjectCommandInput,
|
||||||
const res = await this.s3Client.send(command);
|
): Promise<GetObjectCommandOutput | null> {
|
||||||
if (!res.Body)
|
const command = new GetObjectCommand(config);
|
||||||
throw new HttpException(
|
try {
|
||||||
's3 get object failed',
|
return await this.s3Client.send(command);
|
||||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
} catch (err: unknown) {
|
||||||
);
|
if (err instanceof NoSuchKey) return null;
|
||||||
return res.Body as Readable;
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getObjectList(
|
||||||
|
config: ListObjectsV2CommandInput,
|
||||||
|
): Promise<ListObjectsV2CommandOutput | null> {
|
||||||
|
const command = new ListObjectsV2Command(config);
|
||||||
|
try {
|
||||||
|
return await this.s3Client.send(command);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
if (err instanceof Error)
|
||||||
|
return null;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { INestApplication } from '@nestjs/common';
|
import { INestApplication } from '@nestjs/common';
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
import * as request from 'supertest';
|
import * as request from 'supertest';
|
||||||
import { App } from 'supertest/types';
|
import { App } from 'supertest/types';
|
||||||
import { AppModule } from './../src/app.module';
|
import { AppModule } from './../src/app.module';
|
||||||
@@ -16,10 +17,10 @@ describe('AppController (e2e)', () => {
|
|||||||
await app.init();
|
await app.init();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('/ (GET)', () => {
|
it('/healthz (GET)', () => {
|
||||||
return request(app.getHttpServer())
|
return request(app.getHttpServer())
|
||||||
.get('/')
|
.get('/healthz')
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.expect('Hello World!');
|
.expect('OK');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user