Skip to content

Commit

Permalink
add 401 and 403 errors
Browse files Browse the repository at this point in the history
  • Loading branch information
rcole1919 committed Nov 1, 2024
1 parent ba10871 commit de259ab
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 61 deletions.
14 changes: 10 additions & 4 deletions specification/specification.yml
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ paths:
"400":
description: Введена некорректная информация

"403":
"401":
description: Только авторизованные пользователи могут создавать предложение

get:
Expand Down Expand Up @@ -226,9 +226,12 @@ paths:
"400":
description: Данные введены некорректно

"403":
"401":
description: Только авторизованные пользователи могут редактировать предложение

"403":
description: Доступ к предложению запрещен

delete:
tags:
- offers
Expand All @@ -242,9 +245,12 @@ paths:
"404":
description: Предложение с таким id не найдено.

"403":
"401":
description: Только авторизованные пользователи могут удалять предложение

"403":
description: Доступ к предложению запрещен

get:
tags:
- offers
Expand Down Expand Up @@ -330,7 +336,7 @@ paths:
"404":
description: Предложение с таким id не найдено.

"403":
"401":
description: Только авторизованные пользователи могут оставлять комментарии

components:
Expand Down
24 changes: 14 additions & 10 deletions src/shared/modules/offer/default-offer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ import { ILogger } from '../../libs/logger/types/index.js';
import { ECity, ESortType } from '../../types/index.js';
import { OfferEntity } from './offer.entity.js';

const MOCK_USER = '66f947e7e706754fb39b93a7';

