Working with Route Hooks in Badgeyay

Badgeyay is an open source project developed by FOSSASIA Community to generate badges for conferences and events through a simple user interface.

In this post I am going to explain about the route lifecycle hooks in ember and how we have utilized one lifecycle component to reset the controller state in badge generation form. In Ember every entity has some predefined set of methods, that it goes through the existence of the application. Route is not different from it. Our main goal is to restore the state of the controller every time we entered into a route, so that we receive a clean and new instance and not the previous state. The hook that fits in the situation is setupController(). This method is called after model() hook to set the controller state in the route. We will restore the variables in the controller here in this method to reset it to original state. This will help in removing the messages and progress on the previous state of the page on a new visit.

Procedure

  1. Open the route, where we want to override the hook, and create a method setupController() this will call the base hook and override its behaviour.
setupController(controller, model) {
  this._super(…arguments);
  set(controller, ‘defImages’, model.def_images);
  set(controller, ‘user’, model.user);
  this.set(‘controller.badgeGenerated’, false);
  this.set(‘controller.showProgress’, false);
}

 

As we can see in the method, it first initialises the super constructor and then we are writing the logic for the reset state. This will reset the badgeGenerated and showProgress variable in the controller to mentioned values.

  1. Getting the generated badge link from the last promise resolved to set the value of the variable in the controller action.
sendBadge(badgeData) {

        this.set(‘badgeGenerated’, true);

  },

 

