An open API service indexing awesome lists of open source software.

https://github.com/capcom6-learning/netology-ndse-project


https://github.com/capcom6-learning/netology-ndse-project

Last synced: 11 months ago
JSON representation

Awesome Lists containing this project

README

          

# Курсовой проект «NDSE: настройка окружения и Express.js»

Курсовой проект представляет собой службу доставки. Ваша задача — создать работающее бэкенд-приложение, всеми основными функциями которого можно пользоваться.

## Быстрый старт

Для быстрого старта можно использовать Docker Compose:

```bash
docker-compose up
```

При этом будет запущен сервер MongoDB и само приложение на порту 3000.

Примеры запросов приведены в файле [requests.http](./requests.http).

## Цель
Цель проекта — разработка работающего бэкенд-приложения с API, которое описано в этом документе.

Реализовывать клиент не нужно.

## Содержание

1. Приложение должно содержать следующие **базовые** модули:

- 1.1. Пользователи.
- 1.2. Объявления.
- 1.3. Чат.

2. Приложение должно содержать следующие **функциональные** модули:

- 2.1. Регистрация.
- 2.2. Аутентификация.
- 2.3. Просмотр объявлений.
- 2.4. Управление объявлениями.
- 2.5. Общение.

## 1. Описание базовых модулей

Базовые модули служат для описания бизнес-логики и хранения данных.

### 1.1. Модуль «Пользователи»

Модуль «Пользователи» предназначен для создания, хранения и поиска профилей пользователей.

Модуль «Пользователи» используется функциональными модулями для регистрации и аутентификации.

Данные пользователя должны храниться в MongoDB.

Модель данных `User` пользователя должна содержать следующие поля:

| Название | Тип | Обязательное | Уникальное |
| ------------ | :--------: | :----------: | :--------: |
| \_id | `ObjectId` | да | да |
| email | `string` | да | да |
| passwordHash | `string` | да | нет |
| name | `string` | да | нет |
| contactPhone | `string` | нет | нет |

#### 1.1.1. Функция «Создание пользователя»

```js
const user = await UserModule.create(data);
```

Аргумент `data` должен соответствовать полям модели `User`, кроме \_id.

Результатом работы функции должен быть `Promise`, который резолвится с объектом модели `User`.

#### 1.1.2. Функция «Поиск пользователя по email»

```js
const user = await UserModule.findByEmail(email);
```

Аргумент `email` должен быть строкой.

Результатом работы функции должен быть `Promise`, который резолвится с объектом модели `User` или `null`, если пользователь
не существует.

### 1.2. Модуль «Объявления»

Модуль «Объявления» предназначается для хранения и поиска объявлений.

Модуль «Объявления» используется функциональными модулями для показа списка объявлений, размещения и удаления
объявлений.

Данные объявлений должны храниться в MongoDB.

Модель данных `Advertisement` должна содержать следующие поля:

| Название | Тип | Обязательное | Уникальное |
| ----------- | :--------: | :----------: | :--------: |
| \_id | `ObjectId` | да | да |
| shortText | `string` | да | нет |
| description | `string` | нет | нет |
| images | `string[]` | нет | нет |
| userId | `ObjectId` | да | нет |
| createdAt | `Date` | да | нет |
| updatedAt | `Date` | да | нет |
| tags | `string[]` | нет | нет |
| isDeleted | `boolean` | да | нет |

#### 1.2.1. Функция «Поиск объявления»

```js
const advertisements = await Advertisement.find(params);
```

В объекте `params` должны учитываться следующие поля:

- `shortText` — поиск регулярным выражением;
- `description` — поиск регулярным выражением;
- `userId` — точное совпадение;
- `tags` — значение в базе данных должно включать все искомые значения.

Поиск должен игнорировать записи, которые помечены удалёнными `isDeleted = true`.

Результатом работы функции должен быть `Promise`, который резолвится с массивом объектов модели `Advertisement` или пустым
массивом.

#### 1.2.2. Создание объявления

```js
const advertisement = await Advertisement.create(data);
```

Аргумент `data` должен соответствовать полям модели `Advertisement`, кроме \_id.

Результатом работы функции должен быть `Promise`, который резолвится с объектом модели `Advertisement`.

#### 1.2.3. Удаление объявления

```js
const advertisement = await Advertisement.remove(id);
```

Аргумент `id` должен быть типа `string` или `ObjectId`.

Функция поиска не должна удалять запись из базы данных, а только выставлять значение флага `isDeleted = true`.

### 1.3. Модуль «Чат»

Модуль «Чат» предназначается для хранения чатов и сообщений в чате.

Модуль «Чат» используется функциональными модулями для реализации возможности общения пользователей.

Данные чатов должны храниться в MongoDB.

Модель данных чата `Chat` должна содержать следующие поля:

