Database Listener for User Centric Events

Badgeyay is an open-source utility developed by FOSSASIA to generate badges for conferences and events. The project is separated into two components to ease maintainability. First is the frontend part which is in ember and second part is backend which is in Flask. The choice of database to support backend is PostgreSQL.

Now comes the problem, whenever a user is registered in the database, he should receive  a verification mail, that he is successfully registered on the platform. For this case we have to listen to the database events on User model. This issue has greater extendibility than only sending greeting or verification mail to the user. We can extend this to trigger services that are dependent on user registration, like subscribing the user to some set of services based on the plan he opted while registration and many more.

These type of issues cannot be handled by normal relationship with tables and other entities, there has to be logic in place to support such functionalities. So the challenges for tackling the problem are as follows:

  • Listen to the insert_action in User model
  • Extracting the details necessary for the logic
  • Execute particular logic

Procedure

  1. Attaching insert_action listener to the User model. This function will get triggered whenever an entity is saved in the User model.

<!– HTML generated using hilite.me –>

@db.event.listens_for(User, "after_insert")
def logic(mapper, connection, target): {
......
}
  1. When the function gets triggered, extract the details of the saved user that is necessary for the logic. As currently we are sending greeting mail to the user,we only need the email of the user. Target is the actual saved user passed as argument to the listening function from the library.

<!– HTML generated using hilite.me –>

msg = {}
msg['subject'] = "Welcome to Badgeyay"
msg['receipent'] = target.email
msg['body'] = "It's good to have you onboard with Badgeyay. Welcome to " \
"FOSSASIA Family."
sendMail(msg)
  1. Now the details are passed to sendMail() function for sending mail which uses flask-mail library to send mail to the recipient.
    def sendMail(message):
    if message and message.receipent:
    try:
    msg = Message(
    subject=message.subject,
    sender=app.config['MAIL_USERNAME'], Response(200).generateMessage(
    recipients=[message.receipent],
    body=message.body)
    Mail(app).send(msg)
    except Exception as e:
    return jsonify(
    Response(500).exceptWithMessage(
    str(e),
    'Unable to send the mail'))
    return jsonify(
    Response(200).generateMessage(
    'Mail Sent'))
    else:
    return jsonify(
    Response(403).generateMessage(
    'No data received')) 'No data received'))
    
  2. This will send mail to the user who has been registered to the application.

Similarly we can use separate logics according to the need of the application.

 

The Pull Request for the above functionality is at this Link

Topics Involved

Working on the issue involve following topics:

  • Configuring mail service to allow insecure apps access.
  • Sending mail from the flask-mail to end user
  • Attaching listener to listen for database change
  • Extraction of data from saved object in database sqlalchemy.

Resources

  • Sending Mails Programmatically –  Link
  • Flask Mail Documentation – Link
  • Listening to database events – Link
  • Enabling access to GMAIL to send mails to recipient – Link

Variable Font Size Badgeyay

Badgeyay is a simple badge generator that aims for promoting an open-source tool for generation of badges in PDF format. The project has options to choose from predefined set of images or upload a background image. User can choose from set of fonts and color of the same. But now Badgeyay also has option to choose custom font-size in generation of badges.

To implement font size feature,  first, the component that is determining the font of the label has to be identified. The label that determines the text on the badge is the <text> label and within it, the label that determines the properties of the text is <tspan>. So mainly we need to alter the properties in the tspan.

The property that determines the font size for the badge is font-size and its default value is set to 31.25 px. If the property in the labels changed, then we can see the corresponding changes in the PDF generated from the svg.

Now the challenges were:

  • To Determine the font value from the frontend.
  • Using the same for the font-config.
  • Changing the built svg accordingly.

Procedure

  1. Firstly frontend component has to be changed to incorporate a slider to give input for the variable font size. So a range input is inserted with range from 15 px to 45 px and default as 30 px. The size_print label gets changed dynamically to show the value selected from the range slider.
