Menu
CoddyTech

מיון טקסט

חלק מהיחידה REST APIs במסלול ה-Node.js של Coddy. שיעור 15 מתוך 25.

מערכת המעקב שלך יכולה לסנן משימות. עכשיו אפשר ללקוח לבקש כותרות בסדר אלפביתי בלי לשנות את הסדר השמור. השוואת מיון מחזירה מספר שלילי כשהפריט הראשון אמור להופיע לפני השני, מספר חיובי כשהוא אמור להופיע אחריו, ואפס כשהם שווים.

const sorted = cities.toSorted((a, b) => a.name.localeCompare(b.name));

חיסור מחרוזות לא מספק סדר מועיל. localeCompare משווה טקסט. toSorted מחזירה מערך חדש ומשאירה את המערך המקורי ללא שינוי. מיין את הרשימה המסוננת כך שהסינון והמיון יפעלו יחד. שיעור המיון הבא מוסיף שדות נוספים, סדר יורד וטיפול בשאילתות לא תקינות.

challenge icon

אתגר

קל

המשיכו את מעקב המשימות שלכם בקובץ routes/tasks.js. בנתיב הרשימה, אחרי המסננים הקיימים, הגדירו ש־?sort=title יחזיר את המשימות בסדר אלפביתי לפי הכותרת. ללא הערך הזה, שמרו על ההתנהגות הנוכחית. אל תשנו את סדר המשימות המאוחסן.

נסו GET /api/tasks?sort=title, ואז GET /api/tasks. הרשימה השנייה עדיין חייבת להיות בסדר המקורי שלה. סינון באמצעות ?done=false&sort=title עדיין חייב להחזיר רק משימות שלא הושלמו.

נסו בעצמכם

import { Router } from 'express';
import { z } from 'zod';
import { sendError, sendInvalid } from '../errors.js';
import { nextId, tasks } from '../store.js';

const router = Router();

// משימה שלמה, כפי ש-PUT שולח אותה. ‎.strict()‎ דוחה שדות שאינם מופיעים ברשימה שלו.
const Task = z
    .object({
        title: z.string().trim().min(1).max(100),
        done: z.boolean(),
    })
    .strict();
const NewTask = Task.partial({ done: true });
const TaskChanges = Task.partial();

// True אם למשימה אחרת כבר יש את הכותרת הזו, ללא תלות באותיות גדולות או קטנות.
const titleTaken = (title, exceptId) =>
    tasks.some((t) => t.id !== exceptId && t.title.toLowerCase() === title.toLowerCase());

router.get('/', (req, res) => {
    const { done, q } = req.query;
    let list = tasks;
    if (done !== undefined) {
        if (done !== 'true' && done !== 'false') {
            return sendError(res, 400, 'invalid_query', 'done must be true or false');
        }
        list = list.filter((t) => t.done === (done === 'true'));
    }
    if (q !== undefined) {
        const words = String(q).toLowerCase();
        list = list.filter((t) => t.title.toLowerCase().includes(words));
    }
    res.json(list);
});

router.get('/:id', (req, res) => {
    const task = tasks.find((t) => t.id === Number(req.params.id));
    if (!task) {
        return sendError(res, 404, 'not_found', 'Task not found');
    }
    res.json(task);
});

router.post('/', (req, res) => {
    const result = NewTask.safeParse(req.body);
    if (!result.success) {
        return sendInvalid(res, result.error);
    }
    const { title, done = false } = result.data;
    if (titleTaken(title)) {
        return sendError(res, 409, 'conflict', 'A task with this title already exists');
    }
    const task = { id: nextId(), title, done, createdAt: new Date().toISOString() };
    tasks.push(task);
    res.status(201).location(`/api/tasks/${task.id}`).json(task);
});

router.patch('/:id', (req, res) => {
    const task = tasks.find((t) => t.id === Number(req.params.id));
    if (!task) {
        return sendError(res, 404, 'not_found', 'Task not found');
    }
    const result = TaskChanges.safeParse(req.body);
    if (!result.success) {
        return sendInvalid(res, result.error);
    }
    if (result.data.title !== undefined && titleTaken(result.data.title, task.id)) {
        return sendError(res, 409, 'conflict', 'A task with this title already exists');
    }
    Object.assign(task, result.data);
    res.json(task);
});

router.put('/:id', (req, res) => {
    const index = tasks.findIndex((t) => t.id === Number(req.params.id));
    if (index === -1) {
        return sendError(res, 404, 'not_found', 'Task not found');
    }
    const result = Task.safeParse(req.body);
    if (!result.success) {
        return sendInvalid(res, result.error);
    }
    const { id, createdAt } = tasks[index];
    if (titleTaken(result.data.title, id)) {
        return sendError(res, 409, 'conflict', 'A task with this title already exists');
    }
    tasks[index] = { id, ...result.data, createdAt };
    res.json(tasks[index]);
});

router.delete('/:id', (req, res) => {
    const index = tasks.findIndex((t) => t.id === Number(req.params.id));
    if (index === -1) {
        return sendError(res, 404, 'not_found', 'Task not found');
    }
    tasks.splice(index, 1);
    res.status(204).end();
});

export default router;

כל השיעורים ביחידה REST APIs

תרגלו בעצמכם: Express אונליין