| Название | Тип | Обязательное | Уникальное |
| --------- | :----------------------: | :----------: | :--------: |
| \_id | `ObjectId` | да | да |
| users | [`ObjectId`, `ObjectId`] | да | нет |
| createdAt | `Date` | да | нет |
| messages | `Message[]` | нет | нет |

Модель сообщения `Message` должна содержать следующие поля:

| Название | Тип | Обязательное | Уникальное |
| -------- | :--------: | :----------: | :--------: |
| \_id | `ObjectId` | да | да |
| author | `ObjectId` | да | нет |
| sentAt | `Date` | да | нет |
| text | `string` | да | нет |
| readAt | `Date` | нет | нет |

Сообщение считается прочитанным, когда поле `readAt` не пустое.

#### 1.3.1. Функция «Получить чат между пользователями»

```js
const chat = await Chat.find(users);
```

Аргумент функции `[ObjectId, ObjectId]` — ID пользователей.

Результатом работы функции должен быть `Promise`, который является объектом модели `Chat` или `null`.

#### 1.3.2. Функция «Отправить сообщение»

```js
const message = await Chat.sendMessage(data);
```

Параметры:

| Название | Тип | Обязательное |
| -------- | :--------: | :----------: |
| author | `ObjectId` | да |
| receiver | `ObjectId` | да |
| text | string | да |

При отправке сообщения нужно:

1. Найти чат между `author` и `receiver` по полю `Chat.users`. Если чата нет, то создать его.
2. Добавить в поле `Chat.messages` новое сообщение `Message`. Поле `sentAt` должно соответствовать текущей дате.

Результатом работы функции должен быть `Promise`, который резолвится с объектом модели `Message`.

#### 1.3.3. Подписаться на новые сообщения в чате

```js
Chat.subscribe((data) => {});
```

Функция `Chat.subscribe` должна принимать функцию обратного вызова.

Каждый раз при добавлении сообщения функция обратного вызова должна вызываться со следующими параметрами:

| Название | Тип | Обязательное |
| -------- | :--------: | :----------: |
| chatId | `ObjectId` | да |
| message | `Message` | да |

Оповещения должны быть реализованы через механизм `EventEmitter`.

#### 1.3.4. Функция «Получить историю сообщений чата»

```js
const messages = await Chat.getHistory(id);
```

Аргумент функции `ObjectId` — `_id` чата.

Результатом работы функции должен быть `Promise`, который резолвится с массивом объектов модели `Message`.

## 2. Описание функциональных модулей

Функциональные модули предназначены для реализации функций, доступных конечным пользователям.

### 2.1. Регистрация

`POST /api/signup` — зарегистрироваться.

Пароль не должен храниться в чистом виде. Его нужно хешировать перед отправкой в модуль «Пользователи».

Формат данных при отправке — JSON-объект. Пример запроса:

```json
{
"email": "kulagin@netology.ru",
"password": "ad service",
"name": "Alex Kulagin",
"contactPhone": "+7 123 456 78 90"
}
```

В ответ приходит либо сообщение об ошибке, либо JSON-объект с данными:

```json
{
"data": {
"id": "507f1f77bcf86cd799439011",
"email": "kulagin@netology.ru",
"name": "Alex Kulagin",
"contactPhone": "+7 123 456 78 90"
},
"status": "ok"
}
```

```json
{
"error": "email занят",
"status": "error"
}
```

### 2.2. Аутентификация

`POST /api/signin` — залогиниться.

Для реализации аутентификации пользователя должен использоваться механизм сессий и модуль `Passport.js`.

Если пользователь не существует или пароль не совпадает, то нужно выдавать ошибку `Неверный логин или пароль`.

Так как пароль не должен храниться в чистом виде, его нужно хешировать и сравнивать с хешем пароля пользователя из
модуля «Пользователи».

Формат данных при отправке — JSON-объект. Пример запроса:

```json
{
"email": "kulagin@netology.ru",
"password": "ad service"
}
```

В ответ приходит либо сообщение об ошибке, либо JSON-объект с данными:

```json
{
"data": {
"id": "507f1f77bcf86cd799439011",
"email": "kulagin@netology.ru",
"name": "Alex Kulagin",
"contantPhone": "+7 123 456 78 90"
},
"status": "ok"
}
```

```json
{
"error": "Неверный логин или пароль",
"status": "error"
}
```

### 2.3. Просмотр объявлений

`GET /api/advertisements` — получить список объявлений.

Эти данные публичные, поэтому аутентификация не требуется.

В ответ приходит либо сообщение об ошибке, либо JSON-объект с данными:

```json
{
"data": [
{
"id": "507f1f77bcf86cd799439012",
"shortTitle": "Продам слона",
"description": "kulagin@netology.ru",
"images": [
"/uploads/507f1f77bcf86cd799439011/slon_v_profil.jpg",
"/uploads/507f1f77bcf86cd799439011/slon_v_fas.jpg",
"/uploads/507f1f77bcf86cd799439011/slon_hobot.jpg"
],
"user": {
"id": "507f1f77bcf86cd799439011",
"name": "Alex Kulagin"
},
"createdAt": "2020-12-12T10:00:00.000Z"
}
],
"status": "ok"
}
```

