Skip to content

Commit

Permalink
Merge pull request #16 from FoxMalder-coder/master
Browse files Browse the repository at this point in the history
  • Loading branch information
keksobot authored Nov 6, 2024
2 parents 72e5bf1 + 179583b commit 4531eb7
Show file tree
Hide file tree
Showing 15 changed files with 168 additions and 69 deletions.
10 changes: 5 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
HOST=localhost
PORT=4000
SALT=Something
DB_USER=admin
DB_PASSWORD=test
SALT=secret
DB_HOST=127.0.0.1
DB_USER=admin
DB_PASSWORD=password
DB_PORT=27017
DB_NAME=six-cities
UPLOAD_DIRECTORY=upload
JWT_SECRET=jwt-key
HOST=localhost
STATIC_DIRECTORY=static
JWT_SECRET=secret
69 changes: 64 additions & 5 deletions Workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,26 @@ npm install

Команда запустит процесс установки зависимостей проекта из **npm**.

### Сценарии
### Переменные окружения

Пример заполнения приведен в файле [.env.example]

| Параметр | Значение | Комментарий |
| -------------------- | ------------ | -------------------------------------------------------- |
| **PORT** | _4000_ | Порт сервера |
| **SALT** | _secret_ | Случайная строка (соль) |
| **DB_HOST** | _127.0.0.1_ | IP-адрес для сервера |
| **DB_USER** | _admin_ | Логин базы данных |
| **DB_PASSWORD** | _password_ | Пароль базы данных |
| **DB_PORT** | _27017_ | Порт базы данных |
| **DB_NAME** | _six-cities_ | Название базы данных |
| **UPLOAD_DIRECTORY** | _upload_ | Директория для загрузки |
| **JWT_SECRET** | _jwt-key_ | Ключ для JWT |
| **HOST** | _localhost_ | Хост |
| **STATIC_DIRECTORY** | _static_ | Директория для файлов статики |


## Сценарии

В `package.json` предопределено несколько сценариев.

Expand Down Expand Up @@ -58,21 +77,61 @@ npm run ts -- <Путь к модулю с ts-кодом>

Пакет `ts-node` позволяет выполнить TS-код в Node.js без предварительной компиляции. Используется только на этапе разработки.

#### Запустить моковый сервер

```bash
npm run mock:server
```

Поднимает сервер, необходимый для генерации данных в CLI-приложении.

#### Скомпилировать и запустить CLI приложение
```bash
nmp run CLI -- [параметры]
```

Выполняет сборку проекта, устанавливает аттрибут исполняемого файла `./dist/main.cli.js` и запускает CLI-приложение c указанными параметрами.

#### Запустить проект в dev-mode

```bash
npm run start:dev
```

Запуск проекта в режиме разработки (без сборки, с перезапуском при изменении *.ts и *.json файлов в директории ./src).

#### Запустить проект

1. Установить пакеты
```bash
npm install
```
2. Поднять докер
```bash
npm run docker:up
```
**Обратите внимание**, не забывайте использовать `npm run docker:down`, так как работа на основе volumes.
3. Запустить сервер базы данных
```bash
npm start
```

В процессе запуска проекта будет выполнен процесс «Сборки проекта» и запуска результирующего кода.

#### Запустить JSON-сервер

Для заполнения базы моковыми данными может быть использовано CLI-приложение. Выполняется в новом терминале.
4. Запускается моковый сервер
```bash
npm run mock:server
```
В новом терминале.
5. Команда для генерации случайных n-предложений
```bash
npm run cli -- --generate 10 ./mocks/mock-data.tsv http://localhost:3123/api
```
6. Команда для импорта сгенерированных предложений в базу
```bash
npm run cli -- --import ./mocks/mock-data.tsv admin password localhost six-cities secret
```

Поднимает сервер для работы с предсохраненными моковыми данными.

## Структура проекта

Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
],
"main": "main.js",
"scripts": {
"start": "npm run build && node ./dist/src/main.cli.js",
"dev": "nodemon",
"start": "npm run build && node ./dist/src/main.rest.js | pino-pretty --colorize --translateTime SYS:standard",
"start:dev": "nodemon",
"cli": "npm run build && node ./dist/src/main.cli.js",
"build": "npm run clean && npm run compile",
"lint": "eslint src/ --ext .ts",
"lint:fix": "eslint src/ --ext .ts --fix",
"compile": "tsc -p tsconfig.json",
"clean": "rimraf dist && mkdirp dist/logs",
"ts": "tsc --noEmit && node --no-warnings=ExperimentalWarning --loader ts-node/esm",
Expand Down
4 changes: 2 additions & 2 deletions src/cli/commands/generate.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ export class GenerateCommand implements Command {
}
}

