Построение Node.js CLI для выполнения дел с помощью Prisma и Next.js

Йоу! Как дела? Помните прошлую статью, где мы использовали Prisma с Next.js, сделали CLI и добавили функцию аутентификации? Я заметил, что многие из вас узнали много нового! Так вот! Вы узнаете еще больше, включая создание приложения для списка дел в виде CLI с Prisma и Next.js API для обеспечения наилучшей функциональности приложения!

Подожди, Омар? Зачем нам нужен Prisma/Next.js? Причина в том, что это приложение позволяет синхронизировать ваши задачи из любой точки мира!

Не теряя времени, давайте приступим.

Создание нашего API

Прежде всего, нам нужна база данных, поэтому давайте воспользуемся PlanetScale! Зайдите на сайт planetscale.com и нажмите Get Started, создайте свою базу данных и подождите, пока она будет готова к работе!

Теперь запустите терминал и начните новый проект Next.js с помощью npx, после чего смените директорию:

npx create-next-app
cd to-do-list-app
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь, после того как мы создали наш проект, давайте добавим Prisma и подготовим его, поэтому выполните следующую команду в терминале:

yarn add prisma @prisma/client
yarn prisma init
Войти в полноэкранный режим Выйти из полноэкранного режима

После этого мы получим файл schema.prisma в папке prisma, замените его на следующий:

generator client {
  provider = "prisma-client-js"
  previewFeatures = ["referentialIntegrity"]
}

