lots more item apis
This commit is contained in:
parent
3d7671f979
commit
f342995e89
@ -37,11 +37,16 @@ import { AppStorageService } from './app-storage.service';
|
||||
import {
|
||||
StorageAddExistingItemRequestDto,
|
||||
StorageAddItemRequestDto,
|
||||
StorageItemUpdateRequestDto,
|
||||
StorageStoredItemTransactionRequestDto,
|
||||
StorageStoredItemUpdateRequestDto,
|
||||
} from './dto/storage-add-item-request.dto';
|
||||
import { StorageItemRequestQueryDto } from './dto/storage-item-request.dto';
|
||||
import {
|
||||
StorageItemResponseDto,
|
||||
StorageItemSearchResponseDto,
|
||||
StorageStoredItemResponseDto,
|
||||
StorageTransactionResponseDto,
|
||||
} from './dto/storage-item-response.dto';
|
||||
import {
|
||||
StorageCreateRequestDto,
|
||||
@ -64,6 +69,28 @@ import { StorageSetResponseDto } from './dto/storage-set-response.dto';
|
||||
export class AppStorageController {
|
||||
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)
|
||||
@Get('set/:storageSetId')
|
||||
@ApiParam({ name: 'storageSetId', description: 'Storage set ID' })
|
||||
@ -174,7 +201,7 @@ export class AppStorageController {
|
||||
@Body() body: StorageAddItemRequestDto,
|
||||
@CurrentStorage() storage: Storage,
|
||||
) {
|
||||
return;
|
||||
return this.service.createNewItem(user, storage, body);
|
||||
}
|
||||
|
||||
@Post('item/:storageId/:itemId')
|
||||
@ -189,28 +216,62 @@ export class AppStorageController {
|
||||
@Body() body: StorageAddExistingItemRequestDto,
|
||||
@CurrentStorage() storage: Storage,
|
||||
) {
|
||||
return;
|
||||
return this.service.addExistingItemToStorage(user, storage, itemId, body);
|
||||
}
|
||||
|
||||
@Get(':storageId')
|
||||
@ApiParam({ name: 'storageId', description: 'Storage ID' })
|
||||
@ApiOperation({ summary: 'Get storage by ID' })
|
||||
@ApiOkResponse({ type: StorageResponseDto })
|
||||
async getStorage(
|
||||
@Patch('item/:storageId/:storedItemId')
|
||||
@ApiParam({ name: 'storedItemId', description: 'Stored Item ID' })
|
||||
@ApiOperation({ summary: 'Update a stored items details' })
|
||||
@ApiBody({ type: StorageStoredItemUpdateRequestDto })
|
||||
@ApiOkResponse({ type: StorageStoredItemResponseDto })
|
||||
async updateStoredItem(
|
||||
@Param('storedItemId', ParseIntPipe) storedItemId: number,
|
||||
@Body() body: StorageStoredItemUpdateRequestDto,
|
||||
@CurrentStorage() storage: Storage,
|
||||
): Promise<StorageResponseDto> {
|
||||
return this.service.formatStorageNoItems(storage);
|
||||
) {
|
||||
return this.service.updateStoredItemDetails(storage, storedItemId, body);
|
||||
}
|
||||
|
||||
@Patch(':storageId')
|
||||
@ApiParam({ name: 'storageId', description: 'Storage ID' })
|
||||
@ApiBody({ type: StorageUpdateRequestDto })
|
||||
@ApiOperation({ summary: 'Update storage by ID' })
|
||||
@ApiOkResponse({ type: StorageResponseDto })
|
||||
async updateStorage(
|
||||
@Post('item/:storageId/:storedItemId/transaction')
|
||||
@ApiParam({ name: 'storedItemId', description: 'Stored Item ID' })
|
||||
@ApiOperation({ summary: 'Create a new stored item transaction' })
|
||||
@ApiBody({ type: StorageStoredItemTransactionRequestDto })
|
||||
@ApiOkResponse({ type: StorageTransactionResponseDto })
|
||||
async createStoredItemTransaction(
|
||||
@LoggedInUser() user: User,
|
||||
@Param('storedItemId', ParseIntPipe) storedItemId: number,
|
||||
@Body() body: StorageStoredItemTransactionRequestDto,
|
||||
@CurrentStorage() storage: Storage,
|
||||
@Body() body: StorageUpdateRequestDto,
|
||||
): Promise<StorageResponseDto> {
|
||||
return this.service.updateStorage(storage, body);
|
||||
) {
|
||||
return this.service.createStoredItemTransaction(
|
||||
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 pick from 'lodash.pick';
|
||||
import { BuildingService } from 'src/objects/building/building.service';
|
||||
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 { 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 { 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 { StorageItemSearchResponseDto } from './dto/storage-item-response.dto';
|
||||
import {
|
||||
StorageItemResponseDto,
|
||||
StorageItemSearchResponseDto,
|
||||
StorageStoredItemResponseDto,
|
||||
StorageTransactionResponseDto,
|
||||
} from './dto/storage-item-response.dto';
|
||||
import {
|
||||
StorageCreateRequestDto,
|
||||
StorageUpdateRequestDto,
|
||||
@ -116,6 +133,188 @@ export class AppStorageService {
|
||||
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 {
|
||||
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 {
|
||||
return {
|
||||
...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 {
|
||||
IsBoolean,
|
||||
IsDate,
|
||||
IsDateString,
|
||||
IsEnum,
|
||||
IsNumber,
|
||||
IsObject,
|
||||
@ -62,6 +62,10 @@ export class StorageItemRequestDto {
|
||||
public?: boolean;
|
||||
}
|
||||
|
||||
export class StorageItemUpdateRequestDto extends PartialType(
|
||||
StorageItemRequestDto,
|
||||
) {}
|
||||
|
||||
export class StorageStoredItemRequestDto {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@ -69,21 +73,25 @@ export class StorageStoredItemRequestDto {
|
||||
notes?: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsDate()
|
||||
@IsDateString()
|
||||
@IsOptional()
|
||||
expiresAt?: Date;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsDate()
|
||||
@IsDateString()
|
||||
@IsOptional()
|
||||
acquiredAt?: Date;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsDate()
|
||||
@IsDateString()
|
||||
@IsOptional()
|
||||
consumedAt?: Date;
|
||||
}
|
||||
|
||||
export class StorageStoredItemUpdateRequestDto extends PartialType(
|
||||
StorageStoredItemRequestDto,
|
||||
) {}
|
||||
|
||||
export class StorageStoredItemTransactionRequestDto {
|
||||
@ApiProperty({ type: String, enum: TransactionType })
|
||||
@IsEnum(TransactionType)
|
||||
@ -107,7 +115,7 @@ export class StorageStoredItemTransactionRequestDto {
|
||||
notes?: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsDate()
|
||||
@IsDateString()
|
||||
@IsOptional()
|
||||
actionAt?: Date;
|
||||
}
|
||||
|
@ -22,14 +22,32 @@ export class StorageItemSearchResponseDto extends PickType(Item, [
|
||||
'createdAt',
|
||||
]) {}
|
||||
|
||||
export class StorageTransactionResponseDto extends OmitType(
|
||||
StoredItemTransaction,
|
||||
['storedItem', 'actor'],
|
||||
) {}
|
||||
|
||||
export class StorageStoredItemResponseDto extends OmitType(StoredItem, [
|
||||
'addedBy',
|
||||
'transactions',
|
||||
'storage',
|
||||
'item',
|
||||
]) {
|
||||
@ApiProperty({ type: StorageActorResponse })
|
||||
addedBy: StorageActorResponse;
|
||||
|
||||
@ApiProperty({ type: StorageItemResponseDto })
|
||||
@Type(() => 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(
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { ApiPropertyOptional, OmitType, PickType } from '@nestjs/swagger';
|
||||
import { Storage } from 'src/objects/storage/entities/storage.entity';
|
||||
import { User } from 'src/objects/user/user.entity';
|
||||
import { StorageStoredItemResponseDto } from './storage-item-response.dto';
|
||||
|
||||
export class StorageActorResponse extends PickType(User, [
|
||||
'sub',
|
||||
@ -16,4 +17,10 @@ export class StorageResponseDto extends OmitType(Storage, [
|
||||
]) {
|
||||
@ApiPropertyOptional({ type: StorageActorResponse })
|
||||
addedBy: StorageActorResponse;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
type: () => StorageStoredItemResponseDto,
|
||||
isArray: true,
|
||||
})
|
||||
items?: StorageStoredItemResponseDto[];
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import {
|
||||
Body,
|
||||
ClassSerializerInterceptor,
|
||||
Controller,
|
||||
Get,
|
||||
@ -8,19 +7,19 @@ import {
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiBadRequestResponse,
|
||||
ApiBasicAuth,
|
||||
ApiBearerAuth,
|
||||
ApiBody,
|
||||
ApiOkResponse,
|
||||
ApiOperation,
|
||||
ApiTags,
|
||||
ApiUnauthorizedResponse,
|
||||
} from '@nestjs/swagger';
|
||||
import { User } from 'src/objects/user/user.entity';
|
||||
import { LoggedInUser } from 'src/shared/decorators/user.decorator';
|
||||
import { AuthGuard } from 'src/shared/guards/auth.guard';
|
||||
import { LoginGuard } from 'src/shared/guards/login.guard';
|
||||
import { AppUserService } from './app-user.service';
|
||||
import { UserLoginResponseDto } from './dto/user-login-response.dto';
|
||||
import { UserLoginDto } from './dto/user-login.dto';
|
||||
|
||||
@ApiTags('users')
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@ -31,12 +30,13 @@ export class AppUserController {
|
||||
constructor(private readonly service: AppUserService) {}
|
||||
|
||||
@Post('login')
|
||||
@ApiBody({ type: UserLoginDto })
|
||||
@UseGuards(LoginGuard)
|
||||
@ApiBasicAuth('Email and Password')
|
||||
@ApiOperation({ summary: 'Log in using email and password' })
|
||||
@ApiBadRequestResponse()
|
||||
@ApiUnauthorizedResponse({ description: 'Invalid email or password' })
|
||||
@ApiOkResponse({ type: UserLoginResponseDto })
|
||||
async userDoLogin(@Body() body: UserLoginDto) {
|
||||
return this.service.login(body);
|
||||
async userDoLogin(@LoggedInUser() loginRequest: User) {
|
||||
return this.service.login(loginRequest);
|
||||
}
|
||||
|
||||
@Get()
|
||||
|
@ -1,9 +1,9 @@
|
||||
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 { UserLoginDto } from './dto/user-login.dto';
|
||||
import { UserLoginResponseDto } from './dto/user-login-response.dto';
|
||||
import { AuthService } from 'src/shared/auth/auth.service';
|
||||
import { User } from 'src/objects/user/user.entity';
|
||||
|
||||
@Injectable()
|
||||
export class AppUserService {
|
||||
@ -13,21 +13,13 @@ export class AppUserService {
|
||||
private readonly auth: AuthService,
|
||||
) {}
|
||||
|
||||
async login({ email, password }: UserLoginDto) {
|
||||
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');
|
||||
}
|
||||
|
||||
async login(user: User) {
|
||||
const token = await this.auth.issueJWT(user);
|
||||
|
||||
return new UserLoginResponseDto({
|
||||
accessToken: token,
|
||||
expiresIn: this.auth.expiry,
|
||||
access_token: token,
|
||||
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';
|
||||
|
||||
export class UserLoginResponseDto extends ConstructableDto<UserLoginResponseDto> {
|
||||
@ApiProperty()
|
||||
accessToken: string;
|
||||
access_token: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
refresh_token: string;
|
||||
|
||||
@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',
|
||||
type: 'apiKey',
|
||||
})
|
||||
.addBasicAuth({
|
||||
name: 'Email and Password',
|
||||
description: 'For acquiring a Bearer token',
|
||||
type: 'http',
|
||||
})
|
||||
.build();
|
||||
const document = SwaggerModule.createDocument(app, config);
|
||||
SwaggerModule.setup('api', app, document);
|
||||
|
@ -1,9 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/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 { Item } from './entities/item.entity';
|
||||
import { StorageSet } from './entities/storage-set.entity';
|
||||
@ -23,11 +20,17 @@ export class StorageService {
|
||||
private readonly storedItemRepository: Repository<StoredItem>,
|
||||
@InjectRepository(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 = []) {
|
||||
return this.storageRepository.findOne({
|
||||
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>) {
|
||||
const newStorage = new Storage();
|
||||
Object.assign(newStorage, data);
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Exclude } from 'class-transformer';
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
@ -34,6 +35,7 @@ export class User {
|
||||
emailVerified: boolean;
|
||||
|
||||
@Column()
|
||||
@Exclude()
|
||||
password: string;
|
||||
|
||||
@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