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,
} from '@nestjs/common';
import {
ApiBearerAuth,
ApiBody,
ApiOkResponse,
ApiOperation,
@ -39,6 +40,7 @@ import {
StorageAddItemRequestDto,
StorageItemUpdateRequestDto,
StorageStoredItemTransactionRequestDto,
StorageStoredItemTransactionUpdateRequestDto,
StorageStoredItemUpdateRequestDto,
} from './dto/storage-add-item-request.dto';
import { StorageItemRequestQueryDto } from './dto/storage-item-request.dto';
@ -47,6 +49,7 @@ import {
StorageItemSearchResponseDto,
StorageStoredItemResponseDto,
StorageTransactionResponseDto,
StorageTransactionWithActorResponseDto,
} from './dto/storage-item-response.dto';
import {
StorageCreateRequestDto,
@ -64,6 +67,7 @@ import { StorageSetResponseDto } from './dto/storage-set-response.dto';
})
@ApiTags('storage')
@ApiSecurity('Bearer token')
@ApiBearerAuth('Bearer token')
@UseInterceptors(ClassSerializerInterceptor)
@UseGuards(AuthGuard, BuildingGuard, RoomGuard, StorageGuard)
export class AppStorageController {
@ -232,7 +236,7 @@ export class AppStorageController {
return this.service.updateStoredItemDetails(storage, storedItemId, body);
}
@Post('item/:storageId/:storedItemId/transaction')
@Post('item/:storageId/:storedItemId/transactions')
@ApiParam({ name: 'storedItemId', description: 'Stored Item ID' })
@ApiOperation({ summary: 'Create a new stored item transaction' })
@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')
@ApiParam({ name: 'storedItemId', description: 'Stored Item ID' })
@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 pick from 'lodash.pick';
import { BuildingService } from 'src/objects/building/building.service';
@ -17,6 +21,7 @@ import {
StorageItemUpdateRequestDto,
StorageStoredItemRequestDto,
StorageStoredItemTransactionRequestDto,
StorageStoredItemTransactionUpdateRequestDto,
StorageStoredItemUpdateRequestDto,
} from './dto/storage-add-item-request.dto';
import { StorageItemRequestQueryDto } from './dto/storage-item-request.dto';
@ -25,6 +30,7 @@ import {
StorageItemSearchResponseDto,
StorageStoredItemResponseDto,
StorageTransactionResponseDto,
StorageTransactionWithActorResponseDto,
} from './dto/storage-item-response.dto';
import {
StorageCreateRequestDto,
@ -268,6 +274,7 @@ export class AppStorageService {
storage,
storedItemId,
);
if (!storedItem) {
throw new NotFoundException('Stored item not found');
}
@ -289,14 +296,59 @@ export class AppStorageService {
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) {
const storedItem = await this.storageService.getStoredItemByStorageAndId(
storage,
storedItemId,
['transactions', 'item', 'addedBy'],
['item', 'addedBy'],
);
if (!storedItem) {
throw new NotFoundException('Stored item not found');
@ -315,18 +367,18 @@ export class AppStorageService {
return this.formatStorageWithItems(storage);
}
formatActor(input: User): StorageActorResponse {
return pick(input, ['name', 'sub', 'color']);
private formatActor(input: User): StorageActorResponse {
return pick(input, ['name', 'sub', 'picture', 'color']);
}
formatStorageNoItems(storage: Storage): StorageResponseDto {
private formatStorageNoItems(storage: Storage): StorageResponseDto {
return {
...omit(storage, ['room', 'set', 'items']),
addedBy: storage.addedBy && this.formatActor(storage.addedBy),
};
}
formatStorageWithItems(storage: Storage): StorageResponseDto {
private formatStorageWithItems(storage: Storage): StorageResponseDto {
return {
...omit(storage, ['room', 'set']),
items: !!storage.items?.length
@ -336,25 +388,36 @@ export class AppStorageService {
};
}
formatItem(item: Item): StorageItemResponseDto {
private formatItem(item: Item): StorageItemResponseDto {
return {
...omit(item, ['instances', 'addedBy']),
addedBy: item.addedBy && this.formatActor(item.addedBy),
};
}
formatTransactionNoDetails(
private formatTransaction(
transaction: StoredItemTransaction,
): StorageTransactionResponseDto {
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 {
...omit(storedItem, ['storage']),
transactions: !!storedItem.transactions?.length
? storedItem.transactions.map((transaction) =>
this.formatTransactionNoDetails(transaction),
this.formatTransaction(transaction),
)
: null,
item: storedItem.item ? this.formatItem(storedItem.item) : null,

View File

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

View File

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

View File

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

View File

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

View File

@ -15,14 +15,17 @@ async function bootstrap() {
.addTag('groups')
.addTag('storage')
.addTag('buildings')
.addBearerAuth({
name: 'Bearer token',
type: 'apiKey',
})
.addBasicAuth({
name: 'Email and Password',
description: 'For acquiring a Bearer token',
.addSecurity('Bearer token', {
name: 'Authorization',
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
in: 'header',
})
.addSecurity('Email and Password', {
type: 'http',
description: 'For acquiring the access token',
scheme: 'basic',
})
.build();
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>) {
const newStorage = new Storage();
Object.assign(newStorage, data);