datasource db {
  provider = "mysql"
  url = env("DATABASE_URL")
  referentialIntegrity = "prisma"
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Итак, самое главное, что мы только что изменили поставщика базы данных на mysql, поскольку мы используем базу данных MySQL.

Теперь на приборной панели PlanetScale нажмите на Connect и выберите Prisma в качестве опции для подключения, скопируйте предоставленный файл .env и создайте файл .env в вашем проекте и вставьте то, что вы только что скопировали.

Теперь мы создадим 2 модели, User и Task, поэтому добавьте эти 2 модели в файл схемы Prisma:

model User {
  id Int @id @default(autoincrement())
  username String @unique
  password String
  token String @unique
}

model Task {
  id Int @id @default(autoincrement())
  title String
  userToken String
}
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь ваш файл схемы должен выглядеть следующим образом:

generator client {
  provider = "prisma-client-js"
  previewFeatures = ["referentialIntegrity"]
}

datasource db {
  provider = "mysql"
  url = env("DATABASE_URL")
  referentialIntegrity = "prisma"
}

model User {
  id Int @id @default(autoincrement())
  username String @unique
  password String
  token String @unique
}

model Task {
  id Int @id @default(autoincrement())
  title String
  userToken String
}
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь запустите yarn prisma db push, чтобы синхронизировать вашу базу данных MySQL PlanetScale.

Теперь нам нужно создать наши API файлы, сначала удалите файл pages/api/hello.js и создайте файл register.js в папке pages/api и вставьте следующий код:

import { PrismaClient } from '@prisma/client';

export default function handler(req, res) {
    const prisma = new PrismaClient();
    const { username, password, token } = req.body;
    if (!username || !password || !token) {
        res.status(400).json({
            message: 'Missing username, password or token'
        });
        return;
    }
    prisma.user.findUnique({
        where: {
            username: username
        }
    }).then(user => {
        if (user) {
            res.status(400).json({
                message: 'Username already taken'
            });
        } else {
            prisma.user.findUnique({
                where: {
                    token: token
                }
            }).then(user => {
                if (user) {
                    res.status(400).json({
                        message: 'Token already taken'
                    });
                } else {
                    prisma.user.create({
                        data: {
                            username: username,
                            password: password,
                            token: token
                        }
                    }).then(user => {
                        res.json({
                            message: "User created successfully",
                            user: user
                        });
                    });
                }
            }
            );
        }
    });

}
Вход в полноэкранный режим Выйти из полноэкранного режима

Здесь мы определяем клиента Prisma и создаем переменные для username, password и token. Если один из параметров не указан, мы возвращаем ошибку:

const prisma = new PrismaClient();
    const { username, password, token } = req.body;
    if (!username || !password || !token) {
        res.status(400).json({
            message: 'Missing username, password or token'
        });
        return;
    }
Вход в полноэкранный режим Выйти из полноэкранного режима

А для регистрации мы сначала проверяем, используется ли имя пользователя, если да, то возвращаем ошибку, если нет, то проверяем, используется ли токен, если да, то возвращаем ошибку, как обычно, если нет, то переходим к регистрации:

    prisma.user.findUnique({
        where: {
            username: username
        }
    }).then(user => {
        if (user) {
            res.status(400).json({
                message: 'Username already taken'
            });
        } else {
            prisma.user.findUnique({
                where: {
                    token: token
                }
            }).then(user => {
                if (user) {
                    res.status(400).json({
                        message: 'Token already taken'
                    });
                } else {
                    prisma.user.create({
                        data: {
                            username: username,
                            password: password,
                            token: token
                        }
                    }).then(user => {
                        res.json({
                            message: "User created successfully",
                            user: user
                        });
                    });
                }
            }
            );
        }
    });
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь у нас есть пользователь, который должен иметь возможность аутентификации/логина, создайте файл login.js и вставьте следующее:

import { PrismaClient } from '@prisma/client';

export default function handler(req, res) {
    const prisma = new PrismaClient();
    const { username, password } = req.body;
    if (!username || !password) {
        res.status(400).json({
            message: 'Missing username or password'
        });
        return;
    }
    prisma.user.findUnique({
        where: {
            username: username
        }
    }).then(user => {
        if (user) {
            if (user.password === password) {
                res.json({
                    message: 'User authenticated successfully',
                    userToken: user.token,
                    user: username
                });
            } else {
                res.status(400).json({
                    message: 'Incorrect password'
                });
            }
        } else {
            res.status(400).json({
                message: 'No user found'
            });
        }
    }
    );

}
Вход в полноэкранный режим Выйти из полноэкранного режима

Как обычно, мы определяем клиента Prisma и проверяем, если один из параметров (username или password) отсутствует, и если да, то мы вернем ошибку:

    const prisma = new PrismaClient();
    const { username, password } = req.body;
    if (!username || !password) {
        res.status(400).json({
            message: 'Missing username or password'
        });
        return;
    }
Вход в полноэкранный режим Выйти из полноэкранного режима

Затем мы используем функцию findUnique, чтобы использовать имя пользователя, которое является уникальным для поиска записи/пользователя в таблице, поэтому если мы не нашли пользователя, мы возвращаем ошибку, если мы нашли пользователя с подходящим именем пользователя, мы проверяем, совпадает ли предоставленный пароль с именем пользователя, если пароль правильный, мы возвращаем токен, который является чем-то вроде случайного набора символов & цифр, который трудно угадать, что позволяет вам делать все, что угодно, включая работу с самим API (при отправке запросов вручную), если нет, мы возвращаем ошибку, что пароль неверен:

prisma.user.findUnique({
        where: {
            username: username
        }
    }).then(user => {
        if (user) {
            if (user.password === password) {
                res.json({
                    message: 'User authenticated successfully',
                    userToken: user.token,
                    user: username
                });
            } else {
                res.status(400).json({
                    message: 'Incorrect password'
                });
            }
        } else {
            res.status(400).json({
                message: 'No user found'
            });
        }
    }
    );
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь давайте запустим fun…..! Создайте новый файл create.js, который позволит пользователю создавать новые задания! Вставьте в файл следующий код:

import { PrismaClient } from '@prisma/client';

export default function handler(req, res) {
    const prisma = new PrismaClient();
    const { token, taskTitle } = req.body;
    if (!token || !taskTitle) {
        res.status(400).json({
            message: 'Missing token or task title'
        });
        return;
    }
    prisma.user.findUnique({
        where: {
            token: token
        }
    }).then(user => {
        if (user) {
            prisma.task.create({
                data: {
                    title: taskTitle,
                    userToken: token
                }
            }).then(task => {
                res.json({
                    message: 'Task created successfully',
                });
            });
        } else {
            res.status(400).json({
                message: 'No user found'
            });
        }
    }
    );
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Мы просто пропускаем определение клиента Prisma, поэтому мы используем findUnique, чтобы проверить действителен ли токен пользователя или нет, если нет, мы передаем ошибку, говоря, что такого пользователя нет, если пользователь существует, мы создаем задачу с заданным названием и с этим токеном, а затем возвращаем сообщение об успехе:

    prisma.user.findUnique({
        where: {
            token: token
        }
    }).then(user => {
        if (user) {
            prisma.task.create({
                data: {
                    title: taskTitle,
                    userToken: token
                }
            }).then(task => {
                res.json({
                    message: 'Task created successfully',
                });
            });
        } else {
            res.status(400).json({
                message: 'No user found'
            });
        }
    }
    );
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь, если мы предположим, что кто-то хочет удалить задачу? Создайте delete.js и вставьте следующее:

import { PrismaClient } from '@prisma/client';

export default function handler(req, res) {
    const prisma = new PrismaClient();
    const { token, taskId } = req.body;
    if (!token || !taskId) {
        res.status(400).json({
            message: 'Missing token or task id'
        });
        return;
    }
    prisma.user.findUnique({
        where: {
            token: token
        }
    }).then(user => {
        if (user) {
            prisma.task.findUnique({
                where: {
                    id: taskId
                }
            }).then(task => {
                if (task.userToken === token) {
                    prisma.task.delete({
                        where: {
                            id: taskId
                        }
                    }).then(task => {
                        res.json({
                            message: 'Task deleted successfully',
                        });
                    });
                } else {
                    res.status(400).json({
                        message: 'Task does not belong to the user'
                    });
                }
            }
            );
        } else {
            res.status(400).json({
                message: 'No user found'
            });
        }
    }
    );
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Итак, мы находим пользователя по токену, если мы не нашли пользователя, мы возвращаем ошибку, если мы нашли пользователя, мы находим задачу по ID, мы проверяем, совпадает ли владелец, если да, мы удаляем ее, если нет, мы возвращаем ошибку:

    prisma.user.findUnique({
        where: {
            token: token
        }
    }).then(user => {
        if (user) {
            prisma.task.findUnique({
                where: {
                    id: taskId
                }
            }).then(task => {
                if (task.userToken === token) {
                    prisma.task.delete({
                        where: {
                            id: taskId
                        }
                    }).then(task => {
                        res.json({
                            message: 'Task deleted successfully',
                        });
                    });
                } else {
                    res.status(400).json({
                        message: 'Task does not belong to the user'
                    });
                }
            }
            );
        } else {
            res.status(400).json({
                message: 'No user found'
            });
        }
    }
    );
Вход в полноэкранный режим Выход из полноэкранного режима

И последний, но не менее важный файл view.js, который позволяет пользователю просматривать задания:

import { PrismaClient } from '@prisma/client';

export default function handler(req, res) {
    const prisma = new PrismaClient();
    const { token } = req.body;
    if (!token) {
        res.status(400).json({
            message: 'Missing token'
        });
        return;
    }
    prisma.user.findUnique({
        where: {
            token: token
        }
    }).then(user => {
        if (user) {
            prisma.task.findMany({
                where: {
                    userToken: token
                }
            }).then(tasks => {
                res.json({
                    message: 'Tasks retrieved successfully',
                    tasks: tasks
                });
            });
        } else {
            res.status(400).json({
                message: 'No user found'
            });
        }
    }
    );
}
Войти в полноэкранный режим Выход из полноэкранного режима

Итак, что мы делаем здесь: ищем пользователя, если он не найден, возвращаем ошибку, если пользователь найден, возвращаем все задачи, соответствующие пользователю:

    prisma.user.findUnique({
        where: {
            token: token
        }
    }).then(user => {
        if (user) {
            prisma.task.findMany({
                where: {
                    userToken: token
                }
            }).then(tasks => {
                res.json({
                    message: 'Tasks retrieved successfully',
                    tasks: tasks
                });
            });
        } else {
            res.status(400).json({
                message: 'No user found'
            });
        }
    }
    );
Войти в полноэкранный режим Выход из полноэкранного режима

Теперь, после тяжелой работы мы получили готовый API 🥳!

Создание нашего CLI

Для начала работы выполните следующие команды:

mkdir todocli
cd todocli
npm init -y
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь создайте файл index.js и вставьте в него следующее:

#!/usr/bin/env node

var args = process.argv.slice(2);
var fs = require('fs');
var request = require('request');
command = args[0];

if (command == 'login') {
    options = {
        url: 'http://localhost:3000/api/login',
        method: 'POST',
        json: {
            username: args[1],
            password: args[2]
        }
    };
    request(options, function (error, response, body) {
        if (!error && response.statusCode == 200) {
            console.log(body.message);
            fs.writeFile('./token.txt', body.userToken, function (err) {
                if (err) {
                    return console.log(err);
                }
                console.log("Token saved successfully!");
            }
            );
        } else {
            console.log(body.message);
        }
    });
} else if (command == 'register') {
    options = {
        url: 'http://localhost:3000/api/register',
        method: 'POST',
        json: {
            username: args[1],
            password: args[2],
            token: Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
        }
    };
    request(options, function (error, response, body) {
        if (!error && response.statusCode == 200) {
            console.log(body.message);
            // save token
            fs.writeFile('./token.txt', body.user.token, function (err) {
                if (err) {
                    return console.log(err);
                }
                console.log("Token saved successfully!");
            }
            );
        } else if (body.message=="Token already taken") {
            console.log("Please try registering again!")
        } else {
            console.log(body.message);
        }
    });
} else if (command == 'create') {
    options = {
        url: 'http://localhost:3000/api/create',
        method: 'POST',
        json: {
            taskTitle: args.slice(1).join(' '),
            token: fs.readFileSync('./token.txt', 'utf8')
        }
    };
    request(options, function (error, response, body) {
        if (!error && response.statusCode == 200) {
            console.log(body.message);
        } else {
            console.log(body.message);
        }
    });
} else if (command == 'view') {
    options = {
        url: 'http://localhost:3000/api/view',
        method: 'POST',
        json: {
            token: fs.readFileSync('./token.txt', 'utf8')
        }
    };
    request(options, function (error, response, body) {
        if (!error && response.statusCode == 200) {
            // save the title and id of each task in an array
            var tasks = [];
            for (var i = 0; i < body.tasks.length; i++) {
                tasks.push({
                    id: body.tasks[i].id,
                    title: body.tasks[i].title
                });
            }
            // print the tasks along with their ids
            console.log("Your tasks are:");
            for (var i = 0; i < tasks.length; i++) {
                console.log(tasks[i].id + ": " + tasks[i].title);
            }

        } else {
            console.log(body.message);
        }
    });
} else if (command == 'delete') {
    options = {
        url: 'http://localhost:3000/api/delete',
        method: 'POST',
        json: {
            taskId: args[1],
            token: fs.readFileSync('./token.txt', 'utf8')
        }
    };
    request(options, function (error, response, body) {
        if (!error && response.statusCode == 200) {
            console.log(body.message);
        } else {
            console.log(body.message);
        }
    });
} else if (command == 'help') {
    console.log("Usage: todocli <command> <arguments>");
    console.log("Commands:");
    console.log("login <username> <password>");
    console.log("register <username> <password>");
    console.log("create <task title>");
    console.log("view");
    console.log("delete <task id>");
    console.log("help");
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Просто то, что мы в основном делаем, это получение параметров и зависимости от API, а токен сохраняется в локальном файле txt.

Теперь установите необходимый пакет с помощью npm install request.

Теперь отредактируйте ваш package.json и вместо scripts вставьте следующее:

  "scripts": {
    "start": "node index.js"
  },
  "bin": {
    "todocli": "index.js"
  }
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь ваш package.json должен выглядеть следующим образом:

{
  "name": "todocli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "bin": {
    "todocli": "index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "request": "^2.88.2"
  }
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Теперь мы закончили, выполните npm install . -g и начните использовать приложение!

Протестируйте его — установка не требуется

Просто установите to-do-cli-prisma с помощью команды:

npm i to-do-cli-prisma
Войти в полноэкранный режим Выйти из полноэкранного режима

И проверьте, как им пользоваться, выполнив команду: todocli help.

Важные ссылки 🔗

  • GitHub Repo — Omar8345/to-do-list-app-cli
  • npm Package — to-do-cli-prisma

Спасибо за прочтение этой статьи и надеемся, что вы узнали что-то полезное!

Оцените статью
devanswers.ru
Добавить комментарий