Adding Tickets Relationship with Discount Codes in Open Event Server

Recently (as of this writing), it was discovered that the relationship between discount codes and tickets was not implemented yet in Open Event Server. It turns out that the server has two types of discount codes – discount codes for entire events and discount codes for individual tickets of a specific event. More information on how discount code themselves are implemented in the server can be found in this blog post from 2017 – Discount Codes in Open Event Server.

So, for implementing the relationship of discount codes with tickets, it was decided to be present only for discount codes that have the DiscountCodeSchemaTicket schema, since those are the discount codes that are used for individual tickets. As a first step, the `tickets` attribute of the discount code model was removed, as it was redundant. The already implemented used_for attribute did the same job, and with better validation. At the same time, discount code was added as an attribute.

In the ticket model file:

discount_code_id = db.Column(db.Integer, db.ForeignKey('discount_codes.id', ondelete='CASCADE'))
discount_code = db.relationship('DiscountCode', backref="tickets")

Also, in the __init__ constructor:

def __init__(self, ..., discount_code_id=None, ...):
    ...
    ...
    self.discount_code_id = discount_code_id

After that, we added a discount_code_id field in the ticket schema file:

discount_code_id = fields.Integer(allow_none=True)

In this file, we also removed the redundant tickets field.

Now, we migrated the Open Event Server database via the following commands:

$ python manage.py db migrate

then

$ python manage.py db upgrade

Next, in the discount code schema file, we added the tickets relationship. Note that this is a one-to-many relationship. One discount code (for tickets) can be mapped to many tickets. Here is the code for that relationship, in the discount code schema file, under the DiscountCodeSchemaTicket class:

tickets = Relationship(attribute='tickets',
self_view='v1.discount_code_tickets',
self_view_kwargs={'id': '<id>'},
related_view='v1.ticket_list',
related_view_kwargs={'discount_code_id': '<id>'},
schema='TicketSchemaPublic',
many=True,
type_='ticket')

For this, we, of course, imported the TicketSchemaPublic in this file first. After that, we created a DiscountCodeTicketRelationship class in the discount codes API file:

class DiscountCodeTicketRelationship(ResourceRelationship):
    """
    DiscountCode Ticket Relationship
    """
    decorators = (jwt_required,)
    methods = ['GET', 'PATCH']
    schema = DiscountCodeSchemaTicket
    data_layer = {'session': db.session, 'model': DiscountCode}

The next step was to add the query code to fetch the tickets related to a particular discount code from the database. For this, we added the following snippet to the query() method of the TicketList class in the tickets API file:

if view_kwargs.get('discount_code_id'):
    discount_code = safe_query(self, DiscountCode, 'id', view_kwargs['discount_code_id'], 'discount_code_id')
    # discount_code - ticket :: one-to-many relationship
    query_ = self.session.query(Ticket).filter_by(discount_code_id=discount_code.id)

The only thing that remains now is adding the API routes for this relationship. We do that in the project’s __init__.py file:

api.route(TicketList, 'ticket_list', '/events/<int:event_id>/tickets',
'/events/<event_identifier>/tickets', '/ticket-tags/<int:ticket_tag_id>/tickets',
'/access-codes/<int:access_code_id>/tickets', '/orders/<order_identifier>/tickets',
'/discount-codes/<int:discount_code_id>/tickets')

api.route(DiscountCodeTicketRelationship, 'discount_code_tickets',
'/discount-codes/<int:id>/relationships/tickets')

 

Many routes already map to TicketList, we added one for that comes from discount codes API. Now we can use Postman to check this relationship, and it indeed works as expected, as seen below!

Screen Shot 2018-08-21 at 1.54.22 PM

Here’s the end:

Screen Shot 2018-08-21 at 1.54.35 PM.png

References:

Continue ReadingAdding Tickets Relationship with Discount Codes in Open Event Server

Option to add description to an image in the Phimpme Android app

In the Phimpme Android application, users can perform various operations on images such as editing an image, sharing an image, moving the image to another folder, printing a pdf version of the image and many more. However, another important functionality that has been implemented is the option to add some description to the image. So in this blog post, I will be discussing how we achieved the functionality to add a description to any image.

Step 1

First, we need to add an option in the overflow menu to add description for the image being viewed. The option to add description to an image has been achieved by adding the following lines of code in the  menu_view_pager.xml file.

<item
  android:id=“@+id/action_description”
  android:title=“Description”
  app:showAsAction=“never” />

Step 2

Now after the user chooses the option to add description to the image, an alertdialog with an edittext would be displayed to the user to enter the description. The dialog box with edittext has been implemented with the following lines of XML code.

