See also: API documentation
BDPA has partnered with Business API Solutions LLC to build out a business and opportunity-oriented online social media platform they're calling InBDPA. Yours is one of the teams in the bidding war for this lucrative contract and Business API Solutions wants to see a demo ASAP!
Summary of requirements (15 total)
Caching and ETL (i.e. regularly extracting data from the API and then transforming, storing, and querying it on your own) are essential to success with this problem statement.
The app supports four user types: guests, inners, staff, and administrators; the latter three are all authenticated users while guests are unauthenticated. Authentication occurs via the API. Users interact with content via each other's profiles, which are akin to living resumes, and by filtering through a list of available industry and volunteer opportunities. Each profile and opportunity is reachable via a unique URL. While all users can view profiles, unauthenticated users see only a small subset of user profile information. Similarly, unauthenticated users cannot access any opportunities at all.
The app has at least five views: Home, Profile, Opportunity, Admin, and Auth. The Home view is the landing page of your app. The Profile view renders, depending on the URL, a specific user's profile. The Opportunity view allows an authenticated user to view available opportunities, and allows staff users to create, edit, and delete opportunities. The Admin view allows administrators to create, manage, delete, and impersonate other users and view system-wide statistics. The Auth view is used for handling user authentication.
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, contact us on Slack.
🚧 🚧 To avoid disqualification, please take note of the following:
- Unlike PS2, PS1 is a "chapter-wide problem statement". That is: all students, coaches, and coordinators in the chapter can teach to, talk about, and collaborate on a solution. And then, when the conference comes around, your chapter sends your best five students to finish the job.
- Your solution’s landing page must be available at
http://127.0.0.1:3000
(localhost) on your team's AWS WorkSpace. Judges must not have to type in anything other thanhttp://127.0.0.1:3000
to reach your app. - Your solution’s source code must be located at
%USERPROFILE%\Desktop\source
on your team's AWS WorkSpace. You can have other files located elsewhere, so long as they are also visible from the aforesaid directory. - HTTP requests to the API must be sent with an
Authorization
header (Key
header is deprecated). See the API documentation for details.
Your app will support 4 types of users: guest, inner, staff, and administrator.
- Are unauthenticated users (i.e. don't have to login)
- Can access the Auth view
- Can access only a limited version of the Profile view
- Are authenticated users (i.e. users that have already logged in)
- Can access the full Profile view
- Can access only a limited version of the Opportunity view
- Are authenticated users (i.e. users that have already logged in)
- Can access the full Profile view
- Can access the full Opportunity view
- Are authenticated users (i.e. users that have already logged in)
- Can access the full Profile view
- Can access the full Opportunity view
- Can access the Admin view
- Can impersonate (login as) other users
- Can promote
inner
accounts intostaff
accounts, andstaff
intoadministrator
accounts
Home view: display the InBDPA landing page featuring the hard sell.
This is the "home page" of your service, and is the first page users will land on when they navigate to your app. The purpose of this view is to sell the viewer on the awesomeness of your application/solution while introducing them to your service's features.
A good example would be LinkedIn's landing page.
Profile view: display and/or modify an authenticated user's profile page.
This view is responsible for rendering users' profile information. Each authenticated user has a unique URL that leads directly to their profile page.
When viewed by a guest
, a "limited" version of the view displays the following
in an aesthetically pleasing manner:
- The user's username
- The user's type
- The user's About section (if available)
- The number of first and second -order connections the user
has (e.g. "X connections")
- Note that third-order connections should not be included in this count
When viewed by an authenticated user, the "full" view displays the following in an aesthetically pleasing manner:
- Everything displayed in the "limited" view
- The number of times this user's profile has been viewed
- The number of active sessions currently viewing this profile
- The user's Experience section (if available)
- The user's Education section (if available)
- The user's Volunteering section (if available)
- The user's Skills section (if available)
- The user's status as a first, second, or third -order connection relative to the authenticated viewer, or an appropriate indication if they are not connected
When an authenticated user views a profile to which they're connected, they can also view that profile's first, second, and third -order connections as a list of usernames (i.e. hyperlinks leading to their respective profiles). However, only usernames of users to which the authenticated user is also connected are shown.
For instance: suppose the connection between five users goes A➔B➔C➔D➔E where
user A
is connected to user B
, B
is connected to user C
, and so on. Then
A
, viewing user D
's profile, can see a list of D
's connections since A
and D
are third-order connected. In that list, A
will see a link to C
's
profile but they will not see user E
at all since A
and E
are not
connected. Regardless, E
, as a first-order connection of D
, will still be
counted in the "X connections" displayed on D
's profile, so A
would still
see the "3 connections" count. Note that A
would see "3 connections" and not
"4 connections" when viewing D
's profile since third-order connections
(A...D
) should not be included in this count.
Ensuring your users' social graphs cannot be arbitrarily crawled by the public is a huge privacy and security concern. Your app must balance the need to keep user data private with the inherently leaky abstraction that is social media. This helps prevent the stalking and harassment of your users and is the reason
guest
users cannot see any details about any user connections.
When an authenticated user views their own profile, the full view additionally allows the user to edit their profile's information. Specifically:
- They can change any of the information in any of their sections (About, Experience, Education, etc)
- They can change their public profile URL
Authenticated users can also add and remove other users as connections from this view. There is currently no permission system requirement yet, so any user can connect to or disconnect from any other user at any time.
Each authenticated user has a unique URL that leads directly to their public profile page. Your team is free to decide the scheme of this URL.
For example: suppose a solution were using a potential URL scheme of
{app-url}/in/{profile-name}
. If {app-url}
were https://my-app.com
and
{profile-name}
were 9710435961
, then the profile would be reachable at
https://my-app.com/in/9710435961
.
Using this view, authenticated users must be able to change the {profile-name}
component of their unique URL. By default, the {profile-name}
is a random
alphanumeric string. A user changing their public profile URL must not change
their username or email.
Public profile URLs are not stored at the API level and must be stored locally.
All users must have a profile picture. By default, this picture is provided by Gravatar, an API service that associates people's email addresses with profile pictures of their choosing. Users can also use this view to upload their own cover photo.
A profile's cover photo (aka: background images, profile banners) must be among the first thing people see when this view is loaded. It should be large, spanning most of the width of the page, and appear at the top of the view with short height.
This view must allow users, when viewing their own profile page, to optionally upload their own custom cover photo.
This picture will not be stored in the API and must be stored locally by your solution.
To get a user's profile picture from their email, follow these instructions.
For example, one of the gravatars used by the creator of this problem statement is:
Represented by the following HTML:
<img src="https://www.gravatar.com/avatar/ff8fb2b91d470633184505d5e1f15366" />
To get aesthetically pleasing images for users that whose email addresses are
not associated with a gravatar, just append the ?d=identicon
query string to
the end of all your gravatar URIs. Addresses with gravatars will display like
normal, but those without will get a neat hash-based random image.
For example, here's a gravatar using a fake hash:
Represented by the following HTML:
<img src="https://www.gravatar.com/avatar/12345678912345678912345678900000?d=identicon" />
Users can optionally provide and display specifics about themselves (About section), their industry experience (Experience section), their education (Education section), their volunteer experience (Volunteering section), or their skills (Skills section).
The About section is freeform. Users can provide up to 1,000 characters of text.
The Experience section lists up to 10 items each with a max 100 character title (e.g. "Front End Lead Developer at Google"), start and end date, max 30 character location, and max 250 character description.
The Education section lists up to 10 items each with a max 100 character title (e.g. "University of Chicago"), start and end date, max 30 character location, and max 250 character description.
The Volunteering section lists up to 10 items each with a max 100 character title (e.g. "BDPA Coach"), start and end date, max 30 character location, and max 250 character description.
The Skills section lists up to 10 skills, each with a max string length of 30 characters (e.g. "JavaScript").
Opportunity view: create, manage, and/or view job opportunities.
This view is responsible for rendering opportunities, which are job offers and volunteer positions, as an aesthetically pleasing listing. Each opportunity must have its own unique URL.
When viewed by an inner
user, a "limited" version of this view displays the
following in an aesthetically pleasing manner:
- Opportunities appear sorted in descending order of creation date (most recent opportunities appear first)
- Opportunities are displayed with their max 100 character title, number of views, number of active viewers (sessions)
- Clicking on an opportunity shows the max 3,000 character contents of the
opportunity rendered as
Markdown
- Your app will have to use a library to render the Markdown into HTML when users access the Profile view. Research and explore which Markdown library is best for your purposes. Here are some suggestions to start with
staff
users can additionally access the "full" version of this view, which
allows them to create new opportunities, edit opportunities they've created, and
delete opportunities they've created.
This view cannot be accessed at all by guest
s. When a guest
navigates to
this page, they should be asked to login.
Admin view: view statistics and manage all authenticated users.
This view allows administrator
s to manage existing users and opportunities.
Specifically, administrator
s can:
- Force a non-
administrator
user to log out immediately (i.e. as soon as their next HTTP response from the server) - Impersonate a non-
administrator
user- When impersonating a user, your app will treat the
administrator
as if they had logged out and successfully logged back in as the user they wish to impersonate - When impersonating a user, a button/link must be added to the main
navigation element that, which clicked, allows the
administrator
to return to the Admin view with their originaladministrator
authentication
- When impersonating a user, your app will treat the
- Create new
administrator
,staff
, orinner
accounts - Promote
inner
accounts intostaff
accounts, andstaff
intoadministrator
accounts - View generic statistics about the system including:
- Total number of users in the system
- Total number of opportunities in the system
- Total number of views in the system
- Total number of active viewers (sessions) in the system
Auth view: register new users and authenticate existing users.
guest
users can use this view to authenticate (login) using their username
and their password. This sensitive information is referred to as a user's
credentials.
guest
users can also use this view to register new credentials and create new
inner
accounts.
Your app must use
the API
to authenticate the guest
user instead of retrieving the user's credentials
from a local database. Your app will do this by sending the API a
digest value derived
from the username and password provided. See
the API documentation
for more details.
If authed
, the user can choose to logout, after which your app will treat
them like any other guest
user. Logging a user out does not require a call to
the API.
There is an open registration feature guest
users can use to register a new
account. When they do, they must provide at least the following where required:
- Desired username <required>
- Must be alphanumeric (dashes and underscores are also allowed).
- 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.
- 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:
Your app must use the API to create the new user instead of storing the user's information locally.
- All new users start off as
inner
s. Onlyadministrator
s can promoteinner
accounts intostaff
accounts, orstaff
accounts intoadministrator
accounts. - Usernames and email addresses must be unique. That is: no two users can have the same username or email address. This is enforced at the API level.
guest
users will be prevented from logging in for 1 hour after 3 failed login attempts.guest
users will always see how many attempts they have left.guest
users will have the option to use remember me functionality to maintain long-running authentication state. That is: if aguest
logs in and wants to be "remembered," they will not be logged out until they manually log out.- You must use the API to create new users, authenticate and store their credentials, and handle all user data (where applicable). See the API documentation for details on what the API will store for you. Any data not storable at the API level can be stored locally by your solution.
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. Your app will then allow them to set a new password.
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.
A navigation element containing the BDPA logo, your app title, and a subset of user data is permanently visible to users.
In every view, a navigation element is permanently visible containing the BDPA logo (downloadable here) and the title of your app.
Users' second-order and third-order connections must be tracked by your solution.
For example, suppose user-A
is connected to user-B
, and user-B
is
connected to user-C
, then:
user-A
anduser-B
are considered first-order connections in the system.- One hop: A➔B
user-B
anduser-C
are considered first-order connections in the system.- One hop: B➔C
user-A
anduser-C
are considered second-order connections in the system.- Two hops: A➔B➔C
If user-A
is also connected to user-D
, then:
user-D
anduser-C
are considered third-order connections- Three hops: D➔A➔B➔C
If user-A
were the authenticated user viewing other users via the
Profile view, then:
- When viewing
user-B
's profile,user-A
would see thatuser-B
is one of their first-order connections - When viewing
user-C
's profile,user-A
would see thatuser-C
is one of their second-order connections - When viewing
user-D
's profile,user-A
would see thatuser-D
is one of their first-order connections
Further suppose user-E
is connected to user-D
. Then:
- When viewing
user-E
's profile,user-A
would see thatuser-E
is one of their second-order connections - The system does not consider
user-E
anduser-C
as "connected" since fourth-order connections and beyond are ignored
Note that the API only stores first-order connections. You will have to calculate users' second- and third- order connections.
Hint: to keep things speedy and avoid getting rate limited, keep a denormalized local cache of users' connections. Do this instead of constantly hitting the API O(n3) times just to calculate and recalculate a user's second- and third- order connections, which would likely severely negatively impact your app's performance. Instead, check every now and then for updates to users' connections by leveraging
updatedAfter
when polling the/users
endpoint, and structure your cache such that finding, updating, and deleting connections is efficient enough.
Every view (Home, Profile, Opportunity, Admin, Auth) must use the API to asynchronously update the total number of views (where applicable) and number of active sessions.
When the Profile view loads a specific user's profile, or the Opportunity view loads a particular opportunity, or the Home, Admin, or Auth views are loaded, your app must do two things:
- If viewing a specific profile or opportunity: increment the (monotonic) total number of views associated with that profile or opportunity
- Add or renew an "active session" entry associated with the client
Active sessions represent users (guest
or otherwise) that are currently
interacting with one of these views. When registering a new active session,
you'll receive a
session_id
that you can use to
renew
that session every so often or
manually expire (delete)
it.
Clients should regularly renew active sessions for as long as the user is interacting with one of these views. Once the client navigates away from the view, the session should be manually expired and no longer renewed. That is: shortly after the user leaves a view and, hence, stops being an "active" viewer of a view, your solution must attempt to manually expire the active session associated with said view. There are several methods for determining when a user is trying to navigate away from a web page.
Additionally, when displaying total views and/or total active sessions for a profile/opportunity or in the Admin view, the displayed values must refresh asynchronously.
This type of automatic updating/revalidating of data is called asynchronous or "ajaxian" since it occurs outside the usual synchronous event flow. There are many solutions, including interval revalidation, focus revalidation, and visibility-based revalidation (i.e. updating data only for elements that are currently visible). Another solution is to use 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 load times will earn the most points and the teams with the slowest 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.
Tail latencies (e.g. on startup with a cold cache) are ignored. Only averages are considered.
FOUC may also be penalized.
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.
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.
As for database security, any passwords present in the database must be hashed (not encrypted). We recommend using a salted SHA-256 hash construction or something similar. You don't need to do anything fancy. There are many tutorials for how to safely store passwords and other credentials in a database.
Passwords stored in your database in cleartext, hashed incorrectly, or 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.