Milestone 3 - cheehongw/functional_expressionism GitHub Wiki

Milestone 3

Proposed level of achievement: Apollo 11

Deployments

Front-end: https://github.com/cheehongw/functional_expressionism

Back-end: https://github.com/cheehongw/functional_expressionism-API

The prototype of the website can be found at: https://tinfood-dev.vercel.app

System design

This section will describe how the overall system was designed, while the subsequent sections will outline the designs of each subcomponent in the system, their details and the rationales for structuring the system in the way it is designed.

Front-end design

Landing page

There is no need for visitors to login to use our website, although there are extended features we intend to implement for logged in users. Visitors can choose between the two features available.

We decided to make most of the features in our app available to guests because we want them to "try out" our application first before actually making an account to use the other, extended features. This will help the application more user friendly to guests, hence attracts more traffic.

We opted for a mix of pink color (similar to Tinder), yellow and red color (color suitable for food) for the Header bar to channel the feel of a food application but displayed in a Tinder format (which explains the name Tinfood).

For the header, we decided that we would include a subtitle underneath the application name to give the user a brief description of what our application does.

Login/Signup & One-time setup page

Clicking on the login button on the header bar will direct users to a login/signup page.

Here, users can sign up with their own username and password or with their Google account.

Anticipating that most of the users registering would be directly from their Google account, we moved the Sign in with Google button on top to make it easier for users to navigate to that button. This implementation is based on our "informal" UX testing with our friends and referencing the sign in form design of big website (e.g Reddit)