<android.support.v7.widget.CardView
  android:layout_width=“match_parent”
  android:layout_height=“wrap_content”
  app:cardCornerRadius=“2dp”
  android:id=“@+id/description_dialog_card”>
  <ScrollView
      android:layout_width=“match_parent”
      android:layout_height=“match_parent”>
      <LinearLayout
          android:layout_width=“match_parent”
          android:layout_height=“wrap_content”
          android:orientation=“vertical”>
          <TextView
              android:id=“@+id/description_dialog_title”
              android:layout_width=“match_parent”
              android:textColor=“@color/md_dark_primary_text”
              android:layout_height=“wrap_content”
              android:background=“@color/md_dark_appbar”
              android:padding=“24dp”
              android:text=“@string/type_description”
              android:textSize=“18sp”
              android:textStyle=“bold” />
          <LinearLayout
              android:id=“@+id/rl_description_dialog”
              android:layout_width=“match_parent”
              android:layout_height=“wrap_content”
              android:orientation=“horizontal”
              android:padding=“15dp”>
              <EditText
                  android:id=“@+id/description_edittxt”
                  android:layout_width=“fill_parent”
                  android:layout_height=“wrap_content”
                  android:padding=“15dp”
                  android:hint=“@string/description_hint”
                  android:textColorHint=“@color/grey”
                  android:layout_margin=“20dp”
                  android:gravity=“top|left”
                  android:inputType=“textCapSentences|textMultiLine”
                  android:maxLength=“2000”
                  android:maxLines=“4”
                  android:selectAllOnFocus=“true”/>
              <ImageButton
                  android:layout_width=“@dimen/mic_image”
                  android:layout_height=“@dimen/mic_image”
                  android:layout_alignRight=“@+id/description_edittxt”
                  app2:srcCompat=“@drawable/ic_mic_black”
                  android:layout_gravity=“center”
                  android:background=“@color/transparent”
                  android:paddingEnd=“10dp”
                  android:paddingTop=“12dp”
                  android:id=“@+id/voice_input”/>
          </LinearLayout>
      </LinearLayout>
  </ScrollView>
</android.support.v7.widget.CardView>

The screenshot of the dialog to enter description has been provided below.

Step 3

Now after retrieving the description added by the user, the description is saved in the realm database using the realm model object ImageDescModel which will contain the path of the image and the description added as its attributes. The path of the image has been used as the primary key for the description table. The realm model class used for the above-mentioned operation is described below.

public class ImageDescModel extends RealmObject {
  @PrimaryKey
  private String path;
  private String desc;

  public ImageDescModel() {
  }

  public ImageDescModel(String path, String title) {
      this.path = path;
      this.desc = title;
  }

  public String getId() {
      return path;
  }

  public void setId(String path) {
      this.path = path;
  }

  public String getTitle() {
      return desc;
  }

  public void setTitle(String description) {
      this.desc = description;
  }
}

This is how we have implemented the functionality to add description to images in the Phimpme Android application. To get the full source code, please refer to the Phimpme Android Github repository listed in the resource section below.

Resources

1.Realm for Android – https://realm.io/blog/realm-for-android/

2.Github-Phimpme Android Repository – https://github.com/fossasia/phimpme-android/

3.Working with realm tutorial – https://www.androidhive.info/2016/05/android-working-with-realm-database-replacing-sqlite-core-data/

Continue ReadingOption to add description to an image in the Phimpme Android app

Fetching Private Skill from SUSI Server

SUSI Server needs to provide an API which would return the private skill’s file content. The private skill is used for the botbuilder project. Since the skill is private, the fetching process is little different from that of the public skills. The client has to provide the user’s access_token and a parameter private to access the private skill instead of the public skill. This blog explains how the private skill is being fetched from the SUSI Server.

Understanding the API

The API used to fetch public skill is /cms/getSkill.json, with the following parameters:

  • Model
  • Group
  • Language
  • Skill

These parameters helps to uniquely identify the skill.

The same API is used to fetch private skill too. But the parameters changes to:

  • Access_token
  • Private
  • Group
  • Language
  • Skill

The access token is used to authenticate the user, since the user should only be able to fetch their own private skill. The access token is also used to extract the user’s UUID which will be useful for locating the private skill.

String userId = null;
if (call.get("access_token") != null) { // access tokens can be used by api calls, somehow the stateless equivalent of sessions for browsers
ClientCredential credential = new ClientCredential(ClientCredential.Type.access_token, call.get("access_token"));
Authentication authentication = DAO.getAuthentication(credential);
// check if access_token is valid
if (authentication.getIdentity() != null) {
ClientIdentity identity = authentication.getIdentity();
userId = identity.getUuid();
}
}

 

Fetching the Private Skill from private skill repository

Now that we have all the necessary parameters, we can fetch the private skill from the susi_private_skill_data repository. The susi_private_skill_data folder has the following structure:

Thus to locate the skill, we will need the above parameters which we got from client and the user’s UUID. The following code checks if the client is requesting for private skill and changes the directory path accordingly.

// if client sends private=1 then it is a private skill
String privateSkill = call.get("private", null);
File private_skill_dir = null;
if(privateSkill != null){
private_skill_dir = new File(DAO.private_skill_watch_dir,userId);
}
String model_name = call.get("model", "general");
File model = new File(DAO.model_watch_dir, model_name);
if(privateSkill != null){
model = private_skill_dir;
}

 

Result:

Resources

Continue ReadingFetching Private Skill from SUSI Server

Store User’s Chatbots in SUSI Server

