Дипломный проект представляет собой сайт-агрегатор просмотра и бронирования гостиниц. Ваша задача заключается в разработке бэкенда для сайта-агрегатора с реализацией возможности бронирования гостиниц на диапазон дат.
- Разработка публичного API.
- Разработка API пользователя.
- Разработка API администратора.
- Разработка чата консультанта.
- Node.js;
- Nest.js;
- MongoDB;
- WebSocket.
Оплату бронирования реализовывать не нужно.
В документе приводятся описания разных интерфейсов и типов. Для упрощения описания в этом разделе приводятся общие типы.
type ID = string | ObjectId;Базовые модули используются для описания бизнес-логики и хранения данных.
Модуль «Пользователи» предназначается для создания, хранения и поиска профилей пользователей.
Модуль «Пользователи» используется функциональными модулями для регистрации и аутентификации.
Данные пользователя должны храниться в MongoDB.
Модель данных User пользователя должна содержать поля:
| Название | Тип | Обязательное | Уникальное | По умолчанию |
|---|---|---|---|---|
| _id | ObjectId |
да | да | |
string |
да | да | ||
| passwordHash | string |
да | нет | |
| name | string |
да | нет | |
| contactPhone | string |
нет | нет | |
| role | string |
да | нет | client |
Модуль «Пользователи» должен быть реализован в виде NestJS-модуля и экспортировать сервисы с интерфейсами:
interface SearchUserParams {
limit: number;
offset: number;
email: string;
name: string;
contactPhone: string;
}
interface IUserService {
create(data: Partial<User>): Promise<User>;
findById(id: ID): Promise<User>;
findByEmail(email: string): Promise<User>;
findAll(params: SearchUserParams): Promise<User[]>;
}Поле role может принимать одно из значений:
client,admin,manager.
При поиске IUserService.findAll() поля email, name и contactPhone должны проверяться на частичное совпадение.
Модуль «Гостиницы» предназначается для хранения и поиска гостиниц и комнат.
Модуль «Гостиницы» используется функциональными модулями для показа списка мест для бронирования, а также для их добавления, включения и выключения.
Данные должны храниться в MongoDB.
Модель данных Hotel должна содержать поля:
| Название | Тип | Обязательное | Уникальное | По умолчанию |
|---|---|---|---|---|
| _id | ObjectId |
да | да | |
| title | string |
да | да | |
| description | string |
нет | нет | |
| createdAt | Date |
да | нет | |
| updatedAt | Date |
да | нет |
Модель данных HotelRoom должна содержать поля:
| Название | Тип | Обязательное | Уникальное | По умолчанию |
|---|---|---|---|---|
| _id | ObjectId |
да | да | |
| hotel | ObjectId |
да | нет | |
| description | string |
нет | нет | |
| images | string[] |
нет | нет | [] |
| createdAt | Date |
да | нет | |
| updatedAt | Date |
да | нет | |
| isEnabled | boolean |
да | нет | true |
Свойство hotel должно ссылаться на модель Hotel.
Модуль «Гостиницы» должен быть реализован в виде NestJS-модуля и экспортировать сервисы с интерфейсами:
interface SearchHotelParams {
limit: number;
offset: number;
title: string;
}
interface UpdateHotelParams {
title: string;
description: string;
}
interface IHotelService {
create(data: any): Promise<Hotel>;
findById(id: ID): Promise<Hotel>;
search(params: SearchHotelParams): Promise<Hotel[]>;
update(id: ID, data: UpdateHotelParams): Promise<Hotel>;
}
interface SearchRoomsParams {
limit: number;
offset: number;
hotel: ID;
isEnabled?: boolean;
}
interface HotelRoomService {
create(data: Partial<HotelRoom>): Promise<HotelRoom>;
findById(id: ID): Promise<HotelRoom>;
search(params: SearchRoomsParams): Promise<HotelRoom[]>;
update(id: ID, data: Partial<HotelRoom>): Promise<HotelRoom>;
}В методе search флаг isEnabled может принимать только boolean значения или может быть не передан, тогда должны вернутся все записи:
true— флаг должен использоваться в фильтрации;undefined— если не передан параметр, флаг должен игнорироваться.
Модуль «Брони» предназначен для хранения и получения броней гостиниц конкретного пользователя.
Модуль «Брони» не должен использовать модуль «Пользователи» и модуль «Гостиницы» для получения данных.
Модуль «Брони» не должен хранить данные пользователей и гостиниц.
Модуль «Брони» должен использовать соединение с базой данных.
Данные должны храниться в MongoDB.
Модель данных Reservation должна содержать поля:
| Название | Тип | Обязательное | Уникальное | По умолчанию |
|---|---|---|---|---|
| _id | ObjectId |
да | да | |
| userId | ObjectId |
да | нет | |
| hotelId | ObjectId |
да | нет | |
| roomId | ObjectId |
да | нет | |
| dateStart | Date |
да | нет | |
| dateEnd | Date |
да | нет |
Модуль «Брони» должен быть реализован в виде NestJS-модуля и экспортировать сервисы с интерфейсами:
interface ReservationDto {
userId: ID;
hotelId: ID;
roomId: ID;
dateStart: Date;
dateEnd: Date;
}
interface ReservationSearchOptions {
userId: ID;
dateStart: Date;
dateEnd: Date;
}
interface IReservation {
addReservation(data: ReservationDto): Promise<Reservation>;
removeReservation(id: ID): Promise<void>;
getReservations(
filter: ReservationSearchOptions
): Promise<Array<Reservation>>;
}Метод IReservation.addReservation должен проверять, доступен ли номер на заданную дату.
Модуль «Чат техподдержки» предназначается для хранения обращений в техподдержку и сообщений в чате обращения.
Модуль «Чат техподдержки» используется функциональными модулями для реализации возможности общения пользователей с поддержкой.
Данные чатов должны храниться в MongoDB.
Модель данных чата SupportRequest должна содержать поля:
| Название | Тип | Обязательное | Уникальное |
|---|---|---|---|
| _id | ObjectId |
да | да |
| user | ObjectId |
да | нет |
| createdAt | Date |
да | нет |
| messages | Message[] |
нет | нет |
| isActive | bool |
нет | нет |
Модель сообщения Message должна содержать поля:
| Название | Тип | Обязательное | Уникальное |
|---|---|---|---|
| _id | ObjectId |
да | да |
| author | ObjectId |
да | нет |
| sentAt | Date |
да | нет |
| text | string |
да | нет |
| readAt | Date |
нет | нет |
Сообщение считается прочитанным, когда поле readAt не пустое.
Модуль «Чат техподдержки» должен быть реализован в виде NestJS-модуля и должен экспортировать сервисы с интерфейсами:
interface CreateSupportRequestDto {
user: ID;
text: string;
}
interface SendMessageDto {
author: ID;
supportRequest: ID;
text: string;
}
interface MarkMessagesAsReadDto {
user: ID;
supportRequest: ID;
createdBefore: Date;
}
interface GetChatListParams {
user: ID | null;
isActive: bool;
}
interface ISupportRequestService {
findSupportRequests(params: GetChatListParams): Promise<SupportRequest[]>;
sendMessage(data: SendMessageDto): Promise<Message>;
getMessages(supportRequest: ID): Promise<Message[]>;
subscribe(
handler: (supportRequest: SupportRequest, message: Message) => void
): () => void;
}
interface ISupportRequestClientService {
createSupportRequest(data: CreateSupportRequestDto): Promise<SupportRequest>;
markMessagesAsRead(params: MarkMessagesAsReadDto);
getUnreadCount(supportRequest: ID): Promise<number>;
}
interface ISupportRequestEmployeeService {
markMessagesAsRead(params: MarkMessagesAsReadDto);
getUnreadCount(supportRequest: ID): Promise<number>;
closeRequest(supportRequest: ID): Promise<void>;
}- Метод
ISupportRequestClientService.getUnreadCountдолжен возвращать количество сообщений, которые были отправлены любым сотрудником поддержки и не отмечены прочитанным. - Метод
ISupportRequestClientService.markMessagesAsReadдолжен выставлять текущую дату в поле readAt всем сообщениям, которые не были прочитаны и были отправлены не пользователем. - Метод
ISupportRequestEmployeeService.getUnreadCountдолжен возвращать количество сообщений, которые были отправлены пользователем и не отмечены прочитанными. - Метод
ISupportRequestEmployeeService.markMessagesAsReadдолжен выставлять текущую дату в поле readAt всем сообщениям, которые не были прочитаны и были отправлены пользователем. - Метод
ISupportRequestEmployeeService.closeRequestдолжен менять флагisActiveнаfalse. - Оповещения должны быть реализованы через механизм
EventEmitter.
Должно быть оформлено в виде отдельного NestJS-модуля.
Если пользователь не аутентифицирован или его роль client, то при поиске всегда должен использоваться флаг isEnabled: true.
Основной API для поиска номеров.
GET /api/common/hotel-rooms- limit — количество записей в ответе;
- offset — сдвиг от начала списка;
- hotel — ID гостиницы для фильтра.
[
{
"id": string,
"description": string,
"images": [string],
"hotel": {
"id": string,
"title": string
}
}
]Доступно всем пользователям, включая неаутентифицированных.
Получение подробной информации о номере.
GET /api/common/hotel-rooms/:idОтсутствуют.
{
"id": string,
"description": string,
"images": [string],
"hotel": {
"id": string,
"title": string,
"description": string
}
}Доступно всем пользователям, включая неаутентифицированных.
Добавление гостиницы администратором.
POST /api/admin/hotels/{
"title": string,
"description": string
}{
"id": string,
"title": string,
"description": string
}Доступно только аутентифицированным пользователям с ролью admin.
401- если пользователь не аутентифицирован;403- если роль пользователя неadmin.
Получение списка гостиниц администратором.
GET /api/admin/hotels/- limit - количество записей в ответе.
- offset - сдвиг от начала списка.
- title - фильтр по полю.
{
"id": string,
"title": string,
"description": string
}Доступно только аутентифицированным пользователям с ролью admin.
401- если пользователь не аутентифицирован;403- если роль пользователя неadmin.
Изменение описания гостиницы администратором.
PUT /api/admin/hotels/:id{
"title": string,
"description": string
}{
"id": string,
"title": string,
"description": string
}Доступно только аутентифицированным пользователям с ролью admin.
401- если пользователь не аутентифицирован;403- если роль пользователя неadmin.
Добавление номера гостиницы администратором.
POST /api/admin/hotel-rooms/Этот запрос предполагает загрузку файлов и должен использовать формат multipart/form-data.
description: string
hotelId: string
images[]: File
{
"id": string,
"description": string,
"images": [string],
"isEnabled": boolean,
"hotel": {
"id": string,
"title": string,
"description": string
}
}Доступно только аутентифицированным пользователям с ролью admin.
401- если пользователь не аутентифицирован;403- если роль пользователя неadmin.
Изменение описания номера гостиницы администратором.
PUT /api/admin/hotel-rooms/:idЭтот запрос предполагает загрузку файлов и дожен использовать формат multipart/form-data.
description: string
hotelId: string
isEnabled: boolean
images[]: File | string
При обновлении может быть отправлен одновременно список ссылок на уже загруженные картинки и список файлов с новыми картинками.
При использовании multer список загруженных файлов можно получить через @UploadedFiles(). Этот список нужно объединить со списком, который пришёл в body.
{
"id": string,
"description": string,
"images": [string],
"isEnabled": boolean,
"hotel": {
"id": string,
"title": string,
"description": string
}
}Доступно только аутентифицированным пользователям с ролью admin.
401- если пользователь не аутентифицирован;403- если роль пользователя неadmin.
Должно быть оформлено в виде отдельного NestJS-модуля.
Создаёт бронь на номер на выбранную дату для текущего пользователя.
POST /api/client/reservations{
"hotelRoom": string,
"startDate": string,
"endDate": string
}{
"startDate": string,
"endDate": string,
"hotelRoom": {
"description": string,
"images": [string]
},
"hotel": {
"title": string,
"description": string
}
}Доступно только аутентифицированным пользователям с ролью client.
401- если пользователь не аутентифицирован;403- если роль пользователя неclient;400- если номера с указанным ID не существует или он отключён.
Список броней текущего пользователя.
GET /api/client/reservations[
{
"startDate": string,
"endDate": string,
"hotelRoom": {
"description": string,
"images": [string]
},
"hotel": {
"title": string,
"description": string
}
}
]Доступно только аутентифицированным пользователям с ролью client.
401- если пользователь не аутентифицирован;403- если роль пользователя неclient.
Отменяет бронь пользователя.
DELETE /api/client/reservations/:idПустой ответ.
Доступно только аутентифицированным пользователям с ролью client.
401- если пользователь не аутентифицирован;403- если роль пользователя неclient;403- еслиIDтекущего пользователя не совпадает сIDпользователя в брони;400- если брони с указанным ID не существует.
Список броней конкретного пользователя.
GET /api/manager/reservations/:userId[
{
"startDate": string,
"endDate": string,
"hotelRoom": {
"description": string,
"images": [string]
},
"hotel": {
"title": string,
"description": string
}
}
]Доступно только аутентифицированным пользователям с ролью manager.
401- если пользователь не аутентифицирован;403- если роль пользователя неmanager.
Отменяет бронь пользователя по id брони.
DELETE /api/manager/reservations/:idПустой ответ.
Доступно только аутентифицированным пользователям с ролью manager.
401- если пользователь не аутентифицирован;403- если роль пользователя неmanager;400- если брони с указанным ID не существует.
Должно быть оформлено в виде отдельного NestJS-модуля.
Модуль «Аутентификация и авторизация» предназначен для:
- управления сессией пользователя,
- регистрации пользователей.
Хранение сессии должно реализовываться посредством библиотеки passport.js с хранением сессии в памяти приложения.
Аутентификация пользователя производится с помощью модуля «Пользователи». Каждому пользователю назначается одна из ролей - клиент, администратор, консультант.
Стартует сессию пользователя и выставляет Cookies.
POST /api/auth/login{
"email": string,
"password": string
}{
"email": string,
"name": string,
"contactPhone": string
}Доступно только не аутентифицированным пользователям.
401- если пользователя с указанным email не существует или пароль неверный.
Завершает сессию пользователя и удаляет Cookies.
POST /api/auth/logoutПустой ответ.
Доступно только аутентифицированным пользователям.
Позволяет создать пользователя с ролью client в системе.
POST /api/client/register{
"email": string,
"password": string,
"name": string,
"contactPhone": string
}{
"id": string,
"email": string,
"name": string
}Доступно только не аутентифицированным пользователям.
400- если email уже занят.
Позволяет пользователю с ролью admin создать пользователя в системе.
POST /api/admin/users/{
"email": string,
"password": string,
"name": string,
"contactPhone": string,
"role": string
}{
"id": string,
"email": string,
"name": string,
"contactPhone": string,
"role": string
}Доступно только пользователям с ролью admin.
401- если пользователь не аутентифицирован;403- если роль пользователя неadmin.
Позволяет пользователю с ролью admin создать пользователя в системе.
GET /api/admin/users/
GET /api/manager/users/- limit - количество записей в ответе;
- offset - сдвиг от начала списка;
- name - фильтр по полю;
- email - фильтр по полю;
- contactPhone - фильтр по полю.
[
{
"id": string,
"email": string,
"name": string,
"contactPhone": string
}
]GET /api/admin/users/Доступно только пользователям с ролью admin.
GET /api/manager/users/Доступно только пользователям с ролью manager.
401- если пользователь не аутентифицирован;403- если роль пользователя не подходит.
Позволяет пользователю с ролью client создать обращение в техподдержку.
POST /api/client/support-requests/{
"text": string
}[
{
"id": string,
"createdAt": string,
"isActive": boolean,
"hasNewMessages": boolean
}
]Доступно только пользователям с ролью client.
401- если пользователь не аутентифицирован;403- если роль пользователя не подходит.
Позволяет пользователю с ролью client получить список обращений для текущего пользователя.
GET /api/client/support-requests/- limit - количество записей в ответе;
- offset - сдвиг от начала списка;
- isActive - фильтр по полю.
[
{
"id": string,
"createdAt": string,
"isActive": boolean,
"hasNewMessages": boolean
}
]Доступно только пользователям с ролью client.
401- если пользователь не аутентифицирован;403- если роль пользователя не подходит.
Позволяет пользователю с ролью manager получить список обращений от клиентов.
GET /api/manager/support-requests/- limit - количество записей в ответе;
- offset - сдвиг от начала списка;
- isActive - фильтр по полю.
[
{
"id": string,
"createdAt": string,
"isActive": boolean,
"hasNewMessages": boolean,
"client": {
"id": string,
"name": string,
"email": string,
"contactPhone": string
}
}
]Доступно только пользователям с ролью manager.
401- если пользователь не аутентифицирован;403- если роль пользователя не подходит.
Позволяет пользователю с ролью manager или client получить все сообщения из чата.
GET /api/common/support-requests/:id/messages[
{
"id": string,
"createdAt": string,
"text": string,
"readAt": string,
"author": {
"id": string,
"name": string
}
}
]Доступно только пользователям с ролью manager и пользователю с ролью client, который создал обращение.
401- если пользователь не аутентифицирован;403- если роль пользователя не подходит.
Позволяет пользователю с ролью manager или client отправлять сообщения в чат.
POST /api/common/support-requests/:id/messages{
"text": string
}[
{
"id": string,
"createdAt": string,
"text": string,
"readAt": string,
"author": {
"id": string,
"name": string
}
}
]Доступно только пользователям с ролью manager и пользователю с ролью client, который создал обращение.
401- если пользователь не аутентифицирован;403- если роль пользователя не подходит.
Позволяет пользователю с ролью manager или client отправлять отметку, что сообщения прочитаны.
POST /api/common/support-requests/:id/messages/read{
"createdBefore": string
}{
"success": true
}Доступно только пользователям с ролью manager и пользователю с ролью client, который создал обращение.
401- если пользователь не аутентифицирован;403- если роль пользователя не подходит.
Позволяет пользователю с ролью manager или client получать новые сообщения в чате через WebSocket.
message: subscribeToChat payload: chatId
{
"id": string,
"createdAt": string,
"text": string,
"readAt": string,
"author": {
"id": string,
"name": string
}
}Доступно только пользователям с ролью manager и пользователю с ролью client, который создал обращение.
Для запуска приложения в корне проекта должны находиться следующие файлы:
package.jsonиpackage-lock.jsonс описанными зависимостями,Dockerfileдля сборки образа приложения,docker-compose.yamlс сервисом приложения и сервисом MondoDB,README.meс описанием проекта и вариантами его запуска.
Настройка параметров приложения должна производиться через переменные окружения. Это требование как для запуска в окружении хоста, так и при работе с Docker.
Список переменных окружения должен быть описан в файле .env-example. Этот файл не должен содержать значений. Пример файла:
HTTP_HOST=
HTTP_PORT=
MONGO_URL=Для запуска приложения должен использоваться скрипт npm start, описанный в package.json.
- Если у вас возник вопрос, попробуйте сначала самостоятельно найти ответ в интернете. Навык поиска информации пригодится вам в любой профессиональной деятельности. Если ответ не нашёлся, можно уточнить у руководителя по дипломной работе.
- Если у вас набирается несколько вопросов, присылайте их в виде нумерованного списка. Так дипломному руководителю будет проще отвечать на каждый из них.
- Для лучшего понимания контекста прикрепите к вопросу скриншоты и стрелкой укажите, что именно вызывает вопрос. Программу для создания скриншотов можно скачать по ссылке.
- По возможности задавайте вопросы в комментариях к коду.
- Формулируйте свои вопросы чётко, дополняя их деталями. На сообщения «Ничего не работает», «Всё сломалось» дипломный руководитель не сможет дать комментарии без дополнительных уточнений. Это затянет процесс получения ответа.
- Постарайтесь набраться терпения в ожидании ответа на свои вопросы. Дипломные руководители Нетологии – практикующие разработчики, поэтому они не всегда могут отвечать моментально. Зато их практика даёт возможность делиться с вами не только теорией, но и ценным прикладным опытом.
Рекомендации по работе над дипломом:
- Не откладывайте надолго начало работы над дипломом. В таком случае у вас останется больше времени на получение рекомендаций от руководителя и доработку диплома.
- Разбейте работу над дипломом на части и выполняйте их поочерёдно. Вы будете успевать учитывать комментарии от руководителя и не терять мотивацию на полпути.