This will set the value of the variable to the received object from backend.

  1. Showing the content in frontend based on the values of the variable. When we initially visit the route it is set to as false in the setupController() hook and is changed later on through some promise resolvement in an action.
{{#if badgeGenerated}}

 . . .


{{/if}}

 

This content will be only be shown in the present state and when the user revisits the route the state will be resetted.

Pull Request for the respective issue – https://github.com/fossasia/badgeyay/pull/1313

Resources

Continue ReadingWorking with Route Hooks in Badgeyay

Cloud Function For Sending Mail On Badge Generation in Badgeyay

The task of badgeyay is to generate badges for events and if the user has provided a large data set, then the system will take time to generate badges and we cannot hold the user on the same page for that time. So instead of showing the user the link of the generated badge on the form, what we can do is that we can send a mail to user with the link of generated badge. However listening for the completion of generated badge from the cloud function is not possible, as cloud functions are based on serverless architecture. This can be done using the listeners for the realtime database events.

Generated badge from the backend will be uploaded to the Firebase Storage, but applying a listener for storage events, will not give us the information of the sender and some other metadata. So after uploading the link on the database, we can use the public link generated and can push a dict to the realtime database with the necessary user information for sending mail.

Procedure

  1. Fetching the necessary information to be pushed on the Firebase realtime database.
def send_badge_mail(badgeId, userId, badgeLink):
  ref = firebase_db.reference(‘badgeMails’)
  print(‘Pushing badge generation mail to : ‘, badgeId)
  ref.child(userId).child(datetime.datetime.utcnow().isoformat().replace(‘-‘, ‘_’).replace(‘:’, ‘U’).replace(‘.’, ‘D’)).set({
      ‘badgeId’: badgeId,
      ‘badgeLink’: badgeLink
  })
  print(‘Pushed badge generation mail to : ‘, badgeId)

 

Payload consists of the downloadable link of the badge and the root node is the userID. So whenever any node gets created in this format them the cloud function will be called.

  1. Listening for the database changes at the state of node.
exports.sendBadgeMail = functions.database.ref(‘/badgeMails/{userId}/{date}’)
.onCreate((snapshot, context) => {
  const payload = snapshot.val();
  const uid = context.params.userId;
  return admin.auth().getUser(uid)
    .then(record => {
      return sendBadgeGenMail(uid, record.email, record.displayName, payload[‘badgeId’], payload[‘badgeLink’]);
    })
    .catch(() => { return -1 });
});

 

For the realtime database listener, it should listen to node in the form of badgeMails/<user_id>/<date> as whenever a node of such form will be created in the database the function will get triggered and not for any other data insertions in db. This will save the quota for the cloud function execution.

  1. Sending mail to the user from the payload
function sendBadgeGenMail(uid, email, displayName, badgeId, badgeLink) {
const mailOptions = {
  from: `${APP_NAME}<noreply@firebase.com>`,
  to: email,
};

mailOptions.subject = `Badge Generated ${badgeId}`;
mailOptions.html = `<p> Hello ${displayName || }! Your badge is generated successfully, please visit the <a href=${badgeLink}>link</a> to download badge</p>`;
return mailTransport.sendMail(mailOptions).then(() => {
  writeMailData(uid, “success”, 3);
  return console.log(‘Badge mail sent to: ‘, email)
}).catch((err) => {
  console.error(err.message);
  return -1;
});
}

 

This will send the mail to the user with the generated link.

Pull Request for the above feature at link : Link

Outcome:

 

Resources

Continue ReadingCloud Function For Sending Mail On Badge Generation in Badgeyay

Showing Mail Statistics Using Firebase In Badgeyay

In this blog post I will show how we implemented mail statistics on the admin dashboard of Badgeyay. The problem is that we cannot send a request to an external API from firebase cloud functions in free plan and secondly we have to them bypass the call to realtime database and design a logic to sort out the details from the realtime database using python admin sdk. So, our approach in solving this is that we will use the realtime database with the cloud functions to store the data and using the firebase admin sdk will sort the entries and using binary search will count the statistics efficiently..

Procedure

  1. Configuring the database URL in admin SDK
firebase_admin.initialize_app(cred, {
      ‘storageBucket’: ‘badgeyay-195bf.appspot.com’,
      ‘databaseURL’: ‘https://badgeyay-195bf.firebaseio.com’
  })

 

  1. Write the mail data in realtime database using cloud function on promise resolvement
function writeMailData(uid, state, reason) {
if (state === ‘success’) {
  db.ref(‘mails’)
    .push()
    .set({

      …
    }, err => {
      …

    });
}
}

 

  1. Fetching the mail data from the realtime database in python admin sdk
def get_admin_stat():
  mail_ref = firebasedb.reference(‘mails’)
  mail_resp = mail_ref.get()

  …
  curr_date = datetime.datetime.utcnow()
  prev_month_date = curr_date – relativedelta(months=1)
  last_three_days_date = curr_date – relativedelta(days=3)
  last_seven_days_date = curr_date – relativedelta(days=7)
  last_day_date = curr_date – relativedelta(days=1)
  …

 

This will fetch the current date and them generates the relative dates in past. Then it filters out the data using a for loop and a comparison statement with the date to get the count of the mails. Those count is then passed into a dict and sent through schema.

  1. Fetching the mail stats in admin index panel
mails    : await this.get(‘store’).queryRecord(‘admin-stat-mail’, {})

 

  1. Showing mails on the index page

class=“six wide column”>
          div class=“ui fluid center aligned segment”>
              h3 class=“ui left aligned header”>
                  Messages
              h3>
              div class=“ui divider”>div>
              h3 class=“ui header”>
                  No. of Sent Mails
              h3>
              table class=“ui celled table”>
                  tbody>
                      tr>
                          td> In last 24 hourstd>
                          td class=“right aligned”>
                              {{model.mails.lastDayCount}}
                          td>
                      tr>
                      tr>
                          td>In last 3 daystd>
                          td class=“right aligned”>
                              {{model.mails.lastThreeDays}}
                          td>
                      tr>
                      tr>
                          td> In last 7 days td>
                          td class=“right aligned”>
                              {{model.mails.lastSevenDays}}
                          td>
                      tr>
                      tr>
                          td> In last 1 month td>
                          td class=“right aligned”>
                              {{model.mails.lastMonth}}
                          td>
                      tr>
                  tbody>
              table>
          div>
      

 

PR to the issue: https://github.com/fossasia/badgeyay/pull/1163

Benefits of the approach

This approach is best in the case, when we only are using a subset of services firebase offers and do not want to pay for the services that are not required by the system.

Resources

Continue ReadingShowing Mail Statistics Using Firebase In Badgeyay

Different Text Color On Each Line In Badgeyay

In this blog post I am going to explain about how to create different text color for each line in badges generation in Badgeyay. As the system now has option for different badge size and paper size, currently the system sets same color for each line by mutating the fill parameter in the SVG. The main challenge in mutating the SVG parameter for each badge is the Id. The ID identifies the element, in our case text, and gives access to iterate the SVG through libraries like lxml. So for implementing this feature we first need to manipulate the SVG and assign id’s to the text tag so that it can be easily manipulated through the algorithm.

Procedure

  1. Manipulating the text tag in SVG and assigning a proper ID according to the logic for iteration in the function.
<text

     id=“Person_color_1_1”

     ….>

Person_1_1

</text>

The id of the person in first badge and first line is represented as Person_color_1_1, where the first number denotes the number of badge and second number denotes the line number.

  1. Creating a class for the dimensions of the badges
class Dimen(object):
  def __init__(self, badges, badgeSize, paperSize):
      self.badges = badges
      self.badgeSize = badgeSize
      self.paperSize = paperSize
  1. Creating an initialiser function that stores the dimension objects
badge_config = {}


def init_dimen():
  paper_sizes = [‘A2’, ‘A3’, ‘A4’]
  for paper in paper_sizes:
      if paper == ‘A2’:
          badge_config.__setitem__(paper, {‘4×3’: Dimen(18, ‘4×3’, paper)})
          badge_config[paper][‘4.5×4’] = Dimen(15, ‘4.5×4’, paper)
      elif paper == ‘A3’:
          badge_config.__setitem__(paper, {‘4×3’: Dimen(8, ‘4×3’, paper)})
          badge_config[paper][‘4.5×4’] = Dimen(6, ‘4.5×4’, paper)
      elif paper == ‘A4’:
          badge_config.__setitem__(paper, {‘4×3’: Dimen(6, ‘4×3’, paper)})
          badge_config[paper][‘4.5×4’] = Dimen(2, ‘4.5×4’, paper) 
  1. Selecting the dimension config based on the parameters passed in the function.
dimensions = badge_config[paper_size][badge_size]
  1. Looping criteria is to loop through the number of badges mentioned in the dimension config and through the number of lines which will be five.
for idx in range(1, dimensions.badges + 1):

          for row in range(1, 6):
  1. Selecting the text element with the ID as provided above.
_id = ‘Person_color_{}_{}’.format(idx, row)
              path = element.xpath((“//*[@id='{}’]”).format(_id))[0]
  1. Fill the text color argument of the selected object by changing the value of fill.
style_detail[6] = “fill:” + str(fill[row])

That’s it and now when the loop runs each line will have its individual color as passed in the function. The choice of color is passed as the list named fill.

Resources

Continue ReadingDifferent Text Color On Each Line In Badgeyay

Badges Search Functionality In Admin

Badgeyay is a badge generator service developed by FOSSASIA community for generating badges for conferences and events. Project is separated into frontend that is designed in EmberJS and backend that is created in Flask. Now The Badges tab in admin shows the total badges that are in the system. Along with different meta attributes like Badge creator name, date and other data. The main problem is that it doesn’t provide a way to search the badges from the admin panel. In this blog post i am going to explain about integrating badge search functionality in admin.

Procedure

  1. Integrating form element for searching
<form class=“ui form” {{action ‘findBadges’ on=“submit”}}>
                  

class=“field”>
                      

class=“ui action input”>
                          {{input type=“text” autocomplete=‘off’ value=user placeholder=“Search Badges…”}}
                          
                      


                  </div>
              </form>

 

  1. Main difficulty is to send the data to route components as we can’t pass the props, like we pass in components. The workaround is to send the query as route parameter.
findBadges() {
    var userTerm = this.get(‘user’);
    this.transitionToRoute(‘admin.badges.list’, userTerm);
  }

 

  1. Extracting the query parameter and making request to backend
if (params.badge_status === ‘all’) {
    filter.state = ‘all’;
  } else if (params.badge_status === ‘created’) {
    filter.state = ‘created’;
  } else if (params.badge_status === ‘deleted’) {
    filter.state = ‘deleted’;
  } else {
    filter.filter = params.badge_status;
  }

 

  1. Creating query in controller to fetch the data for the same.
if ‘filter’ in args.keys():
      badges = base_query.filter(User.username.contains(args[‘filter’]))
      badges = badges.paginate(page, app.config[‘POSTS_PER_PAGE’], False)
      schema = AdminBadgeSchema(many=True)
      return jsonify(schema.dump(badges.items).data)

 

Resources

  • Flask Sqlalchemy documentation for query – Link
  • Pull Request for the same – Link
  • Ember Guide – Link
Continue ReadingBadges Search Functionality In Admin

Implement Table Sorting In Badgeyay

In this blog post I am going to explain about implementation of inplace table sorting in badgeyay. This is not about just adding the sortable class as described in the semantic docs, but the data inside the table has different characteristics and needs to be sorted in a different manner. Not like the traditional way of comparing strings as it will not be suitable for dates. For creating a custom comparison function for sorting, either we can implement a custom comparator using JQuery or we can use the data values for comparison. The latter option is more preferable as it can be extended  to different columns in the table.

Procedure

  1. Adding the sortable class in the table, which needs to be sorted.
<table class=“ui sortable table”>

  . . .

</table>

 

  1. We need to enable a javascript function when DOM completely gets loaded.
<script type=“text/javascript”>
 $(‘table’).tablesort();
</script>

 

  1. After this we need to create a template helper to return us the time stamp from the UTC formatted DateTime string. The value that will be returned by the helper will be used as the data value for the column entries.
import { helper } from ‘@ember/component/helper’;

export function extractTimeStamp(date) {
return Math.floor((new Date(date)).getTime() / 100);
}

export default helper(extractTimeStamp);

 

  1. The value that will be returned by the helper will be used as data value for comparison by table sorter.
<td data-sort-value={{extract-time-stamp user.created_at}}>{{sanitizeDate user.created_at}}</td>

 

  1. Now we need that certain columns do not sort, as there is no need. Such columns are photoURL, actions etc. These columns should be ignored by the sorter for sorting, so we will add a class to avoid sorting of these columns.
<th class=“no-sort”>User Photo</th>

Resources

  • Semantic UI table class – Link
  • Data sorting in the table API – Link
  • Pull Request for the same – Link
  • Template helper guide ember – Link
Continue ReadingImplement Table Sorting In Badgeyay

Forgot Password Service in Badgeyay

Badgeyay is an open source badge generator service for generating badges developed by FOSSASIA community for technical events and conferences. The project is divided into two components mainly frontend and backend. After creating the user registration functionality in application, if the user forgets the credentials for the login, then there must be a way to recreate the credentials using a secure channel. This is only valid for the users signed up through email login as for the case of OAuth they must have access to their ID on respective social platform. The main challenges in resetting password for the user is to provide a secure channel. So the problem can be breakdown into following issues:

  • Creating a token for reset action
  • Sending that token via mail to user
  • Verifying that token on the server and giving access
  • Changing the credentials  of the user

Procedure

  1. Generating token for the request to change credentials for the user. The token will be an expiry token and will be expired in the mentioned duration. So the token is valid for only a limited period of time and will prevent fraudulent requests.
def pwd_reset_token():
  data = request.get_json()[‘data’][‘attributes’]
  if ’email’ not in data.keys():
      print(‘Email not found’)
  email = data[’email’]
  user = User.getUser(email=email)
  if not user:
      return ErrorResponse(UserNotFound().message, 422, {‘Content-Type’: ‘application/json’}).respond()
  expire = datetime.datetime.utcnow() + datetime.timedelta(seconds=900)
  token = jwt.encode({
      ‘id’: user.id,
      ‘exp’: expire
  }, app.config.get(‘SECRET_KEY’))
  resetObj = ResetPasswordToken(user.id, token.decode(‘UTF-8’))
  resetObj.save_to_db()
  return jsonify(TokenSchema().dump(resetObj).data)

Model for ResetPasswordToken

class ResetPasswordToken(db.Model):

  __tablename__ = ‘Reset Password Token’

  id = db.Column(db.String, primary_key=True)
  token = db.Column(db.String, nullable=False)

  def __init__(self, uid, token):
      self.id = uid
      self.token = token

  def save_to_db(self):
      try:
          db.session.add(self)
          db.session.commit()
      except Exception as e:
          db.session.rollback()
          db.session.flush()
          print(e)

 

  1. Sending the password reset link via mail to the user. The link will contain the token (expiry token) that will be used to validate the request. For the case we will be using Firebase Cloud functions as an HTTP Trigger.
exports.sendResetMail = functions.https.onRequest((req, res) => {
let token = req.query[‘token’];
let email = req.query[’email’];
res.setHeader(‘Content-Type’, ‘application/json’);
sendResetMail(token, email)
  .then(() => {
    console.log(‘Reset mail sent to’, email);
    res.json({ data: { attributes: { status: 200 }, id: token, type: ‘reset-mails’ } });
    return 0;
  })
  .catch(err => {
    console.error(err);
    res.json({ data: { attributes: { status: 500 }, id: token, type: ‘reset-mails’ } });
    return -1;
  });
});

function sendResetMail(token, email) {
const mailOptions = {
  from: `${APP_NAME}<noreply@firebase.com>`,
  to: email,
};

mailOptions.subject = `Password reset link`;
mailOptions.html = ‘<p>Hey ‘ + email + ‘! Here is your password reset <a href=\” + PASSWORD_RESET_LINK
  + token + ‘\’>Link</a><p>’;
return mailTransport.sendMail(mailOptions);
}

 

  1. Verifying the token on the server side to validate the user request
def validate_reset_token():
  args = request.args
  if ‘token’ in args.keys():
      token = args.get(‘token’)
  resp = {‘id’: token}
  try:
      jwt.decode(token, app.config[‘SECRET_KEY’])
      resp[‘valid’] = True
      return jsonify(ValidTokenSchema().dump(resp).data)
  except Exception as e:
      resp[‘valid’] = False
      print(e)
      return jsonify(ValidTokenSchema().dump(resp).data)

 

  1. After user has access to change the credentials, then user can send a POST request to backend through a form shown in UI to change its password.
def changePwd():
  try:
      data = request.get_json()[‘data’][‘attributes’]
  except Exception as e:
      print(e)
      return ErrorResponse(PayloadNotFound().message, 422, {‘Content-Type’: ‘application/json’}).respond()

  token = data[‘token’]
  try:
      decoded_res = jwt.decode(token, app.config[‘SECRET_KEY’])
  except Exception as e:
      print(e)
      return ErrorResponse(SignatureExpired().message, 422, {‘Content-Type’: ‘application/json’}).respond()

  user = User.getUser(user_id=decoded_res[‘id’])

  if ‘pwd’ not in data.keys():
      return ErrorResponse(PasswordNotFound().message, 422, {‘Content-Type’: ‘application/json’}).respond()

  pwd = data[‘pwd’]
  oldPwd = user.password
  user.password = generate_password_hash(pwd)
  user.save_to_db()

  resp = {‘id’: token}
  if update_firebase_password(user.id, pwd):
      resp[‘status’] = ‘Changed’
      return jsonify(ResetPasswordOperation().dump(resp).data)
  else:
      print(‘Firebase not uploaded’)
      user.password = oldPwd
      user.save_to_db()
      resp[‘status’] = ‘Not Changed’
      return jsonify(ResetPasswordOperation().dump(resp).data)

 

  1. After this the password of the user will be changed and allowed to login through new credentials.

Link to PRs:

  • PR for forgot password reset form – #1
  • PR for implementing forgot password on firebase side – #2
  • PR for password reset mail functionality – #3

Resources

  • HTTP Trigger Cloud functions – Link
  • Nodemailer message configuration – Link
  • Ember Data Guide – Link

 

Continue ReadingForgot Password Service in Badgeyay

Metadata Updation in Badgeyay

Badgeyay is a simple badge generator service to develop badges for technical events and conferences developed by FOSSASIA. Badgeyay is a SPA (Single Page Application) developed in ember, whose backend is in Flask. Now when user logins, he can see an option for user profile, in which all the metadata of its profile can be seen (extracted from Firebase). Now user should be able to change its metadata like profile image and username etc. So we will look how the profile image is being changed and updated in badgeyay.

Procedure

  1. Create function in frontend to listen for onclick events and initiate a file upload dialog box for selecting an image. We will use document property to initiate a dummy click event, else there will be a button with the text to upload a file and that won’t look consistent as we only need an image and nothing else on the UI.

class=“ui small circular image profile-image”>
    “{{user.photoURL}}”>
    “display: none;” id=“profileImageSelector” type=“file” onchange={{action “profileImageSelected”}}>
    

“profile-change” onclick={{action “updateProfileImage”}}>Change


</div>

 

  1. Function to upload file and initiate a dummy click event
updateProfileImage() {
    // Initate a dummy click event
    document.getElementById(‘profileImageSelector’).click();
  },

  profileImageSelected(event) {
    const reader = new FileReader();
    const { target } = event;
    const { files } = target;
    const [file] = files;
    const _this = this;

    reader.onload = () => {
      _this.get(‘sendProfileImage’)(reader.result, file.type.split(‘/’)[1]);
    };

    reader.readAsDataURL(file);
  }

 

  1. Profile update function in the main controller to call the API endpoint to upload the data to backend. This will send the payload to backend which will later upload the image to cloud storage and save in the link in the database.
updateProfileImage(profileImageData, extension) {
    const _this = this;
    const user = this.get(‘store’).peekAll(‘user’);
    user.forEach(user_ => {
      _this.set(‘uid’, user_.get(‘id’));
    });
    let profileImage = _this.get(‘store’).createRecord(‘profile-image’, {
      image   : profileImageData,
      uid   : _this.uid,
      extension : ‘.’ + extension
    });
    profileImage.save()
      .then(record => {
        user.forEach(user_ => {
          user_.set(‘photoURL’, record.photoURL);
        });
      })
      .catch(err => {
        let userErrors = profileImage.get(‘errors.user’);
        if (userErrors !== undefined) {
          _this.set(‘userError’, userErrors);
        }
      });
  }
  1. Route to update profile image from backend
@router.route(‘/profileImage’, methods=[‘POST’])
def update_profile_image():
  try:
      data = request.get_json()[‘data’][‘attributes’]
  except Exception:
      return ErrorResponse(PayloadNotFound().message, 422, {‘Content-Type’: ‘application/json’}).respond()

  if not data[‘image’]:
      return ErrorResponse(ImageNotFound().message, 422, {‘Content-Type’: ‘application/json’}).respond()

  if not data[‘extension’]:
      return ErrorResponse(ExtensionNotFound().message, 422, {‘Content-Type’: ‘application/json’}).respond()

  uid = data[‘uid’]
  image = data[‘image’]
  extension = data[‘extension’]

  try:
      imageName = saveToImage(imageFile=image, extension=extension)
  except Exception:
      return ErrorResponse(ImageNotFound().message, 422, {‘Content-Type’: ‘application/json’}).respond()

  fetch_user, imageLink = update_database(uid, imageName)
  return jsonify(UpdateUserSchema().dump(fetch_user).data)

 

This will first create a temp file with the data URI and them upload that file to cloud storage and generate the link and then update the user in the database.

def update_database(uid, imageName):
  fetch_user = User.getUser(user_id=uid)
  if fetch_user is None:
      return ErrorResponse(UserNotFound(uid).message, 422, {‘Content-Type’: ‘application/json’}).respond()
  imagePath = os.path.join(app.config.get(‘BASE_DIR’), ‘static’, ‘uploads’, ‘image’) + ‘/’ + imageName
  imageLink = fileUploader(imagePath, ‘profile/images/’ + imageName)
  fetch_user.photoURL = imageLink
  fetch_user.save_to_db()

  try:
      os.unlink(imagePath)
  except Exception:
      print(‘Unable to delete the temporary file’)

  return fetch_user, imageLink

 

Link to PR – Link

Topics Involved

  • Google Cloud Admin Storage SDK
  • Ember data

Resources

  • Firebase admin sdk documentation – Link
  • Google Cloud Storage SDK Python – Link
  • Blob Management – Link
  • Documents API – Link
Continue ReadingMetadata Updation in Badgeyay

Uploading Badges To Google Cloud Badgeyay

Badgeyay is an open source project developed by FOSSASIA community. This project mainly aims for generating badges for technical conferences and events.The project is divided into two parts mainly. Backend is developed in flask and frontend is developed in emberjs. The problem is after the badge generation, the flask server is storing and serving those files. In practise this is not a good convention to do so. This should be handled by secondary hosting server program like gunicorn or nginx. Better approach would be to consume the firebase storage and admin sdk for storing the badges on google cloud. This will also offload storage needs from the flask server and also give a public link over the network to access.

Procedure

  1. Get the file path of the temporary badge generated on the flask server. Currently badges are saved in the directory of the image file uploaded and final badge generated is written in all-badges.pdf`
badgePath = os.getcwd() + ‘/static/temporary/’ + badgeFolder
badgePath + ‘/all-badges.pdf’

 

  1. Create the blob path for the storage. Blob can be understood as the final reference to the location where the contents are saved onto the server. This can be a nested directory structure or simply a filename in root directory.
‘badges/’ + badge_created.id + ‘.pdf’

 

In our case it is the id of the badge that is generated in the badges directory.

  1. Function for uploading the file generated in temporary storage to google cloud storage.
def fileUploader(file_path, blob_path):
  bucket = storage.bucket()
  fileUploaderBlob = bucket.blob(blob_path)
  try:
      with open(file_path, ‘rb’) as file_:
          fileUploaderBlob.upload_from_file(file_)
  except Exception as e:
      print(e)
  fileUploaderBlob.make_public()
  return fileUploaderBlob.public_url

 

It creates a bucket using the firebase admin SDK and then open the file from the file path. After opening the file from the path it writes the data to the cloud storage. After the data is written, the blob is made public and the public access link to the blob is fetched, which then later returned and saved in the local database.

Topics Involved

  • Firebase admin sdk for storage
  • Google cloud storage sdk

Resources

  • Firebase admin sdk documentation – Link
  • Google Cloud Storage SDK Python – Link
  • Blob Management – Link

 

Continue ReadingUploading Badges To Google Cloud Badgeyay

Ember Controller for Badge Generation In Badgeyay

Badgeyay is an open source project developed by FOSSASIA Community. This project aims towards giving a platform for badge generation using several customizations options. Current structure of project is in two parts to maintain modularity, which are namely backend, developed in flask, and frontend, developed in ember.

After refactoring the frontend and backend API we need to create a controller for the badge generation in frontend. Controller will help the components to send and receive data from them and prepare the logic for sending request to API so that badges can be generated and can receive the result as response from the server. Particularly we need to create the controller for badge generation route, create-badges.

As there are many customizations option presented to user, we need to chain the requests so that they sync with each other and the logic should not break for the badge generation.

Procedure

  1. Creating the controller from the ember-cli
ember g controller create-badge

 

  1. After the component generation, we need to create actions that can be passed to components. Let’s build action to submit form and then chain the different actions together for the badge generation.
submitForm() {
  const _this = this;
  const user = _this.get(‘store’).peekAll(‘user’);
  let uid;
  user.forEach(user_ => {
    uid = user_.get(‘id’);
  });
  if (uid !== undefined && uid !== ) {
    _this.set(‘uid’, uid);
  }

  let badgeData = {
    uid     : _this.uid,
    badge_size : ‘A3’
  };

  if (_this.csvEnable) {
    badgeData.csv = _this.csvFile;
  }
  if (_this.defFontColor !== && _this.defFontColor !== undefined) {
    badgeData.font_color = ‘#’ + _this.defFontColor;
  }
  if (_this.defFontSize !== && _this.defFontSize !== undefined) {
    badgeData.font_size = _this.defFontSize.toString();
  }
  if (_this.defFont !== && _this.defFont !== undefined) {
    badgeData.font_type = _this.defFont;
  }

  _this.send(‘sendManualData’, badgeData);

},

 

  1. As we can see in the above code snippet that _this.send(action_name, arguments) is calling another action sendManualData. This action then sends a network request to the backend if the Manual data is selected as input source otherwise will go with the CSV upload. If no option is chosen then it will show an error on the user screen, notifying him to select one input source.
sendManualData(badgeData) {
    const _this = this;
    if (_this.manualEnable) {
      let textEntry = _this.get(‘store’).createRecord(‘text-data’, {
        uid   : _this.uid,
        manual_data : _this.get(‘textData’),
        time   : new Date()
      });
      textEntry.save().then(record => {
        _this.set(‘csvFile’, record.filename);
        badgeData.csv = _this.csvFile;
        _this.send(‘sendDefaultImg’, badgeData);
        _this.get(‘notify’).success(‘Text saved Successfully’);
      }).catch(err => {
        let userErrors = textEntry.get(‘errors.user’);
        if (userErrors !== undefined) {
          _this.set(‘userError’, userErrors);
        }
      });
    } else if (_this.csvEnable) {
      if (_this.csvFile !== undefined && _this.csvFile !== ) {
        badgeData.csv = _this.csvFile;
        _this.send(‘sendDefaultImg’, badgeData);
      }
    } else {
      // No Input Source specified Error
    }
  },

 

The above code will choose the manual data if the manual data boolean flag is set else not, and then does a network request and wait for the promise to be resolved. As soon as the promise is resolved it calls another action to for the default image.

  1. After selecting the input source, now the background for the badge has to be selected. It will look for the boolean flags of the defaultImage, backgroundColorImage, customImage and will make the network request accordingly.
sendDefaultImg(badgeData) {
    const _this = this;
    if (_this.defImage) {
      let imageRecord = _this.get(‘store’).createRecord(‘def-image-upload’, {
        uid    : _this.uid,
        defaultImage : _this.defImageName
      });
      imageRecord.save()
        .then(record => {
          _this.set(‘custImgFile’, record.filename);
          badgeData.image = _this.custImgFile;
          _this.send(‘sendBadge’, badgeData);
        })
        .catch(error => {
          let userErrors = imageRecord.get(‘errors.user’);
          if (userErrors !== undefined) {
            _this.set(‘userError’, userErrors);
          }
        });
    } else if (_this.custImage) {
      if (_this.custImgFile !== undefined && _this.custImgFile !== ) {
        badgeData.image = _this.custImgFile;
        _this.send(‘sendBadge’, badgeData);
      }
    } else if (_this.colorImage && _this.defColor !== undefined && _this.defColor !== ) {
      console.log(_this.defColor);
      let imageRecord = _this.get(‘store’).createRecord(‘bg-color’, {
        uid : _this.uid,
        bg_color : _this.defColor
      });
      imageRecord.save()
        .then(record => {
          badgeData.image = record.filename;
          _this.send(‘sendBadge’, badgeData);
        })
        .catch(error => {
          let userErrors = imageRecord.get(‘errors.user’);
          if (userErrors !== undefined) {
            _this.set(‘userError’, userErrors);
          }
        });
    } else {
      // Inflate error for No Image source.
    }
  },

 

After the promise resolvement, the final action is called to send badge data payload to backend api for badge generation.

  1. After the complete preparation of the payload, now it’s time to send the payload to the backend api for the badge generation and after the promise resolvement showing the respective downloadable link in the frontend.
sendBadge(badgeData) {
    const _this = this;
    let badgeRecord = _this.get(‘store’).createRecord(‘badge’, badgeData);
    badgeRecord.save()
      .then(record => {
        _this.set(‘badgeGenerated’, true);
        _this.set(‘genBadge’, record.id);
        _this.get(‘notify’).success(‘Badge generated Successfully’);
      })
      .catch(err => {
        console.error(err.message);
      });
  },

 

Now after the promise resolvement the local variable badgGenerated is set to true so that the success message can be shown in the frontend for successful badge generation along with the link.

Link to respective PR – Link

Topics Involved

  • Chaining of actions and requests
  • Manipulating DOM on the conditional statements
  • Component bindings
  • Ember data
  • Promise resolvement

Resources

  • Link to ember data for the API requests and promise resolvement – Link
  • Implementing Controllers in Ember – Link
  • Chaining actions together in ember – Link
Continue ReadingEmber Controller for Badge Generation In Badgeyay