<li>
<input type="radio" name="fontsize" id="font-size-picker"> Choose font size
</li>
<section id="font-size-input" style="display:none;">
<label for="inputFile" id="size_print"></label>
<div>
<input type="range" id="font-size" max=45 min=15 step=5 value=30  class="form-control" name="font_size">
</div>
</section>
  1. After adding the component, form script is changed to add toggle behaviour to the button. For adding the toggling behaviour in the component, checkbox is used and the value of the label is updated dynamically as the slider value is changed.
$("#size_print").text($("#font-size").val() + " px");

      $("#font-size-picker").click(function () {

          if ($(this).is(":checked")) {

              $("#font-size-input").css("display", "block");

          } else {

              $("#font-size-input").css("display", "none");

          }

      });

      $("#font-size").on('input', function () {

          $("#size_print").text($(this).val() + " px");

      });
  1. After completing the work on the frontend, it is necessary to modify the backend too. The method for choosing custom font has to be refactored. It now checks whether the custom font is set or font size variable is set, and creates a config file for fonts which after use gets deleted.
font_config = {}
   # custom font is specified
   if custom_font != '':
       font_config['font'] = custom_font
   if font_size != '':
       font_config['font_size'] = font_size
   if custom_font != '' or font_size != '':
       json_str = json.dumps(font_config)
       print(json_str)
       f = open(os.path.join(app.config['UPLOAD_FOLDER'], 'fonts.json'), "w+")
       f.write(json_str)
       f.close()
  1. The generator class is modified as well to accommodate the changes, by adding a new class attribute called font_size. We find the keys in the dict object loaded from the file and assign the same to class attribute.
if 'font_size' in self.DATA.keys():
               self.font_size = self.DATA['font_size']
  1. Make the necessary change in the svg, so that font size change can be represented in the generated PDF. Replace the old font size with the new font size specified.
if self.font_size:
           content = content.replace("font-size:31.25px",
                                     "font-size:" + str(self.font_size) + "px")
  1. After all the changes, badge generated will have a different font size.

The Pull request for the above change is at this Link

Topics Involved

Working on this Issue (Link) involve following topics:

  • SVG Label manipulation
  • Sending data from Ember frontend to Backend.
  • Javascript for the toggle radio button.

References

  • Extracting map information from the SVG (Link)
  • Python Documentation for class (Link)
  • About Github Pages- (Link)
  • Ajax Serialize method to serialize the form contents – (Link)

Refactoring and Remodeling Badgeyay API

When we build a full scale production application, we make sure that everything is modeled correctly and accordingly to the need of the code. The code must be properly maintained as well as designed in such a way that it is less prone to errors and bugs.

Badgeyay is also targeting to be a full production application, and in order to achieve it we first need to re-factor the code and model it using a strong yet maintainable structure.

What is the current state of Badgeyay?

Currently Badgeyay is divided into two sub folders.

\badgeyay
    \frontend
    \backend
    .
    .

It is backed by two folders, viz backend and frontend. The ‘backend’ folder handles the API that the service is currently running. The ‘frontend’ folder houses the Ember based frontend logic of the application.

Improvements to Badgeyay Backend

We have worked on improving Backend for Badgeyay. Instead of traditional methods, i.e. current method, of API development; We employ a far better approach of using Flask Blueprint as a method of refactoring the API.

The new backend API resides inside the following structure.

\badgeyay
    \backend
        \blueprint
            \api

The API folder currently holds the new API being formatted from scratch using

  • Flask Blueprint
  • Flask Utilities like jsonify, response etc

The new structure of Badgeyay Backend will follow the following structure

api
    \config
    \controllers
    \helpers
    \models
    \utils
    db.py
    run.py

