Рілейшени
one to many
many to many
many to one
Розглянемо на прикладі onе to many
створимо список тудущек для цього створимо схему
Copy const { model, Schema, Types } = require("mongoose");
const todoSchema = new Schema(
{
title: {
type: String,
required: true,
maxLength: 20,
trim: true,
},
description: {
type: String,
maxLength: 100,
},
due: {
type: Date,
required: true,
},
completed: {
type: Boolean,
default: false,
},
owner: {
type: Types.ObjectId,
ref: "User",
required: [true, "Todo must have an owner.."],
},
},
{
timestamps: true,
versionKey: false,
}
);
const Todo = model("Todo", todoSchema);
module.exports = Todo;
В цьому прикладі із нового це owner. Тип має буди типом айдіщки. реф вказуємо назву колекції в якій якраз зьерігаються онери тобто користувачі.
тобто треба подивитися назву яку ми вказаои в моделі в юзерах.
required означає, що це обов'язковий параметр для тудушки і він не може бути нічийним.
для тудушек створимо окремі роути
Copy const { Router } = require("express");
const { createTodo, getTodosList } = require("../controllers/todoController");
const { protect } = require("../middlewares/authMiddlewares");
const router = Router();
router.use(protect);
router.post("/", createTodo);
// router.get("/", getTodosList);
module.exports = router;
тепер створимо контрололери тудушек
Copy const { catchAsync } = require("../utils");
const todoService = require("../services/todoService");
const Todo = require("../models/todoModel");
exports.createTodo = catchAsync(async (req, res) => {
const { title, description, due } = req.body;
const newTodoData = { title, description, due, owner: req.user };
const newTodo = await Todo.create(newTodoData);
res.status(201).json({
todo: newTodo,
});
});
але роботу з моделлю можна витягнути в окремий сервіс.
Copy const userRolesEnum = require("../constants/userRolesEnum");
const Todo = require("../models/todoModel");
exports.createTodo = (newTodoData, owner) => {
const { title, description, due } = newTodoData;
return Todo.create({
title,
description,
due,
owner,
});
};
І тоді контролер матиме такий вигляд:
Copy const { catchAsync } = require("../utils");
const todoService = require("../services/todoService");
exports.createTodo = catchAsync(async (req, res) => {
const newTodo = await todoService.createTodo(req.body, req.user);
res.status(201).json({
todo: newTodo,
});
});
і не забуваємо підключити роути в індекс файлі
Copy const todoRoutes = require("./routes/todoRoutes");
app.use("/todos", todoRoutes);
але для цього потрібно внести ля запиту наш токен, бо спрацьовує перевірка мілдвар
відправляємо
Copy {
"title": "123",
"description": "456",
"due": "2023-03"
}
отримуємо відповідь
Copy {
"todo": {
"title": "123",
"description": "456",
"due": "2023-03-01T00:00:00.000Z",
"completed": false,
"owner": {
"_id": "64c636290f9fbb9cf5c5a9bd",
"name": "Oleksandr4",
"email": "khomiak4@gmail.com",
"year": 1986,
"role": "user",
"createdAt": "2023-07-30T10:06:33.189Z",
"updatedAt": "2023-07-30T10:06:33.189Z"
},
"_id": "64cbed716fcaecb2ebba390b",
"createdAt": "2023-08-03T18:09:53.492Z",
"updatedAt": "2023-08-03T18:09:53.492Z"
}
}
у нас повертає всього owner, але якщо вказати в контролері (todoService) owner.id, то повернеться тільки id власника
Створимо контролер для отримання тудушек
от сам контролер обробника отримання тудужег
Copy exports.getTodosList = catchAsync(async (req, res) => {
const todos = await todoService.getTodosList();
res.status(200).json({
todos,
total: todos.length,
});
а от сервіс
Copy exports.getTodosList = async () => {
const todos = await Todo.find().populate("owner");
return todos;
};
тотал - кількість результатів
опція populate - дає мождивість повернути розгорнуто оунера але в моделі має бути ref навпроти того поля
const todosQuery = await Todo.find(findOptions).populate(path: "owner", select: 'name email role');
але можна повенрути оунера окремо вкінці
Copy
res.status(200).json({
todos,
total: todos.length,
owner: req.user,
});
а попюдейт прибрати
Copy exports.getTodosList = async () => {
const todos = await Todo.find();
return todos;
};
КВЕРІ БІЛДЕР!
зазвичай виносять в окремий сервіс окремий клас
Запит пагінації з фронтенда передаватиметься як квері рядок
в постмені є квері парамс
і отримання переданих параметрів квері із рядка добиться черех req.query.
$regex - сталі комбінації. можна почитати в техдокументації.
Треба в базу даних додати побільше тудушек З різними значеннями полів щоб відпрацювати пошук
Copy {
"title": "cookies",
"description": "456",
"due": "2023-03"
}
ЗРОБИМО ПОШУК ПО TITLE AБО DESCRIPTION
Copy exports.getTodosList = catchAsync(async (req, res) => {
const todos = await todoService.getTodosList(req.query);
res.status(200).json({
todos,
total: todos.length,
});
});
Copy exports.getTodosList = async (options) => {
const findOptions = { title: { $regex: options.search } };
const todos = await Todo.find(findOptions);
return todos;
};
Пошук відбувається чітко по збігу слова (пошук відбувався суто по тайтлу)
Щоб прописати, щоб регістр не врливав на видачу результатів, тобто , щоб і великими і малими літерами повертадо рещультат є
Copy const findOptions = { title: { $regex: options.search, $options: "i" } };
якщо нап мотрібно зробити пощук на збіг і в полі тайтл і в полі дескріпшн, то пишемо використовуючи такі оператори
Copy const findOptions = {
$or: [
{ title: { $regex: options.search, $options: "i" } },
{ description: { $regex: options.search, $options: "i" } },
],
};
але якщо ми в search не передамо нічого, то у нас буде помилка сервера. у цьому разі потрібно зробити умову, якшо search прийшло то шукаємо за заданим пошуком, якшо немає, то повертаємо всі варіанти
Copy const findOptions = options.search
? {
$or: [
{ title: { $regex: options.search, $options: "i" } },
{ description: { $regex: options.search, $options: "i" } },
],
}
: {};
Зараз повертаються усі тудушки навіть чужі, тому треба робити вибілку тільки серед своїх тудушек
Для цього ми з параметром oblect в сервіси передамо це параметр юзера, щоб робити вибілку по цьому юзеру
Copy exports.getTodosList = catchAsync(async (req, res) => {
const todos = await todoService.getTodosList(req.query, req.user);
res.status(200).json({
todos,
total: todos.length,
});
можна найпростіше зробити owner: user
Copy exports.getTodosList = async (options, user) => {
const findOptions = options.search
? {
$or: [
{ title: { $regex: options.search, $options: "i" }, owner: user },
{ description: { $regex: options.search, $options: "i" }, owner: user },
],
}
: {};
const todos = await Todo.find(findOptions);
return todos;
};
А можна після задання пошукових опцій перевірити для кожної тудушки чи це цього користувача запис і якщо да, то і якщшо це юзер передати користувача його айді і тоді вибілка буде тільки бо його тудузках (модератор і адмін матиме доступ до всіх тудушек)
Copy exports.getTodosList = async (options, user) => {
const findOptions = options.search
? {
$or: [
{ title: { $regex: options.search, $options: "i" } },
{ description: { $regex: options.search, $options: "i" } },
],
}
: {};
if (options.search && user.role === userRolesEnum.USER) {
findOptions.$or.forEach((searchOption) => {
searchOption.owner = user;
});
// for (const searchOption of findOptions.$or) searchOption.owner = user;
}
if (!options.search && user.role === userRolesEnum.USER) {
findOptions.owner = user;
}
const todos = await Todo.find(findOptions);
return todos;
};\
ТЕПЕР СОРТУВАННЯ по параметру і ордеру
(по хорошому на ртримані квері дані теж треба прописувати валідатор, щоб не приходило підозрідих даних - так само як і валідували req.body так само валідуємо req.query)
в квері передаватимемо sort - за яким принципом будемо сортувати (передаємо туди поле бази даних за яким будем осортувати) наприклад title, description...
другий параметр order (від більшого до меншого чи від меншого до більшого DESC і ASC)
методи сортування вказують лдразу після find і вказуємо філди по яких сортуємо наприклад:
Copy const todos = await Todo.find(findOptions).sort("title");
якщо зочемо в зворотньому напрямку сортувати то перед назвою поля ставоять знак мінуса
Copy const todos = await Todo.find(findOptions).sort("-title");
Але в якості параметра сортування можна передати динамічні змінні
тобто, якшо отримуэмо DESC, то ставимо мінус і сортуємо за отриманим полем, якщо поле не передали, то за замовчанням візьмемо title.
Copy const todos = await Todo.find(findOptions).sort(
`${options.order === "DESC" ? "-" : ""}${options.sort || "title"}`
);
Гет запит наприкдаж може бути такий
порефакторимо цей код
примітка решультат пошуку виконується тільки після вказування await
тому код запиту можна розписати так
Copy const todosQuery = Todo.find(findOptions);
todosQuery.sort(
`${options.order === "DESC" ? "-" : ""}${options.sort || "title"}`
);
const todos = await todosQuery;
Це зробили для того, щоб вписувати додаткові параметри запиту
ПАГІНАЦІЯ
пашінація робиться за допомогою limit (скільки повернути - кількість) і skip (скільки пропускаємо після першого результату наприклад якщо 10 то 10 пропустить і з 11 буде видавати(це не індекс, а кількість))
проразуємо формули (отримуємо з квері номер сторінки запиту і макс кількість, яка має повернутися з бекенду)
Наприклад: ліміт 10. це означає, щ о при першій сторінці потрібно пропустити 0 документів, при друшій сторінці -пропустити 10 документів, третя сторінка - 20 документів. (page 1 = skip 0, page 2 = skip 10, page 3 = skip 20)
з опцій отримаємо номер сторінки запиту, якщо параметр не переданий, то за замовчанням сторінка 1. + означає стріншу перетворити в номер. використано тернарник. за такою ж аналогією отримуємо кількість результатів в запиті за замовчанням 5. Виразуємо формуду для скіп:
Copy const paginationPage = options.page ? +options.page : 1;
const paginationLimit = options.limit ? +options.limit : 5;
const skip = (paginationPage - 1) * paginationLimit;
Тепер окремо пропишемо наші параметри запиту
Copy todosQuery.skip(skip).limit(paginationLimit);
і тільки потім запит
Copy const todos = await todosQuery;
і от наприклад повний запит
Повертати також треба загалну кількість результатів, щоб на фронтенді мошли коректно розрахувати кількість сторінок і кількість пагінацій.
Copy const total = await Todo.count(findOptions);
і тоді у наш контролео уже треба повертати не просто todos, а обʼєкт результатів і повної кількості збігів
Copy return { todos, total };
Відповідно потрібно і переробити контролер
Copy const { todos, total } = await todoService.getTodosList(req.query, req.user);
res.status(200).json({
todos,
total,
owner: req.user,
});