See also: API documentation
A multimedia messaging app that "deletes" secret messages after a while isn't a new idea, but Ghost, Inc. has partnered with BDPA to create Ghostmeme anyway! Your team won the contract to build the Ghostmeme messaging app where users can send and receive picture messages known as "memes," keep them private, or add them to semi-public "stories".
Summary of requirements (15 total)
The app supports two user types: guests and authenticated users. Users interact with each other through the exchange of meme images and text. Users can like memes, share their favorite memes, comment on their friends' memes, and share private memes that expire after a while.
The app has at least four views: Chats, Stories, Notifications, and Auth. The Chats view is where users privately exchange direct memes with one another. The Stories view is where users share their memes with their friend groups and interact with the memes said groups share with them. The notifications view lists various alerts to the user. The Auth view is used for handling authentication as only authenticated users can access the other views and create new memes.
Note that time-based data in the API is represented as the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC. See the API documentation for details. Further note that you must use the API to complete this problem statement, including interacting with data from other chapters, though you may consider a hybrid approach where you have your own database storing non-API data.
We're looking for feedback! If you have any opinions or ideas, start a discussion.
Your app will support 2 types of users: guest and authed.
- Are unauthenticated users (i.e. users that have not logged in)
- Can only access the Auth view
- Can NOT view any memes in the system
- Are authenticated users (i.e. users that have already logged in)
- Can access every view
- Can only view memes created by themselves, memes sent via chats, or memes shared by their friends via stories
- Can NOT view vanished memes or comments
Chats view: create, send, and receive memes directly and privately.
From this view, users can send "memes" directly to one another.
Across all views, memes in the API:
- Are objects that include either 1) an image url, 2) a description between 1
and 500 characters, or both
- When creating a new meme, users are asked for an image url. This means users are not uploading their memes to your app, only linking to images hosted elsewhere
- A description is always optional if an image url is provided
- Are rendered as an image (if given)
- If there is a description (i.e.
description
property is non-null), it is displayed near the image or by itself if there is no image - This means meme objects with a null
imageUrl
property and non-nulldescription
property should render as a simple chat message in this view
- If there is a description (i.e.
- Cannot be edited after creation
- The only exception is when manually vanishing a meme in the Chats view
- Usually appear in a scrollable list, colloquially referred to as a "wall," "feed," or "chat"
- Show the username of the user that owns/created them
- Show some sort of timestamp describing when they were created
- Are usually displayed in descending creation order (i.e. newest first)
Users can "create" memes in this view by providing a string URL pointing to an existing image on the internet. For example:
In the Chat view specifically, memes can be assigned an
expiration time. Use the
receiver
property when creating a meme to specify which user to "send" the meme to /
"receive" the meme. The
owner
of the
meme, then, is its sender. It is not required that two users be
friends before they can chat with one another.
Meme objects created for this view must have a valid
user_id
for thereceiver
property, a trueprivate
property, and a nullreplyTo
property. This is the difference between meme objects displayed in the Chats view and meme objects meant for display in other views.
When sending a meme to another user, they can give the meme an expiration time
(stored as
expiredAt
)
representing some point in the future. Once this time elapses, the meme will no
longer appear in the Chats view. This is referred to in the UI as a "vanishing,"
"vanished," or "ghost" 👻 meme.
The app will never show ghost memes. That is: if
0 ≤ expiredAt < Date.now()
, the meme will not appear in the Chats view (or
anywhere else). However, there should be some visual indication in the UI that a
particular meme has vanished from this view.
Users can also manually vanish any previously sent memes. A meme can be manually
vanished by its owner at any point, and even if the meme originally had no
expiration time; i.e., originally expiredAt ≤ -1
, but expiredAt == 0
after
being manually vanished.
Set a meme object's
expiredAt
property to0
ensure it is vanished. On the other hand, whenexpiredAt
is set to-1
(or any negative number), the meme should never automatically vanish and should always be shown in this view.
Stories view: like, share, and comment on memes with friends.
From this view, users can:
- View all the memes they've shared to their personal "story"
- Each user has a single "story" they can add memes to
- The contents of a user's story are only available to that user and their friends
- View 🔎, like ❤️, and comment 🗣️ on memes added to their own story and to
their friends' stories
- Comments are meme objects created in direct response to a meme shared to a
user's story
- This means comments cannot be created in response to other comments
- This also means the text of a comment is stored in the
description
property
- Like other meme objects in the system, comments should be rendered as images
if the
imageUrl
property is not null
- Comments are meme objects created in direct response to a meme shared to a
user's story
- "Manually vanish" (delete) memes they've shared to their story and comments
they've made
- Unlike in the Chats view, meme objects in the Stories view don't expire on a timer and don't have to disappear asynchronously; however, they can be manually vanished (deleted) by the user
- To indicate that a meme or comment has been manually vanished, set its
expiredAt
property to0
- Vanished comments and memes must not be visible in the in the Stories view
- Share a meme they saw on a friend's story directly to their own story (as their own newly created meme)
Memes should be displayed in descending creation order (i.e. newest first).
Users can "create" memes in this view by providing a string URL pointing to an existing image on the internet. For example:
Meme objects created for this view must have a null
receiver
property and a trueprivate
property. If it's a new meme upload,replyTo
must also be null. If instead it's a new comment in response to a meme upload,replyTo
must be a validmeme_id
. This is the difference between meme objects displayed in the Stories view and meme objects meant for display in other views.
Notifications view: manage various informational alerts.
This view shows the user a list of all the memes from their Stories view that have descriptions or comments that mention them. Additionally, users can dismiss notifications they no longer wish to see.
To fully satisfy this requirement, users of your app always receive a notification about being mentioned unless they already dismissed it through your app. That is: teams may find it necessary to rebuild the most recent local data of existing users (perhaps created through other frontends) when they first login.
The API does not store user notification data. That is the responsibility of each individual team's solution. However, the API contains all of the data necessary to reconstruct all of a user's recent notifications.
Auth view: user registration and login.
guest
users can use this view to login using their username or email and
their password. There is an open registration feature guests can use to
register a new account. When they do, they must provide the following where
required:
- Full name <required>
- Phone number
- Email address <required>
- Password <required>
- Password strength must be indicated as well. Weak passwords will be rejected. A weak password is ≤10 characters. A moderate password is 11-17 characters. A strong password is above 17 characters.
- Profile picture file <required>
- The answer to a simple CAPTCHA challenge of some type <required>
- Example:
what is 2+2=?
- Teams must not call out to any API for this. Teams must create the CAPTCHA manually.
- Example:
When registering, the user must choose a picture from their computer and upload it to your app as their profile picture. This is unlike the others views, which only allow a user to provide an image URL (when creating a meme). Additionally, profile picture uploads should be limited to a reasonable file size and dimensions.
Unlike past problem statements, user creation/deletion is managed for you through the API. However, dealing with login credentials, storing uploaded profile pictures, and managing other non-API data is still your team's responsibility.
To fully satisfy this requirement:
- There should be some way to logout of the app
- The absolute maximum image size must be ≤10MB (you should choose a lower limit)
- Allowed image file types must be limited to: JPEG, PNG, and GIF
- Guests will be prevented from logging in for 5 minutes after 3 failed login attempts. Guests will always see how many attempts they have left.
- Guests will have the option to use remember me functionality to maintain long-running authentication state. That is: if guests login and want to be "remembered," they will not be logged out until they manually log out.
authed
users can send and accept friend requests and unfriend other users.
Users can "friend" and "unfriend" other users they encounter in the app by exchanging friend requests. For example, when user A sends a friend request to user B and user B accepts that request, user A's memes appear in user B's Stories view and vice-versa. Conversely, when one user unfriends another, each user should immediately stop seeing the others' memes in their respective Stories view.
To fully satisfy this requirement, users must be able to:
- View a list of their friends and friend requests
- Remove friends
- Send new friend requests by username, phone, email or by interacting with
mentions
- This is the outgoing friend request type
- Accept and reject friend requests sent to them
- This is the incoming friend request type
Additionally, users will be notified when they have outstanding incoming friend requests.
Teams can create a separate view for friendship functionality or merge it into an existing view.
The API only stores information about friendships and outstanding requests between users. It will not stop your app from adding/removing friends or adding/removing requests in strange and nonsensical ways. It is up to your team to implement the business logic of friend management while also handling potentially bad data sent by other teams.
If a user does not remember their password, they can use email to recover their account.
If a user has forgotten their login credentials, they will be able to recover their account by clicking a link in the recovery email sent to their address.
The app must not actually send emails out onto the internet. The sending of emails can be simulated however you want, including outputting the would-be email to the console. The app will not use an external API or service for this. For full points, ensure you document how recovery emails are simulated when submitting your solution so judges can test it.
authed
users can use hashtags and mention other users in comments and
meme descriptions.
Users can "mention" other users in meme descriptions and comments by inputting
an @
followed by the target user's username, e.g.
This meme was inspired by @username!!!
mentions user username
. If the user
does not exist, it's just normal text. If the user does exist, the text becomes
a mention, which is rendered as a link allowing other users to view the
mentioned user's name and profile picture
and add/remove them as a friend. Note that
this does not have to occur in a separate view.
Users can also include hashtags, e.g. #MyHashTag
, in meme descriptions and
comments by inputting a #
followed by a string of alphanumeric characters.
Each hashtag will be rendered as a link pointing to its own
search result.
Additionally, users will be notified when they are mentioned in a new comment or newly uploaded meme's description they have permission to view.
authed
users can search for memes in the system.
Users can search for memes by their description, by hashtags in the description, by owner, by creation time, or some combination thereof. Results can then be sorted by owner or creation time. Users can access this functionality directly from any view in the app.
To fully satisfy this requirement, results should:
- Be visually divided into two tabs/sections: results from chats and results from stories
- Render each meme's image, owner, creation time, and description
- Only include memes the user has permission to see1
- Be ordered by descending creation time initially (i.e. newest memes first)
- Be sortable by the criteria outlined above
1 Currently, users only have permission to see memes from their friends' stories. Showing user X a meme from user Y's story when X and Y are not friends is an egregious privacy violation and security vulnerability.
Like counts will update in the UI in real time; new chat messages/memes will appear without a page refresh.
In the Stories view, updates to a meme's like count and new comments must appear in the UI as they happen without the page refreshing or the user doing anything extra, like pressing a refresh button. In the Chats view, new meme objects should appear as they are received.
This type of automatic updating is called asynchronous or "ajaxian" since it occurs outside the usual synchronous event flow. There are many examples. One way to implement asynchronous features (and some forms of caching) is by using frontend timers to regularly check a source for new data every now and then.
The app will be performant.
The amount of time the application takes to load and sort data, display information, and act on input is progressively penalized. That is: the teams with the fastest (lowest) load times will earn the most points and the teams with the slowest (highest) load times will earn zero points from this requirement.
Average (median) is used to calculate load times. Measurements will include initial page load times and, depending on the other requirements, various other frontend UI response times.
FOUC may also be penalized. Tail latencies (e.g. on startup with a cold cache) are ignored; only averages are considered.
To maximize performance, consider caching (see also) the result of data processing operations and using range queries to retrieve only the data your app hasn't yet processed. Fetching results from a local cache can be upwards of 100x faster than making an API request for the same data, plus you avoid hitting rate limits. However, care must be taken to keep the cache fresh.
Results and lists of items displayed in the frontend UI will be paginated where appropriate.
Pagination is the strategy of showing a limited number of a large set of results and providing a navigation element where users can switch to different "pages" of that large set. A Google search result (which has multiple pages) is a good example of pagination. Infinite scroll, a specific pagination implementation used by the likes of Facebook/Instagram and Twitter, is another good example.
Security: no XSS, SQLI, insecure database, or other trivial security vulnerabilities.
The app will use modern software engineering practices that protect from common XSS, SQL injection, and other security vulnerabilities. Specifically: form inputs and the like will not be vulnerable to SQL injection attacks. User-generated outputs will not be vulnerable to XSS or similar attacks.
Additionally, any passwords stored in local or remote databases must be protected. Your language of choice has built-in tools to handle all this for you, and there are many tutorials for how to safely store passwords in a database. Your users are counting on you to protect them!
When storing secrets, we recommend using SHA256+bcrypt, scrypt, or PBKDF2 depending on your language (and regardless of frontend vs backend). Secrets stored in cleartext or just re-encoded, e.g. with base64, will earn your team zero points for this requirement.
The app will fail gracefully when exceptional conditions are encountered.
This includes handling API errors during fetch, login errors, random exceptions, showing spinners when content needs to load, etc.
Every so often, the API will respond with an
HTTP 555
error instead of fulfilling a request. Further, API requests and responses will be manipulated by the judges in an attempt to break the app. If at any time a user is presented with a non-app error page or a completely blank screen for more than a second or so, your solution may lose points on this requirement.
The frontend UI will be responsive to mobile, tablet, and desktop viewports.
The app will be pleasant to the eye when viewed on a smartphone, tablet, and a desktop viewport. The design and functionality will not "break" across these viewports nor will the app become non-functional.
Judges will view and interact with the app through emulated phone and tablet viewports. If the app breaks when viewed, it will lose points on this and other requirements. We recommend using mobile-first software design principles.