The folders and their use cases are given below

  • \config
    • Contain all the configuration files
    • Configurations about URLs, PostgreSQL etc
  • \controllers
    • This will contain the controllers for our API
    • Controllers will be the house to our routes for APIs
  • \helpers
    • Helpers folder will contain the files directly related to API
  • \models
    • Models folder contains the Schemas for PostgreSQL
    • Classes like User etc will be stored in here
  • \utils
    • Utils will contain the helper functions or classes
    • This classes or functions are not directly connected to the APIs
  • db.py
    • Main python file for Flask SQLAlchemy
  • run.py
    • This is the main entry point.
    • Running this file will run the entire Flask Blueprint API

How does it help?

  • It helps in making the backend more solid.
  • It helps in easy understanding of application with maintained workflow.
  • Since we will be adding a variety of features during Google Summer of Code 2018 therefore we need to have a well structured API with well defined paths for every file being used inside it.
  • It will help in easy maintaining for any maintainer on this project.
  • Development of the API will be faster in this way, since everything is divided into sub parts therefore many people can work on many different possibilities on the same time.

Further Improvements

Since this structure has been setup correctly in Badgeyay now, so we can work on adding separate routes and different functionalities can be added simultaneously.

It ensures faster development of the project.

Resources

Badgeyay: Integrating EmberJS Frontend with Flask Backend

Badgeyay is a simple badge generator with a simple web UI that generates a printable badge in PDFs. The project had gone through different cycles starting from a Flask server to a CLI application then a python library and now API Interface for generation of badges.

According to latest changes in the project structure, now the frontend and backend are independent components developed in Ember JS and Flask respectively. Now there is a need to connect the frontend to the backend, which means the user should see the response on the same page without refresh, if the badge generated successfully. AJAX would fit right into the spot. Asynchronous Javascript and XML also known as AJAX, will enable us to perform asynchronous operation on the page without refreshing the page.

We can make an API call to the Server running in backend or deployed on heroku, but the server is not suitable for doing CORS(Cross-Origin Resource Sharing), ability to share the resources on server with the client having different domain names, but as the server and the frontend are not hosted on the same host  so there is a need to enable the server to accept CORS request calls.

Now the challenges were:

  • Enabling Flask Server to accept CORS requests.
  • AJAX query for sending request to the Flask server.

Procedure

  1. Giving the form an id and creating an AJAX request to the Flask server (may be localhost or deployed on heroku).
<form id=”form1″ action=”” method=”post” enctype=”multipart/form-data” onsubmit=”return validate()”>

 

When the generate button is clicked, an AJAX request is made to the server to generate badges and at the same time prevent the page from refreshing. In the AJAX request we set the CORS header to allow the domain.

 

<script type=”text/javascript”>
$(document).ready(function () {
$(‘#form1’).submit(function (event) {
event.preventDefault();
$.ajaxSetup({
headers: {“Access-Control-Allow-Origin”: “*”}
});
$.ajax({
url: “http://badgeyay-api.herokuapp.com/api/v1.0/generate_badges”,
data: $(this).serialize(),
type: ‘POST’,
success: function (data) {…},
error: function (error) {…}
})
});
})
</script>

 

  1. Import the library and enable the API endpoint to accept CORS requests.
