feat: add alias controller to private and public api

Signed-off-by: Philip Molares <philip.molares@udo.edu>
This commit is contained in:
Philip Molares 2021-08-07 21:28:59 +02:00 committed by David Mehren
parent 2b76d33a23
commit b135333a51
No known key found for this signature in database
GPG key ID: 185982BA4C42B7C3
6 changed files with 549 additions and 0 deletions

View file

@ -0,0 +1,123 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ConfigModule } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
import {
getConnectionToken,
getRepositoryToken,
TypeOrmModule,
} from '@nestjs/typeorm';
import { AuthToken } from '../../../auth/auth-token.entity';
import { Author } from '../../../authors/author.entity';
import appConfigMock from '../../../config/mock/app.config.mock';
import mediaConfigMock from '../../../config/mock/media.config.mock';
import { Group } from '../../../groups/group.entity';
import { GroupsModule } from '../../../groups/groups.module';
import { HistoryEntry } from '../../../history/history-entry.entity';
import { HistoryModule } from '../../../history/history.module';
import { Identity } from '../../../identity/identity.entity';
import { LoggerModule } from '../../../logger/logger.module';
import { MediaUpload } from '../../../media/media-upload.entity';
import { MediaModule } from '../../../media/media.module';
import { Alias } from '../../../notes/alias.entity';
import { AliasService } from '../../../notes/alias.service';
import { Note } from '../../../notes/note.entity';
import { NotesService } from '../../../notes/notes.service';
import { Tag } from '../../../notes/tag.entity';
import { NoteGroupPermission } from '../../../permissions/note-group-permission.entity';
import { NoteUserPermission } from '../../../permissions/note-user-permission.entity';
import { PermissionsModule } from '../../../permissions/permissions.module';
import { Edit } from '../../../revisions/edit.entity';
import { Revision } from '../../../revisions/revision.entity';
import { RevisionsModule } from '../../../revisions/revisions.module';
import { Session } from '../../../users/session.entity';
import { User } from '../../../users/user.entity';
import { UsersModule } from '../../../users/users.module';
import { AliasController } from './alias.controller';
describe('AliasController', () => {
let controller: AliasController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [AliasController],
providers: [
AliasService,
NotesService,
{
provide: getRepositoryToken(Note),
useValue: {},
},
{
provide: getRepositoryToken(Tag),
useValue: {},
},
{
provide: getRepositoryToken(Alias),
useValue: {},
},
{
provide: getRepositoryToken(User),
useValue: {},
},
],
imports: [
RevisionsModule,
UsersModule,
GroupsModule,
LoggerModule,
PermissionsModule,
HistoryModule,
MediaModule,
ConfigModule.forRoot({
isGlobal: true,
load: [appConfigMock, mediaConfigMock],
}),
TypeOrmModule.forRoot(),
],
})
.overrideProvider(getConnectionToken())
.useValue({})
.overrideProvider(getRepositoryToken(Revision))
.useValue({})
.overrideProvider(getRepositoryToken(Edit))
.useValue({})
.overrideProvider(getRepositoryToken(User))
.useValue({})
.overrideProvider(getRepositoryToken(AuthToken))
.useValue({})
.overrideProvider(getRepositoryToken(Identity))
.useValue({})
.overrideProvider(getRepositoryToken(Note))
.useValue({})
.overrideProvider(getRepositoryToken(Tag))
.useValue({})
.overrideProvider(getRepositoryToken(HistoryEntry))
.useValue({})
.overrideProvider(getRepositoryToken(NoteGroupPermission))
.useValue({})
.overrideProvider(getRepositoryToken(NoteUserPermission))
.useValue({})
.overrideProvider(getRepositoryToken(Group))
.useValue({})
.overrideProvider(getRepositoryToken(MediaUpload))
.useValue({})
.overrideProvider(getRepositoryToken(Alias))
.useValue({})
.overrideProvider(getRepositoryToken(Session))
.useValue({})
.overrideProvider(getRepositoryToken(Author))
.useValue({})
.compile();
controller = module.get<AliasController>(AliasController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View file

@ -0,0 +1,140 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import {
BadRequestException,
Body,
Controller,
Delete,
HttpCode,
NotFoundException,
Param,
Post,
Put,
Req,
UnauthorizedException,
} from '@nestjs/common';
import { Request } from 'express';
import {
AlreadyInDBError,
ForbiddenIdError,
NotInDBError,
PrimaryAliasDeletionForbiddenError,
} from '../../../errors/errors';
import { ConsoleLoggerService } from '../../../logger/console-logger.service';
import { AliasCreateDto } from '../../../notes/alias-create.dto';
import { AliasUpdateDto } from '../../../notes/alias-update.dto';
import { AliasDto } from '../../../notes/alias.dto';
import { AliasService } from '../../../notes/alias.service';
import { NotesService } from '../../../notes/notes.service';
import { PermissionsService } from '../../../permissions/permissions.service';
import { UsersService } from '../../../users/users.service';
@Controller('alias')
export class AliasController {
constructor(
private readonly logger: ConsoleLoggerService,
private aliasService: AliasService,
private noteService: NotesService,
private userService: UsersService,
private permissionsService: PermissionsService,
) {
this.logger.setContext(AliasController.name);
}
@Post()
async addAlias(
@Req() req: Request,
@Body() newAliasDto: AliasCreateDto,
): Promise<AliasDto> {
try {
// ToDo: use actual user here
const user = await this.userService.getUserByUsername('hardcoded');
const note = await this.noteService.getNoteByIdOrAlias(
newAliasDto.noteIdOrAlias,
);
if (!this.permissionsService.isOwner(user, note)) {
throw new UnauthorizedException('Reading note denied!');
}
const updatedAlias = await this.aliasService.addAlias(
note,
newAliasDto.newAlias,
);
return this.aliasService.toAliasDto(updatedAlias, note);
} catch (e) {
if (e instanceof AlreadyInDBError) {
throw new BadRequestException(e.message);
}
if (e instanceof ForbiddenIdError) {
throw new BadRequestException(e.message);
}
throw e;
}
}
@Put(':alias')
async makeAliasPrimary(
@Req() req: Request,
@Param('alias') alias: string,
@Body() changeAliasDto: AliasUpdateDto,
): Promise<AliasDto> {
if (!changeAliasDto.primaryAlias) {
throw new BadRequestException(
`The field 'primaryAlias' must be set to 'true'.`,
);
}
try {
// ToDo: use actual user here
const user = await this.userService.getUserByUsername('hardcoded');
const note = await this.noteService.getNoteByIdOrAlias(alias);
if (!this.permissionsService.isOwner(user, note)) {
throw new UnauthorizedException('Reading note denied!');
}
const updatedAlias = await this.aliasService.makeAliasPrimary(
note,
alias,
);
return this.aliasService.toAliasDto(updatedAlias, note);
} catch (e) {
if (e instanceof NotInDBError) {
throw new NotFoundException(e.message);
}
if (e instanceof ForbiddenIdError) {
throw new BadRequestException(e.message);
}
throw e;
}
}
@Delete(':alias')
@HttpCode(204)
async removeAlias(
@Req() req: Request,
@Param('alias') alias: string,
): Promise<void> {
try {
// ToDo: use actual user here
const user = await this.userService.getUserByUsername('hardcoded');
const note = await this.noteService.getNoteByIdOrAlias(alias);
if (!this.permissionsService.isOwner(user, note)) {
throw new UnauthorizedException('Reading note denied!');
}
await this.aliasService.removeAlias(note, alias);
return;
} catch (e) {
if (e instanceof NotInDBError) {
throw new NotFoundException(e.message);
}
if (e instanceof PrimaryAliasDeletionForbiddenError) {
throw new BadRequestException(e.message);
}
if (e instanceof ForbiddenIdError) {
throw new BadRequestException(e.message);
}
throw e;
}
}
}

View file

@ -15,6 +15,7 @@ import { NotesModule } from '../../notes/notes.module';
import { PermissionsModule } from '../../permissions/permissions.module'; import { PermissionsModule } from '../../permissions/permissions.module';
import { RevisionsModule } from '../../revisions/revisions.module'; import { RevisionsModule } from '../../revisions/revisions.module';
import { UsersModule } from '../../users/users.module'; import { UsersModule } from '../../users/users.module';
import { AliasController } from './alias/alias.controller';
import { AuthController } from './auth/auth.controller'; import { AuthController } from './auth/auth.controller';
import { ConfigController } from './config/config.controller'; import { ConfigController } from './config/config.controller';
import { HistoryController } from './me/history/history.controller'; import { HistoryController } from './me/history/history.controller';
@ -43,6 +44,7 @@ import { TokensController } from './tokens/tokens.controller';
HistoryController, HistoryController,
MeController, MeController,
NotesController, NotesController,
AliasController,
AuthController, AuthController,
], ],
}) })