private async write(filePath: string, сount: number) {
private async write(filePath: string, count: number) {
const tsvOfferGenerator = new TSVOfferGenerator(this.initialData);
const tsvFileWriter = new TSVFileWriter(filePath);

for (let i = 0; i < сount; i++) {
for (let i = 0; i < count; i++) {
await tsvFileWriter.write(tsvOfferGenerator.generate());
}
}
Expand Down
Empty file removed src/main.ts
Empty file.
9 changes: 4 additions & 5 deletions src/shared/libs/file-reader/tsv.file-reader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,6 @@ export class TSVFileReader extends EventEmitter implements FileReader {
Number.parseFloat(clearedValue) :
Number.parseInt(clearedValue, 10);

if (typeof result !== 'number') {
throw new Error(`Isn't numeric value - ${value}`);
}

return result;
}

Expand Down Expand Up @@ -100,8 +96,9 @@ export class TSVFileReader extends EventEmitter implements FileReader {

for await (const chunk of readStream) {
remainingData += chunk.toString();
nextLinePosition = remainingData.indexOf('\n');

while ((nextLinePosition = remainingData.indexOf('\n')) >= 0) {
while (nextLinePosition >= 0) {
const singleLine = remainingData.slice(0, nextLinePosition + 1);
remainingData = remainingData.slice(++nextLinePosition);
lineCount++;
Expand All @@ -110,6 +107,8 @@ export class TSVFileReader extends EventEmitter implements FileReader {
await new Promise((resolve) => {
this.emit('line', parsedOffer, resolve);
});

nextLinePosition = remainingData.indexOf('\n');
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/shared/libs/offer-generator/tsv.offer-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ export class TSVOfferGenerator implements OfferGenerator {
}

private getFacilities(): string {
const number = getRandomNumber(0, this.mockData.facilities.length - 1);
const quantity = getRandomNumber(0, this.mockData.facilities.length - 1);

if (number === 0) {
if (quantity === 0) {
return '';
}

return this.uniteToString(getSomeArrayItems(this.mockData.facilities, number));
return this.uniteToString(getSomeArrayItems(this.mockData.facilities, quantity));
}

public generate(): string {
Expand Down
7 changes: 3 additions & 4 deletions src/shared/modules/comment/comment.http
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
# Комментарии
## Создать комментарий

POST http://localhost:4000/comments/672a369e42ff61e8805c6be5 HTTP/1.1
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6Im5pY2tAeWFoY2hvby5uZXQiLCJpZCI6IjY3MmE3NTc4ODljMGUyMDA1ZjYzNzcwNyIsImlhdCI6MTczMDgzNzgzMCwiZXhwIjoxNzMxMDEwNjMwfQ.Sh2RicuyfaxMkXycYEl8AVkBAijvCh-sIERsOxtQ-_8
Authorization: Bearer ---

{
"text": "Фиг знает что",
"text": "Не понравилось",
"offerId": "672a369e42ff61e8805c6be5",
"rating": 1
}

###


## Список комментариев к объявлению
## Получить список комментариев к объявлению
GET http://localhost:4000/comments/672a369e42ff61e8805c6be5 HTTP/1.1

###
5 changes: 5 additions & 0 deletions src/shared/modules/offer/default-offer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,9 @@ export class DefaultOfferService implements OfferService {
public async findPremiumByCity(cityName: string): Promise<DocumentType<OfferEntity>[] | null> {
return this.offerModel.find({ cityName, isPremium: true }).sort({ createdAt: SortType.Down }).limit(DEFAULT_PREMIUM_COUNT).populate('userId').exec();
}

public async checkOwnership(offerId: string, userId: string): Promise<boolean> {
const offer = await this.offerModel.findOne({ _id: offerId });
return offer?.userId?.toString() === userId;
}
}
1 change: 1 addition & 0 deletions src/shared/modules/offer/offer-service.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ export interface OfferService extends DocumentExists {
deleteById(offerId: string): Promise<DocumentType<OfferEntity> | null>;
updateById(offerId: string, dto: UpdateOfferDto): Promise<DocumentType<OfferEntity> | null>;
exists(documentId: string): Promise<boolean>;
checkOwnership(offerId: string, userId: string): Promise<boolean>;
}
44 changes: 36 additions & 8 deletions src/shared/modules/offer/offer.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import { OfferRdo } from './rdo/offer.rdo.js';
import { CreateOfferRequest } from './types/create-offer-request.type.js';
import { UpdateOfferDto } from './dto/update-offer.dto.js';
import { CommentService } from '../comment/index.js';
import { BaseController, DocumentExistsMiddleware, PrivateRouteMiddleware, UploadFileMiddleware, ValidateDtoMiddleware, ValidateObjectIdMiddleware } from '../../libs/rest/index.js';
import { BaseController, DocumentExistsMiddleware, HttpError, PrivateRouteMiddleware, UploadFileMiddleware, ValidateDtoMiddleware, ValidateObjectIdMiddleware } from '../../libs/rest/index.js';
import { ParamCityName } from './types/param-cityname.type.js';
import { Config, RestSchema } from '../../libs/config/index.js';
import { StatusCodes } from 'http-status-codes';
import { UpdateOfferRequest } from './types/update-offer-request.type.js';

@injectable()
export class OfferController extends BaseController {
Expand All @@ -34,6 +36,7 @@ export class OfferController extends BaseController {
new ValidateDtoMiddleware(CreateOfferDto)
]
});

this.addRoute({
path: '/:offerId',
method: 'get',
Expand All @@ -43,6 +46,7 @@ export class OfferController extends BaseController {
new DocumentExistsMiddleware(this.offerService, 'Offer', 'offerId')
]
});

this.addRoute({
path: '/:offerId',
method: 'patch',
Expand All @@ -54,6 +58,7 @@ export class OfferController extends BaseController {
new DocumentExistsMiddleware(this.offerService, 'Offer', 'offerId')
]
});

this.addRoute({
path: '/:offerId',
method: 'delete',
Expand All @@ -64,11 +69,13 @@ export class OfferController extends BaseController {
new DocumentExistsMiddleware(this.offerService, 'Offer', 'offerId')
]
});

this.addRoute({
path: '/premium/:cityName',
method: 'get',
handler: this.getPremium
});

this.addRoute({
path: '/:offerId/preview',
method: 'post',
Expand All @@ -81,6 +88,18 @@ export class OfferController extends BaseController {
});
}

private async checkAccess(offerId: string, userId: string): Promise<void> {
const isOwner = await this.offerService.checkOwnership(offerId, userId);

if (!isOwner) {
throw new HttpError(
StatusCodes.FORBIDDEN,
'Forbidden',
'OfferController',
);
}
}

public async getById({ params }: Request<ParamOfferId>, res: Response): Promise<void> {
const { offerId } = params;
const offer = await this.offerService.findById(offerId);
Expand All @@ -98,22 +117,31 @@ export class OfferController extends BaseController {
this.created(res, fillDTO(OfferRdo, offer));
}

public async delete({ params }: Request<ParamOfferId>, res: Response): Promise<void> {
public async delete({ params, tokenPayload }: Request<ParamOfferId>, res: Response): Promise<void> {
const { offerId } = params;
const offer = await this.offerService.deleteById(offerId);
await this.checkAccess(offerId, tokenPayload.id);
await this.offerService.deleteById(offerId);
await this.commentService.deleteByOfferId(offerId);
this.noContent(res, offer);
this.noContent(res, null);
}

public async update({ body, params }: Request<ParamOfferId, unknown, UpdateOfferDto>, res: Response): Promise<void> {
const updatedOffer = await this.offerService.updateById(params.offerId, body);
this.ok(res, fillDTO(OfferRdo, updatedOffer));
public async update({ params, body, tokenPayload }: UpdateOfferRequest, res: Response): Promise<void> {
const { offerId } = params;
await this.checkAccess(offerId, tokenPayload.id);
const result = await this.offerService.updateById(offerId, body);
const offer = await this.offerService.findById(result?.id);
this.ok(res, fillDTO(OfferRdo, offer));
}

public async getPremium({ params }: Request<ParamCityName>, res: Response) {
const { cityName } = params;

if (!CITIES_LIST.includes(cityName)) {
throw new Error('Wrong city!');
throw new HttpError(
StatusCodes.BAD_REQUEST,
'City in query not included in the list of available cities.',
'OfferController'
);
}

const offers = await this.offerService.findPremiumByCity(cityName);
Expand Down
Loading

0 comments on commit 4531eb7

Please sign in to comment.