Scheduling Jobs to Check Expired Access Token in Yaydoc

In Yaydoc, we use the user access token to do various tasks like pushing documentation, registering webhook and to see it’s status.The user access token is very important to us, so I decided of adding Cron job which checks whether the user token has expired or not. But then one problem was that, if we have more number of users our cron job will send thousands of request at a time so it can break the app. So, I thought of queueing the process. I used `asyc` library for queuing the job.

const github = require("./github")
const queue = require("./queue")

User = require("../model/user");

exports.checkExpiredToken = function () {
  User.count(function (error, count) {
    if (error) {
      console.log(error);
    } else {
      var page = 0;
      if (count < 10) {
        page = 1;
      } else {
        page = count / 10;
        if (page * 10 < count) {
          page = (count + 10) /10;
        }
      }
      for (var i = 0; i <= page; i++) {
        User.paginateUsers(i, 10,
        function (error, users) {
          if (error) {
            console.log(error);
          } else {
            users.forEach(function(user) {
              queue.addTokenRevokedJob(user);
            })
          }
        })
      }
    }
  })
}

In the above code I’m paginating the list of users in the database and then I’m adding each user to the queue.

var tokenRevokedQueue = async.queue(function (user, done) {
  github.retriveUser(user.token, function (error, userData) {
    if (error) {
      if (user.expired === false) {
        mailer.sendMailOnTokenFailure(user.email);
        User.updateUserById(user.id, {
          expired: true
        }, function(error, data) {
          if (error) {
            console.log(error);
          }
        });
      }
      done();
    } else {
      done();
    }
  })
}, 2);

I made this queue with the help of async queue method. In the first parameter, I’m passing the logic and in second parameter, I’m passing how many jobs can be executed asynchronously. I’m checking if the user has revoked the token or not by sending API requests to GitHub’s user API. If it gives a response ‘200’ then the token is valid otherwise it’s invalid. If the user token is invalid, I’m sending email to the user saying that “Your access token in revoked so Sign In once again to continue the service”.

Resources:

Continue ReadingScheduling Jobs to Check Expired Access Token in Yaydoc

Control flow of SUSI AI on Android and database management using Realm

While developing a chat based android application, one of the most important things is keeping track of user’s messages. Since the user might want to access them in the absence of Internet connectivity (i.e remotely) as well, storing them locally is also important.

In SUSI we are using Realm to keep things organized in a systematic manner and constructing model (or adding appropriate attributes) for every new data type which the application needs. Right now we have three main models namely ChatMessage, WebLink and WebSearchModel. These three java classes define the structure of each possible message.  ChatMessage evaluates and classifies incoming response from server either to be an image or map or pie chart or web search url or other valid types of response. WebSearchModel and WebLink models are there to manage those results which contains link to various web searches.

Various result based lists are maintained for smooth flow of application. Messages sent in absence of Internet are stored in a list – nonDelivered. All the messages have an attribute isDelivered which is set to true if and only if they have been queried, otherwise the attribute is set to false which puts it in the nonDelivered list. Once the phone is connected back to the internet and the app is active in foreground, the messages are sent to server, queried and we get the response back in the app’s database where the attributes are assigned accordingly.

 

I will explain a functionality below that will give a more clear view about our coding practices and work flow.

When a user long taps a message, few options are listed(these actions are defined in recycleradapters->ChatFeedRecyclerAdapter.java) from which you may select one. In the code, this triggers the method onActionsItemClicked(). In this Overridden method, we handle what happens when a user clicks on one of the following options from item menu. In this post I’ll be covering only about the star/important message option.

case R.id.menu_item_important:
    nSelected = getSelectedItems().size();
    if (nSelected >0)
    {
        for (int i = nSelected - 1; i >= 0; i--) 
        {
            markImportant(getSelectedItems().get(i));
        }
        if(nSelected == 1) 
        {
            Toast.makeText(context,nSelected+" message 
            marked 
            important",Toast.LENGTH_SHORT).show();
        } 
        else 
        {
            Toast.makeText(context, nSelected + " 
            messages marked important",                                                      
            Toast.LENGTH_SHORT).show();
        }
        Important = realm.where(ChatMessage.class).
        equalTo("isImportant",true)
        .findAll().sort("id");
        for(int i=0;i<important.size();++i)
            Log.i("message ","" + 
            important.get(i).getContent());
        Log.i("total ",""+important.size());
        actionMode.finish();
    }
return true;

We have the count of messages which were selected. Each message having a unique id is looped through and the attribute “isImportant” of each message object is modified accordingly. To modify this field, We call the method markImportant() and pass the id of message which has to be updated.

public void markImportant(final int position) {
    realm.executeTransaction(new Realm.Transaction() {
        @Override
        public void execute(Realm realm) {
            ChatMessage chatMessage = getItem(position);
            chatMessage.setIsImportant(true);
            realm.copyToRealmOrUpdate(chatMessage);
        }
    });
}

This method copies the instance of the message whose id it has received and updates the attribute “isImportant” and finally updates the message instance in the database.

Below given is the code for ImportantMessage activity which will help you understand properly how lists are used to query the database.

public class ImportantMessages extends AppCompatActivity {
 
    private Realm realm;
    private RecyclerView rvChatImportant;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       realm = Realm.getDefaultInstance();
       rvChatImportant = (RecyclerView) findViewById 
                          (R.id.rv_chat_important);
       actionBar.setDisplayHomeAsUpEnabled(true);
       setChatBackground();
       setupAdapter();
 
        //call to other methods
    }
 
    private void setupAdapter() {
        rvChatImportant = (RecyclerView) findViewById 
                          (R.id.rv_chat_important);
        LinearLayoutManager linearLayoutManager = new 
                            LinearLayoutManager(this);
        linearLayoutManager.setStackFromEnd(true);
        rvChatImportant. 
        setLayoutManager(linearLayoutManager);
        rvChatImportant.setHasFixedSize(false);
        RealmResults<ChatMessage> importantMessages = 
        realm.where(ChatMessage.class). 
        equalTo("isImportant",true).findAll().sort("id");
        TextView tv_msg = (TextView) findViewById 
                          (R.id.tv_empty_list);
 
        if(importantMessages.size()!=0)
            tv_msg.setVisibility(View.INVISIBLE);
        else
            tv_msg.setVisibility(View.VISIBLE);
 
        ChatFeedRecyclerAdapter recyclerAdapter = new 
             ChatFeedRecyclerAdapter(Glide.with(this), this, 
             importantMessages, true);
        rvChatImportant.setAdapter(recyclerAdapter);
        rvChatImportant.addOnLayoutChangeListener(new 
        View.OnLayoutChangeListener() {
            @Override
            public void onLayoutChange(View view, int left, 
            int top, int right, int bottom,
            int oldLeft, int oldTop, int oldRight, int 
            oldBottom) {
                if (bottom < oldBottom) {
                    rvChatImportant.postDelayed(new 
                    Runnable() {
                        @Override
                        public void run() {
                            int scrollTo = 
                            rvChatImportant.getA 
                            dapter().getItemCount() - 1;
                            scrollTo = scrollTo >= 0 ? 
                            scrollTo : 0;                             
                            rvChatImportant. 
                            scrollToPosition(scrollTo);
                        }
                    }, 10);
                }
            }
        });
    }
}
Continue ReadingControl flow of SUSI AI on Android and database management using Realm