Users can create their own private skill which corresponds to their susi bot in SUSI Server. We store these private skills inside the susi_private_skill_data directory. We also store the information of the chatbots of each user in chatbot.json file inside the local storage of the server. It contains the list of chatbots created by each user against their UUIDs. This makes it easier and more organised to retrieve the chatbots of the users and their associated settings. This blog explains how the chatbots are being saved in the chatbot.json file in the SUSI server.

Receiving Private Skill from client

The client can create a private skill by accessing the /cms/createSkill.txt API. Along with the other parameters used to create a skill, it also has to send private=1 so that the server can recognise that this is a private skill. The private skills are stored in the folder susi_private_skill_data. The API is made by the CreateSkillService.java file.

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.setHeader("Access-Control-Allow-Origin", "*"); // enable CORS
    String userEmail = null;
    JSONObject json = new JSONObject();
    Part imagePart = req.getPart("image");
    if (req.getParameter("access_token") != null) {
        if (imagePart == null) {
            json.put("accepted", false);
            json.put("message", "Image not given");
        } 

 

Getting skill parameters and Saving skill locally

The client sends various parameters related to the skill. Such as the group name, language, skill name, image, etc. Also to identify the skill as a private skill, it needs to send private=1 parameter. If it is a private skill, then we call the function storePrivateSkillBot().

if(privateSkill != null){
    this.storePrivateSkillBot(userId, skill_name, group_name, language_name);
    try (Git git = DAO.getPrivateGit()) {
        git.add().addFilepattern(".").call();
        // commit the changes
        DAO.pushCommit(git, "Created " + skill_name, userEmail);
        json.put("accepted", true);

    } catch (IOException | GitAPIException e) {
        e.printStackTrace();
        json.put("message", "error: " + e.getMessage());

    }
}

 

Creating the chatbot.json file and saving user’s bot

Inside the function storePrivateSkillBot(), we create the json file in the local storage and save the user’s bot along with their user ids. If the bot with the same name already exists, then we update it or else create a new one.

private static void storePrivateSkillBot(String userId, String skillName, String group, String language) {
    JsonTray chatbot = DAO.chatbot;
    JSONArray userBots = new JSONArray();
    JSONObject botObject = new JSONObject();
    JSONObject userChatBotsObject = new JSONObject();
    if (chatbot.has(userId)) {
        userBots = chatbot.getJSONObject(userId).getJSONArray("chatbots");
    }
        // save a new bot
    botObject.put("name",skillName);
    botObject.put("group",group);
    botObject.put("language",language);
    userBots.put(botObject);
    userChatBotsObject.put("chatbots",userBots);
    chatbot.put(userId,userChatBotsObject,true);
} 

 

In chatbot.json file, on creating private skills, the following json gets stored:

Resources

 

Continue ReadingStore User’s Chatbots in SUSI Server

Creating API to Retrieve Images in SUSI.AI Server

SUSI Server needed to have an API where users can upload and store images. This images will be useful in setting custom theme for the botbuilder or for chat.susi.ai. User can upload image from their systems instead of pasting the link of an image stored in cloud. Also we need an API to retrieve the stored image given its full path. This blog explains how the SUSI Server returns the image stored upon requesting.

Understanding where the image is stored

The images uploaded to the SUSI Server is stored in its local storage. The local storage is where all the json files and other data are stored. We store the images inside “image_uploads” folder. In the get image API, the client will provide the full path of the image i.e the user UUID and the image’s full name. Then we need to fetch this image from the local storage and return it.

Retrieving the image from local storage

The Retrieving image API has the endpoint “/cms/getImage.png”. The servlet file is “GetImageServlet.java”. The client has to send the full path of the image in the parameter “image”. A sample GET request is “http://127.0.0.1:4000/cms/getImage.png?image=963e84467b92c0916b27d157b1d45328/1529692996805_susi icon.png”.

We need to retrieve the given image from the correct path.

Query post = RemoteAccess.evaluate(request);
String image_path = post.get("image","");
File imageFile = new File(DAO.data_dir  + File.separator + "image_uploads" + File.separator+ image_path);
if (!imageFile.exists()) {response.sendError(503, "image does not exist"); return;}
String file = image_path.substring(image_path.indexOf("/")+1);

 

Writing the image in Byte Stream

Now that we have the image file, we need to write that image in a Byte array output stream. We read the pixels data from the image using “FileInputStream” method.

ByteArrayOutputStream data = new ByteArrayOutputStream();
byte[] b = new byte[2048];
InputStream is = new BufferedInputStream(new FileInputStream(imageFile));
int c;
try {
while ((c = is.read(b)) >  0) {data.write(b, 0, c);}
} catch (IOException e) {}

 

Returning the image to the client

Now that we have the image data in the Byte array, we can send that data to the client. Before that we have to set the response type. We set the response type accordingly if the image is png, gif, or jpg. For other types, we the response type as “octet-stream”. Finally we attach the image in the response object and send it.

if (file.endsWith(".png") || (file.length() == 0 && request.getServletPath().endsWith(".png"))) post.setResponse(response, "image/png");
else if (file.endsWith(".gif") || (file.length() == 0 && request.getServletPath().endsWith(".gif"))) post.setResponse(response, "image/gif");
else if (file.endsWith(".jpg") || file.endsWith(".jpeg") || (file.length() == 0 && request.getServletPath().endsWith(".jpg"))) post.setResponse(response, "image/jpeg");
else post.setResponse(response, "application/octet-stream");

ServletOutputStream sos = response.getOutputStream();
sos.write(data.toByteArray());
post.finalize();

 

Result:

Resources

 

Continue ReadingCreating API to Retrieve Images in SUSI.AI Server

Option to rename albums in the Phimpme Android Application

In the Phimpme Android application, users can perform various operations on the albums available such as creating a zip file of the album, hiding an album and many more. However, one another useful functionality that has been added to the Phimpme Android application is the option to rename albums. So in this post, I will be discussing the implementation of the renaming functionality.

Step 1

First we need to add an option in the overflow menu to rename an album while we long-select an album or we are viewing photos inside a particular album. The option in the overflow menu can be added by integrating the following lines of code in the menu_albums.xml file(this file contains the xml code for overflow menu options provided for albums).

<item
android:id=”@+id/renameAlbum”
android:title=”@string/rename”
app:showAsAction=”never” />

Step 2

Now after the user selects the option in the overflow menu to rename the current viewing album, an alertdialog with an edittext would be displayed to the user to enter the new name for the album. The alertdialog containing the edittext has been implemented with the following lines of code.

<android.support.v7.widget.CardView
xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:app=”http://schemas.android.com/apk/vn.mbm.phimp.me”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
app:cardCornerRadius=”2dp”
android:id=”@+id/dialog_chose_provider_title”>
<LinearLayout
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:orientation=”vertical”>
<TextView
android:id=”@+id/rename_title”
android:layout_width=”match_parent”
android:textColor=”@color/md_dark_primary_text”
android:layout_height=”wrap_content”
android:background=”@color/md_dark_appbar”
android:padding=”24dp”
android:text=”@string/rename_photo_action”
android:textSize=”18sp”
android:textStyle=”bold” />
<RelativeLayout
android:id=”@+id/container_edit_text”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:orientation=”vertical”
android:padding=”24dp”/>
</LinearLayout>
</android.support.v7.widget.CardView>

The screenshot of the dialog to enter the new name is provided below.

Step 3

Finally, after retrieving the new name for the album entered by the user, the renameAlbum method is invoked by passing in the application context and new name as the parameters. Now inside the renameAlbum method, a new file path is created by the use of the getAlbumPathRenamed method of the StringUtils class and the current path of the album is changed to the new path generated earlier. Similarly path of each media inside the album is changed accordingly by the use of the method getPhotoPathRenamedAlbumChange of the StrinUtils class by looping through the media objects one by one. The lines of code implemented to perform the above-mentioned operations are provided below.

public boolean renameAlbum(final Context context, String newName) {
 found_id_album = false;
 boolean success;
 File newDir = new File(StringUtils.getAlbumPathRenamed(getPath(), newName));
 File oldDir = new File(getPath());
 success = oldDir.renameTo(newDir);
 if(success) {
    for (final Media m : media) {
       File from = new File(m.getPath());
       File to = new File(StringUtils.getPhotoPathRenamedAlbumChange(m.getPath(), newName));
       scanFile(context, new String[]{ from.getAbsolutePath() });
       scanFile(context, new String[]{ to.getAbsolutePath() }, new MediaScannerConnection.OnScanCompletedListener() {
          @Override
          public void onScanCompleted(String s, Uri uri) {
             if (!found_id_album) {
                id = MediaStoreProvider.getAlbumId(context, s);
                found_id_album = true;
             }
             Log.d(s, “onScanCompleted: “+s);
             m.setPath(s); m.setUri(uri.toString());
          }
       });
    }
    path = newDir.getAbsolutePath();
    name = newName;
 }
 return success;
}

This is how we have implemented the functionality to rename particular albums in the Phimpme Android application. To get the full source code, please refer to the Phimpme Android Github repository listed in the resource section below.

Resources

1.Android Developer Guide – https://developer.android.com/reference/android/media/MediaScannerConnection

2.Github-Phimpme Android Repository – https://github.com/fossasia/phimpme-android/

3.Media Scanner tutorial –https://stackoverflow.com/questions/13270789/how-to-run-media-scanner-in-android

Continue ReadingOption to rename albums in the Phimpme Android Application

Creating API to Upload Images in SUSI.AI Server

SUSI Server needed to have an API where users can upload and store images. This images will be useful in setting custom theme for the botbuilder or for chat.susi.ai. User can upload image from their systems instead of pasting the link of an image stored in cloud. This blog explains how the SUSI Server stores the images in its local storage.

Creating Upload Image Service

UploadImageService.java is the file which creates the API to upload image. After creating the UploadImageService class in this file, we need to include the class in SusiServer.java to enable the API:

// add services
  services = new Class[]{
...
UploadImageService.class

Restricting access rights and giving API endpoint name

The Upload Image API should be available only to logged in users and not to anonymous users. Therefore we need to set the base user role to “USER”.  Also we need to set the name of the API endpoint. Lets set it to “/cms/uploadImage.json”.

@Override
public UserRole getMinimalUserRole() {
return UserRole.USER;
}

@Override
public String getAPIPath() {
return "/cms/uploadImage.json";
}

 

Creating API to accept the Post request

We need to accept the image uploaded by the client in a post request. The post request will contain the following parameters:

  • access_token – used to verify identity of the user and get their UUID
  • image_name – the name of the image
  • image (the image file) – the actual image file

To accept the post request on this route, we define the following function:

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setHeader("Access-Control-Allow-Origin", "*"); // enable CORS
JSONObject json = new JSONObject();
Part imagePart = req.getPart("image");
if (req.getParameter("access_token") != null) {
if (imagePart == null) {
json.put("accepted", false);
json.put("message", "Image not given");
} else {
// save image

 

Getting the correct storage location

We are saving the image in the local storage of the server, i.e inside the “data” folder. Inside it, we store inside the “image_uploads” folder. The image path and name has three parts:

  • User’s UUID as the sub folder’s name
  • System time in milliseconds as the first part of the image’s name
  • The image’s name given by the user as the second part of the image’s name

String image_name = req.getParameter("image_name");
ClientCredential credential = new ClientCredential(ClientCredential.Type.access_token, req.getParameter("access_token"));
Authentication authentication = DAO.getAuthentication(credential);
ClientIdentity identity = authentication.getIdentity();
String userId = identity.getUuid();
String imagePath = DAO.data_dir  + File.separator + "image_uploads" + File.separator + userId;

 

Saving the image

After we have formed the location of the image to be stored, we need to actually write that file to the local storage. Here is the code snippet which does that:

// Reading content for image
Image image = ImageIO.read(imagePartContent);
BufferedImage bi = this.toBufferedImage(image);
// Checks if images directory exists or not. If not then create one
if (!Files.exists(Paths.get(imagePath))) new File(imagePath).mkdirs();
String new_image_name = System.currentTimeMillis() + "_" + image_name;
File p = new File(imagePath + File.separator + new_image_name);
if (p.exists()) p.delete();
ImageIO.write(bi, "jpg", new File(imagePath + File.separator + new_image_name));

 

Thus the image gets stored in the local storage inside of the folder named by user’s UUID

Here, the number 963e84467b92c0916b27d157b1d45328 is the user UUID of the user and 1529692996805 is generated by getting the System time in milliseconds. This helps to differentiate between images of same name uploaded by the same user.

Resources

Continue ReadingCreating API to Upload Images in SUSI.AI Server

Migrating Event Ratings of Open Event with Stored Procedures

Many developers know about procedural languages and have used them in some form or another, but this is really an unpopular tool, despite its power. There are many advantages (and few disadvantages) of these languages, which we will learn about soon. Having a right amount of database-stored procedure code with the help of these languages can really enhance the speed and responsiveness of an application. This article will teach us how procedural languages can be utilized in database management and how they were used recently for a bug fix in Open Event Server.

PostgreSQL, like any other powerful, relational database management system (RDBMS), provides the functionality to create and use stored procedures. Essentially, a stored procedure is database logic code which is saved on the database server. This code can be executed directly in the database, and can (and is!) often used to shift business logic from the application layer of a software to the database layer. This simple shift often has many advantages – including faster execution (as code executes at a lower stack level) and better security. When firing database queries from the application layer (i.e., the code that programmers write for storing programmable objects, performing business logic and so on), it often happens that parameters from the programming language itself are passed in to SQL, which then generates a complete SQL query. For example, here’s how a novice query might look like:

import psycopg2
conn = psycopg2.connect(dbname="oevent", user="john", password="start")
cur = conn.cursor()
name = "Sam"
cur.execute("SELECT * FROM users WHERE name='%s'" % name)  # DANGEROUS!

This is an extremely “exposed” code that can be exploited for malicious access, with a technique called SQL injection. This technique essentially “injects” malicious code via these passed parameters, like the variable name mentioned in the above code. With having stored procedures for business logic, there is no room for SQL injection. They solve this problem by writing the query beforehand, and having the parameterized data as a different entity. The pre-processed query within the corresponding stored procedure now looks like

SELECT * FROM users WHERE name=?

 

The database driver sends the name of this stored procedure (or, in standard parameterised queries, just the query text itself) and a list of parameters, as distinct separate entities in the protocol. More details on how stored procedures enhance security can be found here.

After learning so much about the advantages of stored procedures (which are enabled by procedural languages), let’s write one! Postgres supports multiple languages for writing stored procedures; here we will use PL/pgSQL, which is the most popular choice for Postgres. This procedural language, inspired (heavily) by Oracle’s PL/SQL language, looks very similar to SQL. To use this procedural language, we have to first install it. In Postgres, procedural languages are installed per-database, not server-wide. We can use the popular Postgres client psql for this purpose, or simply the createlang command on the command line:

$ createlang plpgsql yourdb

 

Now let’s create a simple procedure that prints the corresponding grades for the event ratings. In the psql shell, type the following:

CREATE OR REPLACE FUNCTION grade(rating NUMERIC) RETURNS TEXT AS
$$
BEGIN
IF rating < 1 THEN
RETURN 'D';
ELSIF rating < 2 THEN
RETURN 'C';
ELSIF rating < 3 THEN
RETURN 'B';
ELSIF rating < 4 THEN
RETURN 'A';
ELSE
RETURN 'A+';
END IF;
END;
$$ STRICT LANGUAGE plpgsql IMMUTABLE;

 

The first line defines the function signature – its name, parameters’ type and return type. The line specifies which procedural language to use. The IMMUTABLE keyword specifies that the stored procedure – this function, cannot modify the database by itself. The STRICT part says that the function should always return null when any of the arguments are null. So, the function is not executed when there are null arguments, rather a null result is assumed automatically. Finally, everything within the $$ quotes is the logic of our function, in PL/pgSQL syntax. As we can see, the syntax is almost identical to that of plain SQL.

Let us now create some dummy data to test the above stored procedure. In the same psql shell, type the following:

CREATE TABLE ratings(id INTEGER, rating NUMERIC);
INSERT INTO ratings VALUES (1, 2.3), (2, 0.5), (3, 1.7), (4, 4.5), (5, 3.3);

 

The ratings table now looks as follows:

yourdb=# SELECT * FROM ratings;
id | rating
----+--------
1 |    2.3
2 |    0.5
3 |    1.7
4 |    4.5
5 |    3.3
(5 rows)

To test our stored procedure, we fire the following query:

SELECT id, grade(rating) FROM ratings;

The result is:

id | grade
----+-------
1 | B
2 | D
3 | C
4 | A+
5 | A
(5 rows)

Thus, our stored procedure works perfectly! This shows how you can write code for your application in the database itself, the code which would otherwise be present in the application layer. For writing more complex stored procedures, you can always consult the excellent PostgreSQL documentation on this topic!

PL/pgSQL turned out to be especially useful for a recent bug patch in Open Event Server. The rating attribute of event feedbacks was initially (and incorrectly) of String type. When converting it to Float type, I had migrated the schema, but the migration of existing data was nontrivial. Since the earlier ratings were of type String, we weren’t really sure whether the values in the rating column will all contain numbers. Furthermore, we also wanted to round those numbers to the “nearest 0.5”, so that ratings like 4.4 and 2.2 get converted to more conventional values like 4.5 and 2.0 respectively. To accomplish this, I had to add a stored procedure in the related migration file. Here’s how it looked:

class ReplaceableObject(object):
    def __init__(self, name, sqltext):
        self.name = name
        self.sqltext = sqltext

update_rating_func = ReplaceableObject(
    "update_rating(rating text)",
    """
    RETURNS text AS $$
    DECLARE
        r NUMERIC;
    BEGIN
        r = cast(rating as NUMERIC);
        IF r < 0 OR r > 5 THEN
            RETURN '0';
        END IF;
        r = round(r*2) / 2;
        RETURN cast(r as VARCHAR);
    EXCEPTION WHEN invalid_text_representation THEN
        RETURN '0';
    END;
    $$
    STRICT
    LANGUAGE plpgsql IMMUTABLE;
    """)

We created a simple class ReplaceableObject so that the name of the function is, sort of, a separate attribute from its definition, as required by the migration library alembic. Once the stored procedure was defined as above, the upgrade() function of the migration file was modified accordingly:

def upgrade():
    op.create_or_replace_sp(update_rating_func)
    op.execute("UPDATE feedback SET rating=update_rating(rating)")
    op.execute("DROP FUNCTION update_rating(text)")

    op.alter_column('feedback', 'rating',
        existing_type=sa.VARCHAR(),
        type_=sa.Float(),
        existing_nullable=False,
        postgresql_using='rating::double precision')

Notice how the UPDATE query calls our stored procedure update_rating() to make existing ratings of the form we desire. Once that is done, the procedure is no longer needed, so it is DROP-ed and then, finally, the type is changed to Float. So indeed, procedural languages are used for real!

Also, what’s more exciting is that one can write Postgres stored procedures in Python itself! Python is among the four procedural languages supported by PostgreSQL, the other two being Perl and Tcl. Here’s a simple Python example of the grading code we wrote earlier:

CREATE OR REPLACE FUNCTION grade(rating NUMERIC) RETURNS TEXT AS
$$
    if rating < 1:
        return 'D'
    elif rating < 2:
        return 'C'
    elif rating < 3:
        return 'B'
    elif rating < 4:
        return 'A'
    else
        return 'A+'
$$ STRICT LANGUAGE plpython3u IMMUTABLE;

This should work identical to the PL/pgSQL equivalent code above. But don’t forget to change the command for installing the procedural language, for Python it looks like:

$ createlang plpython3u yourdb

Needless to say, the 3 here stands for Python3.

Even with all this flexibility and power, one must note that procedural languages are not designed to replace programming languages. Procedural code can easily become difficult to maintain, and one often needs superuser privileges to create stored procedures. Moreover, portability is a mess with them. That is especially true when moving from one database system (like Postgres) to another (like Microsoft SQL Server). Therefore, for optimum application performance and code maintainability, ensure that there is a healthy balance between your business logic residing in stored procedures and the application layer.

This post covered a lot of information about code residing in the database and its use cases. If you want to know more about best practices involving separation of business logic, please check out this thread on StackExchange’s software engineering website.

Resources

Continue ReadingMigrating Event Ratings of Open Event with Stored Procedures

Implementing Event Average Rating with SQLAlchemy

While implementing Open Event Server version 2, we decided to have a better way of ranking events by their quality. To define the “quality” of events, the programmers decided to accumulate the feedbacks of specific events and take the average of the ratings involved. Thus, the average rating of an event proves to be a good (enough) measure of its quality. While there are many ways to implement aggregate relationships in an app, here I demonstrate a rather modern methodology which insists on storing such aggregates once they’re computed.

Since there is always a space-time/computation tradeoff in software development, this task was no exception. At first, the straightforward idea that came to my mind was to query the Postgres database every time a request for average rating was made. This sounds simple, but with hundreds of events stored on a server, and potentially thousands of users querying for events, this seemed to be a computationally expensive approach. It was costly because the average rating aggregate would be computed for each request, and there could potentially be thousands of such concurrent requests. Therefore, a better idea is to compute the aggregate once, store it in the database (compromising space in the tradeoff mentioned above, but saving a large amount of computation at the same time), and update only when a change is made. In our specific case, the update should happen only when a new rating is added, a rating is deleted or an existing rating is modified. Since the advantages outnumbered the disadvantages, this was the strategy to be implemented.

The first step in implementing average rating was to modify the database model of events accordingly. For this, I performed the necessary imports in the events’ database model file:

from sqlalchemy_utils import aggregated
from app.models.feedback import Feedback

Now comes the tricky part. We want an average_rating column in the events table, that contains the mean rating of events. The values in this column should be updated every time a change is made to the feedbacks table. To perform this sort of functionality, the best, raw tool is a Postgres trigger. A trigger should be created that is fired after every update to the feedbacks table, which should update the average rating values in the events table. Here’s how the raw code of such a trigger looks like:

create or replace function UpdateAverageRating() returns trigger AS
$$
BEGIN
UPDATE events SET average_rating=(
SELECT avg(rating) FROM feedbacks
WHERE event_id=NEW.event_id
GROUP BY event_id
)

WHERE id = NEW.event_id
END
$$
language plpgsql

Fortunately, the translation of such a trigger into SQLAlchemy-speak is not only easy, but also very elegant. The imports I showed above already set the context for this translation.

The event model class looks like the following:

class Event(db.Model):
    """Event object table"""
    __tablename__ = 'events'
    __versioned__ = {
'exclude': ['schedule_published_on', 'created_at']
}
    id = db.Column(db.Integer, primary_key=True)
    identifier = db.Column(db.String)
    name = db.Column(db.String, nullable=False)
    external_event_url = db.Column(db.String)

    

    

    

The list of attributes continues, and to the end of this list, we now add a decorated method:




xcal_url = db.Column(db.String)
is_sponsors_enabled = db.Column(db.Boolean, default=False)
discount_code_id = db.Column(db.Integer, db.ForeignKey(
'discount_codes.id', ondelete='CASCADE'))

@aggregated('feedbacks', db.Column(db.Float))
def average_rating(self):
    return db.func.avg(Feedback.rating)

That’s it with the translation – this slick, decorated method can be thought of as a bridge between Python and the trigger shown earlier that’s usually implemented in the database itself. Once this method is added, we save the model file and perform a database migration:

$ python manage.py db migrate

This generates a migration file associated with our changes. This file shows the following alembic migration code:

"""empty message

Revision ID: 1471fe0d04ee
Revises: 49f3a33f5437
Create Date: 2018-06-08 19:32:47.485543

"""

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '1471fe0d04ee'
down_revision = '49f3a33f5437'


def upgrade():
    op.add_column('events', sa.Column('average_rating', sa.Float(), nullable=True))
    op.add_column('events_version', sa.Column('average_rating', sa.Float(), autoincrement=False, nullable=True))

def downgrade():
    op.drop_column('events_version', 'average_rating')
    op.drop_column('events', 'average_rating')

Now that the file is generated, we upgrade our database state by utilizing this migration file:

$ python manage.py db upgrade

And here are the successful migration logs that immediately follow the upgrade command:

INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO  [alembic.runtime.migration] Will assume transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade 49f3a33f5437 -> 1471fe0d04ee, empty message

 

This completes the implementation of the average rating attribute of events. We can use the same technique to implement other attributes in our server, like sum, and minimum, just to name a couple. We saw how SQLAlchemy so elegantly manages to map all the mapping from Python code to database commands. This is just one of the plethora of advantages of using database object-relational-mappers (ORMs), and Open Event Server utilizes them to full extent.

Resources:

Continue ReadingImplementing Event Average Rating with SQLAlchemy

Enforcing Constraints Throughout a Flask Back-End

Recently it was discovered that Open Event Server does not validate attendees’ tickets. Specifically, it was possible to create an arbitrary number of attendees who’d be attending an event on the same ticket! To fix this, a constraint had to be set up across different layers of Open Event Server, which is based on Flask and Postgres. This post will demonstrate how the constraint was added in the server, and these steps should apply in general to any Flask-based server with a relational back-end.

First of all, the immediate idea that comes after investigating such an issue, is to add a UNIQUE constraint to the database. For this specific case, the problem was in ticket_holders table of the Open Event database. There was originally no check imposed on the ticket_id and event_id columns.

As can be seen in the ticket_holders schema (using the \d+ ticket_holders command), there is no mention of uniqueness on either column. The initial guess was that the combination of ticket_id and event_id should be unique throughout the table to avoid multiple holders attending on the same ticket. However,imposing uniqueness on just the ticket_id column would’ve also worked. So, to be on the safer side, I moved ahead by adding uniqueness on both the columns.

To fix this, we need to make changes to the ticket_holder model. So, in the ticket_holder model file, we add a __table_args__ attribute to the TicketHolder class. This attribute represents the various constraints imposed on the ticket_holders table:

class TicketHolder(db.Model):
    __tablename__ = "ticket_holders"
    __table_args__ = (
db.UniqueConstraint('ticket_id', 'event_id', name='ticket_event'),
) # this is the constraint we add

    id = db.Column(db.Integer, primary_key=True)
    firstname = db.Column(db.String, nullable=False)
    lastname = db.Column(db.String, nullable=False)






The TicketHolder class has attributes named ticket_id and event_id, so to add a unique constraint over them, we pass their names to the UniqueConstraint constructor. Also, any suitable name can be given to the constraint, I chose ‘ticket_event’ to simply emphasize the relationship. Now that we’ve edited the database model file, we have to perform a database migration.

Before we command the migration, we have to remove the entries that potentially violate the constraint we just imposed. As a temporary fix, I connected to the database and deleted all non-unique rows via plain SQL. For a more consistent fix, I will implement this simple deletion code in the database migration file, if need be. So, once the non-unique rows are gone, we perform the database migration as follows:

$ python manage.py db migrate

And then,

$ python manage.py db upgrade

These commands may be different for different projects, but their purpose is the same – to update the database. The upgrade command generates a migration file which looks as follows:

from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils


# revision identifiers, used by Alembic.
revision = '9d21de792967'
down_revision = '194a5a2a44ef'


def upgrade():
op.create_unique_constraint('ticket_event', 'ticket_holders', ['ticket_id', 'event_id'])

def downgrade():
op.drop_constraint('ticket_event', 'ticket_holders', type_='unique')

We can see that the upgrade() function has the command for adding our constraint. Once the database has been upgraded, we can revisit the schema of ticket_holders table (using the \d+ ticket_holders command again). Now we can see that our constraint is added very well in the table schema.

Now, if one tries to create multiple attendees that attend on the same ticket, s/he gets a 500 server error. Here are the related server logs:

2018-06-05 22:04:03.824 IST [46705] ERROR:  duplicate key value violates unique constraint "ticket_event"
2018-06-05 22:04:03.824 IST [46705] DETAIL:  Key (ticket_id, event_id)=(2, 6) already exists.
2018-06-05 22:04:03.824 IST [46705] STATEMENT:  UPDATE ticket_holders SET event_id=6 WHERE ticket_holders.id = 16
127.0.0.1 - - [05/Jun/2018 22:04:03] "POST /v1/attendees HTTP/1.1" 500 -
INFO:werkzeug:127.0.0.1 - - [05/Jun/2018 22:04:03] "POST /v1/attendees HTTP/1.1" 500 -

To get a more graceful error, we also need to make changes in the API schema. This will also allow to validate the data before it gets to the database. So, in the attendees.py file, we need to add a check. This check should extract the ticket and event ids from the data posted and see whether there is already an attendee in the database attending that event on the same ticket. If such an attendee is discovered, the check should raise an error and report it back to the API caller. The suitable place for this check is the before_post() method of the AttendeeListPost class. In any Flask app serving a REST API, such a method (perhaps of a different name) should exist in the API file corresponding to a model. Our check looks like the following within the before_post() method:

from flask_rest_jsonapi import ResourceList
from app.api.helpers.exceptions import ConflictException
from app.models import db
from app.models.ticket_holder import TicketHolder






class AttendeeListPost(ResourceList):
"""
List and create Attendees through direct URL
"""

def before_post(self, args, kwargs, data):
"""
Before post method to check for required relationship and proper permissions
:param args:
:param kwargs:
:param data:
:return:
"""
require_relationship(['ticket', 'event'], data)







if db.session.query(TicketHolder.id).filter_by(
ticket_id=int(data['ticket']), event_id=int(data['event'])
).scalar() is not None:
raise ConflictException(
{'pointer': '/data/attributes/ticket_id'},
"Attendee with this ticket already exists for the same event"
)

Once this check is implemented, we’re all good to go. Now, if an attendee is created that maps to a ticket belonging to an already existing attendee, the following error is sent back to the API caller:

{
"errors": [
{
"status": 409,
"source": {
"pointer": "/data/attributes/ticket_id"
},
"title": "Conflict",
"detail": "Attendee with this ticket already exists for the same event"
}
],
"jsonapi": {
"version": "1.0"
}
}

This completes our work of enforcing this constraint throughout our Flask server. This leads to a more consistent database and potentially avoids confusion at actual events!

Resources:

Continue ReadingEnforcing Constraints Throughout a Flask Back-End