mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-12-28 14:23:52 +00:00
Merge pull request #543 from codimd/media-controller-tests
This commit is contained in:
commit
23c07dc67d
12 changed files with 128 additions and 39 deletions
|
@ -25,7 +25,6 @@
|
|||
"@nestjs/common": "^7.0.0",
|
||||
"@nestjs/core": "^7.0.0",
|
||||
"@nestjs/platform-express": "^7.0.0",
|
||||
"@nestjs/serve-static": "^2.1.3",
|
||||
"@nestjs/swagger": "^4.5.12",
|
||||
"@nestjs/typeorm": "^7.1.0",
|
||||
"class-transformer": "^0.2.3",
|
||||
|
|
|
@ -19,14 +19,12 @@ import {
|
|||
import { ConsoleLoggerService } from '../../../logger/console-logger.service';
|
||||
import { MediaService } from '../../../media/media.service';
|
||||
import { MulterFile } from '../../../media/multer-file.interface';
|
||||
import { NotesService } from '../../../notes/notes.service';
|
||||
|
||||
@Controller('media')
|
||||
export class MediaController {
|
||||
constructor(
|
||||
private readonly logger: ConsoleLoggerService,
|
||||
private mediaService: MediaService,
|
||||
private notesService: NotesService,
|
||||
) {
|
||||
this.logger.setContext(MediaController.name);
|
||||
}
|
||||
|
@ -44,7 +42,11 @@ export class MediaController {
|
|||
'uploadImage',
|
||||
);
|
||||
try {
|
||||
const url = await this.mediaService.saveFile(file, username, noteId);
|
||||
const url = await this.mediaService.saveFile(
|
||||
file.buffer,
|
||||
username,
|
||||
noteId,
|
||||
);
|
||||
return {
|
||||
link: url,
|
||||
};
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { ServeStaticModule } from '@nestjs/serve-static';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { join } from 'path';
|
||||
import { PublicApiModule } from './api/public/public-api.module';
|
||||
import { AuthorsModule } from './authors/authors.module';
|
||||
import { GroupsModule } from './groups/groups.module';
|
||||
|
@ -22,11 +20,6 @@ import { UsersModule } from './users/users.module';
|
|||
autoLoadEntities: true,
|
||||
synchronize: true,
|
||||
}),
|
||||
ServeStaticModule.forRoot({
|
||||
rootPath: join(__dirname, '..'),
|
||||
// TODO: Get uploads directory from config
|
||||
renderPath: 'uploads',
|
||||
}),
|
||||
NotesModule,
|
||||
UsersModule,
|
||||
RevisionsModule,
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { ValidationPipe } from '@nestjs/common';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||
import { AppModule } from './app.module';
|
||||
import { NestConsoleLoggerService } from './logger/nest-console-logger.service';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
const app = await NestFactory.create<NestExpressApplication>(AppModule);
|
||||
const logger = await app.resolve(NestConsoleLoggerService);
|
||||
logger.log('Switching logger', 'AppBootstrap');
|
||||
app.useLogger(logger);
|
||||
|
@ -24,6 +25,10 @@ async function bootstrap() {
|
|||
transform: true,
|
||||
}),
|
||||
);
|
||||
// TODO: Get uploads directory from config
|
||||
app.useStaticAssets('uploads', {
|
||||
prefix: '/uploads',
|
||||
});
|
||||
await app.listen(3000);
|
||||
logger.log('Listening on port 3000', 'AppBootstrap');
|
||||
}
|
||||
|
|
|
@ -7,33 +7,43 @@ import { BackendData } from '../media-upload.entity';
|
|||
|
||||
@Injectable()
|
||||
export class FilesystemBackend implements MediaBackend {
|
||||
// TODO: Get uploads directory from config
|
||||
uploadDirectory = './uploads';
|
||||
|
||||
constructor(private readonly logger: ConsoleLoggerService) {
|
||||
this.logger.setContext(FilesystemBackend.name);
|
||||
}
|
||||
|
||||
private getFilePath(fileName: string): string {
|
||||
return join(this.uploadDirectory, fileName);
|
||||
}
|
||||
|
||||
private async ensureDirectory() {
|
||||
try {
|
||||
await fs.access(this.uploadDirectory);
|
||||
} catch (e) {
|
||||
await fs.mkdir(this.uploadDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
async saveFile(
|
||||
buffer: Buffer,
|
||||
fileName: string,
|
||||
): Promise<[string, BackendData]> {
|
||||
const filePath = FilesystemBackend.getFilePath(fileName);
|
||||
const filePath = this.getFilePath(fileName);
|
||||
this.logger.debug(`Writing file to: ${filePath}`, 'saveFile');
|
||||
await this.ensureDirectory();
|
||||
await fs.writeFile(filePath, buffer, null);
|
||||
return ['/' + filePath, null];
|
||||
}
|
||||
|
||||
async deleteFile(fileName: string, _: BackendData): Promise<void> {
|
||||
return fs.unlink(FilesystemBackend.getFilePath(fileName));
|
||||
return fs.unlink(this.getFilePath(fileName));
|
||||
}
|
||||
|
||||
getFileURL(fileName: string, _: BackendData): Promise<string> {
|
||||
const filePath = FilesystemBackend.getFilePath(fileName);
|
||||
const filePath = this.getFilePath(fileName);
|
||||
// TODO: Add server address to url
|
||||
return Promise.resolve('/' + filePath);
|
||||
}
|
||||
|
||||
private static getFilePath(fileName: string): string {
|
||||
// TODO: Get uploads directory from config
|
||||
const uploadDirectory = './uploads';
|
||||
return join(uploadDirectory, fileName);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import { UsersService } from '../users/users.service';
|
|||
import { BackendType } from './backends/backend-type.enum';
|
||||
import { FilesystemBackend } from './backends/filesystem-backend';
|
||||
import { MediaUpload } from './media-upload.entity';
|
||||
import { MulterFile } from './multer-file.interface';
|
||||
|
||||
@Injectable()
|
||||
export class MediaService {
|
||||
|
@ -44,14 +43,14 @@ export class MediaService {
|
|||
return allowedTypes.includes(mimeType);
|
||||
}
|
||||
|
||||
public async saveFile(file: MulterFile, username: string, noteId: string) {
|
||||
public async saveFile(fileBuffer: Buffer, username: string, noteId: string) {
|
||||
this.logger.debug(
|
||||
`Saving '${file.originalname}' for note '${noteId}' and user '${username}'`,
|
||||
`Saving file for note '${noteId}' and user '${username}'`,
|
||||
'saveFile',
|
||||
);
|
||||
const note = await this.notesService.getNoteByIdOrAlias(noteId);
|
||||
const user = await this.usersService.getUserByUsername(username);
|
||||
const fileTypeResult = await FileType.fromBuffer(file.buffer);
|
||||
const fileTypeResult = await FileType.fromBuffer(fileBuffer);
|
||||
if (!fileTypeResult) {
|
||||
throw new ClientError('Could not detect file type.');
|
||||
}
|
||||
|
@ -68,7 +67,7 @@ export class MediaService {
|
|||
this.logger.debug(`Generated filename: '${mediaUpload.id}'`, 'saveFile');
|
||||
const backend = this.moduleRef.get(FilesystemBackend);
|
||||
const [url, backendData] = await backend.saveFile(
|
||||
file.buffer,
|
||||
fileBuffer,
|
||||
mediaUpload.id,
|
||||
);
|
||||
mediaUpload.backendData = backendData;
|
||||
|
|
|
@ -154,10 +154,15 @@ export class NotesService {
|
|||
],
|
||||
});
|
||||
if (note === undefined) {
|
||||
this.logger.debug(
|
||||
`Could not find note '${noteIdOrAlias}'`,
|
||||
'getNoteByIdOrAlias',
|
||||
);
|
||||
throw new NotInDBError(
|
||||
`Note with id/alias '${noteIdOrAlias}' not found.`,
|
||||
);
|
||||
}
|
||||
this.logger.debug(`Found note '${noteIdOrAlias}'`, 'getNoteByIdOrAlias');
|
||||
return note;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
import { Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import {
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { Column, OneToMany } from 'typeorm/index';
|
||||
import { Note } from '../notes/note.entity';
|
||||
import { AuthToken } from './auth-token.entity';
|
||||
|
@ -15,10 +20,10 @@ export class User {
|
|||
@Column()
|
||||
displayName: string;
|
||||
|
||||
@Column()
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
|
||||
@Column()
|
||||
@UpdateDateColumn()
|
||||
updatedAt: Date;
|
||||
|
||||
@Column({
|
||||
|
|
BIN
test/public-api/fixtures/test.png
Normal file
BIN
test/public-api/fixtures/test.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.7 KiB |
79
test/public-api/media.e2e-spec.ts
Normal file
79
test/public-api/media.e2e-spec.ts
Normal file
|
@ -0,0 +1,79 @@
|
|||
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { promises as fs } from 'fs';
|
||||
import * as request from 'supertest';
|
||||
import { PublicApiModule } from '../../src/api/public/public-api.module';
|
||||
import { GroupsModule } from '../../src/groups/groups.module';
|
||||
import { LoggerModule } from '../../src/logger/logger.module';
|
||||
import { NestConsoleLoggerService } from '../../src/logger/nest-console-logger.service';
|
||||
import { MediaModule } from '../../src/media/media.module';
|
||||
import { MediaService } from '../../src/media/media.service';
|
||||
import { NotesModule } from '../../src/notes/notes.module';
|
||||
import { NotesService } from '../../src/notes/notes.service';
|
||||
import { PermissionsModule } from '../../src/permissions/permissions.module';
|
||||
import { UsersService } from '../../src/users/users.service';
|
||||
|
||||
describe('Notes', () => {
|
||||
let app: NestExpressApplication;
|
||||
let mediaService: MediaService;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
imports: [
|
||||
PublicApiModule,
|
||||
MediaModule,
|
||||
TypeOrmModule.forRoot({
|
||||
type: 'sqlite',
|
||||
database: './hedgedoc-e2e-media.sqlite',
|
||||
autoLoadEntities: true,
|
||||
dropSchema: true,
|
||||
synchronize: true,
|
||||
}),
|
||||
NotesModule,
|
||||
PermissionsModule,
|
||||
GroupsModule,
|
||||
LoggerModule,
|
||||
],
|
||||
}).compile();
|
||||
app = moduleRef.createNestApplication<NestExpressApplication>();
|
||||
app.useStaticAssets('uploads', {
|
||||
prefix: '/uploads',
|
||||
});
|
||||
await app.init();
|
||||
const logger = await app.resolve(NestConsoleLoggerService);
|
||||
logger.log('Switching logger', 'AppBootstrap');
|
||||
app.useLogger(logger);
|
||||
const notesService: NotesService = moduleRef.get('NotesService');
|
||||
await notesService.createNote('test content', 'test_upload_media');
|
||||
const usersService: UsersService = moduleRef.get('UsersService');
|
||||
await usersService.createUser('hardcoded', 'Hard Coded');
|
||||
mediaService = moduleRef.get('MediaService');
|
||||
});
|
||||
|
||||
it('POST /media', async () => {
|
||||
const uploadResponse = await request(app.getHttpServer())
|
||||
.post('/media')
|
||||
.attach('file', 'test/public-api/fixtures/test.png')
|
||||
.set('HedgeDoc-Note', 'test_upload_media')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(201);
|
||||
const path = uploadResponse.body.link;
|
||||
const testImage = await fs.readFile('test/public-api/fixtures/test.png');
|
||||
const downloadResponse = await request(app.getHttpServer()).get(path);
|
||||
expect(downloadResponse.body).toEqual(testImage);
|
||||
});
|
||||
|
||||
it('DELETE /media/{filename}', async () => {
|
||||
const testImage = await fs.readFile('test/public-api/fixtures/test.png');
|
||||
const url = await mediaService.saveFile(
|
||||
testImage,
|
||||
'hardcoded',
|
||||
'test_upload_media',
|
||||
);
|
||||
const filename = url.split('/').pop();
|
||||
await request(app.getHttpServer())
|
||||
.delete('/media/' + filename)
|
||||
.expect(200);
|
||||
});
|
||||
});
|
|
@ -23,9 +23,10 @@ describe('Notes', () => {
|
|||
GroupsModule,
|
||||
TypeOrmModule.forRoot({
|
||||
type: 'sqlite',
|
||||
database: './hedgedoc-e2e.sqlite',
|
||||
database: './hedgedoc-e2e-notes.sqlite',
|
||||
autoLoadEntities: true,
|
||||
synchronize: true,
|
||||
dropSchema: true,
|
||||
}),
|
||||
LoggerModule,
|
||||
],
|
||||
|
@ -34,8 +35,6 @@ describe('Notes', () => {
|
|||
app = moduleRef.createNestApplication();
|
||||
await app.init();
|
||||
notesService = moduleRef.get(NotesService);
|
||||
const noteRepository = moduleRef.get('NoteRepository');
|
||||
noteRepository.clear();
|
||||
});
|
||||
|
||||
it(`POST /notes`, async () => {
|
||||
|
|
|
@ -614,13 +614,6 @@
|
|||
"@angular-devkit/schematics" "9.1.7"
|
||||
fs-extra "9.0.0"
|
||||
|
||||
"@nestjs/serve-static@^2.1.3":
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@nestjs/serve-static/-/serve-static-2.1.3.tgz#bdcb6d3463d193153b334212facc24a9767046e9"
|
||||
integrity sha512-9xyysggaOdfbABWqhty+hAkauDWv/Q8YKHm4OMXdQbQei5tquFuTjiSx8IFDOZeSOKlA9fjBq/2MXCJRSo23SQ==
|
||||
dependencies:
|
||||
path-to-regexp "0.1.7"
|
||||
|
||||
"@nestjs/swagger@^4.5.12":
|
||||
version "4.5.12"
|
||||
resolved "https://registry.yarnpkg.com/@nestjs/swagger/-/swagger-4.5.12.tgz#e8aa65fbb0033007ece1d494b002f47ff472c20b"
|
||||
|
|
Loading…
Reference in a new issue