from flask_cors import CORS
cors = CORS(app, resources={r”/api/*”: {“origins”: “*”}})

 

  1. Add Logic for appending the download link by extracting the download link from the response and replacing the static text in the template with the download link, also changing the download variable to the filename, by stripping the base url from the download link.
if (data[“response”][0][“type”] === “success”) {
$(‘#success’).css(‘visibility’, ‘visible’);
let link = data[“response”][0][“download_link”];
link = link.replace(“backend/app/”, “http://badgeyay-api.herokuapp.com/”);
$(‘#badge-link’).attr(“href”, link);
link = link.replace(“static/badges/”, “”);
$(‘#badge-link’).attr(“download”, link);
}

 

  1. Output the success on the page.
<div id=”success” style=”visibility: hidden;”>
<div class=”flash-success”>Your badges have been created successfully.</div>
<div class=”text-center”>
<a id=”badge-link” href=”http://badgeyay-api.herokuapp.com/static/badges/{{msg}}-badges.pdf”
class=”btn btn-success”
download=”{{msg}}-badges.pdf”>Download as
PDF</a>
</div>
</div>

 

  1. Frontend and Backend now are connected to each other.The Server now accepts CORS requests and response is generated after the user requests from Frontend.

 

The Pull Request with the above changes is on this Link

Topics Involved

Working on this issue (Link)  involves following topics :

  • Enabling Flask Server for CORS
  • Request Headers
  • AJAX request for CORS.

References

Badgeyay: Custom Fonts in generation of badges

Badgeyay is an open source project of FOSSASIA. The main idea for this project is to provide an open-source alternative for badge generation process for any event. It can generate badges according to a predefined config or we can also submit our own custom config for the generation of the badges. We can use custom background, text and other things. One thing that is not present is the choice for choosing a custom font for the badge. I have made a contribution for adding this functionality with selection of some common fonts in the code.

Procedure

  1. Add a Button in index.html for the choice of the font and also preview them at the same time. 
    <label>Choose your font</label>
    <ul style=“list-style-type:none”>
     <li>
        <input type=“radio” name=“fontsource” id=“custfont”> Use Custom font
                        </li>
                        <section id=“custom-font” style=“display: none;”>
        <label for=“inputFile”>Select from following fonts</label>
        <div class=“btn-group”>
           <button type=“button” class=“btn btn-default dropdown-toggle” data-toggle=“dropdown” aria-haspopup=“true” aria-expanded=“false”>
              <span class=“placeholder2”>Select a font</span>
              <span class=“glyphicon glyphicon-chevron-down”></span>
           </button>
           <ul class=“dropdown-menu”>
              {% for i in custom_fonts %}
              <li class=“font-options” style=“font-family:'{{i}}'” data-item=“{{i}}”>{{i}}</li>
              {% endfor %}
           </ul>
        </div>
     </section>
     <input type=“hidden” name=“custfont” value=“”>
    </ul>

     

     

  2. Add javascript for the toggle in the check button and CSS for the Font option button.
.$(“.font-options”).click(function () {
  var i = $(this).data(“item”);
  $(“.placeholder2”).text(i);
  $(“input[name=’custfont’]”).val(i);
});

 

.font-options {
border-bottom: 1px solid darkgray;
padding: 9px;
}

 

  1. Font list is passed in the index page.
CUSTOM_FONTS = [‘monospace’, ‘sans-serif’, ‘sans’, ‘Courier 10 Pitch’, ‘Source Code Pro’]

 

render_template(‘index.html’, default_background=default_background, custom_fonts=CUSTOM_FONTS)

 

  1. Config file for font has been created, so that it can be used by different files.
custom_font = request.form[‘custfont’]
# Custom font is selected for the text
if custom_font != :
  json_str = json.dumps({
      ‘font’: custom_font
  })
  f = open(os.path.join(app.config[‘UPLOAD_FOLDER’], ‘fonts.json’), “w+”)
  f.write(json_str)
  f.close()

 

  1. Font preference is taken from the file at the time of generation of the badge (once only for all the badges in a single run).
font_choice = None
if os.path.isfile(os.path.join(UPLOAD_FOLDER, ‘fonts.json’)):
  DATA = json.load(open(os.path.join(UPLOAD_FOLDER, “fonts.json”)))
  font_choice = DATA[‘font’]

 

  1. Changes in the SVG are made according to the preference for the PDF generation. If the user wants a custom font then it updates the svg using the config else not.
content = CONTENT
if font_choice:
  content = content.replace(“font-family:sans-serif”,
                            “font-family:” + font_choice)
  content = content.replace(“inkscape-font-specification:sans-serif”,
                            “inkscape-font-specification:” + font_choice)
  content = content.replace(“font-family:ubuntu”,
                            “font-family:” + font_choice)
  content = content.replace(“inkscape-font-specification:ubuntu”,
                            “inkscape-font-specification:” + font_choice)

 

  1. Finally the Updated SVG is used for Badge Generation with custom fonts embedded.

Resources

Resources utilised for adding this functionality

  • Fonts in SVG – Link
  • Embed fonts in Inkscape SVG – Link
  • Embed fonts in PDF and SVG – Link

 

Adding a Last Modified At column in Open Event Server

This blog article will illustrate how, with the help of SQLAlchemy, a last modified at column, with complete functionality can be added to the Open Event Server database. To illustrate the process, the blog article will discuss adding the column to the sessions api. Since last modified at is a time field, and will need to be updated each time user successfully updates the session, the logic to implement will be a slightly more complex than a mere addition of a column to the table.

The first obvious step will comprise of adding the column to the database table. To achieve the same, the column will have to be added to the model for the sessions table, as well as the schema.

In app/api/schema/sessions.py:

...
class SessionSchema(Schema):
   """
   Api schema for Session Model
   """
   ...
   last_modified_at = fields.DateTime(dump_only=True)
   ...

And in app/models/sessions.py:

import pytz
...

class Session(db.Model):
   """Session model class"""
   __tablename__ = 'sessions'
   __versioned__ = {
       'exclude': []
   }
   ...
   last_modified_at = db.Column(db.DateTime(timezone=True),   
   default=datetime.datetime.utcnow)
   def init(self, ..., last_modified_at=None))
     #inside init method
     ...
     self.last_modified_at = datetime.datetime.now(pytz.utc)
     ...

NOTE: The users for the open event organiser server will be operating in multiple time zones and hence it is important for all the times to be in sync, hence the open event database maintains all the time in UTC timezone (python’s pytz module takes care of converting user’s local time into UTC time while storing, thus unifying the timezones.) From this, it directly follows that the time needs to be timezone aware hence timezone=true is passed, while defining the column.

Next, while initialising an object of this class, the last modified time is the time of creation, and hence

datetime.now(pytz.utc) is set as the initial value which basically stores the current time in UTC timezone format.

Finally, the logic for updating the last modified at column every time any other value changes for a session record needs to be implemented. SQLAlchemy provides an inbuilt support for detecting update and insert events which have been used to achieve the goal. To quote the official SQLAlchemy Docs,  “SQLAlchemy includes an event API which publishes a wide variety of hooks into the internals of both SQLAlchemy Core and ORM.

@event.listens_for(Session, 'after_update')
def receive_after_update(mapper, connection, target):
  target.last_modified_at = datetime.datetime.now(pytz.utc)

The listens_for() decorator is used to register the event according to the arguments passed to it. In our case, it will register any event on the Session API (sessions table), whenever it updates.

The corresponding function defined below the decorator, receive_after_update(mapper, connection, target) is then called, and session model (table) is the the registered target with the event. It sets the value of the last_modified_at to the current time in the UTC timezone as expected.

Lastly, since the changes have been made to the database schema, the migration file needs to be generated, and the database will be upgraded to alter the structure.

The sequence of steps to be followed on the CLI will be

> python manage.py db migrate
> python manage.py db upgrade

Resources

UI automated testing using Selenium in Badgeyay

With all the major functionalities packed into the badgeyay web application, it was time to add some automation testing to automate the review process in case of known errors and check if code contribution by contributors is not breaking anything. We decided to go with Selenium for our testing requirements.

What is Selenium?

Selenium is a portable software-testing framework for web applications. Selenium provides a playback (formerly also recording) tool for authoring tests without the need to learn a test scripting language. In other words, Selenium does browser automation:, Selenium tells a browser to click some element, populate and submit a form, navigate to a page and any other form of user interaction.

Selenium supports multiple languages including C#, Groovy, Java, Perl, PHP, Python, Ruby and Scala. Here, we are going to use Python (and specifically python 2.7).

First things first:
To install these package run this code on the CLI:

pip install selenium==2.40
pip install nose

Don’t forget to add them in the requirements.txt file

Web Browser:
We also need to have Firefox installed on your machine.

Writing the Test
An automated test automates what you’d do via manual testing – but it is done by the computer. This frees up time and allows you to do other things, as well as repeat your testing. The test code is going to run a series of instructions to interact with a web browser – mimicking how an actual end user would interact with an application. The script is going to navigate the browser, click a button, enter some text input, click a radio button, select a drop down, drag and drop, etc. In short, the code tests the functionality of the web application.

A test for the web page title:

import unittest
from selenium import webdriver

class SampleTest(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        cls.driver = webdriver.Firefox()
        cls.driver.get('http://badgeyay-dev.herokuapp.com/')

    def test_title(self):
        self.assertEqual(self.driver.title, 'Badgeyay')

    @classmethod
    def tearDownClass(cls):
        cls.driver.quit()

 

Run the test using nose test.py

Clicking the element
For our next test, we click the menu button, and check if the menu becomes visible.

elem = self.driver.find_element_by_css_selector(".custom-menu-content")
self.driver.find_element_by_css_selector(".glyphicon-th").click()
self.assertTrue(elem.is_displayed())

 

Uploading a CSV file:
For our next test, we upload a CSV file and see if a success message pops up.

def test_upload(self):
        Imagepath = os.path.abspath(os.path.join(os.getcwd(), 'badges/badge_1.png'))
        CSVpath = os.path.abspath(os.path.join(os.getcwd(), 'sample/vip.png.csv'))
        self.driver.find_element_by_name("file").send_keys(CSVpath)
        self.driver.find_element_by_name("image").send_keys(Imagepath)
        self.driver.find_element_by_css_selector("form .btn-primary").click()
        time.sleep(3)
        success = self.driver.find_element_by_css_selector(".flash-success")
        self.assertIn(u'Your badges has been successfully generated!', success.text)

 

The entire code can be found on: https://github.com/fossasia/badgeyay/tree/development/app/tests

We can also use the Phantom.js package along with Selenium for UI testing purposes without opening a web browser. We use this for badgeyay to run the tests for every commit in Travis CI which cannot open a program window.

Resources

Make Flask Fast and Reliable – Simple Steps

Flask is a microframework for Python, which is mostly used in web-backend development.There are projects in FOSSASIA that are using flask for development purposes such as Open Event Server, Query Server, Badgeyay. Optimization is indeed one of the most important steps for a successful software product. So, in this post some few off- the-hook tricks will be shown which will make your flask-app more fast and reliable.

Flask-Compress

  1. Flask-Compress is a python package which basically provides de-facto lossless compression  to your Flask application.
  2. Enough with the theory, now let’s understand the coding part:
    1. First install the module

2. Then for a basic setup

3.That’s it! All it takes is just few lines of code to make your flask app optimized .To know more about the module check out flask-compress module.

Requirements Directory

  1. A common practice amongst different FOSSASIA  projects which involves dividing requirements.txt files for development,testing as well as production.
  2. Basically when projects either use TRAVIS CI for testing or are deployed to Cloud Services like Heroku, there are some modules which are not really required at some places.  For example: gunicorn is only required for deployment purposes and not for development.
  3. So how about we have a separate directory wherein different .txt files are created for different purposes.
  4. Below is the image of file directory structure followed for requirements in badgeyay project.

  1. As you can see different .txt files are created for different purposes
    1. dev.txt – for development
    2. prod.txt – for production(i.e. deployment)
    3. test.txt – for testing.

Resources

badgeYAY – An abrupt flow of code

Badgeyay is a web application which takes a CSV file, an image file and an optional config.json file, and converts them into a PDF file which consist of a set of badges as per the data in the CSV and the image as its background. In order to contribute to the badgeyay repository, a contributor is expected to have some knowledge of Python Flask, HTML and CSS. An understanding of git version control system is inevitable in open source.

Flask – Web development in baby steps

First things first – Having a local copy

Sign up for GitHub and head over to the Badgeyay repository. Then follow these steps.

  1. Go ahead and Fork the repository
  2. Star the repository
  3. Get the clone of the forked version on you local machine using git clone https://github.com/<username>/badgeyay.git
  4. Add upstream using git remote add upstream https://github.com/fossasia/badgeyay.git

How a flask application works

A flask application basically consists of an app.py or main.py file which is run using the command python main.py

The main.py file consists of:


from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run(debug=True)

This snippet starts the flask server at localhost:5000 and index.html template gets rendered on visiting the root url. All the templates reside in templates folder while the static asset files are stored in static folder.

Steps:

  1. First. we imported the Flask class and a function render_template.
  2. Next, we created a new instance of the Flask class.
  3. We then mapped the URL / to the function index(). Now, when someone visits this URL, the function index() will execute.
  4. The function index() uses the Flask function render_template() to render the index.html template we just created from the templates/ folder to the browser.
  5. Finally, we use run() to run our app on a local server. We’ll set the debug flag to true, so we can view any applicable error messages if something goes wrong, and so that the local server automatically reloads after we’ve made changes to the code.

The template consists of a base layout which is extended by the pages.

templates/layout.html

<!DOCTYPE html>
<html>
<head>
<title>Flask App</title>
</head>
<body>
<header>
<h1 class="logo">Flask App</h1>
</header>

{% block content %}
{% endblock %}

</body>
</html>

templates/index.html

{% extends "layout.html" %}
{% block content %}
<h2>Welcome to the Flask app</h2>
<h3>This is the index page for the Flask app</h3>
<h3>{% endblock %}</h3>

With this and a little understanding of python, and you are all set to contribute to flask repositories such as badgeyay.

Resources

Open Event API Server: Implementing FAQ Types

In the Open Event Server, there was a long standing request of the users to enable the event organisers to create a FAQ section.

The API of the FAQ section was implemented subsequently. The FAQ API allowed the user to specify the following request schema

{
 "data": {
   "type": "faq",
   "relationships": {
     "event": {
       "data": {
         "type": "event",
         "id": "1"
       }
     }
   },
   "attributes": {
     "question": "Sample Question",
     "answer": "Sample Answer"
   }
 }
}

 

But, what if the user wanted to group certain questions under a specific category. There was no solution in the FAQ API for that. So a new API, FAQ-Types was created.

Why make a separate API for it?

Another question that arose while designing the FAQ-Types API was whether it was necessary to add a separate API for it or not. Consider that a type attribute was simply added to the FAQ API itself. It would mean the client would have to specify the type of the FAQ record every time a new record is being created for the same. This would mean trusting that the user will always enter the same spelling for questions falling under the same type. The user cannot be trusted on this front. Thus the separate API made sure that the types remain controlled and multiple entries for the same type are not there.

Helps in handling large number of records:

Another concern was what if there were a large number of FAQ records under the same FAQ-Type. Entering the type for each of those questions would be cumbersome for the user. The FAQ-Type would also overcome this problem

Following is the request schema for the FAQ-Types API

{
 "data": {
   "attributes": {
     "name": "abc"
   },
   "type": "faq-type",
   "relationships": {
     "event": {
       "data": {
         "id": "1",
         "type": "event"
       }
     }
   }
 }
}

 

Additionally:

  • FAQ to FAQ-type is a many to one relation.
  • A single FAQ can only belong to one Type
  • The FAQ-type relationship will be optional, if the user wants different sections, he/she can add it ,if not, it’s the user’s choice.

Related links