Giter Site home page Giter Site logo

kontent-ai / sample-app-express-js Goto Github PK

View Code? Open in Web Editor NEW
1.0 25.0 3.0 12.45 MB

The Dancing Goat demo site created using Express.js and Pug templates using Kontent.ai as a data source.

License: MIT License

CSS 60.13% JavaScript 29.23% Pug 10.64%
delivery pug express expressjs pug-templates kontent-ai algolia azure azurecognitiveservice push-notifications

sample-app-express-js's Introduction

Kontent sample Express.js web application

Stack Overflow Discord

This is an Express application meant for use with the Dancing Goat sample project within Kontent.ai. This fully featured project contains marketing content for Dancing Goat – an imaginary chain of coffee shops. If you don't have your own Sample Project, any admin of a Kontent.ai subscription can generate one.

You can read more about our JavaScript SDKs

Setup

  1. Clone the repository

  2. Create a .env file on the root and set the projectId variable to your sample project's Project ID:

    • You can use env.example as a template for the .env file
    projectId=<your project ID>
  3. Run the following commands:

    npm install
    npm start

The application will then be available at localhost:3000 (configurable in /bin/www).

⚠️ Due to the optional webhook integration, we've hard-coded the language codes available to the application in app.js. If necessary, you can update the languages there to match the code names in Kontent:

const supportedLangs = ["en-US", "es-ES"];
const languageNames = ["English", "Spanish"];

The first language in the list will be used as the default language for the application.

Algolia Search Integration

You can test Algolia search functionality on the project's Article content types. Register for an account on Algolia and copy the App ID and Admin API key from the API Keys tab and set the variables in .env. Also create an indexName with any name you'd like:

algoliaKey=<key>
algoliaApp=<app name>
indexName=dancing_goat

The application will automatically create, configure, and populate a search index when you visit the /algolia route. It will redirect you to the home page when finished, and you should immediately be able to search for articles using the search bar.

To check out the code used to create the index, see app.js:

