Merge pull request #786 from hedgedoc/refactor/dto

This commit is contained in:
David Mehren 2021-02-01 20:19:36 +01:00 committed by GitHub
commit 6bce4c241b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 206 additions and 161 deletions

View file

@ -78,7 +78,10 @@ export class MeController {
@UseGuards(TokenAuthGuard)
@Get('notes')
getMyNotes(@Request() req): NoteMetadataDto[] {
return this.notesService.getUserNotes(req.user.userName);
async getMyNotes(@Request() req): Promise<NoteMetadataDto[]> {
const notes = await this.notesService.getUserNotes(req.user);
return Promise.all(
notes.map((note) => this.notesService.toNoteMetadataDto(note)),
);
}
}

View file

@ -29,6 +29,7 @@ import { MediaService } from '../../../media/media.service';
import { MulterFile } from '../../../media/multer-file.interface';
import { TokenAuthGuard } from '../../../auth/token-auth.guard';
import { ApiSecurity } from '@nestjs/swagger';
import { MediaUploadUrlDto } from '../../../media/media-upload-url.dto';
@ApiSecurity('token')
@Controller('media')
@ -47,7 +48,7 @@ export class MediaController {
@Request() req,
@UploadedFile() file: MulterFile,
@Headers('HedgeDoc-Note') noteId: string,
) {
): Promise<MediaUploadUrlDto> {
const username = req.user.userName;
this.logger.debug(
`Recieved filename '${file.originalname}' for note '${noteId}' from user '${username}'`,
@ -59,9 +60,7 @@ export class MediaController {
username,
noteId,
);
return {
link: url,
};
return this.mediaService.toMediaUploadUrlDto(url);
} catch (e) {
if (e instanceof ClientError || e instanceof NotInDBError) {
throw new BadRequestException(e.message);
@ -72,7 +71,10 @@ export class MediaController {
@UseGuards(TokenAuthGuard)
@Delete(':filename')
async deleteMedia(@Request() req, @Param('filename') filename: string) {
async deleteMedia(
@Request() req,
@Param('filename') filename: string,
): Promise<void> {
const username = req.user.userName;
try {
await this.mediaService.deleteFile(filename, username);

View file

@ -8,6 +8,7 @@ import { Controller, Get, UseGuards } from '@nestjs/common';
import { MonitoringService } from '../../../monitoring/monitoring.service';
import { TokenAuthGuard } from '../../../auth/token-auth.guard';
import { ApiSecurity } from '@nestjs/swagger';
import { ServerStatusDto } from '../../../monitoring/server-status.dto';
@ApiSecurity('token')
@Controller('monitoring')
@ -16,7 +17,8 @@ export class MonitoringController {
@UseGuards(TokenAuthGuard)
@Get()
getStatus() {
getStatus(): Promise<ServerStatusDto> {
// TODO: toServerStatusDto.
return this.monitoringService.getServerStatus();
}

View file

@ -19,12 +19,19 @@ import {
} from '@nestjs/common';
import { NotInDBError } from '../../../errors/errors';
import { ConsoleLoggerService } from '../../../logger/console-logger.service';
import { NotePermissionsUpdateDto } from '../../../notes/note-permissions.dto';
import {
NotePermissionsDto,
NotePermissionsUpdateDto,
} from '../../../notes/note-permissions.dto';
import { NotesService } from '../../../notes/notes.service';
import { RevisionsService } from '../../../revisions/revisions.service';
import { MarkdownBody } from '../../utils/markdownbody-decorator';
import { TokenAuthGuard } from '../../../auth/token-auth.guard';
import { ApiSecurity } from '@nestjs/swagger';
import { NoteDto } from '../../../notes/note.dto';
import { NoteMetadataDto } from '../../../notes/note-metadata.dto';
import { RevisionMetadataDto } from '../../../revisions/revision-metadata.dto';
import { RevisionDto } from '../../../revisions/revision.dto';
@ApiSecurity('token')
@Controller('notes')
@ -39,18 +46,42 @@ export class NotesController {
@UseGuards(TokenAuthGuard)
@Post()
async createNote(@Request() req, @MarkdownBody() text: string) {
async createNote(
@Request() req,
@MarkdownBody() text: string,
): Promise<NoteDto> {
// ToDo: provide user for createNoteDto
this.logger.debug('Got raw markdown:\n' + text);
return this.noteService.createNoteDto(text);
return this.noteService.toNoteDto(
await this.noteService.createNote(text, undefined, req.user),
);
}
@UseGuards(TokenAuthGuard)
@Post(':noteAlias')
async createNamedNote(
@Request() req,
@Param('noteAlias') noteAlias: string,
@MarkdownBody() text: string,
): Promise<NoteDto> {
// ToDo: check if user is allowed to view this note
this.logger.debug('Got raw markdown:\n' + text);
return this.noteService.toNoteDto(
await this.noteService.createNote(text, noteAlias, req.user),
);
}
@UseGuards(TokenAuthGuard)
@Get(':noteIdOrAlias')
async getNote(@Request() req, @Param('noteIdOrAlias') noteIdOrAlias: string) {
async getNote(
@Request() req,
@Param('noteIdOrAlias') noteIdOrAlias: string,
): Promise<NoteDto> {
// ToDo: check if user is allowed to view this note
try {
return await this.noteService.getNoteDtoByIdOrAlias(noteIdOrAlias);
return this.noteService.toNoteDto(
await this.noteService.getNoteByIdOrAlias(noteIdOrAlias),
);
} catch (e) {
if (e instanceof NotInDBError) {
throw new NotFoundException(e.message);
@ -59,24 +90,12 @@ export class NotesController {
}
}
@UseGuards(TokenAuthGuard)
@Post(':noteAlias')
async createNamedNote(
@Request() req,
@Param('noteAlias') noteAlias: string,
@MarkdownBody() text: string,
) {
// ToDo: check if user is allowed to view this note
this.logger.debug('Got raw markdown:\n' + text);
return this.noteService.createNoteDto(text, noteAlias);
}
@UseGuards(TokenAuthGuard)
@Delete(':noteIdOrAlias')
async deleteNote(
@Request() req,
@Param('noteIdOrAlias') noteIdOrAlias: string,
) {
): Promise<void> {
// ToDo: check if user is allowed to delete this note
this.logger.debug('Deleting note: ' + noteIdOrAlias);
try {
@ -97,11 +116,13 @@ export class NotesController {
@Request() req,
@Param('noteIdOrAlias') noteIdOrAlias: string,
@MarkdownBody() text: string,
) {
): Promise<NoteDto> {
// ToDo: check if user is allowed to change this note
this.logger.debug('Got raw markdown:\n' + text);
try {
return await this.noteService.updateNoteByIdOrAlias(noteIdOrAlias, text);
return this.noteService.toNoteDto(
await this.noteService.updateNoteByIdOrAlias(noteIdOrAlias, text),
);
} catch (e) {
if (e instanceof NotInDBError) {
throw new NotFoundException(e.message);
@ -116,7 +137,7 @@ export class NotesController {
async getNoteContent(
@Request() req,
@Param('noteIdOrAlias') noteIdOrAlias: string,
) {
): Promise<string> {
// ToDo: check if user is allowed to view this notes content
try {
return await this.noteService.getNoteContent(noteIdOrAlias);
@ -133,10 +154,12 @@ export class NotesController {
async getNoteMetadata(
@Request() req,
@Param('noteIdOrAlias') noteIdOrAlias: string,
) {
): Promise<NoteMetadataDto> {
// ToDo: check if user is allowed to view this notes metadata
try {
return await this.noteService.getNoteMetadata(noteIdOrAlias);
return this.noteService.toNoteMetadataDto(
await this.noteService.getNoteByIdOrAlias(noteIdOrAlias),
);
} catch (e) {
if (e instanceof NotInDBError) {
throw new NotFoundException(e.message);
@ -151,12 +174,11 @@ export class NotesController {
@Request() req,
@Param('noteIdOrAlias') noteIdOrAlias: string,
@Body() updateDto: NotePermissionsUpdateDto,
) {
): Promise<NotePermissionsDto> {
// ToDo: check if user is allowed to view this notes permissions
try {
return await this.noteService.updateNotePermissions(
noteIdOrAlias,
updateDto,
return this.noteService.toNotePermissionsDto(
await this.noteService.updateNotePermissions(noteIdOrAlias, updateDto),
);
} catch (e) {
if (e instanceof NotInDBError) {
@ -171,12 +193,17 @@ export class NotesController {
async getNoteRevisions(
@Request() req,
@Param('noteIdOrAlias') noteIdOrAlias: string,
) {
): Promise<RevisionMetadataDto[]> {
// ToDo: check if user is allowed to view this notes revisions
try {
return await this.revisionsService.getNoteRevisionMetadatas(
const revisions = await this.revisionsService.getAllRevisions(
noteIdOrAlias,
);
return Promise.all(
revisions.map((revision) =>
this.revisionsService.toRevisionMetadataDto(revision),
),
);
} catch (e) {
if (e instanceof NotInDBError) {
throw new NotFoundException(e.message);
@ -191,12 +218,11 @@ export class NotesController {
@Request() req,
@Param('noteIdOrAlias') noteIdOrAlias: string,
@Param('revisionId') revisionId: number,
) {
): Promise<RevisionDto> {
// ToDo: check if user is allowed to view this notes revision
try {
return await this.revisionsService.getNoteRevision(
noteIdOrAlias,
revisionId,
return this.revisionsService.toRevisionDto(
await this.revisionsService.getRevision(noteIdOrAlias, revisionId),
);
} catch (e) {
if (e instanceof NotInDBError) {

View file

@ -5,14 +5,20 @@
*/
import { ExecutionContext, Injectable } from '@nestjs/common';
import { UsersService } from '../users/users.service';
import { User } from '../users/user.entity';
@Injectable()
export class MockAuthGuard {
canActivate(context: ExecutionContext) {
private user: User;
constructor(private usersService: UsersService) {}
async canActivate(context: ExecutionContext) {
const req = context.switchToHttp().getRequest();
req.user = {
userName: 'hardcoded',
};
if (!this.user) {
this.user = await this.usersService.createUser('hardcoded', 'Testy');
}
req.user = this.user;
return true;
}
}

View file

@ -0,0 +1,12 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { IsString } from 'class-validator';
export class MediaUploadUrlDto {
@IsString()
link: string;
}

View file

@ -18,6 +18,7 @@ import { BackendType } from './backends/backend-type.enum';
import { FilesystemBackend } from './backends/filesystem-backend';
import { MediaBackend } from './media-backend.interface';
import { MediaUpload } from './media-upload.entity';
import { MediaUploadUrlDto } from './media-upload-url.dto';
@Injectable()
export class MediaService {
@ -58,7 +59,11 @@ export class MediaService {
return allowedTypes.includes(mimeType);
}
public async saveFile(fileBuffer: Buffer, username: string, noteId: string) {
public async saveFile(
fileBuffer: Buffer,
username: string,
noteId: string,
): Promise<string> {
this.logger.debug(
`Saving file for note '${noteId}' and user '${username}'`,
'saveFile',
@ -88,7 +93,7 @@ export class MediaService {
return url;
}
public async deleteFile(filename: string, username: string) {
public async deleteFile(filename: string, username: string): Promise<void> {
this.logger.debug(
`Deleting '${filename}' for user '${username}'`,
'deleteFile',
@ -132,4 +137,10 @@ export class MediaService {
return this.moduleRef.get(FilesystemBackend);
}
}
toMediaUploadUrlDto(url: string): MediaUploadUrlDto {
return {
link: url,
};
}
}

View file

@ -35,48 +35,26 @@ export class NotesService {
this.logger.setContext(NotesService.name);
}
getUserNotes(username: string): NoteMetadataDto[] {
getUserNotes(user: User): Note[] {
this.logger.warn('Using hardcoded data!');
return [
{
alias: null,
createTime: new Date(),
description: 'Very descriptive text.',
editedBy: [],
id: 'foobar-barfoo',
permissions: {
owner: {
displayName: 'foo',
userName: 'fooUser',
email: 'foo@example.com',
photo: '',
},
sharedToUsers: [],
sharedToGroups: [],
},
alias: null,
shortid: 'abc',
owner: user,
description: 'Very descriptive text.',
userPermissions: [],
groupPermissions: [],
tags: [],
revisions: Promise.resolve([]),
authorColors: [],
title: 'Title!',
updateTime: new Date(),
updateUser: {
displayName: 'foo',
userName: 'fooUser',
email: 'foo@example.com',
photo: '',
},
viewCount: 42,
viewcount: 42,
},
];
}
async createNoteDto(
noteContent: string,
alias?: NoteMetadataDto['alias'],
owner?: User,
): Promise<NoteDto> {
const note = await this.createNote(noteContent, alias, owner);
return this.toNoteDto(note);
}
async createNote(
noteContent: string,
alias?: NoteMetadataDto['alias'],
@ -96,7 +74,7 @@ export class NotesService {
return this.noteRepository.save(newNote);
}
async getCurrentContent(note: Note) {
async getCurrentContent(note: Note): Promise<string> {
return (await this.getLatestRevision(note)).content;
}
@ -108,42 +86,6 @@ export class NotesService {
return this.revisionsService.getFirstRevision(note.id);
}
async getMetadata(note: Note): Promise<NoteMetadataDto> {
return {
// TODO: Convert DB UUID to base64
id: note.id,
alias: note.alias,
title: note.title,
createTime: (await this.getFirstRevision(note)).createdAt,
description: note.description,
editedBy: note.authorColors.map(
(authorColor) => authorColor.user.userName,
),
// TODO: Extract into method
permissions: {
owner: this.usersService.toUserDto(note.owner),
sharedToUsers: note.userPermissions.map((noteUserPermission) => ({
user: this.usersService.toUserDto(noteUserPermission.user),
canEdit: noteUserPermission.canEdit,
})),
sharedToGroups: note.groupPermissions.map((noteGroupPermission) => ({
group: noteGroupPermission.group,
canEdit: noteGroupPermission.canEdit,
})),
},
tags: note.tags.map((tag) => tag.name),
updateTime: (await this.getLatestRevision(note)).createdAt,
// TODO: Get actual updateUser
updateUser: {
displayName: 'Hardcoded User',
userName: 'hardcoded',
email: 'foo@example.com',
photo: '',
},
viewCount: 42,
};
}
async getNoteByIdOrAlias(noteIdOrAlias: string): Promise<Note> {
this.logger.debug(
`Trying to find note '${noteIdOrAlias}'`,
@ -178,45 +120,50 @@ export class NotesService {
return note;
}
async getNoteDtoByIdOrAlias(noteIdOrAlias: string): Promise<NoteDto> {
const note = await this.getNoteByIdOrAlias(noteIdOrAlias);
return this.toNoteDto(note);
}
async deleteNoteByIdOrAlias(noteIdOrAlias: string) {
const note = await this.getNoteByIdOrAlias(noteIdOrAlias);
return await this.noteRepository.remove(note);
}
async updateNoteByIdOrAlias(noteIdOrAlias: string, noteContent: string) {
async updateNoteByIdOrAlias(
noteIdOrAlias: string,
noteContent: string,
): Promise<Note> {
const note = await this.getNoteByIdOrAlias(noteIdOrAlias);
const revisions = await note.revisions;
//TODO: Calculate patch
revisions.push(Revision.create(noteContent, noteContent));
note.revisions = Promise.resolve(revisions);
await this.noteRepository.save(note);
return this.toNoteDto(note);
}
async getNoteMetadata(noteIdOrAlias: string): Promise<NoteMetadataDto> {
const note = await this.getNoteByIdOrAlias(noteIdOrAlias);
return this.getMetadata(note);
return this.noteRepository.save(note);
}
updateNotePermissions(
noteIdOrAlias: string,
newPermissions: NotePermissionsUpdateDto,
): NotePermissionsDto {
): Note {
this.logger.warn('Using hardcoded data!');
return {
id: 'foobar-barfoo',
alias: null,
shortid: 'abc',
owner: {
displayName: 'foo',
userName: 'fooUser',
email: 'foo@example.com',
photo: '',
authTokens: [],
createdAt: new Date(),
displayName: 'hardcoded',
id: '1',
identities: [],
ownedNotes: [],
updatedAt: new Date(),
userName: 'Testy',
},
sharedToUsers: [],
sharedToGroups: [],
description: 'Very descriptive text.',
userPermissions: [],
groupPermissions: [],
tags: [],
revisions: Promise.resolve([]),
authorColors: [],
title: 'Title!',
viewcount: 42,
};
}
@ -225,10 +172,50 @@ export class NotesService {
return this.getCurrentContent(note);
}
async toNotePermissionsDto(note: Note): Promise<NotePermissionsDto> {
return {
owner: this.usersService.toUserDto(note.owner),
sharedToUsers: note.userPermissions.map((noteUserPermission) => ({
user: this.usersService.toUserDto(noteUserPermission.user),
canEdit: noteUserPermission.canEdit,
})),
sharedToGroups: note.groupPermissions.map((noteGroupPermission) => ({
group: noteGroupPermission.group,
canEdit: noteGroupPermission.canEdit,
})),
};
}
async toNoteMetadataDto(note: Note): Promise<NoteMetadataDto> {
return {
// TODO: Convert DB UUID to base64
id: note.id,
alias: note.alias,
title: note.title,
createTime: (await this.getFirstRevision(note)).createdAt,
description: note.description,
editedBy: note.authorColors.map(
(authorColor) => authorColor.user.userName,
),
// TODO: Extract into method
permissions: await this.toNotePermissionsDto(note),
tags: note.tags.map((tag) => tag.name),
updateTime: (await this.getLatestRevision(note)).createdAt,
// TODO: Get actual updateUser
updateUser: {
displayName: 'Hardcoded User',
userName: 'hardcoded',
email: 'foo@example.com',
photo: '',
},
viewCount: 42,
};
}
async toNoteDto(note: Note): Promise<NoteDto> {
return {
content: await this.getCurrentContent(note),
metadata: await this.getMetadata(note),
metadata: await this.toNoteMetadataDto(note),
editedByAtPosition: [],
};
}

View file

@ -24,30 +24,26 @@ export class RevisionsService {
this.logger.setContext(RevisionsService.name);
}
async getNoteRevisionMetadatas(
noteIdOrAlias: string,
): Promise<RevisionMetadataDto[]> {
async getAllRevisions(noteIdOrAlias: string): Promise<Revision[]> {
const note = await this.notesService.getNoteByIdOrAlias(noteIdOrAlias);
const revisions = await this.revisionRepository.find({
return await this.revisionRepository.find({
where: {
note: note.id,
note: note,
},
});
return revisions.map((revision) => this.toMetadataDto(revision));
}
async getNoteRevision(
async getRevision(
noteIdOrAlias: string,
revisionId: number,
): Promise<RevisionDto> {
): Promise<Revision> {
const note = await this.notesService.getNoteByIdOrAlias(noteIdOrAlias);
const revision = await this.revisionRepository.findOne({
return await this.revisionRepository.findOne({
where: {
id: revisionId,
note: note,
},
});
return this.toDto(revision);
}
getLatestRevision(noteId: string): Promise<Revision> {
@ -73,7 +69,7 @@ export class RevisionsService {
});
}
toMetadataDto(revision: Revision): RevisionMetadataDto {
toRevisionMetadataDto(revision: Revision): RevisionMetadataDto {
return {
id: revision.id,
length: revision.length,
@ -81,7 +77,7 @@ export class RevisionsService {
};
}
toDto(revision: Revision): RevisionDto {
toRevisionDto(revision: Revision): RevisionDto {
return {
id: revision.id,
content: revision.content,
@ -90,7 +86,7 @@ export class RevisionsService {
};
}
createRevision(content: string) {
createRevision(content: string): Revision {
// TODO: Add previous revision
// TODO: Calculate patch
// TODO: Save metadata

View file

@ -20,7 +20,6 @@ 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';
import { AuthModule } from '../../src/auth/auth.module';
import { TokenAuthGuard } from '../../src/auth/token-auth.guard';
import { MockAuthGuard } from '../../src/auth/mock-auth.guard';
@ -65,8 +64,6 @@ describe('Notes', () => {
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');
});

View file

@ -20,7 +20,7 @@ import { PermissionsModule } from '../../src/permissions/permissions.module';
import { AuthModule } from '../../src/auth/auth.module';
import { TokenAuthGuard } from '../../src/auth/token-auth.guard';
import { MockAuthGuard } from '../../src/auth/mock-auth.guard';
import { UsersService } from '../../src/users/users.service';
import { UsersModule } from '../../src/users/users.module';
describe('Notes', () => {
let app: INestApplication;
@ -46,6 +46,7 @@ describe('Notes', () => {
}),
LoggerModule,
AuthModule,
UsersModule,
],
})
.overrideGuard(TokenAuthGuard)
@ -55,8 +56,6 @@ describe('Notes', () => {
app = moduleRef.createNestApplication();
await app.init();
notesService = moduleRef.get(NotesService);
const usersService: UsersService = moduleRef.get('UsersService');
await usersService.createUser('testy', 'Testy McTestFace');
});
it(`POST /notes`, async () => {
@ -69,8 +68,9 @@ describe('Notes', () => {
.expect(201);
expect(response.body.metadata?.id).toBeDefined();
expect(
(await notesService.getNoteDtoByIdOrAlias(response.body.metadata.id))
.content,
await notesService.getCurrentContent(
await notesService.getNoteByIdOrAlias(response.body.metadata.id),
),
).toEqual(newNote);
});
@ -100,8 +100,9 @@ describe('Notes', () => {
.expect(201);
expect(response.body.metadata?.id).toBeDefined();
return expect(
(await notesService.getNoteDtoByIdOrAlias(response.body.metadata.id))
.content,
await notesService.getCurrentContent(
await notesService.getNoteByIdOrAlias(response.body.metadata?.id),
),
).toEqual(newNote);
});
@ -125,7 +126,9 @@ describe('Notes', () => {
.send('New note text')
.expect(200);
await expect(
(await notesService.getNoteDtoByIdOrAlias('test4')).content,
await notesService.getCurrentContent(
await notesService.getNoteByIdOrAlias('test4'),
),
).toEqual('New note text');
expect(response.body.content).toEqual('New note text');