swagger security, update transaction

This commit is contained in:
Evert Prants 2023-01-15 08:42:40 +02:00
parent f342995e89
commit 43b9dd941b
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
8 changed files with 147 additions and 20 deletions

View File

@ -13,6 +13,7 @@ import {
UseInterceptors, UseInterceptors,
} from '@nestjs/common'; } from '@nestjs/common';
import { import {
ApiBearerAuth,
ApiBody, ApiBody,
ApiOkResponse, ApiOkResponse,
ApiOperation, ApiOperation,
@ -39,6 +40,7 @@ import {
StorageAddItemRequestDto, StorageAddItemRequestDto,
StorageItemUpdateRequestDto, StorageItemUpdateRequestDto,
StorageStoredItemTransactionRequestDto, StorageStoredItemTransactionRequestDto,
StorageStoredItemTransactionUpdateRequestDto,
StorageStoredItemUpdateRequestDto, StorageStoredItemUpdateRequestDto,
} from './dto/storage-add-item-request.dto'; } from './dto/storage-add-item-request.dto';
import { StorageItemRequestQueryDto } from './dto/storage-item-request.dto'; import { StorageItemRequestQueryDto } from './dto/storage-item-request.dto';
@ -47,6 +49,7 @@ import {
StorageItemSearchResponseDto, StorageItemSearchResponseDto,
StorageStoredItemResponseDto, StorageStoredItemResponseDto,
StorageTransactionResponseDto, StorageTransactionResponseDto,
StorageTransactionWithActorResponseDto,
} from './dto/storage-item-response.dto'; } from './dto/storage-item-response.dto';
import { import {
StorageCreateRequestDto, StorageCreateRequestDto,
@ -64,6 +67,7 @@ import { StorageSetResponseDto } from './dto/storage-set-response.dto';
}) })
@ApiTags('storage') @ApiTags('storage')
@ApiSecurity('Bearer token') @ApiSecurity('Bearer token')
@ApiBearerAuth('Bearer token')
@UseInterceptors(ClassSerializerInterceptor) @UseInterceptors(ClassSerializerInterceptor)
@UseGuards(AuthGuard, BuildingGuard, RoomGuard, StorageGuard) @UseGuards(AuthGuard, BuildingGuard, RoomGuard, StorageGuard)
export class AppStorageController { export class AppStorageController {
@ -232,7 +236,7 @@ export class AppStorageController {
return this.service.updateStoredItemDetails(storage, storedItemId, body); return this.service.updateStoredItemDetails(storage, storedItemId, body);
} }
@Post('item/:storageId/:storedItemId/transaction') @Post('item/:storageId/:storedItemId/transactions')
@ApiParam({ name: 'storedItemId', description: 'Stored Item ID' }) @ApiParam({ name: 'storedItemId', description: 'Stored Item ID' })
@ApiOperation({ summary: 'Create a new stored item transaction' }) @ApiOperation({ summary: 'Create a new stored item transaction' })
@ApiBody({ type: StorageStoredItemTransactionRequestDto }) @ApiBody({ type: StorageStoredItemTransactionRequestDto })
@ -251,6 +255,39 @@ export class AppStorageController {
); );
} }
@Get('item/:storageId/:storedItemId/transactions')
@ApiParam({ name: 'storedItemId', description: 'Stored Item ID' })
@ApiOperation({ summary: 'Get a stored items transactions' })
@ApiOkResponse({
type: StorageTransactionWithActorResponseDto,
isArray: true,
})
async getStoredItemTransactions(
@Param('storedItemId', ParseIntPipe) storedItemId: number,
@CurrentStorage() storage: Storage,
) {
return this.service.getStoredItemTransaction(storage, storedItemId);
}
@Patch('item/:storageId/:storedItemId/transactions/:transactionId')
@ApiParam({ name: 'storedItemId', description: 'Stored Item ID' })
@ApiOperation({ summary: 'Update a stored item transaction' })
@ApiBody({ type: StorageStoredItemTransactionUpdateRequestDto })
@ApiOkResponse({ type: StorageTransactionResponseDto })
async updateStoredItemTransaction(
@LoggedInUser() user: User,
@Param('storedItemId', ParseIntPipe) storedItemId: number,
@Param('transactionId', ParseIntPipe) transactionId: number,
@Body() body: StorageStoredItemTransactionUpdateRequestDto,
) {
return this.service.updateStoredItemTransaction(
user,
storedItemId,
transactionId,
body,
);
}
@Get('item/:storageId/:storedItemId') @Get('item/:storageId/:storedItemId')
@ApiParam({ name: 'storedItemId', description: 'Stored Item ID' }) @ApiParam({ name: 'storedItemId', description: 'Stored Item ID' })
@ApiOperation({ summary: 'Get a stored items details' }) @ApiOperation({ summary: 'Get a stored items details' })

View File

