Menu
CoddyTech

Ordinare il testo

Fa parte della sezione API REST del percorso Node.js di Coddy. Lezione 15 di 25.

Il tuo tracker può filtrare le attività. Ora consenti a un client di richiedere titoli in ordine alfabetico senza modificare l’ordine memorizzato. Un confronto di ordinamento restituisce un numero negativo quando il primo elemento deve precedere il secondo, un numero positivo quando deve seguirlo e zero quando sono equivalenti.

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

Sottrarre stringhe non fornisce un ordine utile. localeCompare confronta invece il testo. toSorted restituisce un nuovo array, lasciando invariato quello originale. Ordina l’elenco filtrato, così il filtraggio e l’ordinamento funzionano insieme. La seguente lezione sull’ordinamento aggiunge altri campi, l’ordine decrescente e la gestione delle query non valide.

challenge icon

Sfida

Facile

Continua il tuo gestore di attività in routes/tasks.js. Nella route dell’elenco, dopo i filtri esistenti, fai in modo che ?sort=title restituisca le attività in ordine alfabetico per titolo. Senza quel valore, mantieni il comportamento attuale. Non modificare l’ordine delle attività memorizzate.

Prova GET /api/tasks?sort=title, poi GET /api/tasks. Il secondo elenco deve essere ancora nell’ordine originale. Il filtro con ?done=false&sort=title deve continuare a restituire solo le attività non completate.

Provalo tu

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

const router = Router();

// Un'attività completa, così come viene inviata da PUT. .strict() rifiuta i campi non elencati.
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 se un'altra attività ha già questo titolo, indipendentemente dalle maiuscole e dalle minuscole.
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;

Tutte le lezioni di API REST

Esercitati da solo: Playground Express