//generate Algolia index
app.use('/:lang/algolia', function (req, res, next) {
  let client = algoliasearch(process.env.algoliaApp, process.env.algoliaKey);
  let index = client.initIndex(process.env.indexName);
  //etc...
}

To view the search functionality, see /routes/search.js.

Automatic content translation

There is a /webhook route that you can use with workflow webhooks to automatically submit an English language variant to Microsoft's Translator Text Cognitive Service, translate the variant into other supported languages, and create new language variants in Kontent.

At the moment, this integration only works if you are using 4-letter language code names in Kontent.ai (e.g. "es-es"). The application's supported languages can be modified in app.js.

First, you need to create an Azure Cognitive Services account for the Translator Text service. Then, add Key 1 from the Keys tab to .env:

translationKey=<key>

Depending on how your translation service is configured in Azure, you may also need to add the service's region to the .env, e.g.:

translationRegion=westus2

If you are running the project locally, you can still test webhooks using ngrok (or a similar program). To use ngrok, follow their setup guide and in step 4 use the port number the Express application will run on (3000 by default). When you're done running ngrok, you should see something like the following:

ngrok

Copy the URL from the Forwarding section and paste it into a new Kontent.ai webhook's URL address with the /webhook path appended:

webhook

While you're there, add a workflow step to Workflow steps of content items to watch and remove any other events. This is the workflow step that will trigger the webhook, once any language variant is placed in that step.

Also, copy the Secret and add it to .env, then grab the Content Management API key from the API keys tab:

contentManagementKey=<CM API key>
webhookSecret=<secret>

ℹ The translation process is prepared to translate only article items.

Run your Express application, then move an English language variant into the workflow step you selected in the webhook. You should see some debugging information in the console when the webhook is consumed, then you will find your new language variants in the Draft step!

Sending push notifications

This application can also send push notifications to visitors whenever a content item in Kontent.ai is published. You can read this blog post to read more about how it works and how to set it up from scratch.

To start, you need to create a new content type in Kontent.ai with the codename "push_notification" and the following elements:

  • title: Text
  • body: Text
  • icon: Asset
  • vibrate: Multiple choice (checkbox with single value "Yes")
  • url: Text

Next, go to the Project settings > Webhooks page in Kontent.ai and create a new webhook. We want to send push notifications whenever an item of our push_notification type is published, so select "Publish" from the Content item events to watch drop-down.

push webhook

For the URL address, use the /push endpoint, e.g. https://mysite.com/push. You can also run the project locally as in the Automatic content translation section and enter the ngrok URL with /push at the end.

NOTE: Management API webhook triggers are supported as well. Use /push_cm endpoint instead.

Copy the Secret and add it to .env with the "pushSecret" key:

pushSecret=<secret>

Save the webhook. Open up a Command prompt and install web-push then generate VAPID keys for the project:

npm i web-push -g
web-push generate-vapid-keys

Copy the Public and Private key to the .env file:

vapidPublicKey=<public key>
vapidPrivateKey=<private key>

Also add the Public key to the top of /public/scripts/client.js:

const publicVapidKey = "<public key>";

The application uses SQLite database to store push notification subscriptions. Make sure to specify dbPath in the .env file, e.g.:

const dbPath = subs.sqlite;

The database will be created automatically on first subscribe attempt.

You're ready to test the notification now! Make sure to access your site via https; push notifications will not work over insecure connections. When you access the site, your browser will prompt you to accept notifications from the website. Accept it, and you should see a successful POST to /subscribe in the browser's Network tab.

Now that you're subscribed, head over to Kontent.ai and create a new content item using the push_notification content type. When you publish it, the webhook will shortly trigger and a notification will appear on your desktop:

push demo

Documentation

Read full documentation and code samples for the JavaScript Delivery SDK.

Feedback & Contributing

Check out the contributing page to see the best places to file issues, start discussions, and begin contributing.

sample-app-express-js's People

Contributors

colliercz avatar ivankiral avatar kentico-ericd avatar kontent-ai-bot avatar petrsvihlik avatar pokornyd avatar simply007 avatar

Stargazers

 avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sample-app-express-js's Issues

Incorrect implementation of observable pattern

Implementation of observable pattern is sadly incorrect.

Following is based on https://github.com/Kentico/cloud-expressjs-app/blob/master/repositories/coffee-repository.js#L14 .

As far as I understand it, the ensureItems method should return observable (maybe name it more appropriate) because you are subscribing it in https://github.com/Kentico/cloud-expressjs-app/blob/master/routes/coffee.js#L9, however you are not working with the response so the subscription doestn't do anything because data are actually resolver in https://github.com/Kentico/cloud-expressjs-app/blob/master/repositories/coffee-repository.js#L31

So basically you cannot use subscribe in ensureItems, but rather you should always use valid observable that will load data from KC (= no need for createDummyObservable).

You can use RxJS's map operator if you need to transform data inside ensureItems and still return observable.

Do not call subscribe inside a subscribe (https://github.com/Kentico/cloud-expressjs-app/blob/master/repositories/coffee-repository.js#L35). Instead you should use dedicated RxJS operators such as zip, flatMap, switchMap or something similar based on your needs. If the data does not depend on each other and you want them resolved simultaneously, you would probably want to use zip (https://www.learnrxjs.io/operators/combination/zip.html)

Why are you calling this? https://github.com/Kentico/cloud-expressjs-app/blob/master/repositories/coffee-repository.js#L37 This code should not be there and you should unsubscribe when you switch pages. There should be some event that gets triggered when you go from page to page so that you can notify your repository to cancel requests. Best implementation is done with takeUntil as can be discussed here: https://medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87

Why are you calling defer (https://github.com/Kentico/cloud-expressjs-app/blob/master/repositories/coffee-repository.js#L19) ? Defer should not be necessary, you only need to return one single observable that will contain data (e..g items) that you wont to work with and then in https://github.com/Kentico/cloud-expressjs-app/blob/master/routes/coffee.js#L10 you extends 'subscribe' with parameter that will contains your data.

Calling complete is not necessary (https://github.com/Kentico/cloud-expressjs-app/blob/master/repositories/coffee-repository.js#L38)

The method in https://github.com/Kentico/cloud-expressjs-app/blob/master/routes/coffee.js#L7 doesn't do anything as the actual logic is performed inside the ensure items and this observable doesn't really need to be there (but it should! As the logic should be moved here from ensureItems). Thats why your app still works even when you return an empty observable (https://github.com/Kentico/cloud-expressjs-app/blob/master/repositories/coffee-repository.js#L14)

Sorry if these comments are misplaced or difficult to read. I didn't really know how to describe it better. If you have any questions, feel free to let me know. I would recommend trying to 'fix' this particular use case before moving out and fixing the rest.

Upsert Errors when Content Type has a URL Slug

If the content type has a URL Slug the upsert fails with the following error:

"The URL slug element is missing the 'mode' property. Provide either 'autogenerated' or 'custom' for the 'mode' value."

Create eslint script and run it before build + use only local dependencies

Eslint should be run as a command when you build/before publish etc.. for that you can create script in your package.json.

I'm using tslint (I assume eslint is very similar) this way:

"ts-lint-local": "./node_modules/.bin/tslint",
"ts-lint:run": "npm run ts-lint-local

Also, user should ideally not be expected to install any global dependencies in order to run your app. All dependencies that you need to use to build / run the app should refer to local dependencies in your node modules. You can see an example of my tslint above.

Repo name (minor)

Excellent work getting this updated for the rebranding! One minor thing regarding the repo name though. To fall in line with the naming conventions we're using this should be named kontent-sample-app-expressjs

Extract API keys definition to .env

Motivation

It is easily possible to commit API keys as a part of the pull request in delivery.js file.

Currently, it is possible to configure the project manually

When you configure your project manually it is required to temporarily change the source code in delivery.js file and that could lead to committing this unintended change that was used for, let's say testing purposes.

Proposed solution

If the project ID was loaded from the environment, as an environment variable, it would eliminate the possibility for unintended commit to delivery.js.
Loading environment variables is possible utins dotenv library.

Best practice nowadays is to use environment variables stored in .env file.

  • Set up .gitignore to ignore .env file
  • Configure delivery.js to use .env definition for key (projectId )
  • Update Readme

Additional context

Implement typeResolvers for sdk

You are using implicitly typed models and it would be nice to use strongly typed models in case you would want to use rich text resolving, link resolving or maybe extends classes with additional methods.

Unsubscribe from all observables

Whenever you subscribe to an Observable, you also need to unsubscribe when it's no longer required (e.g. user switched to different page, component is destroyed etc...), otherwise you have memory leaks in your app and it can even affect the functionaltity of site (because observables are asynchronous so if you execute 2 requests and the second finishes before first one, it will override the result)

I would implement it with takeUntil (see https://medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87). You will have to decide when observables should be unsubscribed though as I'm not that familiar the flow of express apps.

Prepare automatic deployment

Motivation

Currently, there is no check if changes des not break the site.

Proposed solution

Implement automatic deployment with the preview deployment for PRs - ideally to Netlify.

Additional context

Add any other context, screenshots, or reference links about the feature request here.

Unify const + let and remove var

In some cases you are using const such as:

const { Observable, defer } = require('rxjs');

But in body of your code you are almost exclusively using var. Is there any reason for this? Since you can use const, use it where appropriate and same for let. Do not use var at all.

Naming convention

Naming is somewhat inconsistent, for example:

var DeliveryClient = require('../delivery');
var LinkResolver = require('../resolvers/LinkResolver');

../delivery is exported as an instance of DeliveryClient so it should be camelCase. Filename is correctly camelCase.

../resolvers/LinkResolver is exported as function so it should be camelCase as well. Filename is for some reason Pascal case, but it should be camelCase.

I guess it doesn't matter what naming convention you use as much as having some consistency. Generic rules are:

camelCase for variables and functions, PascalCase for types(classes), and UPPERCASE_SNAKE_CASE for constants.

For filenames I would use lowercase only + dashes to split up some words (e.g. 'aboutus' -> 'about-us' https://github.com/Kentico/cloud-expressjs-app/blob/bf3344636da309d4e8491d2da3fe3f595383a4fd/routes/aboutus.js)

Repository as classes

Is there any reasons you are using functions for repositories? Why not use standard classes? Classes are supported for a long time now in javascript (https://javascript.info/class)

With that you would also be able to remove these strange lines of code:

if (!(this instanceof CoffeeRepository)) return new CoffeeRepository();

Translated text/rich text elements in Content Type Snippets

Motivation

Why is this feature required? What problems does it solve?

Currently, text/rich text elements which are part of a Content-Type Snippet, are not translated.

Proposed solution

To include the data of content snippets, it would need to loop through the content type's elements and call the snippet endpoint for each one. Then, you could add the elements in the response to some array that holds all of the type elements.
It would be similar to here: https://github.com/Kentico/kontent-google-sheets-add-on/blob/master/types.gs#L55.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.