@ -1,4 +1,8 @@
import { Injectable, NotFoundException } from '@nestjs/common'; import {
Injectable,
NotFoundException,
UnauthorizedException,
} from '@nestjs/common';
import omit from 'lodash.omit'; import omit from 'lodash.omit';
import pick from 'lodash.pick'; import pick from 'lodash.pick';
import { BuildingService } from 'src/objects/building/building.service'; import { BuildingService } from 'src/objects/building/building.service';
@ -17,6 +21,7 @@ import {
StorageItemUpdateRequestDto, StorageItemUpdateRequestDto,
StorageStoredItemRequestDto, StorageStoredItemRequestDto,
StorageStoredItemTransactionRequestDto, StorageStoredItemTransactionRequestDto,
StorageStoredItemTransactionUpdateRequestDto,
StorageStoredItemUpdateRequestDto, StorageStoredItemUpdateRequestDto,
} from './dto/storage-add-item-request.dto'; } from './dto/storage-add-item-request.dto';
import { StorageItemRequestQueryDto } from './dto/storage-item-request.dto'; import { StorageItemRequestQueryDto } from './dto/storage-item-request.dto';
@ -25,6 +30,7 @@ import {
StorageItemSearchResponseDto, StorageItemSearchResponseDto,
StorageStoredItemResponseDto, StorageStoredItemResponseDto,
StorageTransactionResponseDto, StorageTransactionResponseDto,
StorageTransactionWithActorResponseDto,
} from './dto/storage-item-response.dto'; } from './dto/storage-item-response.dto';
import { import {
StorageCreateRequestDto, StorageCreateRequestDto,
@ -268,6 +274,7 @@ export class AppStorageService {
storage, storage,
storedItemId, storedItemId,
); );
if (!storedItem) { if (!storedItem) {
throw new NotFoundException('Stored item not found'); throw new NotFoundException('Stored item not found');
} }
@ -289,14 +296,59 @@ export class AppStorageService {
await this.storageService.saveStoredItem(storedItem); await this.storageService.saveStoredItem(storedItem);
} }
return this.formatTransactionNoDetails(transaction); return this.formatTransaction(transaction);
}
async updateStoredItemTransaction(
user: User,
storedItemId: number,
transactionId: number,
body: StorageStoredItemTransactionUpdateRequestDto,
) {
const storedItemTransaction =
await this.storageService.getStoredItemTransactionById(
storedItemId,
transactionId,
['actor'],
);
if (!storedItemTransaction) {
throw new NotFoundException('Stored item not found');
}
if (storedItemTransaction.actor.sub === user.sub) {
throw new UnauthorizedException(
'You can only update your own transactions',
);
}
Object.assign(storedItemTransaction, body);
await this.storageService.saveStoredItemTransaction(storedItemTransaction);
return this.formatTransactionWithActor(storedItemTransaction);
}
async getStoredItemTransaction(storage: Storage, storedItemId: number) {
const storedItem = await this.storageService.getStoredItemByStorageAndId(
storage,
storedItemId,
['transactions', 'transactions.actor'],
);
if (!storedItem) {
throw new NotFoundException('Stored item not found');
}
return storedItem.transactions.map((transaction) =>
this.formatTransactionWithActor(transaction),
);
} }
async getStoredItemDetails(storage: Storage, storedItemId: number) { async getStoredItemDetails(storage: Storage, storedItemId: number) {
const storedItem = await this.storageService.getStoredItemByStorageAndId( const storedItem = await this.storageService.getStoredItemByStorageAndId(
storage, storage,
storedItemId, storedItemId,
['transactions', 'item', 'addedBy'], ['item', 'addedBy'],
); );
if (!storedItem) { if (!storedItem) {
throw new NotFoundException('Stored item not found'); throw new NotFoundException('Stored item not found');
@ -315,18 +367,18 @@ export class AppStorageService {
return this.formatStorageWithItems(storage); return this.formatStorageWithItems(storage);
} }
formatActor(input: User): StorageActorResponse { private formatActor(input: User): StorageActorResponse {
return pick(input, ['name', 'sub', 'color']); return pick(input, ['name', 'sub', 'picture', 'color']);
} }
formatStorageNoItems(storage: Storage): StorageResponseDto { private formatStorageNoItems(storage: Storage): StorageResponseDto {
return { return {
...omit(storage, ['room', 'set', 'items']), ...omit(storage, ['room', 'set', 'items']),
addedBy: storage.addedBy && this.formatActor(storage.addedBy), addedBy: storage.addedBy && this.formatActor(storage.addedBy),
}; };
} }
formatStorageWithItems(storage: Storage): StorageResponseDto { private formatStorageWithItems(storage: Storage): StorageResponseDto {
return { return {
...omit(storage, ['room', 'set']), ...omit(storage, ['room', 'set']),
items: !!storage.items?.length items: !!storage.items?.length
@ -336,25 +388,36 @@ export class AppStorageService {
}; };
} }
formatItem(item: Item): StorageItemResponseDto { private formatItem(item: Item): StorageItemResponseDto {
return { return {
...omit(item, ['instances', 'addedBy']), ...omit(item, ['instances', 'addedBy']),
addedBy: item.addedBy && this.formatActor(item.addedBy), addedBy: item.addedBy && this.formatActor(item.addedBy),
}; };
} }
formatTransactionNoDetails( private formatTransaction(
transaction: StoredItemTransaction, transaction: StoredItemTransaction,
): StorageTransactionResponseDto { ): StorageTransactionResponseDto {
return omit(transaction, ['storedItem', 'actor']); return omit(transaction, ['storedItem', 'actor']);
} }
formatStoredItem(storedItem: StoredItem): StorageStoredItemResponseDto { private formatTransactionWithActor(
transaction: StoredItemTransaction,
): StorageTransactionWithActorResponseDto {
return {
...omit(transaction, ['storedItem']),
actor: transaction.actor && this.formatActor(transaction.actor),
};
}
private formatStoredItem(
storedItem: StoredItem,
): StorageStoredItemResponseDto {
return { return {
...omit(storedItem, ['storage']), ...omit(storedItem, ['storage']),
transactions: !!storedItem.transactions?.length transactions: !!storedItem.transactions?.length
? storedItem.transactions.map((transaction) => ? storedItem.transactions.map((transaction) =>
this.formatTransactionNoDetails(transaction), this.formatTransaction(transaction),
) )
: null, : null,
item: storedItem.item ? this.formatItem(storedItem.item) : null, item: storedItem.item ? this.formatItem(storedItem.item) : null,

View File

@ -120,6 +120,10 @@ export class StorageStoredItemTransactionRequestDto {
actionAt?: Date; actionAt?: Date;
} }
export class StorageStoredItemTransactionUpdateRequestDto extends PartialType(
StorageStoredItemTransactionRequestDto,
) {}
export class StorageAddExistingItemRequestDto { export class StorageAddExistingItemRequestDto {
@ApiPropertyOptional({ type: StorageStoredItemRequestDto }) @ApiPropertyOptional({ type: StorageStoredItemRequestDto })
@Type(() => StorageStoredItemRequestDto) @Type(() => StorageStoredItemRequestDto)

View File

@ -27,6 +27,11 @@ export class StorageTransactionResponseDto extends OmitType(
['storedItem', 'actor'], ['storedItem', 'actor'],
) {} ) {}
export class StorageTransactionWithActorResponseDto extends StorageTransactionResponseDto {
@ApiProperty({ type: StorageActorResponse })
actor: StorageActorResponse;
}
export class StorageStoredItemResponseDto extends OmitType(StoredItem, [ export class StorageStoredItemResponseDto extends OmitType(StoredItem, [
'addedBy', 'addedBy',
'transactions', 'transactions',

View File

@ -6,6 +6,7 @@ import { StorageStoredItemResponseDto } from './storage-item-response.dto';
export class StorageActorResponse extends PickType(User, [ export class StorageActorResponse extends PickType(User, [
'sub', 'sub',
'name', 'name',
'picture',
'color', 'color',
]) {} ]) {}

View File

@ -42,7 +42,7 @@ export class AppUserController {
@Get() @Get()
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
@ApiOperation({ summary: 'Get logged in user' }) @ApiOperation({ summary: 'Get logged in user' })
@ApiBearerAuth('Bearer auth') @ApiBearerAuth('Bearer token')
async userGetInfo(@LoggedInUser() user: User) { async userGetInfo(@LoggedInUser() user: User) {
return { return {
name: user.name, name: user.name,

View File

@ -15,14 +15,17 @@ async function bootstrap() {
.addTag('groups') .addTag('groups')
.addTag('storage') .addTag('storage')
.addTag('buildings') .addTag('buildings')
.addBearerAuth({ .addSecurity('Bearer token', {
name: 'Bearer token', name: 'Authorization',
type: 'apiKey',
})
.addBasicAuth({
name: 'Email and Password',
description: 'For acquiring a Bearer token',
type: 'http', type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
in: 'header',
})
.addSecurity('Email and Password', {
type: 'http',
description: 'For acquiring the access token',
scheme: 'basic',
}) })
.build(); .build();
const document = SwaggerModule.createDocument(app, config); const document = SwaggerModule.createDocument(app, config);

View File

@ -272,6 +272,20 @@ export class StorageService {
}); });
} }
async getStoredItemTransactionById(
storedItemId: number,
storedItemTransactionId: number,
relations = [],
) {
return this.transactionRepository.findOne({
where: {
id: storedItemTransactionId,
storedItem: { id: storedItemId },
},
relations,
});
}
async saveStorage(data: Partial<Storage>) { async saveStorage(data: Partial<Storage>) {
const newStorage = new Storage(); const newStorage = new Storage();
Object.assign(newStorage, data); Object.assign(newStorage, data);