عمليات الإدراج أو التحديث
جزء من قسم قواعد البيانات باستخدام MongoDB في رحلة Node.js على Coddy. الدرس 10 من 18.
يقيّم كل قارئ الكتاب مرة واحدة. وينبغي أن يؤدي تقييمه مرة أخرى إلى تغيير النجوم، لا إلى إضافة تقييم ثانٍ.
باستخدام { upsert: true }، تحدّث updateOne المستند الذي يطابق المرشّح، وإذا لم يطابق أي مستند، تُدرج مستندًا مبنيًا على حقول المرشّح بالإضافة إلى التحديث. يغطي استدعاء واحد المرة الأولى وكل مرة بعدها.
// مستند إعدادات واحد لكل مستخدم
await settings.updateOne(
{ user: 'ada' },
{ $set: { theme: 'dark' } },
{ upsert: true }
);يحدّد المرشّح ما يجعل المستند فريدًا؛ وهنا، مستند واحد لكل مستخدم. تُضبط upsertedId في النتيجة عندما يُدرج الاستدعاء مستندًا. لا يمكن لـ insertOne العادي فعل ذلك: فهو يضيف مستندًا في كل مرة.
التحدي
سهل- تحتوي مجموعة
ratingsعلى{ bookId, user, stars }، ويكونbookIdهو ObjectId الخاص بالكتاب. - يحافظ
PUT /books/:id/ratingsمع{ "user": "ada", "stars": 4 }على تقييم واحد لكل مستخدم لكل كتاب، ويستجيب بالرمز200مع{ user, stars }. - يعرض
GET /books/:id/ratingsتقييمات ذلك الكتاب.
قيّم كتابًا مرتين باسم ada، ثم اعرض التقييمات.
جرّب بنفسك
import express from 'express';
import { MongoClient, ObjectId } from 'mongodb';
const client = new MongoClient('mongodb://localhost:27017');
const db = client.db('shelf');
const books = db.collection('books');
await books.createIndex({ isbn: 1 }, { unique: true });
await books.insertMany([
{ title: 'Dune', author: 'Frank Herbert', year: 1965, isbn: '9780441013593', genre: 'scifi' },
{ title: 'Kindred', author: 'Octavia E. Butler', year: 1979, isbn: '9780807083697', genre: 'scifi' },
{ title: 'Emma', author: 'Jane Austen', year: 1815, isbn: '9780141439587', genre: 'classic' },
]);
const app = express();
app.use(express.json());
// تُرجع كل حالة خطأ استجابةً بالشكل { error: message }.
function sendError(res, status, message) {
res.status(status).json({ error: message });
}
// القيمة :id بصيغة ObjectId، أو null بعد الردّ بالرمز 400 إذا كانت قيمة id غير صالحة.
function parseId(req, res) {
if (ObjectId.isValid(req.params.id)) {
return new ObjectId(req.params.id);
}
sendError(res, 400, 'Not a valid id');
return null;
}
app.get('/', (req, res) => {
res.send('Bookshelf API');
});
app.get('/books', async (req, res) => {
const filter = {};
if (req.query.author) {
filter.author = req.query.author;
}
if (req.query.from || req.query.to) {
filter.year = {};
if (req.query.from) {
filter.year.$gte = Number(req.query.from);
}
if (req.query.to) {
filter.year.$lte = Number(req.query.to);
}
}
const list = await books.find(filter).toArray();
res.json(list);
});
app.post('/books', async (req, res) => {
const book = req.body;
try {
await books.insertOne(book);
} catch (err) {
if (err.code === 11000) {
return sendError(res, 409, 'That ISBN is already on the shelf');
}
throw err;
}
res.status(201).json(book);
});
app.get('/books/newest', async (req, res) => {
const n = Number(req.query.n) || 3;
const list = await books
.find({}, { projection: { title: 1, year: 1, _id: 0 } })
.sort({ year: -1 })
.limit(n)
.toArray();
res.json(list);
});
app.get('/books/:id', async (req, res) => {
const id = parseId(req, res);
if (!id) return;
const book = await books.findOne({ _id: id });
if (!book) {
return sendError(res, 404, 'No such book');
}
res.json(book);
});
app.patch('/books/:id', async (req, res) => {
const id = parseId(req, res);
if (!id) return;
const result = await books.updateOne({ _id: id }, { $set: req.body });
if (result.matchedCount === 0) {
return sendError(res, 404, 'No such book');
}
res.json(await books.findOne({ _id: id }));
});
app.post('/books/:id/copies', async (req, res) => {
const id = parseId(req, res);
if (!id) return;
const result = await books.updateOne({ _id: id }, { $inc: { copies: 1 } });
if (result.matchedCount === 0) {
return sendError(res, 404, 'No such book');
}
res.json(await books.findOne({ _id: id }));
});
app.post('/books/:id/tags', async (req, res) => {
const id = parseId(req, res);
if (!id) return;
const result = await books.updateOne({ _id: id }, { $push: { tags: req.body.tag } });
if (result.matchedCount === 0) {
return sendError(res, 404, 'No such book');
}
res.json(await books.findOne({ _id: id }));
});
app.delete('/books/:id', async (req, res) => {
const id = parseId(req, res);
if (!id) return;
const result = await books.deleteOne({ _id: id });
if (result.deletedCount === 0) {
return sendError(res, 404, 'No such book');
}
res.status(204).end();
});
app.listen(3000, () => {
console.log('Bookshelf is running on port 3000');
});