Автор: Экекента Одионенфе С.

Приходя в ресторан, первое, что вам представляют, это меню. Это действие повторяется каждый раз, когда вы идете в ресторан. Точно так же многие события или действия повторяются в программной инженерии. Поэтому приятно, что мы постоянно используем концепцию DRY (не повторяйся), чтобы упростить задачу.

Предпосылки

Чтобы следовать этой статье, вам понадобится:

  • Учетная запись Twilio (для службы SMS)
  • Node.js (версия 14 и выше)
  • Учетная запись Gmail (для электронной почты)

Контроллеры в Strapi

Эти два понятия связаны. Контроллеры — это место, где хранятся действия. Эти действия запускаются, когда клиент запрашивает определенный маршрут, определенный в коде. Контроллеры несут ответственность за управление потоком любого приложения, которое следует за MVC framework,, включая Strapi.

Услуги в Страпи

Услуги помогают вам с принципом DRY, поскольку они делают то, что даже означают; они служат. Это многократно используемые функции, которые упрощают логику контроллеров.

Всякий раз, когда вы создаете новый тип контента или модель, Strapi создает новый сервисный файл, который ничего не делает, но может переопределить общий сервис, созданный в node_module.

Давайте развернем проект, создав новое приложение, если у вас его еще нет.

npx create-strapi-app@latest my-project --quickstart
    //Or
    yarn create strapi-app my-project --quickstart

После установки перейдите к http://localhost:1337/admin и заполните форму, чтобы создать первого пользователя-администратора.

Создание сервисов

Сначала мы создадим API с его конфигурациями, контроллером и сервисом.

npm run strapi generate

Затем сделайте следующее.

  • Выберите api в качестве генератора.
  • Введите comment в качестве имени.
  • Этот API не для плагина, выберите n.

Ваш выбор должен выглядеть так, как показано на скриншоте ниже:

Затем сгенерируйте тип контента с помощью команды Strapi generate ниже:

npm run strapi generate

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

Ваша кодовая база должна выглядеть так:

Приведенная выше команда создаст пустую коллекцию с именем Комментарии.

Мы хотим использовать сгенерированный сервис Strapi для отправки SMS, когда пользователь создает новый комментарий. Тем не менее, мы можем достичь бесконечных функциональных возможностей с помощью сервисов Strapi Generated Services.

Использование сервисов для отправки SMS

Создайте файл с именем sms.js в папке ./api/comment/services и добавьте в него этот код:

'use strict';
    module.exports = {};

Мы будем отправлять SMS всякий раз, когда пользователь создает комментарий с помощью Twilio. Давайте установим Twilio с помощью следующей команды:

Копирование учетных данных Twilio

Войдите в свою учетную запись Twilio или создайте ее, если у вас ее еще нет здесь. Теперь скопируйте свои ACCOUNT SID и AUTH TOKEN.

Вставьте следующее в файл .env, расположенный в ./env:

TWILIO_ACCOUNT_SID = AC82a29b91a67xxxxxxxxx
TWILIO_AUTH_TOKEN = 81682479468249xxxxxxxxxxx
MYNUM = +23490xxxxxxx
TWILIONUM  = +16463xxxxxx

Где AC82a29b91a67xxxxxxxxx — это ваше точное ACCOUNT SID, а 81682479468249xxxxxxxxxxx — это то же самое AUTH TOKEN, которое вы скопировали из своей учетной записи Twilio. TWILIONUM будет точным номером телефона, предоставленным Twilio, а MYNUM должен быть номером получателя.

Затем мы создадим функцию, которая будет экспортирована и будет глобально доступна через файл strapi.services.

В нашем служебном файле по адресу ./api/comment/services/sms.js:

module.exports = {
      sendSms() {
        const accountSid = process.env.TWILIO_ACCOUNT_SID;
        const authToken = process.env.TWILIO_AUTH_TOKEN;
        const myNum = process.env.MYNUM;
        const twilioNum = process.env.TWILIONUM;
        const client = require("twilio")(accountSid, authToken);
        client.messages
          .create({
            body: "Hello Admin, someone just posted a comment",
            from: twilioNum, //the phone number provided by Twillio
            to: myNum, // your own phone number
          })
          .then((message) => console.log(message.sid));
      },
    };

Запуск службы SMS

Теперь давайте перейдем к ./api/comment/controllers/comment.js и определим, что будет происходить всякий раз, когда пользователь комментирует наше приложение.