View file

@ -0,0 +1,123 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ConfigModule } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
import {
getConnectionToken,
getRepositoryToken,
TypeOrmModule,
} from '@nestjs/typeorm';
import { AuthToken } from '../../../auth/auth-token.entity';
import { Author } from '../../../authors/author.entity';
import appConfigMock from '../../../config/mock/app.config.mock';
import mediaConfigMock from '../../../config/mock/media.config.mock';
import { Group } from '../../../groups/group.entity';
import { GroupsModule } from '../../../groups/groups.module';
import { HistoryEntry } from '../../../history/history-entry.entity';
import { HistoryModule } from '../../../history/history.module';
import { Identity } from '../../../identity/identity.entity';
import { LoggerModule } from '../../../logger/logger.module';
import { MediaUpload } from '../../../media/media-upload.entity';
import { MediaModule } from '../../../media/media.module';
import { Alias } from '../../../notes/alias.entity';
import { AliasService } from '../../../notes/alias.service';
import { Note } from '../../../notes/note.entity';
import { NotesService } from '../../../notes/notes.service';
import { Tag } from '../../../notes/tag.entity';
import { NoteGroupPermission } from '../../../permissions/note-group-permission.entity';
import { NoteUserPermission } from '../../../permissions/note-user-permission.entity';
import { PermissionsModule } from '../../../permissions/permissions.module';
import { Edit } from '../../../revisions/edit.entity';
import { Revision } from '../../../revisions/revision.entity';
import { RevisionsModule } from '../../../revisions/revisions.module';
import { Session } from '../../../users/session.entity';
import { User } from '../../../users/user.entity';
import { UsersModule } from '../../../users/users.module';
import { AliasController } from './alias.controller';
describe('AliasController', () => {
let controller: AliasController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [AliasController],
providers: [
AliasService,
NotesService,
{
provide: getRepositoryToken(Note),
useValue: {},
},
{
provide: getRepositoryToken(Tag),
useValue: {},
},
{
provide: getRepositoryToken(Alias),
useValue: {},
},
{
provide: getRepositoryToken(User),
useValue: {},
},
],
imports: [
RevisionsModule,
UsersModule,
GroupsModule,
LoggerModule,
PermissionsModule,
HistoryModule,
MediaModule,
ConfigModule.forRoot({
isGlobal: true,
load: [appConfigMock, mediaConfigMock],
}),
TypeOrmModule.forRoot(),
],
})
.overrideProvider(getConnectionToken())
.useValue({})
.overrideProvider(getRepositoryToken(Revision))
.useValue({})
.overrideProvider(getRepositoryToken(Edit))
.useValue({})
.overrideProvider(getRepositoryToken(User))
.useValue({})
.overrideProvider(getRepositoryToken(AuthToken))
.useValue({})
.overrideProvider(getRepositoryToken(Identity))
.useValue({})
.overrideProvider(getRepositoryToken(Note))
.useValue({})
.overrideProvider(getRepositoryToken(Tag))
.useValue({})
.overrideProvider(getRepositoryToken(HistoryEntry))
.useValue({})
.overrideProvider(getRepositoryToken(NoteGroupPermission))
.useValue({})
.overrideProvider(getRepositoryToken(NoteUserPermission))
.useValue({})
.overrideProvider(getRepositoryToken(Group))
.useValue({})
.overrideProvider(getRepositoryToken(MediaUpload))
.useValue({})
.overrideProvider(getRepositoryToken(Alias))
.useValue({})
.overrideProvider(getRepositoryToken(Session))
.useValue({})
.overrideProvider(getRepositoryToken(Author))
.useValue({})
.compile();
controller = module.get<AliasController>(AliasController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View file

@ -0,0 +1,159 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import {
BadRequestException,
Body,
Controller,
Delete,
HttpCode,
NotFoundException,
Param,
Post,
Put,
UnauthorizedException,
UseGuards,
} from '@nestjs/common';
import {
ApiNoContentResponse,
ApiOkResponse,
ApiSecurity,
ApiTags,
} from '@nestjs/swagger';
import { TokenAuthGuard } from '../../../auth/token.strategy';
import {
AlreadyInDBError,
ForbiddenIdError,
NotInDBError,
PrimaryAliasDeletionForbiddenError,
} from '../../../errors/errors';
import { ConsoleLoggerService } from '../../../logger/console-logger.service';
import { AliasCreateDto } from '../../../notes/alias-create.dto';
import { AliasUpdateDto } from '../../../notes/alias-update.dto';
import { AliasDto } from '../../../notes/alias.dto';
import { AliasService } from '../../../notes/alias.service';
import { NotesService } from '../../../notes/notes.service';
import { PermissionsService } from '../../../permissions/permissions.service';
import { User } from '../../../users/user.entity';
import { FullApi } from '../../utils/fullapi-decorator';
import { RequestUser } from '../../utils/request-user.decorator';
@ApiTags('alias')
@ApiSecurity('token')
@Controller('alias')
export class AliasController {
constructor(
private readonly logger: ConsoleLoggerService,
private aliasService: AliasService,
private noteService: NotesService,
private permissionsService: PermissionsService,
) {
this.logger.setContext(AliasController.name);
}
@UseGuards(TokenAuthGuard)
@Post()
@ApiOkResponse({
description: 'The new alias',
type: AliasDto,
})
@FullApi
async addAlias(
@RequestUser() user: User,
@Body() newAliasDto: AliasCreateDto,
): Promise<AliasDto> {
try {
const note = await this.noteService.getNoteByIdOrAlias(
newAliasDto.noteIdOrAlias,
);
if (!this.permissionsService.isOwner(user, note)) {
throw new UnauthorizedException('Reading note denied!');
}
const updatedAlias = await this.aliasService.addAlias(
note,
newAliasDto.newAlias,
);
return this.aliasService.toAliasDto(updatedAlias, note);
} catch (e) {
if (e instanceof AlreadyInDBError) {
throw new BadRequestException(e.message);
}
if (e instanceof ForbiddenIdError) {
throw new BadRequestException(e.message);
}
throw e;
}
}
@UseGuards(TokenAuthGuard)
@Put(':alias')
@ApiOkResponse({
description: 'The updated alias',
type: AliasDto,
})
@FullApi
async makeAliasPrimary(
@RequestUser() user: User,
@Param('alias') alias: string,
@Body() changeAliasDto: AliasUpdateDto,
): Promise<AliasDto> {
if (!changeAliasDto.primaryAlias) {
throw new BadRequestException(
`The field 'primaryAlias' must be set to 'true'.`,
);
}
try {
const note = await this.noteService.getNoteByIdOrAlias(alias);
if (!this.permissionsService.isOwner(user, note)) {
throw new UnauthorizedException('Reading note denied!');
}
const updatedAlias = await this.aliasService.makeAliasPrimary(
note,
alias,
);
return this.aliasService.toAliasDto(updatedAlias, note);
} catch (e) {
if (e instanceof NotInDBError) {
throw new NotFoundException(e.message);
}
if (e instanceof ForbiddenIdError) {
throw new BadRequestException(e.message);
}
throw e;
}
}
@UseGuards(TokenAuthGuard)
@Delete(':alias')
@HttpCode(204)
@ApiNoContentResponse({
description: 'The alias was deleted',
})
@FullApi
async removeAlias(
@RequestUser() user: User,
@Param('alias') alias: string,
): Promise<void> {
try {
const note = await this.noteService.getNoteByIdOrAlias(alias);
if (!this.permissionsService.isOwner(user, note)) {
throw new UnauthorizedException('Reading note denied!');
}
await this.aliasService.removeAlias(note, alias);
} catch (e) {
if (e instanceof NotInDBError) {
throw new NotFoundException(e.message);
}
if (e instanceof PrimaryAliasDeletionForbiddenError) {
throw new BadRequestException(e.message);
}
if (e instanceof ForbiddenIdError) {
throw new BadRequestException(e.message);
}
throw e;
}
}
}

View file

@ -13,6 +13,7 @@ import { NotesModule } from '../../notes/notes.module';
import { PermissionsModule } from '../../permissions/permissions.module'; import { PermissionsModule } from '../../permissions/permissions.module';
import { RevisionsModule } from '../../revisions/revisions.module'; import { RevisionsModule } from '../../revisions/revisions.module';
import { UsersModule } from '../../users/users.module'; import { UsersModule } from '../../users/users.module';
import { AliasController } from './alias/alias.controller';
import { MeController } from './me/me.controller'; import { MeController } from './me/me.controller';
import { MediaController } from './media/media.controller'; import { MediaController } from './media/media.controller';
import { MonitoringController } from './monitoring/monitoring.controller'; import { MonitoringController } from './monitoring/monitoring.controller';
@ -30,6 +31,7 @@ import { NotesController } from './notes/notes.controller';
PermissionsModule, PermissionsModule,
], ],
controllers: [ controllers: [
AliasController,
MeController, MeController,
NotesController, NotesController,
MediaController, MediaController,