- docker-compose build
- docker-compose up
Дипломный проект представляет собой сайт-агрегатор просмотра и бронирования гостиниц. Ваша задача заключается в разработке бэкенда для сайта-агрегатора с реализацией возможности бронирования гостиниц на диапазон дат.
- Разработка публичного апи.
- Разработка апи пользователя.
- Разработка апи администратора.
- Разработка чата консультанта.
- 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): Promoise<User[]>;
}
Поле role
может принимать одно из следующих значений:
client
admin
manager
При поиске IUserService.findAll()
поля email
, name
и contactPhone
должны проверяться на частичное совпадение.
Модуль "Гостиницы" предназначается для хранения и поиска гостиниц и комнат.
Модуль "Гостиницы" используется функциональными модулями для показа списка мест для бронирования, а также для их добавления, включения и выключения.
Данные должны храниться в MongoDb.
Модель данных Hotel
должна содержать следующие поля:
название | тип | обязательное | уникальное | по умолчанию |
---|---|---|---|---|
_id | ObjectId |
да | да | |
title | ObjectId |
да | нет | |
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 IHotelService {
create(data: any): Promise<Hotel>;
findById(id: ID): Promise<Hotel>;
search(params: Pick<Hotel, "title">): Promise<Hotel[]>;
}
interface SearchRoomsParams {
limit: number;
offset: number;
title: string;
isEnabled?: true;
}
interface HotelRoomService {
create(data: Partial<HotelRoom>): Promise<HotelRoom>;
findById(id: ID, isEnabled?: true): Promise<HotelRoom>;
search(params: SearchRoomsParams): Promise<HotelRoom[]>;
update(id: ID, data: Partial<HotelRoom>): Promise<HotelRoom>;
}
В методах findById
и search
флаг isEnabled
может принимать только значения:
true
- флаг должен использоваться в фильтрации,undefined
- флаг должен игнорироваться.
Модуль "Брони" предназначен для хранения и получения броней гостиниц конкретного пользователя.
Модуль "Брони" не должен использовать Модуль "Пользователи" и Модуль "Гостиницы" для получения данных.
Модуль "Брони" не должен хранить данные пользователей и гостиниц.
Модуль "Брони" должен использовать соединение с базой данных.
Данные должны храниться в MongoDb.
Модель данных Reservation
должна содержать следующие поля:
название | тип | обязательное | уникальное | по умолчанию |
---|---|---|---|---|
_id | ObjectId |
да | да | |
userId | ObjectId |
да | нет | |
hotelId | ObjectId |
да | нет | |
roomId | ObjectId |
да | нет | |
dateStart | Date |
да | нет | |
dateEnd | Date |
да | нет |
Модуль "Брони" должен быть реализован в виде NestJS модуля и экспортировать сервисы со следующими интерфейсами:
interface ReservationDto {
user: ID;
hotel: ID;
room: ID;
dateStart: Date;
dateEnd: Date;
}
interface ReservationSearchOptions {
user: 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<Message[]>;
}
interface ISupportRequestEmployeeService {
markMessagesAsRead(params: MarkMessagesAsReadDto);
getUnreadCount(supportRequest: ID): Promise<Message[]>;
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,
"title": string,
"images": [string],
"hotel": {
"id": string,
"title": string
}
}
]
Доступно всем пользователям, включая неаутентифицированных.
Получение подробной информации о номере.
GET /api/common/hotel-rooms/:id
Отсутствуют.
{
"id": string,
"title": 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 - сдвиг от начала списка.
{
"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
.
title: string
description: string
hotelId: string
images[]: File
{
"id": string,
"title": 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
.
title: string
description: string
hotelId: string
isEnabled: boolean
images[]: File | string
При обновлении может быть отправлен одновременно список ссылок на уже загруженные картинки и список файлов с новыми картинками.
При использовании multer
список загруженных файлов можно получить через @UploadedFiles()
. Этот список нужно объденить со списком, который пришел в body
.
{
"id": string,
"title": 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": {
"title": string,
"description": string,
"images": [string]
},
"hotel": {
"title": string,
"description": string
}
}
Доступно только аутентифицированным пользователям с ролью client
.
401
- если пользователь не аутентифицирован403
- если роль пользователя неclient
400
- если номер с указанным ID не существует или отключен
Список броней текущего пользователя.
GET /api/client/reservations
[
{
"startDate": string,
"endDate": string,
"hotelRoom": {
"title": string,
"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": {
"title": string,
"description": string,
"images": [string]
},
"hotel": {
"title": string,
"description": string
}
}
]
Доступно только аутентифицированным пользователям с ролью manager
.
401
- если пользователь не аутентифицирован403
- если роль пользователя неmanager
Отменяет бронь пользователя.
DELETE /api/manager/reservations/:userId/:reservationId
Пустой ответ.
Доступно только аутентифицированным пользователям с ролью manager
.
401
- если пользователь не аутентифицирован403
- если роль пользователя неmanager
400
- если бронь с указанным ID для пользователя с указанным ID не существует
Должно быть оформлено в виде отдельного NestJS модуля.
Модуль "Аутентификация и авторизация" предназначен:
- для управления сессией пользователя,
- для регистрации пользователей.
Хранение сессии должно реализовываться посредством библиотеки passportjs с хранением сессии в памяти приложения.
Аутентификация пользователя производится с помощью модуля "Пользователи". Каждому пользователю назначается одна из ролей - клиент, администратор, консультант.
Стартует сессию пользователя и выставляет Cookie.
POST /api/auth/login
{
"email": string,
"password": string
}
{
"email": string,
"name": string,
"contactPhone": string
}
Доступно только не аутентифицированным пользователям.
401
- если пользователь с указанным email не существет или пароль неверный
Завершает сессию пользователя и удаляет Cookie.
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
с сервисом приложения и сервисом mondodbREADME.me
с описанием проекта и вариантами его запуска
Настройка параметров приложения должна производиться через переменные окружения. Это требование как для запуска в окрежении хоста, так и при работе с docker.
Список переменных окружения должен быть описан в файле .env-example
. Этот файл не должен содержать значений. Пример файла:
HTTP_HOST=
HTTP_PORT=
MONGO_URL=
Для запуска приложения должен использоваться скрипт npm start
, описанный в package.json
Что поможет решить большинство частых проблем:
- Попробовать найти ответ сначала самому в интернете. Скилл поиска ответов пригодится вам в профессиональной деятельности. И только после этого спрашивать дипломного руководителя.
- Если вопросов больше одного, то присылайте их в виде нумерованного списка. Так дипломному руководителю будет проще отвечать на каждый из них.
- При необходимости прикрепите к вопросу скриншоты и стрелочкой покажите, где не получается. Программу для этого можно скачать здесь https://app.prntscr.com/ru/
- По возможности, задавайте вопросы в комментариях к коду.
- Начинать работу над дипломом как можно раньше! Чтобы было больше времени на правки.
- Делайте диплом частями, а не всё сразу. Если сделать всё сразу, то количество комментариев от дипломного руководителя может вас деморализовать.
Что может стать источником проблем:
- Вопросы вида «Ничего не работает. Не запускается. Всё сломалось.». Дипломный руководитель не сможет ответить на такой вопрос без дополнительных уточнений. Цените своё время и время других.
- Откладывание диплом на последний момент.
- Ожидание моментального ответа на свой вопрос. Дипломные руководители - работающие разработчики, которые занимаются, кроме преподавания, своими проектами. Их время ограничено, поэтому постарайтесь задавать правильные вопросы, чтобы получать быстрые ответы!