lots more item apis
This commit is contained in:
parent
3d7671f979
commit
f342995e89
@ -37,11 +37,16 @@ import { AppStorageService } from './app-storage.service';
|
|||||||
import {
|
import {
|
||||||
StorageAddExistingItemRequestDto,
|
StorageAddExistingItemRequestDto,
|
||||||
StorageAddItemRequestDto,
|
StorageAddItemRequestDto,
|
||||||
|
StorageItemUpdateRequestDto,
|
||||||
|
StorageStoredItemTransactionRequestDto,
|
||||||
|
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';
|
||||||
import {
|
import {
|
||||||
|
StorageItemResponseDto,
|
||||||
StorageItemSearchResponseDto,
|
StorageItemSearchResponseDto,
|
||||||
StorageStoredItemResponseDto,
|
StorageStoredItemResponseDto,
|
||||||
|
StorageTransactionResponseDto,
|
||||||
} from './dto/storage-item-response.dto';
|
} from './dto/storage-item-response.dto';
|
||||||
import {
|
import {
|
||||||
StorageCreateRequestDto,
|
StorageCreateRequestDto,
|
||||||
@ -64,6 +69,28 @@ import { StorageSetResponseDto } from './dto/storage-set-response.dto';
|
|||||||
export class AppStorageController {
|
export class AppStorageController {
|
||||||
constructor(private readonly service: AppStorageService) {}
|
constructor(private readonly service: AppStorageService) {}
|
||||||
|
|
||||||
|
@Get('storages/:storageId')
|
||||||
|
@ApiParam({ name: 'storageId', description: 'Storage ID' })
|
||||||
|
@ApiOperation({ summary: 'Get storage by ID' })
|
||||||
|
@ApiOkResponse({ type: StorageResponseDto })
|
||||||
|
async getStorage(
|
||||||
|
@CurrentStorage() storage: Storage,
|
||||||
|
): Promise<StorageResponseDto> {
|
||||||
|
return this.service.getStorageWithItems(storage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch('storages/:storageId')
|
||||||
|
@ApiParam({ name: 'storageId', description: 'Storage ID' })
|
||||||
|
@ApiBody({ type: StorageUpdateRequestDto })
|
||||||
|
@ApiOperation({ summary: 'Update storage by ID' })
|
||||||
|
@ApiOkResponse({ type: StorageResponseDto })
|
||||||
|
async updateStorage(
|
||||||
|
@CurrentStorage() storage: Storage,
|
||||||
|
@Body() body: StorageUpdateRequestDto,
|
||||||
|
): Promise<StorageResponseDto> {
|
||||||
|
return this.service.updateStorage(storage, body);
|
||||||
|
}
|
||||||
|
|
||||||
@UseGuards(StorageSetGuard)
|
@UseGuards(StorageSetGuard)
|
||||||
@Get('set/:storageSetId')
|
@Get('set/:storageSetId')
|
||||||
@ApiParam({ name: 'storageSetId', description: 'Storage set ID' })
|
@ApiParam({ name: 'storageSetId', description: 'Storage set ID' })
|
||||||
@ -174,7 +201,7 @@ export class AppStorageController {
|
|||||||
@Body() body: StorageAddItemRequestDto,
|
@Body() body: StorageAddItemRequestDto,
|
||||||
@CurrentStorage() storage: Storage,
|
@CurrentStorage() storage: Storage,
|
||||||
) {
|
) {
|
||||||
return;
|
return this.service.createNewItem(user, storage, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('item/:storageId/:itemId')
|
@Post('item/:storageId/:itemId')
|
||||||
@ -189,28 +216,62 @@ export class AppStorageController {
|
|||||||
@Body() body: StorageAddExistingItemRequestDto,
|
@Body() body: StorageAddExistingItemRequestDto,
|
||||||
@CurrentStorage() storage: Storage,
|
@CurrentStorage() storage: Storage,
|
||||||
) {
|
) {
|
||||||
return;
|
return this.service.addExistingItemToStorage(user, storage, itemId, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':storageId')
|
@Patch('item/:storageId/:storedItemId')
|
||||||
@ApiParam({ name: 'storageId', description: 'Storage ID' })
|
@ApiParam({ name: 'storedItemId', description: 'Stored Item ID' })
|
||||||
@ApiOperation({ summary: 'Get storage by ID' })
|
@ApiOperation({ summary: 'Update a stored items details' })
|
||||||
@ApiOkResponse({ type: StorageResponseDto })
|
@ApiBody({ type: StorageStoredItemUpdateRequestDto })
|
||||||
async getStorage(
|
@ApiOkResponse({ type: StorageStoredItemResponseDto })
|
||||||
|
async updateStoredItem(
|
||||||
|
@Param('storedItemId', ParseIntPipe) storedItemId: number,
|
||||||
|
@Body() body: StorageStoredItemUpdateRequestDto,
|
||||||
@CurrentStorage() storage: Storage,
|
@CurrentStorage() storage: Storage,
|
||||||
): Promise<StorageResponseDto> {
|
) {
|
||||||
return this.service.formatStorageNoItems(storage);
|
return this.service.updateStoredItemDetails(storage, storedItemId, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch(':storageId')
|
@Post('item/:storageId/:storedItemId/transaction')
|
||||||
@ApiParam({ name: 'storageId', description: 'Storage ID' })
|
@ApiParam({ name: 'storedItemId', description: 'Stored Item ID' })
|
||||||
@ApiBody({ type: StorageUpdateRequestDto })
|
@ApiOperation({ summary: 'Create a new stored item transaction' })
|
||||||
@ApiOperation({ summary: 'Update storage by ID' })
|
@ApiBody({ type: StorageStoredItemTransactionRequestDto })
|
||||||
@ApiOkResponse({ type: StorageResponseDto })
|
@ApiOkResponse({ type: StorageTransactionResponseDto })
|
||||||
async updateStorage(
|
async createStoredItemTransaction(
|
||||||
|
@LoggedInUser() user: User,
|
||||||
|
@Param('storedItemId', ParseIntPipe) storedItemId: number,
|
||||||
|
@Body() body: StorageStoredItemTransactionRequestDto,
|
||||||
@CurrentStorage() storage: Storage,
|
@CurrentStorage() storage: Storage,
|
||||||
@Body() body: StorageUpdateRequestDto,
|
) {
|
||||||
): Promise<StorageResponseDto> {
|
return this.service.createStoredItemTransaction(
|
||||||
return this.service.updateStorage(storage, body);
|
user,
|
||||||
|
storage,
|
||||||
|
storedItemId,
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('item/:storageId/:storedItemId')
|
||||||
|
@ApiParam({ name: 'storedItemId', description: 'Stored Item ID' })
|
||||||
|
@ApiOperation({ summary: 'Get a stored items details' })
|
||||||
|
@ApiOkResponse({ type: StorageStoredItemResponseDto })
|
||||||
|
async getStoredItem(
|
||||||
|
@Param('storedItemId', ParseIntPipe) storedItemId: number,
|
||||||
|
@CurrentStorage() storage: Storage,
|
||||||
|
) {
|
||||||
|
return this.service.getStoredItemDetails(storage, storedItemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch('item/:itemId')
|
||||||
|
@ApiParam({ name: 'itemId', description: 'Item ID' })
|
||||||
|
@ApiBody({ type: StorageItemUpdateRequestDto })
|
||||||
|
@ApiOperation({ summary: 'Update an item owned by the current user' })
|
||||||
|
@ApiOkResponse({ type: StorageItemResponseDto })
|
||||||
|
async updateItem(
|
||||||
|
@Param('itemId', ParseIntPipe) itemId: number,
|
||||||
|
@LoggedInUser() user: User,
|
||||||
|
@Body() body: StorageItemUpdateRequestDto,
|
||||||
|
) {
|
||||||
|
return this.service.updateOwnedItem(user, itemId, body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,31 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable, NotFoundException } 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';
|
||||||
import { Room } from 'src/objects/building/entities/room.entity';
|
import { Room } from 'src/objects/building/entities/room.entity';
|
||||||
|
import { StoredItemTransaction } from 'src/objects/storage/entities/item-transaction.entity';
|
||||||
|
import { Item } from 'src/objects/storage/entities/item.entity';
|
||||||
import { StorageSet } from 'src/objects/storage/entities/storage-set.entity';
|
import { StorageSet } from 'src/objects/storage/entities/storage-set.entity';
|
||||||
import { Storage } from 'src/objects/storage/entities/storage.entity';
|
import { Storage } from 'src/objects/storage/entities/storage.entity';
|
||||||
|
import { StoredItem } from 'src/objects/storage/entities/stored-item.entity';
|
||||||
|
import { TransactionType } from 'src/objects/storage/enums/transaction-type.enum';
|
||||||
import { StorageService } from 'src/objects/storage/storage.service';
|
import { StorageService } from 'src/objects/storage/storage.service';
|
||||||
import { User } from 'src/objects/user/user.entity';
|
import { User } from 'src/objects/user/user.entity';
|
||||||
|
import {
|
||||||
|
StorageAddExistingItemRequestDto,
|
||||||
|
StorageAddItemRequestDto,
|
||||||
|
StorageItemUpdateRequestDto,
|
||||||
|
StorageStoredItemRequestDto,
|
||||||
|
StorageStoredItemTransactionRequestDto,
|
||||||
|
StorageStoredItemUpdateRequestDto,
|
||||||
|
} from './dto/storage-add-item-request.dto';
|
||||||
import { StorageItemRequestQueryDto } from './dto/storage-item-request.dto';
|
import { StorageItemRequestQueryDto } from './dto/storage-item-request.dto';
|
||||||
import { StorageItemSearchResponseDto } from './dto/storage-item-response.dto';
|
import {
|
||||||
|
StorageItemResponseDto,
|
||||||
|
StorageItemSearchResponseDto,
|
||||||
|
StorageStoredItemResponseDto,
|
||||||
|
StorageTransactionResponseDto,
|
||||||
|
} from './dto/storage-item-response.dto';
|
||||||
import {
|
import {
|
||||||
StorageCreateRequestDto,
|
StorageCreateRequestDto,
|
||||||
StorageUpdateRequestDto,
|
StorageUpdateRequestDto,
|
||||||
@ -116,6 +133,188 @@ export class AppStorageService {
|
|||||||
return responses;
|
return responses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createStoredItem(
|
||||||
|
user: User,
|
||||||
|
item: Item,
|
||||||
|
storage: Storage,
|
||||||
|
transactionInfo: StorageStoredItemTransactionRequestDto,
|
||||||
|
additionalInfo?: StorageStoredItemRequestDto,
|
||||||
|
) {
|
||||||
|
// Create stored item
|
||||||
|
let storedItem = new StoredItem();
|
||||||
|
storedItem.addedBy = user;
|
||||||
|
storedItem.item = item;
|
||||||
|
storedItem.storage = storage;
|
||||||
|
additionalInfo && Object.assign(storedItem, additionalInfo);
|
||||||
|
|
||||||
|
storedItem = await this.storageService.saveStoredItem(storedItem);
|
||||||
|
|
||||||
|
// Create transaction
|
||||||
|
let transaction = new StoredItemTransaction();
|
||||||
|
transaction.actor = user;
|
||||||
|
transaction.storedItem = storedItem;
|
||||||
|
Object.assign(transaction, transactionInfo);
|
||||||
|
|
||||||
|
transaction = await this.storageService.saveStoredItemTransaction(
|
||||||
|
transaction,
|
||||||
|
);
|
||||||
|
|
||||||
|
storedItem.transactions = [transaction];
|
||||||
|
|
||||||
|
return new StorageStoredItemResponseDto(this.formatStoredItem(storedItem));
|
||||||
|
}
|
||||||
|
|
||||||
|
async createNewItem(
|
||||||
|
user: User,
|
||||||
|
storage: Storage,
|
||||||
|
body: StorageAddItemRequestDto,
|
||||||
|
) {
|
||||||
|
// Create item
|
||||||
|
let item = new Item();
|
||||||
|
item.addedBy = user;
|
||||||
|
Object.assign(
|
||||||
|
item,
|
||||||
|
pick(body, [
|
||||||
|
'displayName',
|
||||||
|
'type',
|
||||||
|
'barcode',
|
||||||
|
'consumable',
|
||||||
|
'image',
|
||||||
|
'weight',
|
||||||
|
'url',
|
||||||
|
'notes',
|
||||||
|
'public',
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
item = await this.storageService.saveItem(item);
|
||||||
|
|
||||||
|
return this.createStoredItem(
|
||||||
|
user,
|
||||||
|
item,
|
||||||
|
storage,
|
||||||
|
body.transactionInfo,
|
||||||
|
body.additionalInfo,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async addExistingItemToStorage(
|
||||||
|
user: User,
|
||||||
|
storage: Storage,
|
||||||
|
itemId: number,
|
||||||
|
body: StorageAddExistingItemRequestDto,
|
||||||
|
) {
|
||||||
|
const item = await this.storageService.getItemByIdBySub(itemId, user.sub);
|
||||||
|
if (!item) {
|
||||||
|
throw new NotFoundException('Item not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.createStoredItem(
|
||||||
|
user,
|
||||||
|
item,
|
||||||
|
storage,
|
||||||
|
body.transactionInfo,
|
||||||
|
body.additionalInfo,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateOwnedItem(
|
||||||
|
user: User,
|
||||||
|
itemId: number,
|
||||||
|
body: StorageItemUpdateRequestDto,
|
||||||
|
) {
|
||||||
|
const item = await this.storageService.getItemByIdOwnedBySub(
|
||||||
|
itemId,
|
||||||
|
user.sub,
|
||||||
|
);
|
||||||
|
if (!item) {
|
||||||
|
throw new NotFoundException('Item not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(item, body);
|
||||||
|
|
||||||
|
await this.storageService.saveItem(item);
|
||||||
|
|
||||||
|
return this.formatItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateStoredItemDetails(
|
||||||
|
storage: Storage,
|
||||||
|
storedItemId: number,
|
||||||
|
body: StorageStoredItemUpdateRequestDto,
|
||||||
|
) {
|
||||||
|
const storedItem = await this.storageService.getStoredItemByStorageAndId(
|
||||||
|
storage,
|
||||||
|
storedItemId,
|
||||||
|
);
|
||||||
|
if (!storedItem) {
|
||||||
|
throw new NotFoundException('Stored item not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(storedItem, body);
|
||||||
|
|
||||||
|
await this.storageService.saveStoredItem(storedItem);
|
||||||
|
|
||||||
|
return this.formatStoredItem(storedItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createStoredItemTransaction(
|
||||||
|
user: User,
|
||||||
|
storage: Storage,
|
||||||
|
storedItemId: number,
|
||||||
|
body: StorageStoredItemTransactionRequestDto,
|
||||||
|
) {
|
||||||
|
const storedItem = await this.storageService.getStoredItemByStorageAndId(
|
||||||
|
storage,
|
||||||
|
storedItemId,
|
||||||
|
);
|
||||||
|
if (!storedItem) {
|
||||||
|
throw new NotFoundException('Stored item not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const transaction = await this.storageService.saveStoredItemTransaction({
|
||||||
|
...body,
|
||||||
|
actor: user,
|
||||||
|
storedItem,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
[
|
||||||
|
TransactionType.SOLD,
|
||||||
|
TransactionType.DESTROYED,
|
||||||
|
TransactionType.BINNED,
|
||||||
|
].includes(body.type)
|
||||||
|
) {
|
||||||
|
storedItem.consumedAt = new Date();
|
||||||
|
await this.storageService.saveStoredItem(storedItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.formatTransactionNoDetails(transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getStoredItemDetails(storage: Storage, storedItemId: number) {
|
||||||
|
const storedItem = await this.storageService.getStoredItemByStorageAndId(
|
||||||
|
storage,
|
||||||
|
storedItemId,
|
||||||
|
['transactions', 'item', 'addedBy'],
|
||||||
|
);
|
||||||
|
if (!storedItem) {
|
||||||
|
throw new NotFoundException('Stored item not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.formatStoredItem(storedItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getStorageWithItems(storage: Storage) {
|
||||||
|
storage = await this.storageService.getStorageById(storage.id, [
|
||||||
|
'items',
|
||||||
|
'items.addedBy',
|
||||||
|
'items.item',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return this.formatStorageWithItems(storage);
|
||||||
|
}
|
||||||
|
|
||||||
formatActor(input: User): StorageActorResponse {
|
formatActor(input: User): StorageActorResponse {
|
||||||
return pick(input, ['name', 'sub', 'color']);
|
return pick(input, ['name', 'sub', 'color']);
|
||||||
}
|
}
|
||||||
@ -127,6 +326,42 @@ export class AppStorageService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
formatStorageWithItems(storage: Storage): StorageResponseDto {
|
||||||
|
return {
|
||||||
|
...omit(storage, ['room', 'set']),
|
||||||
|
items: !!storage.items?.length
|
||||||
|
? storage.items.map((item) => this.formatStoredItem(item))
|
||||||
|
: null,
|
||||||
|
addedBy: storage.addedBy && this.formatActor(storage.addedBy),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
formatItem(item: Item): StorageItemResponseDto {
|
||||||
|
return {
|
||||||
|
...omit(item, ['instances', 'addedBy']),
|
||||||
|
addedBy: item.addedBy && this.formatActor(item.addedBy),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
formatTransactionNoDetails(
|
||||||
|
transaction: StoredItemTransaction,
|
||||||
|
): StorageTransactionResponseDto {
|
||||||
|
return omit(transaction, ['storedItem', 'actor']);
|
||||||
|
}
|
||||||
|
|
||||||
|
formatStoredItem(storedItem: StoredItem): StorageStoredItemResponseDto {
|
||||||
|
return {
|
||||||
|
...omit(storedItem, ['storage']),
|
||||||
|
transactions: !!storedItem.transactions?.length
|
||||||
|
? storedItem.transactions.map((transaction) =>
|
||||||
|
this.formatTransactionNoDetails(transaction),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
item: storedItem.item ? this.formatItem(storedItem.item) : null,
|
||||||
|
addedBy: storedItem.addedBy ? this.formatActor(storedItem.addedBy) : null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
formatStorageSetNoItems(set: StorageSet): StorageSetResponseDto {
|
formatStorageSetNoItems(set: StorageSet): StorageSetResponseDto {
|
||||||
return {
|
return {
|
||||||
...omit(set, ['room']),
|
...omit(set, ['room']),
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional, PartialType } from '@nestjs/swagger';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
IsBoolean,
|
IsBoolean,
|
||||||
IsDate,
|
IsDateString,
|
||||||
IsEnum,
|
IsEnum,
|
||||||
IsNumber,
|
IsNumber,
|
||||||
IsObject,
|
IsObject,
|
||||||
@ -62,6 +62,10 @@ export class StorageItemRequestDto {
|
|||||||
public?: boolean;
|
public?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class StorageItemUpdateRequestDto extends PartialType(
|
||||||
|
StorageItemRequestDto,
|
||||||
|
) {}
|
||||||
|
|
||||||
export class StorageStoredItemRequestDto {
|
export class StorageStoredItemRequestDto {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
@ -69,21 +73,25 @@ export class StorageStoredItemRequestDto {
|
|||||||
notes?: string;
|
notes?: string;
|
||||||
|
|
||||||
@ApiPropertyOptional()
|
@ApiPropertyOptional()
|
||||||
@IsDate()
|
@IsDateString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
expiresAt?: Date;
|
expiresAt?: Date;
|
||||||
|
|
||||||
@ApiPropertyOptional()
|
@ApiPropertyOptional()
|
||||||
@IsDate()
|
@IsDateString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
acquiredAt?: Date;
|
acquiredAt?: Date;
|
||||||
|
|
||||||
@ApiPropertyOptional()
|
@ApiPropertyOptional()
|
||||||
@IsDate()
|
@IsDateString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
consumedAt?: Date;
|
consumedAt?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class StorageStoredItemUpdateRequestDto extends PartialType(
|
||||||
|
StorageStoredItemRequestDto,
|
||||||
|
) {}
|
||||||
|
|
||||||
export class StorageStoredItemTransactionRequestDto {
|
export class StorageStoredItemTransactionRequestDto {
|
||||||
@ApiProperty({ type: String, enum: TransactionType })
|
@ApiProperty({ type: String, enum: TransactionType })
|
||||||
@IsEnum(TransactionType)
|
@IsEnum(TransactionType)
|
||||||
@ -107,7 +115,7 @@ export class StorageStoredItemTransactionRequestDto {
|
|||||||
notes?: string;
|
notes?: string;
|
||||||
|
|
||||||
@ApiPropertyOptional()
|
@ApiPropertyOptional()
|
||||||
@IsDate()
|
@IsDateString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
actionAt?: Date;
|
actionAt?: Date;
|
||||||
}
|
}
|
||||||
|
@ -22,14 +22,32 @@ export class StorageItemSearchResponseDto extends PickType(Item, [
|
|||||||
'createdAt',
|
'createdAt',
|
||||||
]) {}
|
]) {}
|
||||||
|
|
||||||
|
export class StorageTransactionResponseDto extends OmitType(
|
||||||
|
StoredItemTransaction,
|
||||||
|
['storedItem', 'actor'],
|
||||||
|
) {}
|
||||||
|
|
||||||
export class StorageStoredItemResponseDto extends OmitType(StoredItem, [
|
export class StorageStoredItemResponseDto extends OmitType(StoredItem, [
|
||||||
|
'addedBy',
|
||||||
'transactions',
|
'transactions',
|
||||||
'storage',
|
'storage',
|
||||||
'item',
|
'item',
|
||||||
]) {
|
]) {
|
||||||
|
@ApiProperty({ type: StorageActorResponse })
|
||||||
|
addedBy: StorageActorResponse;
|
||||||
|
|
||||||
@ApiProperty({ type: StorageItemResponseDto })
|
@ApiProperty({ type: StorageItemResponseDto })
|
||||||
@Type(() => StorageItemResponseDto)
|
@Type(() => StorageItemResponseDto)
|
||||||
item: StorageItemResponseDto;
|
item: StorageItemResponseDto;
|
||||||
|
|
||||||
|
@ApiProperty({ type: StorageTransactionResponseDto, isArray: true })
|
||||||
|
@Type(() => StorageTransactionResponseDto)
|
||||||
|
transactions: StorageTransactionResponseDto[];
|
||||||
|
|
||||||
|
constructor(obj: Partial<StorageStoredItemResponseDto>) {
|
||||||
|
super(obj);
|
||||||
|
Object.assign(this, obj);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class StorageStoredItemTransactionDto extends OmitType(
|
export class StorageStoredItemTransactionDto extends OmitType(
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { ApiPropertyOptional, OmitType, PickType } from '@nestjs/swagger';
|
import { ApiPropertyOptional, OmitType, PickType } from '@nestjs/swagger';
|
||||||
import { Storage } from 'src/objects/storage/entities/storage.entity';
|
import { Storage } from 'src/objects/storage/entities/storage.entity';
|
||||||
import { User } from 'src/objects/user/user.entity';
|
import { User } from 'src/objects/user/user.entity';
|
||||||
|
import { StorageStoredItemResponseDto } from './storage-item-response.dto';
|
||||||
|
|
||||||
export class StorageActorResponse extends PickType(User, [
|
export class StorageActorResponse extends PickType(User, [
|
||||||
'sub',
|
'sub',
|
||||||
@ -16,4 +17,10 @@ export class StorageResponseDto extends OmitType(Storage, [
|
|||||||
]) {
|
]) {
|
||||||
@ApiPropertyOptional({ type: StorageActorResponse })
|
@ApiPropertyOptional({ type: StorageActorResponse })
|
||||||
addedBy: StorageActorResponse;
|
addedBy: StorageActorResponse;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
type: () => StorageStoredItemResponseDto,
|
||||||
|
isArray: true,
|
||||||
|
})
|
||||||
|
items?: StorageStoredItemResponseDto[];
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
Body,
|
|
||||||
ClassSerializerInterceptor,
|
ClassSerializerInterceptor,
|
||||||
Controller,
|
Controller,
|
||||||
Get,
|
Get,
|
||||||
@ -8,19 +7,19 @@ import {
|
|||||||
UseInterceptors,
|
UseInterceptors,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
ApiBadRequestResponse,
|
ApiBasicAuth,
|
||||||
ApiBearerAuth,
|
ApiBearerAuth,
|
||||||
ApiBody,
|
|
||||||
ApiOkResponse,
|
ApiOkResponse,
|
||||||
ApiOperation,
|
ApiOperation,
|
||||||
ApiTags,
|
ApiTags,
|
||||||
|
ApiUnauthorizedResponse,
|
||||||
} from '@nestjs/swagger';
|
} from '@nestjs/swagger';
|
||||||
import { User } from 'src/objects/user/user.entity';
|
import { User } from 'src/objects/user/user.entity';
|
||||||
import { LoggedInUser } from 'src/shared/decorators/user.decorator';
|
import { LoggedInUser } from 'src/shared/decorators/user.decorator';
|
||||||
import { AuthGuard } from 'src/shared/guards/auth.guard';
|
import { AuthGuard } from 'src/shared/guards/auth.guard';
|
||||||
|
import { LoginGuard } from 'src/shared/guards/login.guard';
|
||||||
import { AppUserService } from './app-user.service';
|
import { AppUserService } from './app-user.service';
|
||||||
import { UserLoginResponseDto } from './dto/user-login-response.dto';
|
import { UserLoginResponseDto } from './dto/user-login-response.dto';
|
||||||
import { UserLoginDto } from './dto/user-login.dto';
|
|
||||||
|
|
||||||
@ApiTags('users')
|
@ApiTags('users')
|
||||||
@UseInterceptors(ClassSerializerInterceptor)
|
@UseInterceptors(ClassSerializerInterceptor)
|
||||||
@ -31,12 +30,13 @@ export class AppUserController {
|
|||||||
constructor(private readonly service: AppUserService) {}
|
constructor(private readonly service: AppUserService) {}
|
||||||
|
|
||||||
@Post('login')
|
@Post('login')
|
||||||
@ApiBody({ type: UserLoginDto })
|
@UseGuards(LoginGuard)
|
||||||
|
@ApiBasicAuth('Email and Password')
|
||||||
@ApiOperation({ summary: 'Log in using email and password' })
|
@ApiOperation({ summary: 'Log in using email and password' })
|
||||||
@ApiBadRequestResponse()
|
@ApiUnauthorizedResponse({ description: 'Invalid email or password' })
|
||||||
@ApiOkResponse({ type: UserLoginResponseDto })
|
@ApiOkResponse({ type: UserLoginResponseDto })
|
||||||
async userDoLogin(@Body() body: UserLoginDto) {
|
async userDoLogin(@LoggedInUser() loginRequest: User) {
|
||||||
return this.service.login(body);
|
return this.service.login(loginRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { UserService } from 'src/objects/user/user.service';
|
import { UserService } from 'src/objects/user/user.service';
|
||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { UserLoginDto } from './dto/user-login.dto';
|
|
||||||
import { UserLoginResponseDto } from './dto/user-login-response.dto';
|
import { UserLoginResponseDto } from './dto/user-login-response.dto';
|
||||||
import { AuthService } from 'src/shared/auth/auth.service';
|
import { AuthService } from 'src/shared/auth/auth.service';
|
||||||
|
import { User } from 'src/objects/user/user.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AppUserService {
|
export class AppUserService {
|
||||||
@ -13,21 +13,13 @@ export class AppUserService {
|
|||||||
private readonly auth: AuthService,
|
private readonly auth: AuthService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async login({ email, password }: UserLoginDto) {
|
async login(user: User) {
|
||||||
const user = await this.userService.getUserByEmail(email);
|
|
||||||
if (!user) {
|
|
||||||
throw new BadRequestException('Invalid username or password');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(await this.auth.comparePassword(user, password))) {
|
|
||||||
throw new BadRequestException('Invalid username or password');
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = await this.auth.issueJWT(user);
|
const token = await this.auth.issueJWT(user);
|
||||||
|
|
||||||
return new UserLoginResponseDto({
|
return new UserLoginResponseDto({
|
||||||
accessToken: token,
|
access_token: token,
|
||||||
expiresIn: this.auth.expiry,
|
expires_in: this.auth.expiry,
|
||||||
|
token_type: 'Bearer',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
import { ConstructableDto } from 'src/shared/dto/constructable.dto';
|
import { ConstructableDto } from 'src/shared/dto/constructable.dto';
|
||||||
|
|
||||||
export class UserLoginResponseDto extends ConstructableDto<UserLoginResponseDto> {
|
export class UserLoginResponseDto extends ConstructableDto<UserLoginResponseDto> {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
accessToken: string;
|
access_token: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
refresh_token: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
expiresIn: number;
|
expires_in: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
token_type: string;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
|
||||||
import { IsEmail, IsString } from 'class-validator';
|
|
||||||
|
|
||||||
export class UserLoginDto {
|
|
||||||
@ApiProperty()
|
|
||||||
@IsEmail()
|
|
||||||
email: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
@IsString()
|
|
||||||
password: string;
|
|
||||||
}
|
|
@ -19,6 +19,11 @@ async function bootstrap() {
|
|||||||
name: 'Bearer token',
|
name: 'Bearer token',
|
||||||
type: 'apiKey',
|
type: 'apiKey',
|
||||||
})
|
})
|
||||||
|
.addBasicAuth({
|
||||||
|
name: 'Email and Password',
|
||||||
|
description: 'For acquiring a Bearer token',
|
||||||
|
type: 'http',
|
||||||
|
})
|
||||||
.build();
|
.build();
|
||||||
const document = SwaggerModule.createDocument(app, config);
|
const document = SwaggerModule.createDocument(app, config);
|
||||||
SwaggerModule.setup('api', app, document);
|
SwaggerModule.setup('api', app, document);
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { ILike, Repository } from 'typeorm';
|
import { ILike, Repository } from 'typeorm';
|
||||||
import { BuildingService } from '../building/building.service';
|
|
||||||
import { GroupService } from '../group/group.service';
|
|
||||||
import { UserService } from '../user/user.service';
|
|
||||||
import { StoredItemTransaction } from './entities/item-transaction.entity';
|
import { StoredItemTransaction } from './entities/item-transaction.entity';
|
||||||
import { Item } from './entities/item.entity';
|
import { Item } from './entities/item.entity';
|
||||||
import { StorageSet } from './entities/storage-set.entity';
|
import { StorageSet } from './entities/storage-set.entity';
|
||||||
@ -23,11 +20,17 @@ export class StorageService {
|
|||||||
private readonly storedItemRepository: Repository<StoredItem>,
|
private readonly storedItemRepository: Repository<StoredItem>,
|
||||||
@InjectRepository(StoredItemTransaction)
|
@InjectRepository(StoredItemTransaction)
|
||||||
private readonly transactionRepository: Repository<StoredItemTransaction>,
|
private readonly transactionRepository: Repository<StoredItemTransaction>,
|
||||||
private readonly groupService: GroupService,
|
|
||||||
private readonly userService: UserService,
|
|
||||||
private readonly buildingService: BuildingService,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
async getStorageById(id: number, relations = []) {
|
||||||
|
return this.storageRepository.findOne({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
relations,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async getStorageByIdAndSub(id: number, sub: string, relations = []) {
|
async getStorageByIdAndSub(id: number, sub: string, relations = []) {
|
||||||
return this.storageRepository.findOne({
|
return this.storageRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
@ -216,6 +219,59 @@ export class StorageService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getItemByIdBySub(id: number, sub: string) {
|
||||||
|
return this.itemRepository.findOne({
|
||||||
|
where: [
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
addedBy: {
|
||||||
|
sub,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
public: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
addedBy: {
|
||||||
|
groups: {
|
||||||
|
members: {
|
||||||
|
sub,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getItemByIdOwnedBySub(id: number, sub: string, relations = []) {
|
||||||
|
return this.itemRepository.findOne({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
addedBy: {
|
||||||
|
sub,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
relations,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getStoredItemByStorageAndId(
|
||||||
|
storage: Storage,
|
||||||
|
storedItemId: number,
|
||||||
|
relations = [],
|
||||||
|
) {
|
||||||
|
return this.storedItemRepository.findOne({
|
||||||
|
where: {
|
||||||
|
id: storedItemId,
|
||||||
|
storage: { id: storage.id },
|
||||||
|
},
|
||||||
|
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);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { Exclude } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
@ -34,6 +35,7 @@ export class User {
|
|||||||
emailVerified: boolean;
|
emailVerified: boolean;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
|
@Exclude()
|
||||||
password: string;
|
password: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
|
45
src/shared/guards/login.guard.ts
Normal file
45
src/shared/guards/login.guard.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import {
|
||||||
|
Injectable,
|
||||||
|
CanActivate,
|
||||||
|
ExecutionContext,
|
||||||
|
UnauthorizedException,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { UserService } from 'src/objects/user/user.service';
|
||||||
|
import { AuthService } from '../auth/auth.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LoginGuard implements CanActivate {
|
||||||
|
constructor(
|
||||||
|
private readonly authService: AuthService,
|
||||||
|
private readonly userService: UserService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
|
const http = context.switchToHttp();
|
||||||
|
const request = http.getRequest();
|
||||||
|
const response = http.getResponse();
|
||||||
|
|
||||||
|
const authHeader = request.header('authorization');
|
||||||
|
if (!authHeader) return false;
|
||||||
|
|
||||||
|
const [method, token] = authHeader.split(' ');
|
||||||
|
if (!token || method !== 'Basic') return false;
|
||||||
|
|
||||||
|
const [email, password] = Buffer.from(token, 'base64')
|
||||||
|
.toString()
|
||||||
|
.split(':');
|
||||||
|
|
||||||
|
const user = await this.userService.getUserByEmail(email);
|
||||||
|
if (!user) {
|
||||||
|
throw new UnauthorizedException('Invalid username or password');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await this.authService.comparePassword(user, password))) {
|
||||||
|
throw new UnauthorizedException('Invalid username or password');
|
||||||
|
}
|
||||||
|
|
||||||
|
response.locals.user = user;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user