mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-23 11:37:02 -04:00
Merge pull request #1049 from hedgedoc/publicApi/noteMedia
This commit is contained in:
commit
6fab7583f0
5 changed files with 143 additions and 1 deletions
|
@ -27,7 +27,10 @@ import { NoteUserPermission } from '../../../permissions/note-user-permission.en
|
||||||
import { Group } from '../../../groups/group.entity';
|
import { Group } from '../../../groups/group.entity';
|
||||||
import { GroupsModule } from '../../../groups/groups.module';
|
import { GroupsModule } from '../../../groups/groups.module';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
import { MediaModule } from '../../../media/media.module';
|
||||||
|
import { MediaUpload } from '../../../media/media-upload.entity';
|
||||||
import appConfigMock from '../../../config/app.config.mock';
|
import appConfigMock from '../../../config/app.config.mock';
|
||||||
|
import mediaConfigMock from '../../../config/media.config.mock';
|
||||||
|
|
||||||
describe('Notes Controller', () => {
|
describe('Notes Controller', () => {
|
||||||
let controller: NotesController;
|
let controller: NotesController;
|
||||||
|
@ -53,9 +56,10 @@ describe('Notes Controller', () => {
|
||||||
LoggerModule,
|
LoggerModule,
|
||||||
PermissionsModule,
|
PermissionsModule,
|
||||||
HistoryModule,
|
HistoryModule,
|
||||||
|
MediaModule,
|
||||||
ConfigModule.forRoot({
|
ConfigModule.forRoot({
|
||||||
isGlobal: true,
|
isGlobal: true,
|
||||||
load: [appConfigMock],
|
load: [appConfigMock, mediaConfigMock],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
@ -85,6 +89,8 @@ describe('Notes Controller', () => {
|
||||||
.useValue({})
|
.useValue({})
|
||||||
.overrideProvider(getRepositoryToken(Group))
|
.overrideProvider(getRepositoryToken(Group))
|
||||||
.useValue({})
|
.useValue({})
|
||||||
|
.overrideProvider(getRepositoryToken(MediaUpload))
|
||||||
|
.useValue({})
|
||||||
.compile();
|
.compile();
|
||||||
|
|
||||||
controller = module.get<NotesController>(NotesController);
|
controller = module.get<NotesController>(NotesController);
|
||||||
|
|
|
@ -60,6 +60,8 @@ import {
|
||||||
successfullyDeletedDescription,
|
successfullyDeletedDescription,
|
||||||
unauthorizedDescription,
|
unauthorizedDescription,
|
||||||
} from '../../utils/descriptions';
|
} from '../../utils/descriptions';
|
||||||
|
import { MediaUploadDto } from '../../../media/media-upload.dto';
|
||||||
|
import { MediaService } from '../../../media/media.service';
|
||||||
|
|
||||||
@ApiTags('notes')
|
@ApiTags('notes')
|
||||||
@ApiSecurity('token')
|
@ApiSecurity('token')
|
||||||
|
@ -71,6 +73,7 @@ export class NotesController {
|
||||||
private revisionsService: RevisionsService,
|
private revisionsService: RevisionsService,
|
||||||
private permissionsService: PermissionsService,
|
private permissionsService: PermissionsService,
|
||||||
private historyService: HistoryService,
|
private historyService: HistoryService,
|
||||||
|
private mediaService: MediaService,
|
||||||
) {
|
) {
|
||||||
this.logger.setContext(NotesController.name);
|
this.logger.setContext(NotesController.name);
|
||||||
}
|
}
|
||||||
|
@ -389,4 +392,31 @@ export class NotesController {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UseGuards(TokenAuthGuard)
|
||||||
|
@Get(':noteIdOrAlias/media')
|
||||||
|
@ApiOkResponse({
|
||||||
|
description: 'All media uploads of the note',
|
||||||
|
isArray: true,
|
||||||
|
type: MediaUploadDto,
|
||||||
|
})
|
||||||
|
@ApiUnauthorizedResponse({ description: unauthorizedDescription })
|
||||||
|
async getNotesMedia(
|
||||||
|
@Req() req: Request,
|
||||||
|
@Param('noteIdOrAlias') noteIdOrAlias: string,
|
||||||
|
): Promise<MediaUploadDto[]> {
|
||||||
|
try {
|
||||||
|
const note = await this.noteService.getNoteByIdOrAlias(noteIdOrAlias);
|
||||||
|
if (!this.permissionsService.mayRead(req.user, note)) {
|
||||||
|
throw new UnauthorizedException('Reading note denied!');
|
||||||
|
}
|
||||||
|
const media = await this.mediaService.listUploadsByNote(note);
|
||||||
|
return media.map((media) => this.mediaService.toMediaUploadDto(media));
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof NotInDBError) {
|
||||||
|
throw new NotFoundException(e.message);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -264,4 +264,40 @@ describe('MediaService', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('listUploadsByNote', () => {
|
||||||
|
describe('works', () => {
|
||||||
|
it('with one upload to note', async () => {
|
||||||
|
const mockMediaUploadEntry = {
|
||||||
|
id: 'testMediaUpload',
|
||||||
|
backendData: 'testBackendData',
|
||||||
|
note: {
|
||||||
|
id: '123',
|
||||||
|
} as Note,
|
||||||
|
} as MediaUpload;
|
||||||
|
jest
|
||||||
|
.spyOn(mediaRepo, 'find')
|
||||||
|
.mockResolvedValueOnce([mockMediaUploadEntry]);
|
||||||
|
const mediaList = await service.listUploadsByNote({
|
||||||
|
id: '123',
|
||||||
|
} as Note);
|
||||||
|
expect(mediaList).toEqual([mockMediaUploadEntry]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('without uploads to note', async () => {
|
||||||
|
jest.spyOn(mediaRepo, 'find').mockResolvedValueOnce([]);
|
||||||
|
const mediaList = await service.listUploadsByNote({
|
||||||
|
id: '123',
|
||||||
|
} as Note);
|
||||||
|
expect(mediaList).toEqual([]);
|
||||||
|
});
|
||||||
|
it('with error (undefined as return value of find)', async () => {
|
||||||
|
jest.spyOn(mediaRepo, 'find').mockResolvedValueOnce(undefined);
|
||||||
|
const mediaList = await service.listUploadsByNote({
|
||||||
|
id: '123',
|
||||||
|
} as Note);
|
||||||
|
expect(mediaList).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,6 +24,7 @@ import { AzureBackend } from './backends/azure-backend';
|
||||||
import { ImgurBackend } from './backends/imgur-backend';
|
import { ImgurBackend } from './backends/imgur-backend';
|
||||||
import { User } from '../users/user.entity';
|
import { User } from '../users/user.entity';
|
||||||
import { MediaUploadDto } from './media-upload.dto';
|
import { MediaUploadDto } from './media-upload.dto';
|
||||||
|
import { Note } from '../notes/note.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MediaService {
|
export class MediaService {
|
||||||
|
@ -175,6 +176,23 @@ export class MediaService {
|
||||||
return mediaUploads;
|
return mediaUploads;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @async
|
||||||
|
* List all uploads by a specific note
|
||||||
|
* @param {Note} note - the specific user
|
||||||
|
* @return {MediaUpload[]} arary of media uploads owned by the user
|
||||||
|
*/
|
||||||
|
async listUploadsByNote(note: Note): Promise<MediaUpload[]> {
|
||||||
|
const mediaUploads = await this.mediaUploadRepository.find({
|
||||||
|
where: { note: note },
|
||||||
|
relations: ['user', 'note'],
|
||||||
|
});
|
||||||
|
if (mediaUploads === undefined) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return mediaUploads;
|
||||||
|
}
|
||||||
|
|
||||||
private chooseBackendType(): BackendType {
|
private chooseBackendType(): BackendType {
|
||||||
switch (this.mediaConfig.backend.use) {
|
switch (this.mediaConfig.backend.use) {
|
||||||
case 'filesystem':
|
case 'filesystem':
|
||||||
|
|
|
@ -29,11 +29,15 @@ import { MockAuthGuard } from '../../src/auth/mock-auth.guard';
|
||||||
import { UsersService } from '../../src/users/users.service';
|
import { UsersService } from '../../src/users/users.service';
|
||||||
import { User } from '../../src/users/user.entity';
|
import { User } from '../../src/users/user.entity';
|
||||||
import { UsersModule } from '../../src/users/users.module';
|
import { UsersModule } from '../../src/users/users.module';
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
import { MediaService } from '../../src/media/media.service';
|
||||||
|
|
||||||
describe('Notes', () => {
|
describe('Notes', () => {
|
||||||
let app: INestApplication;
|
let app: INestApplication;
|
||||||
let notesService: NotesService;
|
let notesService: NotesService;
|
||||||
|
let mediaService: MediaService;
|
||||||
let user: User;
|
let user: User;
|
||||||
|
let user2: User;
|
||||||
let content: string;
|
let content: string;
|
||||||
let forbiddenNoteId: string;
|
let forbiddenNoteId: string;
|
||||||
|
|
||||||
|
@ -69,8 +73,10 @@ describe('Notes', () => {
|
||||||
app = moduleRef.createNestApplication();
|
app = moduleRef.createNestApplication();
|
||||||
await app.init();
|
await app.init();
|
||||||
notesService = moduleRef.get(NotesService);
|
notesService = moduleRef.get(NotesService);
|
||||||
|
mediaService = moduleRef.get(MediaService);
|
||||||
const userService = moduleRef.get(UsersService);
|
const userService = moduleRef.get(UsersService);
|
||||||
user = await userService.createUser('hardcoded', 'Testy');
|
user = await userService.createUser('hardcoded', 'Testy');
|
||||||
|
user2 = await userService.createUser('hardcoded2', 'Max Mustermann');
|
||||||
content = 'This is a test note.';
|
content = 'This is a test note.';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -322,6 +328,52 @@ describe('Notes', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('GET /notes/{note}/media', () => {
|
||||||
|
it('works', async () => {
|
||||||
|
const note = await notesService.createNote(content, 'test9', user);
|
||||||
|
const extraNote = await notesService.createNote(content, 'test10', user);
|
||||||
|
const httpServer = app.getHttpServer();
|
||||||
|
const response = await request(httpServer)
|
||||||
|
.get(`/notes/${note.id}/media/`)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200);
|
||||||
|
expect(response.body).toHaveLength(0);
|
||||||
|
|
||||||
|
const testImage = await fs.readFile('test/public-api/fixtures/test.png');
|
||||||
|
const url0 = await mediaService.saveFile(testImage, 'hardcoded', note.id);
|
||||||
|
const url1 = await mediaService.saveFile(
|
||||||
|
testImage,
|
||||||
|
'hardcoded',
|
||||||
|
extraNote.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const responseAfter = await request(httpServer)
|
||||||
|
.get(`/notes/${note.id}/media/`)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200);
|
||||||
|
expect(responseAfter.body).toHaveLength(1);
|
||||||
|
expect(responseAfter.body[0].url).toEqual(url0);
|
||||||
|
expect(responseAfter.body[0].url).not.toEqual(url1);
|
||||||
|
});
|
||||||
|
it('fails, when note does not exist', async () => {
|
||||||
|
await request(app.getHttpServer())
|
||||||
|
.get(`/notes/i_dont_exist/media/`)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(404);
|
||||||
|
});
|
||||||
|
it("fails, when user can't read note", async () => {
|
||||||
|
const note = await notesService.createNote(
|
||||||
|
'This is a test note.',
|
||||||
|
'test11',
|
||||||
|
user2,
|
||||||
|
);
|
||||||
|
await request(app.getHttpServer())
|
||||||
|
.get(`/notes/${note.id}/media/`)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(401);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await app.close();
|
await app.close();
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue