M-PIC is an aggregator-type app that helps you organize your photos from various photo-sharing platforms, such as Facebook, Twitter and Unsplash.
It was built by two students from their passion for quality photography, tired of wasting time by manually organizing their photos and sharing them on so many social platforms.
M-PIC is the all-in-one platform you'll ever need in order to save the time needed to organize your photo collection, letting you create more wonderful memories with your loved ones.
The website is live on Heroku. Also, check out the demo on YouTube!
- Iulian Oleniuc (2B3)
- Elisabeta Dima (2B3)
M-PIC lets users manage their photos posted on various social media platforms and get various information about each photo like the hashtags associated and the numbers of shares and likes. It is also possible to perform multi-criteria search on their photo collection and to apply filters.
The home page contains a sign-in form alongside a link to the sign-up page designed for users with no M-PIC account. The only fields the user must fill in order to access the app are email and password. They can also check whether they want the browser to keep them logged in. Under the form there is a brief description of the app that hightlights its main features.
The sign up page contains only a form that requires the users to provide their email address and a password in case they have no M-PIC account. If they already have an account, they can click the sign-in button and will be redirected to the home page where they can enter their credentials.
After the user has managed to sign in, they are welcomed by the main page of the app, where all the photos the user has posted on the social media accounts saved in M-PIC are displayed in a chronological order. Here it is possible to search for certain photos based on the platforms they were posted on and sort them by the number of shares, likes and date of the post.
The edit mode appears when a photo is clicked and contains multiple filters that can be applied to it and some information about the photo as well: the date and the platform it was posted on, the associated hashtags and the number of likes and shares received on that platform.
On the upper right corner of the page, there is a navigation button that has three options: switch to the my profiles page, access the documentation or sign out of the app.
The app provides the option to download information about all displayed photos as a CSV file when the user clicks the icon on the lower left corner of the page. This mode can be exited in two different ways: either with the "x" sign on the right that simply returns to the main page or with the check sign next to it that also saves the edited photo in the user's collection.
On this page, the user can see all the linked profiles and view or delete them. For every profile M-PIC displays three main social engagement indicators: number of posted photos, followers and total number of likes received. The user can also add a new profile on a certain platform using the "+" sign on the lower left corner.
On the upper right corner of the page, there is the same navigation button except for the first option which redirects the user to the my photos page.
The UI/UX mockup of the app was designed in Figma. The frontend was written in vanilla HTML, CSS and JavaScript. The backend is mainly built in Node.js with the help of the APIs provided by the social media platforms: Graph API (from Facebook), Twitter API and Unplash API.
In order to use M-PIC, a user needs a stable connection to the internet and a browser. Thanks to the responsiveness of the app, M-PIC provides the best user experience possible, regardless of the screen size of the device used. The data exchange between the client and the server will be performed through a HTTPS protocol.
The source files are organized in the following hierarchy:
public
css
favicons
images
avif
jpg
webp
js
src
controllers
models
views
The files stored in public are frontend resources located at the same URL (relative to the app domain) as their local path (relative to the root directory). These include the css files used in styling the pages, the favicons (for light-theme and dark-theme), the images used in the front-page auto-scrolling gallery animation (in the modern and efficient avif and webp formats, with fallback to jpg for old browsers as well) and the client-side js scripts.
The files located in src are ued directly by the backend and are organized in an MVC fashion. The controllers directory includes two files, namely router.js and templater.js, defining an object-oriented model for the router and for the templating engine used in dynamically generating pages server-side, as well as some controller- files defining the routes of the application. The next directory in src is models and it stores the SQL scripts used in managing the database. The last one is views, where the reusable HTML components are defined.
Router is the class which manages the application routes. These include the regular pages (sign-in, sign-up, my-photos, my-profiles), the resources in the public directory and the internal and external APIs. The main methods of this class are the following:
mime(dir, type)adds routes for each file ofdirin the given MIME-typepage404(file)sets the 404 page as the HTML code insidefilepostgres(config)creates a connection-pool for the PostgreSQL databaseget/post/put/delete(url, callback)creates a route with the given HTTP method andcallbackfunctionlisten()starts listening to requests
A very important aspect is the structure of the callback function. It takes three parameters:
sqlthe database connection associated with this requestcall(fun, args)calls a PLpgSQL function with the given name and parameters and returns its result in JSON
reqa façade for the Node-likereqobject, containingcookdecodes thetokencookie sent in the request headerbodythe JSON body of the request or its URL params object if the HTTP method was GET
resa façade for the Node-likeresobject, containinggoto(url)makes a redirect tourlhtml(html)sends an HTML page as responsecode(code)sets the HTTP status code of the responsecook(user_id, remember)sends to the client a JWT-encoded token with the given informationjson(json)sends thejsonobject as response
We use the following controllers:
controller-pagesdefines the routes for the four main pages as well as for the three authorization pages (for Facebook, Twitter and Unsplash)controller-internaldefines the internal API endpoints, detailed in section3.1.controller-{{platform}}defines the external API endpoints, those communicating with each of the three platforms, detailes in section3.2.
Regarding the models, tables.sql contains the script for creating the database tables, and the following files contain PLpgSQL functions in order to easily communicate with the database:
functions-userssign_in(email, password)sign_up(email, password)delete_user(id)
functions-profilesadd_profile(user_id, platform, code)get_profile(id)get_profiles(user_id)set_profile_token(id, token)delete_profile(id)
functions-photosadd_photo(user_id, uri, created_at)get_photos(user_id)
The tables are:
usersemailpassword
profilesuser_idFKplatformnamecodeauthorization codetokenauthorization token(s)
photosuser_idFKuriname of the file inmediawhere it's storedcreated_atdate when the photo was posted
Templater is the class associated with our own templating engine. It's similar to React and it reduces the amount of duplicate code and it also helps us write declarative code for the user interface. Let's analyise its syntax:
- The views are structured in components, defined using the
componenttag.<component name="Image"> <picture> <source srcset="/public/images/avif/{{$id}}.avif" type="image/avif"> <source srcset="/public/images/webp/{{$id}}.webp" type="image/webp"> <img src="/public/images/jpg/{{$id}}.jpg" alt="photo"> </picture> </component>
- Inside double-curly-braces we can write (arbitrarily-complex) JavaScript expressions.
<a href="{{$altPage.replace(/ /g, '-')}}"> <!-- or --> <Welcome crtTitle="'sign in'" altTitle="'sign up'" fields="[ { id: 'email', type: 'email', icon: 'envelope' }, { id: 'password', type: 'password', icon: 'lock' } ]" >
- There is also a
fortag for repeating patterns. In order to refer to a context-variable (an attribute of the custom-component), we use the$sign.<for itr="script" arr="['global', ...$scripts]"> <script src="/public/js/{{$script}}.js" defer></script> </for>
In this section we will discuss about how certain features of M-PIC were implemented.
The internal APIs communicate directly with the database and are called client-side through the AJAX fetch function.
- GET
/api/sign-invalidates credentials - POST
/api/sign-upcreates an account - DELETE
/api/deletedeletes a profile - POST
/api/uploaduploads an edited photo of a user - GET
/api/photosreturns the edited photos of a user
Those are used for authorizing Facebook, Twitter or Unsplash apps.
- POST
/api/{{platform}}/authorizecreates the profile in the database and sets itscode– the value returned through the URL params of the corresponding authorization page - PUT
/api/{{platform}}/tokengenerates the authorizationtokenfor the given profile - GET
/api/{{platform}}/profilereturns profile-metadata by calling many platform-specific APIs alongside the/photosroute - GET
/api/{{platform}}/photosreturns photos-metadata by calling many platform-specific APIs
The user sessions are stored in JSON Web Tokens like this:
{
"user_id": "the database user_id",
"duration": "the duration of the session in seconds",
"iat": "the timestamp the token was issued at"
}The duration can be either one minute or one hour, depending on whether the user has checked the remember me box or not.
The tokens are set in the client and send to the server using cookies. It validates them and eventually redirects the user to another page accordingly. For example, if a user is not signed in, a request to /my-photos redirects them to /sign-in.
The photos are aggregated using the external APIs when the user accesses the /my-photos route. They are rendered server-side with data- attributes. When the client receives the page, it clones the img nodes. After that, the sorting, filtering and searching features become just some operations on an array.
The search is based on giving a set of tags. A score is calculated for each photo based on how similar its tags are to the search tags and how many they match. An Edit-Distance algorithm was used, namely the Levenshtein Distance.
The user can edit a photo using CSS filters. The result is drawn on an imaginary canvas and is converted to a base64 representation, which is further send to the server. It generates a random file name for the photo, using a function from the crypto library. Then, it saves the photo at media/{{uri}}.png and also adds uri to the user's photos list in the database.