@injectable()
export class DefaultOfferService implements IOfferService {
constructor(
Expand All @@ -36,26 +34,26 @@ export class DefaultOfferService implements IOfferService {
return result;
}

public async findById(offerId: string): Promise<DocumentType<OfferEntity> | null> {
public async findById(offerId: string, userId: string): Promise<DocumentType<OfferEntity> | null> {
const result = await this.offerModel
.aggregate([
{ $match: { '_id': new Types.ObjectId(offerId) } },
...populateComments,
...populateAuthor,
...getIsFavorite(MOCK_USER, offerId),
...getIsFavorite(userId, offerId),
])
.exec();

return result[0] || null;
}

public async find(count?: number): Promise<DocumentType<OfferEntity>[]> {
public async find(count: number, userId: string): Promise<DocumentType<OfferEntity>[]> {
const limit = count || DEFAULT_OFFER_COUNT;

const result = await this.offerModel
.aggregate([
...populateComments,
...getIsFavorite(MOCK_USER),
...getIsFavorite(userId),
{ $sort: { createdAt: ESortType.Desc } },
{ $limit: limit },
])
Expand All @@ -64,28 +62,34 @@ export class DefaultOfferService implements IOfferService {
return result;
}

public async findPremium(city: ECity): Promise<DocumentType<OfferEntity>[]> {
public async findPremium(city: ECity, userId: string): Promise<DocumentType<OfferEntity>[]> {
return this.offerModel
.aggregate([
{ $match: {
city,
isPremium: true,
} },
...populateComments,
...getIsFavorite(MOCK_USER),
...getIsFavorite(userId),
{ $sort: { createdAt: ESortType.Desc } },
{ $limit: MAX_PREMIUM_NUMBER },
]);
}

public async updateById(offerId: string, dto: UpdateOfferDTO): Promise<DocumentType<OfferEntity> | null> {
public async updateById(offerId: string, _userId: string, dto: UpdateOfferDTO): Promise<DocumentType<OfferEntity> | null> {
return this.offerModel
.findByIdAndUpdate(offerId, dto, { new: true });
}

public async deleteById(offerId: string): Promise<DocumentType<OfferEntity> | null> {
public async deleteById(offerId: string, _userId: string): Promise<DocumentType<OfferEntity> | null> {
return this.offerModel
.findByIdAndDelete(offerId)
.exec();
}

public async isOwnOffer(offerId: string, userId: string): Promise<boolean> {
const offer = await this.offerModel.findOne({ _id: offerId });

return offer?.authorId?.toString() === userId;
}
}
42 changes: 25 additions & 17 deletions src/shared/modules/offer/offer.aggregation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,20 +53,28 @@ export const populateComments = [
{ $unset: 'comments' },
];

export const getIsFavorite = (userId: string, offerId: string = '') => ([
{
$lookup: {
from: 'users',
pipeline: [
{ $match: { '_id': new Types.ObjectId(userId) } },
{ $project: { favorites: 1 } }
],
as: 'currentUser'
},
},
{ $unwind: '$currentUser' },
{ $addFields: { isFavorite: {
$in: [offerId ? new Types.ObjectId(offerId) : '$_id' , '$currentUser.favorites']
} }},
{ $unset: 'currentUser' }
]);
export const getIsFavorite = (userId: string, offerId: string = '') => {
if (userId) {
return [
{
$lookup: {
from: 'users',
pipeline: [
{ $match: { '_id': new Types.ObjectId(userId) } },
{ $project: { favorites: 1 } }
],
as: 'currentUser'
},
},
{ $unwind: '$currentUser' },
{ $addFields: { isFavorite: {
$in: [offerId ? new Types.ObjectId(offerId) : '$_id' , '$currentUser.favorites']
} }},
{ $unset: 'currentUser' }
];
}

return [
{ $addFields: { isFavorite: false } },
];
};
42 changes: 32 additions & 10 deletions src/shared/modules/offer/offer.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,18 @@ export class OfferController extends BaseController {
});
}

public async index({ query }: Request<unknown, unknown, unknown, TQueryCount>, res: Response): Promise<void> {
public async index({ query, tokenPayload }: Request<unknown, unknown, unknown, TQueryCount>, res: Response): Promise<void> {
if (query.count !== undefined && !Number.parseInt(query.count as string, RADIX)) {
throw new HttpError(
StatusCodes.BAD_REQUEST,
'Count query must be an integer',
'UserController',
);
}
const offers = await this.offerService.find(Number.parseInt(query?.count as string, RADIX));
const offers = await this.offerService.find(
Number.parseInt(query?.count as string, RADIX),
tokenPayload?.id,
);
const responseData = fillDTO(ShortOfferRDO, offers);
this.ok(res, responseData);
}
Expand All @@ -102,31 +105,50 @@ export class OfferController extends BaseController {
this.created(res, fillDTO(FullOfferRDO, result));
}

public async show(req: Request, res: Response): Promise<void> {
const existsOffer = await this.offerService.findById(req.params.offerId);
public async show({ tokenPayload, params }: Request, res: Response): Promise<void> {
const existsOffer = await this.offerService.findById(params.offerId, tokenPayload?.id);

const responseData = fillDTO(FullOfferRDO, existsOffer);
this.ok(res, responseData);
}

public async delete(req: Request, res: Response): Promise<void> {
const result = await this.offerService.deleteById(req.params.offerId);
public async delete({ tokenPayload, params }: Request, res: Response): Promise<void> {
if (! (await this.offerService.isOwnOffer(params.offerId, tokenPayload.id))) {
throw new HttpError(
StatusCodes.FORBIDDEN,
'Forbidden',
'OfferController',
);
}
const result = await this.offerService.deleteById(params.offerId, tokenPayload.id);
this.noContent(res, result);
}

public async update(
req: Request,
{ body, params, tokenPayload }: Request,
res: Response,
): Promise<void> {
const result = await this.offerService.updateById(req.params.offerId, req.body as UpdateOfferDTO);
if (! (await this.offerService.isOwnOffer(params.offerId, tokenPayload.id))) {
throw new HttpError(
StatusCodes.FORBIDDEN,
'Forbidden',
'OfferController',
);
}

const result = await this.offerService.updateById(
params.offerId,
tokenPayload.id,
body as UpdateOfferDTO,
);
this.ok(res, fillDTO(FullOfferRDO, result));
}

public async premium(
{ body }: Request<Record<string, unknown>, Record<string, unknown>, PremiumOfferDTO>,
{ body, tokenPayload }: Request<Record<string, unknown>, Record<string, unknown>, PremiumOfferDTO>,
res: Response,
): Promise<void> {
const result = await this.offerService.findPremium(body.city);
const result = await this.offerService.findPremium(body.city, tokenPayload?.id);
this.ok(res, fillDTO(FullOfferRDO, result));
}
}
13 changes: 8 additions & 5 deletions src/shared/modules/offer/offer.http
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@
## Получить список предложений
GET http://localhost:4000/offers HTTP/1.1
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6Im5ld0BvdmVybG9vay5uZXQiLCJuYW1lIjoiQXV0aCIsImlkIjoiNjcyNTMxMDdjNjE2NzZlYTczNGE4YjAzIiwiaWF0IjoxNzMwNDk1NjEyLCJleHAiOjE3MzA2Njg0MTJ9.KbhaENJBtEWavsUY4oLOjrYF0N4XhmJkOxXPJTWSjdU

###

## Получить предложение по id
GET http://localhost:4000/offers/66f947e7e706754fb39b93ae HTTP/1.1
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6Im5ld0BvdmVybG9vay5uZXQiLCJuYW1lIjoiQXV0aCIsImlkIjoiNjcyNTMxMDdjNjE2NzZlYTczNGE4YjAzIiwiaWF0IjoxNzMwNDk1NjEyLCJleHAiOjE3MzA2Njg0MTJ9.KbhaENJBtEWavsUY4oLOjrYF0N4XhmJkOxXPJTWSjdU

###

## Удалить предложение по id
DELETE http://localhost:4000/offers/66f947e7e706754fb39b93a4 HTTP/1.1
DELETE http://localhost:4000/offers/672555f786660d7734313d1d HTTP/1.1
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6Im5ld0BvdmVybG9vay5uZXQiLCJuYW1lIjoiQXV0aCIsImlkIjoiNjcyNTMxMDdjNjE2NzZlYTczNGE4YjAzIiwiaWF0IjoxNzMwNDk1NjEyLCJleHAiOjE3MzA2Njg0MTJ9.KbhaENJBtEWavsUY4oLOjrYF0N4XhmJkOxXPJTWSjdU

###

Expand All @@ -30,20 +33,20 @@ Content-Type: application/json
## Создать предложение
POST http://localhost:4000/offers HTTP/1.1
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6Im5ld0BvdmVybG9vay5uZXQiLCJuYW1lIjoiQXV0aCIsImlkIjoiNjcyNTMxMDdjNjE2NzZlYTczNGE4YjAzIiwiaWF0IjoxNzMwNDk1NjEyLCJleHAiOjE3MzA2Njg0MTJ9.KbhaENJBtEWavsUY4oLOjrYF0N4XhmJkOxXPJTWSjdU

{
"title": "ычмчам23423 ыыыСозданный432423423",
"description": "JGdsfdfsdf",
"title": "For test eeeeee",
"description": "JGdsfdfsdf dwewedwe ",
"city": "Paris",
"previewImagePath": "sfsdf.jpg",
"photos": ["sdfsfaa.jpg", "sccccc.jpg"],
"photos": ["sdfsfaa.jpg", "scc3ccc.jpg", "sccccc.jpg", "sccc.jpg", "scccc.jpg", "scceccc.jpg"],
"isPremium": true,
"housingType": "house",
"roomsNumber": 3,
"visitorsNumber": 3,
"price": 10000,
"facilities": ["Breakfast", "Air conditioning"],
"authorId": "66f947e7e706754fb39b93a7",
"coords": {
"latitude": 50.846557,
"longitude": 4.351697
Expand Down
11 changes: 6 additions & 5 deletions src/shared/modules/offer/types/offer-service.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import { ECity } from '../../../types/index.js';

export interface IOfferService extends IDocumentExists {
create(dto: CreateOfferDTO): Promise<DocumentType<OfferEntity>>;
findById(offerId: string): Promise<DocumentType<OfferEntity> | null>;
find(count?: number): Promise<DocumentType<OfferEntity>[]>;
deleteById(offerId: string): Promise<DocumentType<OfferEntity> | null>;
updateById(offerId: string, dto: UpdateOfferDTO): Promise<DocumentType<OfferEntity> | null>;
findPremium(city: ECity): Promise<DocumentType<OfferEntity>[]>;
findById(offerId: string, userId: string): Promise<DocumentType<OfferEntity> | null>;
find(count: number, userId: string): Promise<DocumentType<OfferEntity>[]>;
deleteById(offerId: string, userId: string): Promise<DocumentType<OfferEntity> | null>;
updateById(offerId: string, userId: string, dto: UpdateOfferDTO): Promise<DocumentType<OfferEntity> | null>;
findPremium(city: ECity, userId: string): Promise<DocumentType<OfferEntity>[]>;
isOwnOffer(offerId: string, userId: string): Promise<boolean>;
}
Loading

0 comments on commit de259ab

Please sign in to comment.