Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { ObjectId } from 'mongodb';
import ClanInventoryBuilderFactory from '../../data/clanInventoryBuilderFactory';
import StockModule from '../../modules/stock.module';
import { StockService } from '../../../../clanInventory/stock/stock.service';
import { getNonExisting_id } from '../../../test_utils/util/getNonExisting_id';

describe('StockService.getStockClanId() test suite', () => {
let stockService: StockService;
const stockBuilder = ClanInventoryBuilderFactory.getBuilder('Stock');
const stockModel = StockModule.getStockModel();

beforeEach(async () => {
stockService = await StockModule.getStockService();
});

it('Should find clan _id of the stock', async () => {
const clan_id = new ObjectId();
const stockToCreate = stockBuilder.setClanId(clan_id).build();
const createdStock = await stockModel.create(stockToCreate);

const [stockClan_id, errors] = await stockService.getStockClanId(
createdStock._id,
);

expect(errors).toBeNull();
expect(stockClan_id).toBe(clan_id.toString());
});

it('Should return the clan _id as a string', async () => {
const clan_id = new ObjectId();
const stockToCreate = stockBuilder.setClanId(clan_id).build();
const createdStock = await stockModel.create(stockToCreate);

const [stockClan_id] = await stockService.getStockClanId(createdStock._id);

expect(typeof stockClan_id).toBe('string');
});

it('Should find clan _id of the specified stock only', async () => {
const clan1_id = new ObjectId();
const clan2_id = new ObjectId();

const createdStock1 = await stockModel.create(
stockBuilder.setClanId(clan1_id).build(),
);
const createdStock2 = await stockModel.create(
stockBuilder.setClanId(clan2_id).build(),
);

const [stock1Clan_id, stock1Errors] = await stockService.getStockClanId(
createdStock1._id,
);
const [stock2Clan_id, stock2Errors] = await stockService.getStockClanId(
createdStock2._id,
);

expect(stock1Errors).toBeNull();
expect(stock2Errors).toBeNull();
expect(stock1Clan_id).toBe(clan1_id.toString());
expect(stock2Clan_id).toBe(clan2_id.toString());
});

it('Should return NOT_FOUND SE if stock with provided _id does not exists', async () => {
const [stockClan_id, errors] =
await stockService.getStockClanId(getNonExisting_id());

expect(stockClan_id).toBeNull();
expect(errors).toContainSE_NOT_FOUND();
});

it('Should return VALIDATION SE if provided _id param is not a mongo id', async () => {
const [stockClan_id, errors] =
await stockService.getStockClanId('not-mongo-id');

expect(stockClan_id).toBeNull();
expect(errors).toContainSE_VALIDATION();
});
});
24 changes: 22 additions & 2 deletions src/clanInventory/item/item.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,32 @@ export class ItemController {
dto: ItemDto,
modelName: ModelName.ITEM,
},
errors: [400, 404],
errors: [400, 403, 404],
})
@Get('/:_id')
@Authorize({ action: Action.read, subject: ItemDto })
@UniformResponse(ModelName.ITEM)
public get(@Param() param: _idDto) {
public async get(@Param() param: _idDto, @LoggedUser() user: User) {
const [itemClan_id, errors] = await this.itemHelper.getItemClanId(
param._id,
);
if (errors || !itemClan_id) return [null, errors];

const playerClan = await this.playerModel.findById(user.player_id);

if (playerClan.clan_id.toString() !== itemClan_id)
return [
null,
[
new APIError({
reason: APIErrorReason.NOT_AUTHORIZED,
field: '_id',
value: param._id,
message: "Cannot view another Clan's Items",
}),
],
];

return this.itemService.readOneById(param._id);
}

Expand Down
36 changes: 31 additions & 5 deletions src/clanInventory/stock/stock.controller.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { Controller, Get, Param, ParseIntPipe, Query } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { StockService } from './stock.service';
import { StockDto } from './dto/stock.dto';
import { ItemDto } from '../item/dto/item.dto';
import { User } from '../../auth/user';
import { Player } from '../../player/schemas/player.schema';
import { Authorize } from '../../authorization/decorator/Authorize';
import { Action } from '../../authorization/enum/action.enum';
import { APIError } from '../../common/controller/APIError';
import { APIErrorReason } from '../../common/controller/APIErrorReason';
import { GetAllQuery } from '../../common/decorator/param/GetAllQuery';
import { LoggedUser } from '../../common/decorator/param/LoggedUser.decorator';
import { UniformResponse } from '../../common/decorator/response/UniformResponse';
Expand All @@ -19,25 +24,46 @@ import { Environment } from '../../common/enum/environment.enum';

@Controller('stock')
export class StockController {
public constructor(private readonly service: StockService) {}
public constructor(
private readonly service: StockService,
@InjectModel(ModelName.PLAYER) private readonly playerModel: Model<Player>,
) {}

/**
* Get stock items by Stock _id
* Get logged-in Player's Clan stock items by Stock _id
*
* @remarks Returns the list of Items currently stored in the Stock with the given _id.
* @remarks Returns the list of Items currently stored in the logged-in Player's Clan Stock with the given _id.
*/
@ApiResponseDescription({
success: {
dto: ItemDto,
modelName: ModelName.ITEM,
returnsArray: true,
},
errors: [400, 401, 404],
errors: [400, 401, 403, 404],
})
@Get('/:_id')
@Authorize({ action: Action.read, subject: StockDto })
@UniformResponse(ModelName.ITEM)
public get(@Param() param: _idDto) {
public async get(@Param() param: _idDto, @LoggedUser() user: User) {
const [stockClan_id, errors] = await this.service.getStockClanId(param._id);
if (errors || !stockClan_id) return [null, errors];

const playerClan = await this.playerModel.findById(user.player_id);

if (playerClan.clan_id.toString() !== stockClan_id)
return [
null,
[
new APIError({
reason: APIErrorReason.NOT_AUTHORIZED,
field: '_id',
value: param._id,
message: "Cannot view another Clan's Stock",
}),
],
];

return this.service.readItemsByStockId(param._id);
}

Expand Down
17 changes: 17 additions & 0 deletions src/clanInventory/stock/stock.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,23 @@ export class StockService {
return [{ ...stockObject, FleaMarketItem: fleaMarketItems ?? [] }, null];
}

/**
* Retrieves the clan ID that owns the Stock.
*
* @param _id - The Mongo _id of the Clan Stock.
* @returns A promise that resolves to a tuple where the first element is the clan ID (string) or null and the second element is either null or an array of ServiceErrors.
*/
async getStockClanId(
_id: string,
): Promise<[string | null, ServiceError[] | null]> {
const [stock, errors] = await this.basicService.readOneById<StockDto>(_id, {
select: ['clan_id'],
});
if (errors) return [null, errors];

return [stock.clan_id.toString(), null];
}

/**
* Reads all Items stored in the specified Stock.
*
Expand Down
Loading