Дипломный проект представляет собой сайт-агрегатор просмотра и бронирования гостиниц. Ваша задача заключается в разработке бэкенда для сайта-агрегатора с реализацией возможности бронирования гостиниц на диапазон дат.
- Разработка публичного 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
.
- Если у вас возник вопрос, попробуйте сначала самостоятельно найти ответ в интернете. Навык поиска информации пригодится вам в любой профессиональной деятельности. Если ответ не нашёлся, можно уточнить у руководителя по дипломной работе.
- Если у вас набирается несколько вопросов, присылайте их в виде нумерованного списка. Так дипломному руководителю будет проще отвечать на каждый из них.
- Для лучшего понимания контекста прикрепите к вопросу скриншоты и стрелкой укажите, что именно вызывает вопрос. Программу для создания скриншотов можно скачать по ссылке.
- По возможности задавайте вопросы в комментариях к коду.
- Формулируйте свои вопросы чётко, дополняя их деталями. На сообщения «Ничего не работает», «Всё сломалось» дипломный руководитель не сможет дать комментарии без дополнительных уточнений. Это затянет процесс получения ответа.
- Постарайтесь набраться терпения в ожидании ответа на свои вопросы. Дипломные руководители Нетологии – практикующие разработчики, поэтому они не всегда могут отвечать моментально. Зато их практика даёт возможность делиться с вами не только теорией, но и ценным прикладным опытом.
Рекомендации по работе над дипломом:
- Не откладывайте надолго начало работы над дипломом. В таком случае у вас останется больше времени на получение рекомендаций от руководителя и доработку диплома.
- Разбейте работу над дипломом на части и выполняйте их поочерёдно. Вы будете успевать учитывать комментарии от руководителя и не терять мотивацию на полпути.