Also, upon the first sign in after the user succesfully created an account, they will be redirected to a One-time setup page where they can pick an avatar image and username for their account. This feature was mainly implemented to support the reviews and contribution features. By enabling the user to have an (ideally) unique user identity, this helps other user report malicious intent of individuals (mass downvoting of a particular dish, easily identify users with helpful/horrendous reviews. This also helps admins later on to ban/remove accounts with malicious intent, or to have a easier time identifying and approving edits from users with quality contributions, hence saves a lot of time.

Stall Information

This feature remains the core of our website. It provides the information visitors want when they visit the website.

  1. Locations Listing page

    • List of locations in NUS
  2. Stall Listing page

    • List of stalls at a particular location
    • Clicking on the stall will bring up a floating preview card of the stall
      • preview card shows stall information such as opening hours, price range, halal info etc.
      • click again to enter menu listing page
  3. Stall-front page

    • Landing page associated with the stall
      • Display Ratings, comments and description of the stall
      • Scroll down to view menu of the stall

This feature allows users to have a detailed list of every single dish in NUS and their respective reviews. Knowing the reviews and getting the latest image of the dish via the contribution system implemented here helps the user make more informed decision of what to eat today. The user can decide on what to eat before coming to the stall, hence saving a lot of queueing time and increasing the chance of finding a delicious dish.

Dish suggestion

This page aims to serve suggestions to the user when they have no idea what to each. It offers a Tinder-like layout for users to choose among a set of options presented to them. Users can:

  • Swipe right/ Click on the right arrow if the user like the recommended dish.
  • Swipe left/ Click on the left arrow if the user disliked the recommended dish.
  • The cards swiped right will reappear until the user settle on one single choice.

There are 2 modes for the user to choose from

  1. Random: recommend 7 totally random dish for the user.
  2. Our top picks: recommend 7 best dish for the user according to a weightage system.

This feature is useful to people who are indecisive about what to eat by limiting the number of options of dishes to consider. More adventurous users can try out the random mode to get fresh recommendations, while others can use the our top picks option to get the best dishes according to a preset scoring system.

An instruction modal page is implemented to help users easily identify the functionality of this feature. From our UX testing, we found out that it is quite hard for our friends to grasp the functionality of this feature.

The cards are designed such that longer dish names are split into two equal parts. This helps a lot in user experience as longer dish names either causes poor visibility of the dish (the react-text-fit squeezes longer dish names into text that is barely readable) or the user have to scroll the page to see the entire dish if we use a fixed size for the dish name. Scrolling is bad as the swiping sensitivity is quite high hence gestures like swiping up and down to see the content of the card might cause the card to be swiped. We did prevent this by disallowing up and down swiping direction for the cards.

We decided to get rid of the review dropdown content from Milestone 2 as this is a feature only applicable to Laptop/PC devices, also opening the dropdown menu force the user to scroll the page to view the whole card, which is undesirable.

User Dashboard

The profile page for a logged in user. A user can control their account from this page and view other

  1. Control account credentials

    • Change profile pic/ display name/ password
    • Delete account
  2. User interactions(⚠️ unimplemented)

    • Shows stall visit history
    • Show comments and reviews made by user in timeline

We want to enable the user to see their favorite stalls and previous reviews here. The rest follows what is needed in a profile page of any web application

Navigation for both non signed in and signed in users is handled by the React Router library.

The page is designed using Material-UI and the library to support the card swiping in the Suggestions page is from the react-tinder-card library

Back-end design

On the backend side, the NodeJS server exposes a set of API endpoints, using ExpressJS to handle routing. Internally, the application follows a layered design, separating the HTTP layer from the service layer and the mongoDB clusters.

Backend Architecture

HTTP Layer

Within the HTTP layer contains routers and controllers, each with their own purpose.

Type What it does
Routers Defines the API endpoints/routes an external caller can use to query the application, and routes each HTTP request to the appropriate controller.
Controllers Receives the HTTP request object from the Routers, pull out request parameters and passes them as relevant calls to services.

Due to the lack of experience and time in meeting milestone 2, we have merged the controllers with the services in the early stage of our implementation. After milestone 3, we have separated the concerns between new controllers and services.

Business Logic Layer

The business logic layer is ideally free from any HTTP/ express logic, and are functions/files that exist outside the HTTP context of our application. The business logic layer contains the logic of fetching and modeling data, and manipulating the data if need be. This now-useful data will then be returned to the controller, for the controller to send out as a HTTP response.

Type What it does
Services Performs the task of fetching data from the database and manipulating the fetched data to a more useful form
Models Defines the data structures and objects used in our application. This determines the logical structure of our database and how data is related across collections.

Services

In the early stages of our development, it seemed unnecessary to us to abstract out the service aspect from the controllers. However, the usefulness of separating the services and controllers became apparent when it came to testing:

unit-testing just run node services/getLikeStatus.js !

For example, unit tests can be performed easily on a service function as seen above, since we can just run the js file of the function itself and console.log() the output. If we had combined the controllers and services, we would need to run the whole application and send mock HTTP requests to localhost (as we did previously 😔) to console.log() the output, which is time consuming and tedious.

Moreover, by separating concerns, we can pinpoint where the errors occurred without having to second-guess whether it was a HTTP problem or a mongoose problem. Being able to perform unit tests easily made developing with mongoose much less stressful and improved morale considerably.

Models

model diagram Model Diagram

As it stands, our application contains 5 data models, which encapsulate the 5 types of data that we have: Location, Stall, Dish, Likes and Ratings, and how they relate to each other, as seen in the diagram above. For example, there is a one-to-many relationship from locations to stalls and from stalls to dishes. In a non-relational database, this relationship is modeled by having each dish reference one stall, and each stall referencing one location.

The diagram also gives an idea of what fields are available in each model.

Initially, we intended to have a user model that contains a likes and ratedItems field to model the relation between liked/rated items and a user. However, deep nesting is difficult to work with using mongoose/mongoDB, and might not be the right mindset to have when using a non-relational database. As such, we have a Likes and Ratings model with relevant fields to represent a "Like" and a "Rating", even though such things might be unnecessary in a relational database.

Feature Limitations

Laptop/PC design

While we initially had plans to design for both laptop and mobile users, we could only afford the time to do a thorough frontend UI implementation for our mobile interface. Consequently, our current laptop/PC interface is a "bigger" version of a mobile/tablet website design.

We did have enough time to develop the wireframes for the desktop version. Unfortunately, due to time constraints, we did not have enough time to implement everything we planned out.

You can view the design and wireframes of the desktop version here

Stall and dishes database incomplete

Due to COVID-19 restrictions, we are not able to get every single menu of food stalls around NUS. Also another problem is that it is impossible (even without COVID restrictions) to gather a complete set of every dish image because it requires us to have images from people who actually try the dishes out. A workaround solution to this issue is to implement a Contributions system to let the users contribute dish images and dish prices.

Instead of the actual dish images at the food stalls, we add in the illustration images of that dish at other locations so users on UX testing can feel more like they were using an actual web application which listed out every stalls in NUS.

Location feature limitations

The update of the stall opening and closing hour is not implemented yet.

Suggestion feature limitations

There are bugs in the suggestion feature front-end where if the user refreshes the site immediately after getting the 7 suggestions, the suggestions listed are the same and in the same order. To actually get different suggestion, one can view the website on a different device or use Incognito mode on browsers. This is mainly due to the browser deciding that the suggestion page does not need any rerendering and thus the fetch function which runs when the page is rendered does not run, hence there is no chance of updating the food suggestion.

Also, this suggestion does not remember the dishes the users previously picked yet. We were planning to introduce a Freshness metric, which is the latest time that the user pick that particular dish. If the dish was picked in a week's time, it will have a really low (even negative) Freshness score.

Insufficient time to implement features

As Orbital is only a three months project, we did not have enough time to implement the Rating, Review and Contribution System that we had hoped to implement.

Software Engineering Practices

This section outlines the tools that the team is currently employing that adheres to some of the industry best practices.

Git Version Control

Git is used for version control, with the repositories being hosted on Github. The development process makes use of one single branch. Despite this, the workload allocated to both members is not related to one another thus there is low chances of conflict.

Issues are raised in the repository to organize the priority of work and clearer allocation.

To manage our code properly, we utilised branching in Git. We have two working branches, Development and Production. By working on the development branch rather than the production, and pulling from the development branch, we avoid any unintended code updates to our remote production branch. The production branch does not have any error in the code and should preferably be stable.

Work assignment

Code assignments are given on the Issue tab of the Github repository. Issues are raised and assigned to the two team members such that there is little overlap between the two, hence compartmentalize the work and improve performance.

Code Linting

ESLint is used to ensure that the code written adheres to the industry standard linting practices. The linting convention used is the AirBnB standard, with the default linting rules for the React components. This should help standardise the codebase, ensuring that code is neat and readable, boosting the debugging progress. Prettier is also used to take care of the code formatting

Continuous Deployment using Vercel

We use Vercel to help deploy the newest features and bug fixes of the development branch as soon as possible. From there we performed manual testing of the features implemented and did some user testing to make alterations to the features implemented. Once the development build is build ready, we release it to the production build. Developers are able to see the changes via email.

Quality Assurance

For Quality Assurance, we have chosen to utilise unit and integration testing. We center our testing approach to ensure that new code pushed to deployment will not break existing features.

Integration testing (front-end)

Sign in (Login) & Forget Password

Testing objective: Guests are able to log into the site with correct credentials, authentication behavior matches the authentication persistance of the user chooses upon signing in, non-verified users can not sign in, registered users are able to reset their password using the Password Forget feature.

Action Expected Result Result
User attempts to log in using registered and verified email and password Login Success ✔️
User attempts to log in using their Google account Login Success ✔️
User logs in successfully Redirect to the main page or the One-time setup page ✔️
User attempts to log in using unregistered email Login Fail ✔️
User attempts to log in using registered email but non-verified Login Fail, Notify the user the email is not verified then Send a verification email to the registered email address ✔️
User attempts to log in with the wrong password for the account Login Fail ✔️
User attempts to log in using verified email address and correct password, and chose to persist the logged in state The user is still logged in after they close the tab or close the browser ✔️
User attempts to log in using verified email address and correct password, and chose not to persist the logged in state The user is signed out after they close the tab or close the browser ✔️
User logs in using their Google account The user is still logged in after they close the tab or close the browser ✔️
User forgets their password and typed their account email to the forget password form A password reset email is sent which contains a link for password changing ✔️
User can log in with a new password after password change Login Success ✔️
User attempts to type in non-existant email in the password forget form An error is displayed on the screen ✔️
Exploitative use of password reset email to spam password reset email to a email address of an existing account Limit the number of password reset email allowed per day

Sign up

Testing objective: Guests are able to register as users with non-existing email address. Upon successful account creation, a verification email should be sent to the registered email inbox.

Action Expected Result Result
User attempts to create an account using their email account and safe password Sign up Success ✔️
User attempts to create an account using their Google account Sign up Success, redirect the user to the main page or one time ✔️
User creates their account successfully Send a verification email, redirect the user to the log in page ✔️
User attempts to create an account with existing email Sign up Fail, an error is displayed on the screen ✔️
User attempts to create an account with weak password or no password Sign up Fail ✔️
User attempts to sign up using an illegal, but correct syntax email (e.g [email protected]) Sign up Fail
User attempts to sign up using a wrong syntax email Sign up Fail ✔️

One-time Setup

Testing objective: All users signed in for the first time can customize their account avatar and username.

Action Expected Result Result
User logs in for the first time Redirect to One-time Setup page ✔️
User provide a unique account username and an image for the account avatar User is signed in, gets redirected to the main page ✔️
User does not provide a unique username for the account One-time Setup Fail, an error is displayed on the screen ✔️
User does not provide an image to use as the account avatar One-time Setup Fail, an error is displayed on the screen ✔️
User logs in using Google account User is logged in, does not get redirected to One-time Setup page, instead the main page ✔️

Change Profile (includes Password Change, Avatar Change, Username Change and Delete Account)

Testing objective: All registered users are able to change their account details and delete their account

Action Expected Result Result
User attempts to change their password with a safe password User have to type the current password and the new password to a form ✔️
User attempts to change their password with a unsafe password Change Password Fail, an error is displayed on the screen ✔️
User attempts to change their password but type in the wrong current password Change Password Fail, an error is displayed on the screen ✔️
User changes their password successfully Redirect the user to the profile page ✔️
User attempts to change their avatar picture and has selected an image Change Avatar Success ✔️
User attempts to change their avatar picture but has not selected an image Change Avatar Fail ✔️
User changes their avatar image successfully Redirect the user to the profile page with the new avatar image ✔️
User attempts to change their username with a unique username Change Username Success ✔️
User attempts to change their username with a invalid or existing username Change Username Fail, an error is displayed on the screen ✔️
User changes their username successfully Redirect the user to the profile page with the new username ✔️
User attempts to delete their account User has to type their current password before deleting the account ✔️
User attempts to delete their account using the wrong password Delete Account Fail ✔️
User attemps to delete a Google signed-in account Delete Account Success
User deletes their account successfully Redirect to the main page, signed out ✔️
User attempts to log in using a deleted account Log in Fail, an error is displayed on the screen ✔️

User Context & Page Accessibility & Sign out

Testing objective: Ensures that non-registered users are not allowed to access pages restricted to registered users. Registered users can access any pages of the web application. Users failing to complete the One-time setup cannot access any restricted pages before completing the setup.

Action Expected Result Result
User signed in successfully Information of the current signed in user (Email, Avatar Image URL, Username) is available to every component when requested by other component ✔️
Signed in user attempts to access pages restricted to signed in users User can access the pages ✔️
Non signed in user attempts to access pages restricted to signed in users User can not access the pages, instead get redirected to the login page ✔️
Signed in user attempts to access pages not restricted to signed in users User can access the pages ✔️
Non signed in user attempts to access pages not restricted to signed in users User can access the pages ✔️
User attempts to sign out Sign out Success ✔️
Signed out user attempts to access pages restricted to signed in users User can not access the pages, instead get redirected to the login page ✔️
Signed out user attempts to go back to pages restricted to signed in users User can not access the pages, instead get redirected to the login page ✔️
Signed in user attempts to go to the Sign In, Sign Up and One-time setup page Redirect to the main page ✔️
User signing in for the first time and has not done the One-time setup page and attempts to access restricted pages to signed in users Redirect to the One-time setup page ✔️
User signing in, attempts to bypass the One-time setup page and go to the pages not restricted to signed in users The user is not signed in, avatar icon on the header does not show up ✔️
Non signed in user attempts to access the One-time setup page User get redirected to the Login page ✔️

Suggestion page

Testing objective: The user should be able to get the suggestions and can use the swipe feature both by clicking the 2 buttons below the cards or swiping the screen.

Action Expected Result Result
User pick a suggestion mode The suggestions are fetched on front-end side ✔️
User swipes right The cards retain in the recommendation card deck ✔️
User swipes left The cards disappear from the recommendation card deck ✔️
User swipes right at the last card of the deck If there is only one swiped right dish then a prompt will appear asking if this is the final dish choice. Else the previously swiped right cards will reappear ✔️
User swipes left at the last card of the deck If there is only one swiped right card then a prompt will appear along with that card asking if this is the final dish choice.

Else if there is no swiped right card then a prompt will appear to inform that there is no suggestion available.

Else the prviously swiped right cards will reappear
✔️
User attempts to swipe up and down These gestures are not registered and the card returns back in its original position ✔️
User swipes in valid direction The card flies away from the screen ✔️
User presses on the Swipe Left button The card flies to the left side of the screen ✔️
User presses on the Swipe Right button The card flies to the right side of the screen ✔️
User finishes the session Either the card which contains the chosen dish or an "apology" page appears ✔️

Location listing page

The user should be able to see every stalls with their respective menus in NUS with the chosen sorting order.

Action Expected Result Result
User loads the page for the first time Page loads with results displayed in default alphabetical order ✔️
User sorts by distance, but denies browser location access Page displays loading icon for the tooltip ✔️
User sorts by distance, and allows browser to access location List sorts, and tooltips display the current distance from the user ✔️
User selects sorting choice from the drop down menu Drop down menu base changes text to user choice when closed ✔️

User acceptance testing

After the prototype of Tinfood is ready, acceptance testing was conducted. The sample size for the acceptance testing is around 15, mainly composed of NUS students. The rating and feedback we got on the form, and also from our informal UX testing with our friends have helped us a lot in refining the UI of the web application.

You can view the form (and feel free to give us feedback too) at this link

Backend API Tests

On the backend side, we use Postman to semi-automate the testing of our API endpoints. As we discovered Postman quite late into the game, we did not have time to appreciate all of its automation features. However, the ability to save HTTP requests made it easier for us to perform checks individually on all the exposed routes.

We adopted a specification-based testing approach to ensure all the endpoints meet the expected functionality. For example, when given a certain set of inputs (defined by the endpoint, method and parameters), we would expect a certain output (status code and response body).

The table below shows our API test plan.

page 1 page 2 page 3 page 4 page 5

Testing Limitations

We would also like to acknowledge that there are several limitations that arise from our a testing approach

Manual testing of Front-end components

This really slowed down the progress of the project as we have to ensure that every time a new change is committed, we would not have any more bugs. Luckily the components of our application are mostly compartmentalize hence the testing of the components are usually done in a very narrow scope.

Software Security Measures

Firebase authentication (Frontend)

By setting up Firebase rules to only allow authenticated requests and hide the API keys, we disallow any access to the Firebase storage/Firestore which stores the data of users. Users are only allowed to edit their own profiles/delete their own account. Firebase authentication abstracts the proper hashing of password and other security measures for a database.

Backend authentication

Certain endpoints such as those for likes and ratings are user-centric and hence authentication is required as well. Typically, authentication and user creation is handled on the backend for most applications. However, since we had implemented firebase authentication on the frontend before embarking on our backend, any user request from the frontend has to be identified and authenticated. Thankfully, firebase offers an idToken feature that allows us to do just that. By attaching this idToken in the Authorization header of a HTTP request, only users logged in on our frontend can use these user-exclusive functions.

By appending an Express middleware whose sole purpose is to contact firebase and verify that an idToken is valid, we can specify which endpoints require authentication and hence user-centric.

Backend Cache-control

Another security feature present on our backend is the usage of cache-control. As we are using a free Heroku server, we are rate-limited and we do not want users to flood our server with unnecessary network requests. Data that is rarely updated is suitable for caching, and our website has a lot of such data. For example, the lists of locations, stalls and their respective dishes are rarely updated, and could benefit from being cached. As such, we have attached a cache-control header in our response object through the use of a custom Express middleware, which tells the browser to use the cached data if the data that was cached is still 'fresh'.

Security Vulnerabilities

Spamming of rubbish data and password reset emails

Even though we have a verification system where an user is forced to click on a verification link sent to the registered email address to use the app, Firebase actually creates a new user account nonetheless. We have to manually check if the account is already verified before granting the user access to restricted pages of the web application. Exploitative users can easily flood the authentication database by spamming rubbish (but correct syntax) emails (such as [email protected]) to flood the Firebase database, consequently costing the developers (us) a lot of money. There is no limit on file sizing so a person with malicious intent can upload a 100MB image as their profile picture also. There are currently no clean-up functionality available on Firebase to clear out these sorts of entries so they persist in the database until manually removed by an admin.

Also, currently we have no measures of protecting the user from flooding of password reset emails. There is no way to enforce a limitation on how many password reset emails can be sent in a day. An attacker can easily spam the password reset form, sending hundreds of password reset email to an user if they know beforehand their registered email address. Firebase mitigate the damage of this exploit by only allowing password reset email to be sent to registered email addresses, nonetheless this exploit can cause great annoyance to the victim.

Responses to Milestone Evaluations

We have compiled a list of suggestions from the previous milestone reviews we want to respond. We are grateful for the time spent by the other groups and our advisor to review our project. While there are a lot of interesting features suggested to enhance the user experience of our application, we do not have enough time to implement all of it. Although we did not respond to the reviews previously, we did take all of those into consideration when we discuss the goals in Milestone 3.

Suggestion: The team can do system testing and come out with different kind of test cases to ensure the prototype work as intended. Also, the team can share the website link to NUS chat group to let students/staff to test out and get feedback on the user experience.

Response: System testing are carried out on the front-end side manually. Indeed, before we merge the production branch with the development one, we try to "use" every feature of the application and try to think of ways that can mess up the frontend or backend code. For the user experience, we asked our friends in NUS to give insights on how to improve the UI and how the optimal algorithm to suggest the best dish for an user would be, or how many dishes are enough to help an undecisive user decide on the dish they want today.

Problem: Some core features have issues such as unable to change username, delete account not working as intended(When signing using google account).

Response: We are aware of this issue. To delete a user, we need to reauthenticate the user. This is documented in this link. We have to prompt the user to re-provide their sign-in credentials. Compared to email and password login in which the user only needs to retype their current password and we have enough credentials to allow account deletion, Firebase does not have any concrete documentation on how to get the credentials for reauthentication for users signed in with Firebase. Two possible solutions are either to adapt this outdated solution on Stack Overflow, or to create a Cloud function to support this operation. Both methods require heavy refractoring of the current code so we ultimately decided not to waste more time into resolving this issue to spend time on more important objectives.

Suggestion: The testing documentation is great. You may wish to add an objective for each test as well as images (screenshots) to show proof that the test cases passed.

Response: Thanks for the kind words! We did include the objectives for the testing this time. Images/Screenshots to show proof that the test cases passed are impossible on front-end components as there are a lot of testing criteria (especially when it comes to authentication), hence an addition of screenshots would make the document too lengthy.

Suggestion: For README, the information is sufficient but too scattered because there are too many links to direct to. We need to open many tabs to view the information. For video, we think that the backend code is too small to see.

Response: We are going to include everything in a single README page this time! We will try to record the video with bigger font sizes this time.

Response to all the reviews not included here: Again, thank you for your time spent in reviewing our project! We have made alterations to our product and README in accordance to the reviews given.

Project Log

You can view the commit log of the two repos here:

Frontend: https://github.com/cheehongw/functional_expressionism/commits/main

Backend: https://github.com/cheehongw/functional_expressionism-API/commits/main

No. Task Date Chee Hong (hours) Vinh (hours) Remarks
Start of Milestone 3
23. Improve look of index page 28 June 3 1 Closes issue.
24. Routing redesign and implementing GET API endpoints for locations, stalls and dishes 30 June - 5 July 30 0
25. Learning the basics of Linux, MongoDB and NodeJS 30 June - 7 July 0 20
26. Improving the overall UI of the web application 7 July - 27 July 0 60 Closes issue #6, implements and tests different UI for the Tinder suggestion cards, choosing the overall color palette of the web application, refining the mobile version of the suggestion page.
27. Connect Location List to backend API 7 July 6 0
28. Implement frontend routing between LocationList -> StallList 9 July 3 0
29. Refactor frontend code/ code review 9 - 10 July 6 0 Code cleanup and improve functionality of some frontend components
30. Add loading screen for location page 11 July 1 0 Closes issue #8
31. Implement UI for StallList and Preview page 13 - 15 July 10 0 Closes issues #2 #3
32. Adding the illustration images for the missing dish images, correct inconsistencies in the database 14 - 15 July 0 5
32. Squash bugs 16 -17 July 3 0
33. Implement Stall landing page 17 July 7 0 Closes issue #4
34. Implement routes for handling likes 19 - 22 July 6 0 Researched and implemented authentication middleware and database design for handling likes
35. User testing 22 July - 26 July 0 10 Conduct user testing via form or informal session to further improve the UI.
36. Connect favorite functionality with backend 22 July 4 0 Fixed bug as well
37. Implement routes for rating system 23 July 3 0 Much faster after implementing like system
38. Add route to GET list of likes made by user 24-25 July 6 0
⚠️ **GitHub.com Fallback** ⚠️