`GET /api/advertisements/:id` — получить данные объявления.

Эти данные публичные, поэтому аутентификация не требуется.

В ответ приходит либо сообщение об ошибке, либо JSON-объект с данными:

```json
{
"data": {
"id": "507f1f77bcf86cd799439012",
"shortTitle": "Продам слона",
"description": "kulagin@netology.ru",
"images": [
"/uploads/507f1f77bcf86cd799439011/slon_v_profil.jpg",
"/uploads/507f1f77bcf86cd799439011/slon_v_fas.jpg",
"/uploads/507f1f77bcf86cd799439011/slon_hobot.jpg"
],
"user": {
"id": "507f1f77bcf86cd799439011",
"name": "Alex Kulagin"
},
"createdAt": "2020-12-12T10:00:00.000Z"
},
"status": "ok"
}
```

### 2.4. Управление объявлениями

`POST /api/advertisements` — создать объявление.

Эти данные приватные и требуют проверки аутентификации.

Формат данных при отправке — `FormData`. Пример запроса:

| Поле | Тип |
| ----------- | -------- |
| shortTitle | `string` |
| description | `string` |
| images | File[] |

Обработка загруженных файлов должна производиться с помощью библиотеки multer.

В ответ приходит либо сообщение об ошибке, либо JSON-объект с данными:

```json
{
"data": [
{
"id": "507f1f77bcf86cd799439012",
"shortTitle": "Продам слона",
"description": "kulagin@netology.ru",
"images": [
"/uploads/507f1f77bcf86cd799439011/slon_v_profil.jpg",
"/uploads/507f1f77bcf86cd799439011/slon_v_fas.jpg",
"/uploads/507f1f77bcf86cd799439011/slon_hobot.jpg"
],
"user": {
"id": "507f1f77bcf86cd799439011",
"name": "Alex Kulagin"
},
"createdAt": "2020-12-12T10:00:00.000Z"
}
],
"status": "ok"
}
```

Если пользователь не аутентифицирован и пытается создать объявление, то в ответ должен получить JSON-объект с ошибкой
и HTTP-код 401.

`DELETE /api/advertisements/:id` — удалить объявление.

Эти данные приватные и требуют проверки аутентификации.

Если пользователь не аутентифицирован и пытается создать объявление, то в ответ должен получить JSON-объект с ошибкой
и HTTP-код 401.

Если пользователь аутентифицирован, но не является автором объявления, то в ответ должен получить JSON-объект с ошибкой
и HTTP-код 403.

### 2.5. Общение

Модуль «Общение» предназначен для онлайн-общения пользователей.

Модуль должен использовать библиотеку Socket.IO.

Для подписки на обновления в чатах модуль должен использовать функционал «Подписка» модуля «Чат».

Сообщения, приходящие в `socket`:

- `getHistory` — получить историю сообщений из чата;
- `sendMessage` — отправить сообщение пользователю.

События, отправляемые через `socket`:

- `newMessage` — отправлено новое сообщение;
- `chatHistory` — ответ на событие `getHistory`.

**Событие `getHistory`**

Событие `getHistory` должно принимать в данных ID собеседника.

По `id` собеседника и `id` текущего пользователя нужно найти чат через функцию «Получить чат между пользователями». Далее для этого чата нужно получить историю сообщений и отправить её в ответ c событием `chatHistory`.

**Событие `sendMessage`**

Событие `sendMessage` должно получить следующие данные:

| Поле | Тип |
| -------- | -------- |
| receiver | `string` |
| text | `string` |

Полученные данные должны передаваться в функцию «Отправить сообщение» модуля «Чат». Для поля `author` должен использоваться ID текущего пользователя.

**Событие `newMessage`**

Событие `newMessage` должно вызываться каждый раз, когда в чат отправляется сообщение.

При подключении нового клиента должна создаваться подписка на новые сообщения в чате (модуль «Чат»). Полученное сообщение передаётся целиком клиенту.

# Запуск приложения

Для запуска приложения в корне проекта должны находиться следующие файлы:

- `package.json` и `package-lock.json` с описанными зависимостями,
- `Dockerfile` для сборки образа приложения,
- `docker-compose.yaml` с сервисом приложения и сервисом MongoDB,
- `README.md` с описанием проекта и вариантами его запуска.

Настройка параметров приложения должна производиться через переменные окружения. Это требование как для запуска в окружении хоста, так и при работе с Docker.

Список переменных окружения должен быть описан в файле `.env-example`. Этот файл не должен содержать значений. Пример файла:

```bash
HTTP_HOST=
HTTP_PORT=
MONGO_URL=
```

Для запуска приложения должен использоваться скрипт `npm start`, описанный в `package.json`.