В нашем ./api/comment/controllers/comment.js мы будем вызывать глобальный strapi.services и другие методы, которые мы создали в ./api/comment/services/sms.js.

module.exports = {
        async create(ctx) {
        strapi.service("api::comment.sms").sendSms();
        return await strapi
          .service("api::comment.comment")
          .create(ctx.request.body);
        },
    
        async get(ctx) {
        return await strapi
          .service("api::comment.comment")
          .get(ctx.request.body);
      },
    }

Всякий раз, когда мы делаем запрос на публикацию в коллекциях комментариев, он звонит в Таможенную службу, которая связывается с сервером Twilio и отправляет нам SMS. Теперь создайте сервис create в ./api/comment/service/comment.js, чтобы сохранить фактический комментарий в нашу коллекцию.

"use strict"
    module.exports = () => ({
      async create(data) {
        return await strapi.entityService.create("api::comment.comment", {
          data,
        });
      },
    
       async get() {
        return await strapi.entityService.findMany("api::comment.comment");
      },
    });

Finally, configure a route for our `create` service in `./api/comment/routes/comment.js` with the code snippet below:


    module.exports = {
      routes: [
        {
          method: "POST",
          path: "/comment",
          handler: "comment.create",
          config: {
            policies: [],
            middlewares: [],
          },
        },
        {
          method: "GET",
          path: "/comment",
          handler: "comment.get",
          config: {
            policies: [],
            middlewares: [],
          },
        },
      ],
    };

Создание нового комментария с почтальоном

Мы можем проверить, будет ли доставлено SMS, когда мы попытаемся создать новый комментарий, сделав почтовый запрос. Убедитесь, что у вас есть права на создание в вашей роли приложения, перейдя к Settings->USERS & PERMISSIONS PLUGIN->Roles-> Public:

Поэтому мы будем использовать Почтальон для отправки запроса POST на этот URL-адрес http://localhost:1337/comments. Заполните следующие данные JSON в теле запроса и нажмите кнопку Отправить.

{"user": "Precious",
    "description": "I just want to comment that Strapi is dope"}

Вы также должны получить SMS, доставленное на ваш номер телефона.

Использование сервисов для отправки электронных писем

Далее мы поговорим о том, как отправлять электронные письма с помощью пользовательских сервисов. Мы постараемся уведомить себя, когда товар будет добавлен в коллекцию. Мы должны получить уведомление по электронной почте.

Давайте создадим для этого новый API:

npm run strapi generate

Команда создаст новую папку в ./api/ с именем product со следующими маршрутами подпапок, services controllers. Мы будем использовать пакет с именем nodemailer. Поэтому убедитесь, что вы установили его с помощью приведенной ниже команды.

npm install nodemailer

Создание коллекции продуктов

Давайте создадим еще одну коллекцию для API нашего продукта с помощью команды generate.

npm run strapi generate

Теперь вставьте следующие коды в сервис нашего только что созданного продукта, найденного в ./api/product/services/product.js..

const toEmail = process.env.TOEMAIL;
    const welcome = process.env.WELCOME;
    module.exports = {
      async create(data) {
        const response = await strapi.entityService.create("api::product.product", {
          data,
        });
        strapi
          .service("api::comment.sendmail")
          .send(
            welcome,
            toEmail,
            "Welcome",
            `A product has been created ${entity.name}`
          );
        return response;
      },
    };

Затем создайте контроллер для службы create в файле ./api/product/controllers/product.js с помощью приведенного ниже кода:

module.exports = {
      async create(ctx) {
        return await strapi
          .service("api::prooduct.prooduct")
          .create(ctx.request.body);
      },
    };

Затем настройте маршрут в файле ./api/product/routes/product.js с помощью кода ниже:

module.exports = {
      routes: [
        {
         method: 'POST',
         path: '/product',
         handler: 'product.create',
         config: {
           policies: [],
           middlewares: [],
         },
        },
      ],
    };

Убедитесь, что у вас есть права на создание в вашей роли приложения в продукте (Settings->USERS & PERMISSIONS PLUGIN->Roles-> Public). И, конечно же, все ваши переменные среды (TOEMAIL и WELCOME) определены в файле .env.

Создайте файл asendmail.js в ./api/sendmail/services/ и добавьте следующий код:

const nodemailer = require('nodemailer');
    const userEmail = process.env.MYEMAIL
    const userPass = process.env.MYPASS
    // Create reusable transporter object using SMTP transport.
    const transporter = nodemailer.createTransport({
      service: 'Gmail',
      auth: {
        user: userEmail,
        pass: userPass,
      },
    });
    module.exports = {
      send: (from, to, subject, text) => {
        // Setup e-mail data.
        const options = {
          from,
          to,
          subject,
          text,
        };
        // Return a promise of the function that sends the email.
        return transporter.sendMail(options);
    
      },
    };

Кроме того, определите все переменные среды (MYEMAIL и MYPASS) в файле .env. Это ваш адрес электронной почты Gmail и пароль для доступа к нему. К сожалению, чтобы наше приложение имело доступ к нашей электронной почте, нам нужно немного снизить безопасность Gmail. Это связано с тем, что Google не разрешает сторонним приложениям получать доступ к своим учетным записям без разрешения.

Перейдите в свои аккаунты Google и включите менее безопасный доступ к приложениям.

Затем создайте контроллер для службы create API нашего продукта. Теперь мы создадим новый продукт в Postman (HTTP-клиент). Отправьте запрос Post на этот URL-адрес http://localhost:1337/products. Добавьте данные JSON ниже в тело запроса:

{
"name": "A headphone",
"price": 2000
}

Когда вы нажмете кнопку «Отправить», вы должны получить следующий ответ, если все пройдет успешно:

{
    "id": 5,
    "name": "A headphone",
    "price": 2000,
    "createdAt": "2022-05-05T12:23:09.965Z",
    "updatedAt": "2022-05-05T12:23:09.965Z"
}

Вы также должны получить уведомление на свою электронную почту, как показано ниже, если все пройдет успешно:

Эта задача «Уведомление по электронной почте» — лишь малая часть того, чего вы можете достичь с помощью Strapi Services. Варианты использования Сервисов безграничны. Вы можете сделать любую бизнес-логику.

Создание приложения для комментариев

Что это за концепция сервисов в Strapi без реального примера того, как это работает? Поэтому я буду использовать Reactjs, чтобы показать вам один из многих способов работы сервисов в Strapi. Давайте отойдем от нашего текущего проекта Strapi. Вместо этого мы создадим новое приложение с помощью create-react-app.

В другом каталоге запустите эту команду, чтобы создать новое приложение React:

npx create-react-app strapicomment

Я решил назвать свое приложение страпокомментарием (свое можно назвать как угодно). После того, как наше реагирующее приложение было создано, давайте перейдем в его каталог и запустим приложение.

cd strapicomment
    yarn start

Приведенная выше команда настроит наше приложение React, и оно запустится с http://localhost:3000/.

Затем откройте кодовую базу в любом редакторе кода по вашему выбору. Я буду использовать VSCode для этого примера:

Уборка

Мы очистим проект и удалим некоторые ненужные коды с помощью шаблона React Quickstart. В папке src удалите файл logo.svg и создайте папку с именем components (куда будут помещены все наши компоненты).

Затем скопируйте и вставьте этот код, чтобы заменить существующий код в App.js:

function App() {
      return (
        <div className="App">
          <h1>Hello React</h1>
        </div>
      );
    }
    export default App;

Давайте создадим три компонента в .src/components, а именно Form.js, List.jsx и Comment.jsx. В наш Form.js вставьте следующие коды.

import React, { Component } from "react";
    export default class Form extends Component {
      constructor(props) {
        super(props);
        this.state = {
          loading: false,
          error: "",
          comment: {
            user: "",
            description: ""
          }
        };
        // bind context to methods
        this.handleFieldChange = this.handleFieldChange.bind(this);
        this.onSubmit = this.onSubmit.bind(this);
      }
      /**
       * Handle form input field changes & update the state
       */
      handleFieldChange = event => {
        const { value, name } = event.target;
        this.setState({
          ...this.state,
          comment: {
            ...this.state.comment,
            [name]: value
          }
        });
      };
      /**
       * Form submit handler
       */
      onSubmit(el) {
        // prevent default form submission
        el.preventDefault();
        if (!this.isFormValid()) {
          this.setState({ error: "All fields are required." });
          return;
        }
        // loading status and clear error
        this.setState({ error: "", loading: true });
        // persist the comments on server
        let { comment } = this.state;
        fetch("http://localhost:1337/api/comment", {
          headers:{'Content-type':'application/json'},
          method: "post",
          body: JSON.stringify(comment)
        })
          .then(res => res.json())
          .then(res => {
            if (res.error) {
              this.setState({ loading: false, error: res.error });
            } else {
              this.props.addComment(comment);
    
              this.setState({
                loading: false,
                comment: { ...comment, description: "" }
              });
            }
          })
          .catch(err => {
            this.setState({
              error: "yo! something is sideways",
              loading: false
            });
          });
      }
      /**
       * Simple validation
       */
      isFormValid() {
        return this.state.comment.user !== "" && this.state.comment.description !== "";
      }
      renderError() {
        return this.state.error ? (
          <div className="alert alert-danger">{this.state.error}</div>
        ) : null;
      }
      render() {
        return (
          <React.Fragment>
            <form method="post" onSubmit={this.onSubmit}>
              <div className="form-group">
                <input
                  onChange={this.handleFieldChange}
                  value={this.state.comment.user}
                  className="form-control"
                  placeholder="UserName"
                  name="user"
                  type="text"
                />
              </div>
              <div className="form-group">
                <textarea
                  onChange={this.handleFieldChange}
                  value={this.state.comment.description}
                  className="form-control"
                  placeholder="Your Comment"
                  name="description"
                  rows="5"
                />
              </div>
              {this.renderError()}
              <div className="form-group">
                <button disabled={this.state.loading} className="btn btn-primary">
                  Comment &#10148;
                </button>
              </div>
            </form>
          </React.Fragment>
        );
      }
    }

