mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-28 16:40:58 -05:00
Add GET /me/media
Returns all media files uploaded by the authenticated user. Signed-off-by: Yannick Bungers <git@innay.de>
This commit is contained in:
parent
7a7b3d3a50
commit
f47d85b301
7 changed files with 174 additions and 10 deletions
|
@ -908,19 +908,21 @@ components:
|
||||||
MediaUpload:
|
MediaUpload:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
note:
|
|
||||||
type: string
|
|
||||||
description: ID of the note the file was uploaded to
|
|
||||||
user:
|
|
||||||
type: string
|
|
||||||
description: username of the user who uploaded the file
|
|
||||||
url:
|
url:
|
||||||
type: string
|
type: string
|
||||||
description: URL of the file
|
description: URL of the file
|
||||||
|
owningNote:
|
||||||
|
type: string
|
||||||
|
description: ID of the note the file was uploaded to
|
||||||
createdAt:
|
createdAt:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
description: Date when the file was upladed
|
description: Date when the file was upladed
|
||||||
|
owningUser:
|
||||||
|
type: string
|
||||||
|
description: username of the user who uploaded the file
|
||||||
|
|
||||||
|
|
||||||
examples:
|
examples:
|
||||||
markdownExample:
|
markdownExample:
|
||||||
value: '# Some header\nSome normal text. **Some bold text**'
|
value: '# Some header\nSome normal text. **Some bold text**'
|
||||||
|
|
|
@ -23,7 +23,10 @@ import { HistoryEntry } from '../../../history/history-entry.entity';
|
||||||
import { NoteGroupPermission } from '../../../permissions/note-group-permission.entity';
|
import { NoteGroupPermission } from '../../../permissions/note-group-permission.entity';
|
||||||
import { NoteUserPermission } from '../../../permissions/note-user-permission.entity';
|
import { NoteUserPermission } from '../../../permissions/note-user-permission.entity';
|
||||||
import { Group } from '../../../groups/group.entity';
|
import { Group } from '../../../groups/group.entity';
|
||||||
|
import { MediaModule } from '../../../media/media.module';
|
||||||
|
import { MediaUpload } from '../../../media/media-upload.entity';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
import mediaConfigMock from '../../../config/media.config.mock';
|
||||||
import appConfigMock from '../../../config/app.config.mock';
|
import appConfigMock from '../../../config/app.config.mock';
|
||||||
|
|
||||||
describe('Me Controller', () => {
|
describe('Me Controller', () => {
|
||||||
|
@ -33,14 +36,19 @@ describe('Me Controller', () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
controllers: [MeController],
|
controllers: [MeController],
|
||||||
imports: [
|
imports: [
|
||||||
UsersModule,
|
ConfigModule.forRoot({
|
||||||
HistoryModule,
|
isGlobal: true,
|
||||||
NotesModule,
|
load: [mediaConfigMock],
|
||||||
LoggerModule,
|
}),
|
||||||
ConfigModule.forRoot({
|
ConfigModule.forRoot({
|
||||||
isGlobal: true,
|
isGlobal: true,
|
||||||
load: [appConfigMock],
|
load: [appConfigMock],
|
||||||
}),
|
}),
|
||||||
|
UsersModule,
|
||||||
|
HistoryModule,
|
||||||
|
NotesModule,
|
||||||
|
LoggerModule,
|
||||||
|
MediaModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
.overrideProvider(getRepositoryToken(User))
|
.overrideProvider(getRepositoryToken(User))
|
||||||
|
@ -67,6 +75,8 @@ describe('Me Controller', () => {
|
||||||
.useValue({})
|
.useValue({})
|
||||||
.overrideProvider(getRepositoryToken(Group))
|
.overrideProvider(getRepositoryToken(Group))
|
||||||
.useValue({})
|
.useValue({})
|
||||||
|
.overrideProvider(getRepositoryToken(MediaUpload))
|
||||||
|
.useValue({})
|
||||||
.compile();
|
.compile();
|
||||||
|
|
||||||
controller = module.get<MeController>(MeController);
|
controller = module.get<MeController>(MeController);
|
||||||
|
|
|
@ -28,6 +28,9 @@ import { HistoryEntryDto } from '../../../history/history-entry.dto';
|
||||||
import { UserInfoDto } from '../../../users/user-info.dto';
|
import { UserInfoDto } from '../../../users/user-info.dto';
|
||||||
import { NotInDBError } from '../../../errors/errors';
|
import { NotInDBError } from '../../../errors/errors';
|
||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
|
import { MediaService } from '../../../media/media.service';
|
||||||
|
import { MediaUploadUrlDto } from '../../../media/media-upload-url.dto';
|
||||||
|
import { MediaUploadDto } from '../../../media/media-upload.dto';
|
||||||
|
|
||||||
@ApiTags('me')
|
@ApiTags('me')
|
||||||
@ApiSecurity('token')
|
@ApiSecurity('token')
|
||||||
|
@ -38,6 +41,7 @@ export class MeController {
|
||||||
private usersService: UsersService,
|
private usersService: UsersService,
|
||||||
private historyService: HistoryService,
|
private historyService: HistoryService,
|
||||||
private notesService: NotesService,
|
private notesService: NotesService,
|
||||||
|
private mediaService: MediaService,
|
||||||
) {
|
) {
|
||||||
this.logger.setContext(MeController.name);
|
this.logger.setContext(MeController.name);
|
||||||
}
|
}
|
||||||
|
@ -129,4 +133,11 @@ export class MeController {
|
||||||
(await notes).map((note) => this.notesService.toNoteMetadataDto(note)),
|
(await notes).map((note) => this.notesService.toNoteMetadataDto(note)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UseGuards(TokenAuthGuard)
|
||||||
|
@Get('media')
|
||||||
|
async getMyMedia(@Req() req: Request): Promise<MediaUploadDto[]> {
|
||||||
|
const media = await this.mediaService.listUploadsByUser(req.user);
|
||||||
|
return media.map((media) => this.mediaService.toMediaUploadDto(media));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
37
src/media/media-upload.dto.ts
Normal file
37
src/media/media-upload.dto.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { IsDate, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class MediaUploadDto {
|
||||||
|
/**
|
||||||
|
* The link to the media file.
|
||||||
|
* @example "https://example.com/uploads/testfile123.jpg"
|
||||||
|
*/
|
||||||
|
@IsString()
|
||||||
|
url: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The noteId of the note to which the uploaded file is linked to.
|
||||||
|
* @example "noteId" TODO how looks a note id?
|
||||||
|
*/
|
||||||
|
@IsString()
|
||||||
|
noteId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The date when the upload objects was created.
|
||||||
|
* @example "2020-12-01 12:23:34"
|
||||||
|
*/
|
||||||
|
@IsDate()
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The userName of the user which uploaded the media file.
|
||||||
|
* @example "testuser5"
|
||||||
|
*/
|
||||||
|
@IsString()
|
||||||
|
userName: string;
|
||||||
|
}
|
|
@ -229,4 +229,39 @@ describe('MediaService', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('listUploadsByUser', () => {
|
||||||
|
describe('works', () => {
|
||||||
|
it('with one upload from user', async () => {
|
||||||
|
const mockMediaUploadEntry = {
|
||||||
|
id: 'testMediaUpload',
|
||||||
|
backendData: 'testBackendData',
|
||||||
|
user: {
|
||||||
|
userName: 'hardcoded',
|
||||||
|
} as User,
|
||||||
|
} as MediaUpload;
|
||||||
|
jest
|
||||||
|
.spyOn(mediaRepo, 'find')
|
||||||
|
.mockResolvedValueOnce([mockMediaUploadEntry]);
|
||||||
|
expect(
|
||||||
|
await service.listUploadsByUser({ userName: 'hardcoded' } as User),
|
||||||
|
).toEqual([mockMediaUploadEntry]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('without uploads from user', async () => {
|
||||||
|
jest.spyOn(mediaRepo, 'find').mockResolvedValueOnce([]);
|
||||||
|
const mediaList = await service.listUploadsByUser({
|
||||||
|
userName: 'hardcoded',
|
||||||
|
} as User);
|
||||||
|
expect(mediaList).toEqual([]);
|
||||||
|
});
|
||||||
|
it('with error (undefined as return value of find)', async () => {
|
||||||
|
jest.spyOn(mediaRepo, 'find').mockResolvedValueOnce(undefined);
|
||||||
|
const mediaList = await service.listUploadsByUser({
|
||||||
|
userName: 'hardcoded',
|
||||||
|
} as User);
|
||||||
|
expect(mediaList).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,6 +22,8 @@ import { MediaUploadUrlDto } from './media-upload-url.dto';
|
||||||
import { S3Backend } from './backends/s3-backend';
|
import { S3Backend } from './backends/s3-backend';
|
||||||
import { AzureBackend } from './backends/azure-backend';
|
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 { MediaUploadDto } from './media-upload.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MediaService {
|
export class MediaService {
|
||||||
|
@ -157,6 +159,23 @@ export class MediaService {
|
||||||
return mediaUpload;
|
return mediaUpload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @async
|
||||||
|
* List all uploads by a specific user
|
||||||
|
* @param {User} user - the specific user
|
||||||
|
* @return {MediaUpload[]} arary of media uploads owned by the user
|
||||||
|
*/
|
||||||
|
async listUploadsByUser(user: User): Promise<MediaUpload[]> {
|
||||||
|
const mediaUploads = await this.mediaUploadRepository.find({
|
||||||
|
where: { user: user },
|
||||||
|
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':
|
||||||
|
@ -183,6 +202,15 @@ export class MediaService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toMediaUploadDto(mediaUpload: MediaUpload): MediaUploadDto {
|
||||||
|
return {
|
||||||
|
url: mediaUpload.fileUrl,
|
||||||
|
noteId: mediaUpload.note.id,
|
||||||
|
createdAt: mediaUpload.createdAt,
|
||||||
|
userName: mediaUpload.user.userName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
toMediaUploadUrlDto(url: string): MediaUploadUrlDto {
|
toMediaUploadUrlDto(url: string): MediaUploadUrlDto {
|
||||||
return {
|
return {
|
||||||
link: url,
|
link: url,
|
||||||
|
|
|
@ -33,6 +33,9 @@ import { ConfigModule } from '@nestjs/config';
|
||||||
import mediaConfigMock from '../../src/config/media.config.mock';
|
import mediaConfigMock from '../../src/config/media.config.mock';
|
||||||
import appConfigMock from '../../src/config/app.config.mock';
|
import appConfigMock from '../../src/config/app.config.mock';
|
||||||
import { User } from '../../src/users/user.entity';
|
import { User } from '../../src/users/user.entity';
|
||||||
|
import { MediaService } from '../../src/media/media.service';
|
||||||
|
import { MediaModule } from '../../src/media/media.module';
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
import { NoteMetadataDto } from '../../src/notes/note-metadata.dto';
|
import { NoteMetadataDto } from '../../src/notes/note-metadata.dto';
|
||||||
|
|
||||||
// TODO Tests have to be reworked using UserService functions
|
// TODO Tests have to be reworked using UserService functions
|
||||||
|
@ -42,6 +45,7 @@ describe('Notes', () => {
|
||||||
let historyService: HistoryService;
|
let historyService: HistoryService;
|
||||||
let notesService: NotesService;
|
let notesService: NotesService;
|
||||||
let userService: UsersService;
|
let userService: UsersService;
|
||||||
|
let mediaService: MediaService;
|
||||||
let user: User;
|
let user: User;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
|
@ -66,6 +70,7 @@ describe('Notes', () => {
|
||||||
AuthModule,
|
AuthModule,
|
||||||
UsersModule,
|
UsersModule,
|
||||||
HistoryModule,
|
HistoryModule,
|
||||||
|
MediaModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
.overrideGuard(TokenAuthGuard)
|
.overrideGuard(TokenAuthGuard)
|
||||||
|
@ -75,6 +80,7 @@ describe('Notes', () => {
|
||||||
notesService = moduleRef.get(NotesService);
|
notesService = moduleRef.get(NotesService);
|
||||||
historyService = moduleRef.get(HistoryService);
|
historyService = moduleRef.get(HistoryService);
|
||||||
userService = moduleRef.get(UsersService);
|
userService = moduleRef.get(UsersService);
|
||||||
|
mediaService = moduleRef.get(MediaService);
|
||||||
user = await userService.createUser('hardcoded', 'Testy');
|
user = await userService.createUser('hardcoded', 'Testy');
|
||||||
await app.init();
|
await app.init();
|
||||||
});
|
});
|
||||||
|
@ -222,6 +228,41 @@ describe('Notes', () => {
|
||||||
expect(noteMetaDtos[0].updateUser.userName).toEqual(user.userName);
|
expect(noteMetaDtos[0].updateUser.userName).toEqual(user.userName);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('GET /me/media', async () => {
|
||||||
|
const note1 = await notesService.createNote(
|
||||||
|
'This is a test note.',
|
||||||
|
'test8',
|
||||||
|
await userService.getUserByUsername('hardcoded'),
|
||||||
|
);
|
||||||
|
const note2 = await notesService.createNote(
|
||||||
|
'This is a test note.',
|
||||||
|
'test9',
|
||||||
|
await userService.getUserByUsername('hardcoded'),
|
||||||
|
);
|
||||||
|
const httpServer = app.getHttpServer();
|
||||||
|
const response1 = await request(httpServer)
|
||||||
|
.get('/me/media/')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200);
|
||||||
|
expect(response1.body).toHaveLength(0);
|
||||||
|
|
||||||
|
const testImage = await fs.readFile('test/public-api/fixtures/test.png');
|
||||||
|
const url0 = await mediaService.saveFile(testImage, 'hardcoded', note1.id);
|
||||||
|
const url1 = await mediaService.saveFile(testImage, 'hardcoded', note1.id);
|
||||||
|
const url2 = await mediaService.saveFile(testImage, 'hardcoded', note2.id);
|
||||||
|
const url3 = await mediaService.saveFile(testImage, 'hardcoded', note2.id);
|
||||||
|
|
||||||
|
const response = await request(httpServer)
|
||||||
|
.get('/me/media/')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200);
|
||||||
|
expect(response.body).toHaveLength(4);
|
||||||
|
expect(response.body[0].url).toEqual(url0);
|
||||||
|
expect(response.body[1].url).toEqual(url1);
|
||||||
|
expect(response.body[2].url).toEqual(url2);
|
||||||
|
expect(response.body[3].url).toEqual(url3);
|
||||||
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await app.close();
|
await app.close();
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue