Допустим моя компания кому-то звонит и что-то продаёт и мне нужна простая CRM, которая позволит вести справочник контактов и наглядно отслеживать их состояние в плане продажи.

Сейчас мы с вами сделаем такую систему с нуля за считанные минуты. Для этого мы будем использовать фрэймворк Allcountjs.

Самые нетерпеливые могут сразу же посмотреть на результат.



Что за AllcountJS?


Так как это первая статья на русском про AllcountJS, то скажу пару слов о самом фреймворке.
AllcountJS — это фреймворк c открытым исходным кодом для быстрой разработки веб и мобильных приложений на Node.js. AllcountJS построен на MEAN стеке (MongoDB, Express, AngularJS, NodeJS). Развивается поддержка не только MongoDB, но и других БД, в первую очередь SQL.

Центральная часть AllcountJS приложения это конфигурационный .js файл с декларативным описанием структуры приложения: сущности, отношения между ними, права доступа и т.д.

CRUD операции к сущностям, управление пользователями, настройки прав доступа, и REST API до всех функций приложения — всё это доступно сразу, без необходимости написания дополнительного кода.

Базовый веб-интерфейс приложения генерируется автоматически. Естественно его можно дорабатывать и изменять как угодно — только необходимы знания AngularJS и и языка шаблонов jade. Если возможностей фреймворка окажется недостаточно, возможно расширение функциональности через использование механизма внедрения зависимостей.

Установка и запуск


Начать работу с AllcountJS можно несколькими способами: в качестве самостоятельного приложения, как модуль другого NodeJS приложения или запустить приложение на AllcountJS.com.

Самый простой способ увидеть AllcountJS в деле — это просто запустить одно из демо-приложений в галерее.

Рассмотрим и вариант с отдельным приложением. Для этого должны быть установлены MongoDB, NodeJS и Git. (если у вас Ubuntu то вы можете посмотреть скринкаст по установке ). Для установки AllcountJS выполним:
npm install -g allcountjs-cli
allcountjs init cusdevcrm-allcount
cd cusdevcrm-allcount
npm install


Далее откройте app-config/main.js в директории с приложением и замените его содержимое следующим кодом:
A.app({
  appName: "CusDev CRM",
  appIcon: "phone",
  onlyAuthenticated: true,
  menuItems: [
    {
      name: "Contact",
      entityTypeId: "Contact",
      icon: "user"
    }, {
      name: "Board",
      entityTypeId: "FlowBoard",
      icon: "bars"
    }, {
      name: "Statuses",
      entityTypeId: "Status",
      icon: "sort"
    }
  ],
  entities: function(Fields) {
    return {
      Contact: {
        fields: {
          name: Fields.text("Name").required(),
          company: Fields.text("Company").required(),
          site: Fields.text("Site"),
          email: Fields.text("Email"),
          skype: Fields.text("Skype"),
          phone: Fields.text("Phone"),
          lastContactDate: Fields.date('Last contact date'),
          status: Fields.fixedReference("Status", "Status")
        },
        views: {
          FlowBoard: {
            customView: "board"
          }
        }
      },
      Status: {
        fields: {
          name: Fields.text("Name").required(),
          order: Fields.integer("Order").required()
        },
        sorting: [['order', 1]],
        referenceName: "name"
      }
    }
  },
});

Теперь давайте подробнее разберёмся с тем что же этот код делает.

Общие настройки приложения


Вся конфигурация приложения располагается внутри единственного метода. Название и иконка приложения задаются с помощью свойств appName и appIcon. AllcountJS использует иконки Font Awesome. Вы можете выбрать любую иконку и использовать её в приложении просто сославшись на неё по имени. При ссылке на иконку необходимо отбросить префикс fa-. Мы возьмем обычный телефонный значок для нашей «CusDev CRM».
 appName: "CusDev CRM",
 appIcon: "phone",

За настройку аутентификации отвечает свойство onlyAuthenticated. Оно определяет возможность использования приложения незарегистрированными пользователями. Мы же не хотим что бы доступ до CRM был у всех, поэтому:
onlyAuthenticated: true

Далее в конфигурации идёт пункт menuItems, но мы к нему вернёмся после того как опишем сущности и их представления.

Контакты и статусы


Теперь мы готовы к тому что бы описать наши бизнес-сущности.
Опишем сущность Contact. Пусть у контакта будут два обязательных текстовых поля — Name и Company. Несколько текстовых полей с информацией о способах связи, дата последнего контакта и текущий статус контакта.
Поле status — это ссылка на сущность статус в которых может находиться контакт (например “Написали”, “Ответил”, “Готов на встречу”).
entities: function(Fields) {
    return {
      Contact: {   
        fields: {    
          name: Fields.text("Name").required(),    
          company: Fields.text("Company").required(),    
          site: Fields.text("Site"),   
          email: Fields.text("Email"),   
          skype: Fields.text("Skype"),    
          phone: Fields.text("Phone"),
          lastContactDate: Fields.date('Last contact date'),    
          status: Fields.fixedReference("Status", "Status")    
        }
      },    
      Status: {    
        fields: {    
          name: Fields.text("Name").required(),   
          order: Fields.integer("Order").required()    
        },    
        sorting: [['order', 1]],    
        referenceName: "name"    
      }    
    }   
  }


Отображение на доске


Каждая сущность может иметь представления (вью). Они задаются в свойстве view. Представления в AllcountJS похожи на представления в SQL. Они не занимают дополнительного места в БД, но вы можете работать с ними как с обычными сущностями. Представления можно использовать для того что бы обеспечить специальное поведение, интерфейс и права доступа.

В нашем случае мы будем использовать представления для того что бы сделать специальный UI в виде интерактивной доски для отображения контактов. Зададим для контакта представление FlowBoard а внутри него, в свойстве customView, UI шаблон board.
views: {    
  FlowBoard: {    
    customView: "board"   
  }   
}

Он ссылается на .jade файл, содержащий код шаблона. AllcountJS использует шаблонизатор jade для генерации конечного HTML для веб интерфейсов. Подробнее о jade можно прочитать на jade-lang.com
В комплекте AllcountJS есть шаблон канбан доски с драг-анд-дропом. Наше представление board является его расширением. Создадим файл board.jade со следующим кодом внутри:
extends project/card-board    
block panelBody    
  .panel-body    
    h4 {{item.name}}    
    p {{item.company}}    
    p {{item.lastContactDate | date}} 



Меню


Теперь мы дошли и до меню нашего приложения. Оно задаётся в свойстве menuItems и состоит из ссылок на сущности и представления.
menuItems: [    
    {    
      name: "Contact",    
      entityTypeId: "Contact",    
      icon: "user"    
    }, {    
      name: "Board",    
      entityTypeId: "FlowBoard",    
      icon: "bars"    
    }, {    
      name: "Statuses",    
      entityTypeId: "Status",    
      icon: "sort"    
    }    
  ]


REST API


Если у нас есть какое-нибудь другое приложение, которое нужно интегрировать с нашей CRM, то это не будет проблемой, т.к. все функции приложения доступны через REST API.

Для начала нужно получить токен для доступа. Допустим наша CRM располагается на https://localhost:9080, в таком случае необходимо отправить HTTP POST запрос на
https://localhost:9080/api/sign-in
С таким JSON содержимым в теле:
{"username": "admin", "password": "admin"}

В ответ вернётся примерно такой ответ:
{"token":"56026b8ad7939dcb552a1668:PSDhU6x_VeIzqPYtIATXzEdMTLE"}

Теперь можно, например, получить список всех контактов. Для этого отправим HTTP GET запрос, но уже с заголовком
X-Access-Token в который передадим полученный токен из предыдущего запроса.
На https://localhost:9080/api/entity/FlowBoard
или напрямую на https://localhost:9080/api/entity/Contact
В ответ вы получите список всех контактов в формате JSON. Естественно что вы можете также удалять, создавать и обновлять ваши контакты через API.

AllcountJS может больше


В статье, на примере простой CRM показана лишь малая часть возможностей AllcountJS. В демо-приложении кроме выше рассмотренного есть ещё и русская локализация, которую можно отключить вписав forcelocale: «en» в раздел с общими настройками. А завершает конфигурацию скрипт по добавлению тестовых данных.

В следующих статьях мы продолжим знакомить вас с устройством и возможностями фреймворка. В одной из следующих статей мы покажем как можно сделать и мобильное приложение к нашей CRM. Тоже достаточно быстро. Кому не терпится узнать больше и кого не пугает английский — добро пожаловать на официальный сайт allcountjs.com.

Комментарии (20)


  1. lair
    04.11.2015 11:57

    Для начала нужно получить токен для доступа. [...] необходимо отправить HTTP POST[...] c таким JSON содержимым в теле:

    Ауч, почему очередной велосипед вместо OAuth?


    1. paveltiunov
      04.11.2015 13:53

      lair, спасибо за вопрос!
      Если коротко, то мы поддерживаем пока только JWT-like подход авторизации API для клиентов, которые владеют паролем напрямую. Например, это используется для мобильных приложений. OAuth для поддержки авторизации третьих сторон в планах. Подробнее про разницу подходов можно почитать здесь: www.seedbox.com/en/blog/2015/06/05/oauth-2-vs-json-web-tokens-comment-securiser-un-api


      1. lair
        04.11.2015 14:51

        Так в OAuth 2 это тоже есть, называется client credentials flow. Зачем велосипед?


        1. paveltiunov
          04.11.2015 15:31

          lair, Вы действительно правы. OAuth 2 полностью поддерживает этот сценарий. Когда мы внедрим OAuth, он вероятно станет нашим основным механизмом авторизации. На момент разработки текущего механизма стояла задача обеспечить авторизацию только между двумя сторонами. JWT подход решает эту задачу. OAuth 2 также ее решает, но он сложнее в реализации. JWT подход был выбран из-за простоты и скорости разработки.


          1. lair
            04.11.2015 15:57

            OAuth 2 также ее решает, но он сложнее в реализации.

            Вы хотите сказать, что для node.js нет готовых решений?


            1. paveltiunov
              04.11.2015 16:29
              +1

              Сергей, хочу сказать решения есть. Вот например: github.com/thomseddon/node-oauth2-server. Но для того, чтобы его использовать, нужно как минимум решить проблему с хранением токенов. Мы поддерживаем как MongoDB так и базы данных SQL. Поэтому здесь задача немного усложняется. Т.е. это делается, но на это нужно было бы потратить немного больше времени, чем на JWT. Мы приветствуем любые инициативы относительно развития проекта. Их можно писать прямо сюда: github.com/allcount/allcountjs/issues. Если Вы знаете как можно быстро и просто реализовать OAuth 2, то мы с удовольствием примем от Вас pull request.


  1. jMas
    04.11.2015 15:06

    [mr.accuracy]На сайте написано, что за 10 минут. ;)[/mr.accuracy]
    Заинтересовало, в качестве автогенератора апи для мобильных приложений.


    1. paveltiunov
      04.11.2015 15:50

      jMas, спасибо за обратную связь!
      Вы нас поймали! :) Я думаю Максим поделится своими соображениями, где возникает разница на 5 минут.
      Рады, что смогли Вас заинтересовать! Для гибридных мобильных приложений у нас также есть интеграция с ionic (http://ionicframework.com/). Если есть вопросы, то мы доступны в чате Gitter: gitter.im/allcount/allcountjs, в группе facebook: www.facebook.com/groups/allcountjs и здесь.


    1. Sorro
      04.11.2015 16:39

      Англоговорящие поймут быстрее, и за 15 все успеют)


  1. DbLogs
    05.11.2015 02:32

    Хмм… А вот на Orienteer можно и вообще за 5 минут такое сделать причем вообще не кодируя. Но тема да — интересная… Какие есть ограничения у фреймворка? Т.е. какие задачи гарантировано не стали бы решать AllCountsJS?


  1. paveltiunov
    05.11.2015 12:33

    DbLogs, здорово! Спасибо за ссылку!
    Не рекомендую использовать AllcountJS, где нет работы с БД, т.к. его использование становится бессмысленным. AllcountJS заточен на то, чтобы сделать разработку всего, что связано с CRUD максимально простым не ограничивая возможности эко-системы Node.js. Любые бизнес приложения (CRM например) это по сути своей в основном CRUD и AllcountJS для этого хорошо подходит.
    Также мы постараемся раскрыть в ближайших публикациях как применить AllcountJS к продуктовым историям, где есть backoffice. Например, на днях мы запустили zrum.ru — поисковик по наличию мотозапчастей, на который потратили 6 часов работы. Там внутренняя БД запчастей синхронизируется с внешними excel файлами, которые лежат в открытом доступе по URL. При этом из коробки есть возможность вести каталог вручную.


  1. yurash
    05.11.2015 12:56

    Двойственные впечатления, с одной стороны — довольно любопытно. Хотелось бы так же взглянуть на roadmap и узнать планы монетизации. С другой стороны — лично меня angular смущает, мне нравятся библиотеки попроще. Более того, я не уверен, что вы с ангуляром всё делаете правильно, т.к. на вашем же allcountjs.com (который, как я прочёл, написан с использованием самого себя) вижу пару мест где во время загрузки показываются знаменитые {{...}}


    1. jMas
      05.11.2015 13:41

      Это можно полечить с помощью docs.angularjs.org/api/ng/directive/ngCloak, но ИМХО это не критичный «недочет», если использовать систему в качестве админки, пользователи все равно ничего не увидят.


    1. paveltiunov
      05.11.2015 14:11
      +1

      yurash, спасибо за обратную связь!
      Основной план по монетизации пока хостинг и on-premise DevOps решения. Мы используем CoreOS + Docker инфраструктуру для изолированного запуска приложений. Именно она используется для запуска демонстраций.
      По roadmap основные направления выглядят на данный момент так:
      — Интеграции и двусторонний обмен данными с наиболее популярными сервисами (Почта, файловые хранилища, социальные сети, SIP, SMS, Платежные системы и т.д.)
      — Поддержка наиболее популярных SQL БД (MySQL, MSSQL, Oracle, и т.д.). Сейчас есть Postgres.
      — Поддержка различных библиотек представления и выделение angular представления в отдельный модуль. Пока кандидаты на реализацию: React и ReactNative для мобильных устройств. Будем рады услышать здесь дополнительные предложения по реализации.

      Насчет angular: спасибо за бдительность! Там действительно не хватало ng-cloak. Это хак, который прячет нутро angular. Исправили. Что касается сложности/простоты angular: у него есть свои плюсы и минусы. Мы его взяли за основу, т.к. он на данный момент самый популярный JS MVC framework. Но мы на него не завязаны и строим AllcountJS не зависимым от представления. Здесь примем любую помощь в создании AllcountJS frontend'а с помощью других framework'ов!


      1. DbLogs
        06.11.2015 07:46

        Павел, а где непосредственно CoreOS запускаете? DO? Или что-то свое? Используете какое-нибудь сущетсвующее решение для «заказа» запуска нового докера?


        1. paveltiunov
          06.11.2015 10:53

          DbLogs, сейчас используем DigitalOcean. Но мы не привязаны к хостингу и планируем разворачивать свою инфраструктуру там, где будет требоваться заказчику. В том числе и в частных облаках как on-premise решение.
          Для запуска Docker контейнеров используем github.com/coreos/fleet.


          1. DbLogs
            06.11.2015 18:09

            Если не секрет, то как решаете вопрос с персистентностью контейнеров? Или пока с этим не заморачивались и нода «стирается», ну или контейнер привязыватеся к той или иной ноде?


            1. paveltiunov
              07.11.2015 11:18

              DbLogs, не секрет. Никак не решаем. Контейнер уже содержит, все что необходимо для запуска приложения почти также как это сделано в Heroku.


              1. DbLogs
                08.11.2015 02:54

                В том то и дело, что хероку при перезапуске все что пользователь «наделал» стирается полностью. Там если нужна персистентность, то надо использовать внешние ресурсы. А мы в Orienteer, естественно, хотели бы, чтобы при остановке ноды данные пользователя не пропадали. В docker есть volume фишка, но когда у тебя облако, то возникает проблема переноса volume на другую ноду по необходимости. Обычно люди через ZFS решают, но хотелось бы что-нибудь из коробки…


                1. paveltiunov
                  08.11.2015 17:17

                  DbLogs, проблема с переносом данных в облаке действительно сложная. Поэтому мы также как и Heroku используем БД или внешние сервисы для хранения данных.