Я использую bootstrap для базового стиля. Я решил внести его через CDN, поэтому перейдите в общую папку в корневом каталоге, найдите index.html и вставьте это между тегами заголовка:

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" 
    integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" 
    crossorigin="anonymous">

В наш List.jsx вставляем следующие коды.

import React from "react";
    import Comment from "./Comment";
    export default function List(props) {
      return (
        <div className="commentList">
          <h5 className="text-muted mb-4">
            <span className="badge badge-success">{props.comments.length}</span>{" "}
            Comment{props.comments.length > 0 ? "s" : ""}
          </h5>
          {props.comments.length === 0 && !props.loading ? (
            <div className="alert text-center alert-info">
              Be the first to comment
            </div>
          ) : null}
          {props.comments.map((comment, index) => (
            <Comment key={index} comment={comment} />
          ))}
        </div>
      );
    }

Что мы здесь делаем, так это сопоставляем и отображаем доступные комментарии. Если такового нет, вы будете первым, кто оставит комментарий. В наш Comment.jsx вставьте следующие коды.

import React from "react";
    export default function Comment(props) {
      const { user, description } = props.comment;
      return (
        <div className="media mb-3">
          <div className="media-body p-2 shadow-sm rounded bg-light border">
            <h6 className="mt-0 mb-1 text-muted">{user}</h6>
            {description}
          </div>
        </div>
      );
    }

Вернитесь к App.js в папке src, замените его кодами ниже.

import React, { Component } from "react";
    import List from "./components/List";
    import Form from "./components/Form";
    class App extends Component {
      constructor(props) {
        super(props);
        this.state = {
          comments: [],
          loading: false
        };
        this.addComment = this.addComment.bind(this);
      }
      componentDidMount() {
        // loading
        this.setState({ loading: true });
        // get all the comments
        fetch("http://localhost:1337/api/comment")
          .then(res => res.json())
          .then(res => {
            this.setState({
              comments: res,
              loading: false
            });
          })
          .catch(err => {
            this.setState({ loading: false });
          });
      }
    
      addComment(comment) {
        this.setState({
          loading: false,
          comments: [comment, ...this.state.comments]
        });
      }
      render() {
    
        return (
          <div className="App container bg-light shadow">
    
            <div className="row">
              <div className="col-4  pt-3 border-right">
                <h6>Speak your Truth</h6>
                <Form addComment={this.addComment} />
              </div>
              <div className="col-8  pt-3 bg-white">
                <List
                  loading={this.state.loading}
                  comments={this.state.comments}
                />
              </div>
            </div>
          </div>
        );
      }
    }
    export default App;

Мы успешно создали наше приложение. Всякий раз, когда пользователь комментирует, мы получаем уведомление через SMS. Мы можем сделать то же самое с электронной почтой или любой функциональностью, которая приходит нам в голову.

Ссылки на гитхаб

Код приложения React и бэкенда Strapi доступен здесь.

Заключение

Услуги Strapi предлагают множество преимуществ, что упрощает разработку. Мы видели, как это работает в маленьком приложении, которое отправляет SMS с использованием Twillio API всякий раз, когда пользователь комментирует наше приложение. Мы также увидели, как создавать уведомления по электронной почте с помощью Strapi Services.