https://github.com/nhan0504/El-Pollo-Loco
This project requires NodeJS version 17 or later to run. You can install it from https://nodejs.org/en/download/package-manager.
- Add a file called
.env
with the following contents in the/backend
directory.
DB_HOST=
DB_USER=
DB_PASSWORD=
DB_NAME=
SESSION_SECRET=super_secret_string
//Change this if the frontend is running on a different url
REQUEST_ORIGIN_URL=http://localhost:3001
//For testing purpose
TESTUSER=<your account username>
TESTPASS=<your account password>
Note that the first four fields will have to be filled with private information (you can ask one of the developers for that info). In the /frontend
directory, make sure that the contents of next.config.mjs
are
/** @type {import('next').NextConfig} */
const nextConfig = {
env: {
//Change this if backend is running on a different port
BACKEND_URL: 'http://localhost:3000',
},
};
export default nextConfig;
- Open a new terminal window,
cd
into/backend
, and runnpm install && npm start
to install dependencies and start the backend server. The default port it will be running on is 3000. - Open a new terminal window,
cd
into/frontend
and runnpm install && npm start
to install dependencies and start the frontend client. The default port it will be running on is 3000. However, in package.json we have set the port to 3001 because port 300 is used by backend - Go to http://localhost:3001/discover to access the webpage.
Unless otherwise specified, routes return status 200
or 201
on success, and 500
if an internal error is encountered. Routes flagged with "(Authenticated)" will return status 401
if the client's browser does not provide a valid authentication cookie (name=poll_cookie
).
Code located in backend/routes/auth.js
.
POST /auth/signup
Parameter | Type | Description |
---|---|---|
username |
string |
Required. Display name. |
password |
string |
Required. Plaintext password. |
fname |
string |
Required. Real first name. |
lname |
string |
Required. Real last name. |
email |
string |
Required. Email address. |
Returns status 409
if a user with the same username or email already exists.
POST /auth/login/
Parameter | Type | Description |
---|---|---|
username |
string |
Required. Username of account. |
password |
string |
Required. Plaintext password. |
Returns status 401
if the password is invaild, or if the user does not exist. Client is provided with a cookie expiring 24 hours after issuing (name=poll_cookie
).
POST /auth/logout/
Deauthenticates cookie provided by client browser.
GET /auth/is_authenticated
Returns status 401
if client is not logged in upon request.
GET /auth/profile/
Returns status 401
if client is not logged in upon request. Returns the following object.
{
username: string,
email: string,
user_id: int
}
Code located in backend/routes/users.js
.
GET /users/:user_id/followers/
Returns the following object.
{
followers: string[],
total_followers: number
}
Parameter | Type | Description |
---|---|---|
:user_id |
int |
Required. ID of account. |
GET /users/:user_id/following/
Returns the following object.
{
following: string[],
total_followers: number
}
Parameter | Type | Description |
---|---|---|
:user_id |
int |
Required. ID of account. |
GET /users/:user_id/follow/
Returns status 404
if the client is already following the specified user. Updates client profile to follow the user specified by :user_id
.
Parameter | Type | Description |
---|---|---|
:user_id |
int |
Required. ID of account to follow. |
GET /users/:user_id/unfollow/
Returns status 404
if the client is not following the specified user prior to the request.
Updates client profile to unfollow the user specified by :user_id
.
Parameter | Type | Description |
---|---|---|
:user_id |
int |
Required. ID of account to unfollow. |
Code located in backend/routes/tags.js
.
GET /tags/follow/:tag_name/
Returns status 404
if the specified tag does not exist. Updates client profile to follow the tag specified by :tag_name
.
Parameter | Type | Description |
---|---|---|
:tag_name |
string |
Required. Name of tag to follow. |
GET /tags/unfollow/:tag_name/
Returns status 404
if the specified tag does not exist or client is not following the tag. Updates client profile to unfollow the tag specified by :tag_name
.
Parameter | Type | Description |
---|---|---|
:tag_name |
string |
Required. Name of tag to unfollow. |
GET /tags/
Returns status 404
if the client is not following any tags. Returns string[]
.
Code located in backend/routes/polls
.
GET /polls/:poll_id
Returns status 404
if the specified poll does not exist. Returns the following object.
{
poll_id: int,
user_id: int,
title: string,
created_at: timestamp,
options: { option_id: int, option_text: string }[],
tags: { tag_id: int, tag_name: string }[]
}
Parameter | Type | Description |
---|---|---|
:poll_id |
int |
Required. ID of poll. |
POST /polls/
Creates a poll attached to the client's account.
Parameter | Type | Description |
---|---|---|
title |
string |
Required. Title of poll. |
options |
string[] |
Required. List of options. |
tags |
string[] |
Required. List of tags. |
DELETE /polls/:poll_id
Returns status 404
if the specified poll does not exist prior to the request. Deletes the specified poll.
Parameter | Type | Description |
---|---|---|
:poll_id |
int |
Required. ID of poll. |
GET /polls/comments/:poll_id
Returns the following object.
{
username: string,
user_id: int,
comment_id: int,
parent_id: int,
comment: string
}
Parameter | Type | Description |
---|---|---|
:poll_id |
int |
Required. ID of poll. |
POST /polls/comments/
Posts a comment to the poll specified by poll_id
.
Parameter | Type | Description |
---|---|---|
poll_id |
int |
Required. ID of poll. |
parent_id |
int |
Optional. ID of parent comment. |
comment |
string |
Required. Content of comment. |
GET /polls/comments/:comment_id
Deletes the specified comment.
Parameter | Type | Description |
---|---|---|
:comment_id |
int |
Required. ID of comment. |
Code located in backend/routes/feed
.
All routes return the following array of objects.
{
poll_id: int,
title: string,
created_at: timestamp,
username: string,
user_id: int,
vote_count: int,
tags: string, //Comma-delimited tags
score: int,
options: { option_id: int, option_text: string, vote_count: int }[]
}[]
All routes take the following URL parameter.
Parameter | Type | Description |
---|---|---|
:page_num |
int |
Required. Page number of feed. |
GET /feed/:page_num
Returns 404
if no polls exist.
GET /feed/friends/:page_num
GET /feed/tags/:page_num
GET /feed/user/:page_num
=======
The frontend is split into components, which match up with routing as supported by Next.JS. This project relied on Material UI for components such as a Card, Button, Page Bar, Login, etc.
Signin.tsx has the login components. Likewise, Signup has the sign up components. Each of them rely on AuthContext, which is a context at the Contexts folder that is passed into each component that needs to know if the user is signed in or not.
This folder has all the components for the Feed in Discover, Freinds, and Following. The parent file is feed.tsx
which calls the rest of the files. Each PollCard has a username of who made it (addFriend.tsx
), a comment box (comments.tsx
). When the comments are open, we load pollCardNoComments.tsx
. To search, we use search.tsx
, and to create a poll, we use PollForm.tsx
. The buttons on the feed are in feedButtons.tsx
.
profile.tsx
renders the profile page. This shows user stats (how many polls made, how many votes, etc.) and any polls a user made.
Lastly, we have a nav bar in navigation.tsx
which has the Home button, Profile, and log in/log out.
We decided to use TS due to its easy integration with fetch requests, and how it merged well with the frontend and backend. Typing added a layer of specificity we found important. Most of us have used the language before, which meant we were able to write code faster, since it was familiar.
Sort of the traditional route when developing a frontend, we decided to use React. React came with a lot of tools, such as Effects and State we could leverage to our benefit when developing components that would change. It also helped us write code that was slightly more abstracted. Moreover, due to its popularity, we were able to debug and find solutions faster.
There were a few reasons why we chose MySQL. Firstly, it was a easily configurable relational database system which was perfect for the features in our app. Secondly, some of the team members were familiar with it so it made it a more enticing option over other relational database systems.
In terms of style and design, we decided to use Material UI. This was a great decision, as it came with a lot of pre-implemented features we could just pop into our project. This also allowed us to spend less time on the style while having a clean and nice UX.
We use Next.js because it allows us to create API routes alongside with our React components. It's a popular framework to work with React to build web application because of serverside rendering making web app load faster
This is a platform for hosting created by the creators of Nex.js. Therefore, we chose this platform for hosting as it's compatible and easy to host our frontend created with Next.js
We decided to use Heroku to host the backend as it's free for students and easy to use.
Tags do not show up on polls until they have already been entered once into the database (i.e. a new tag not already in the database will not show up the first time a user tries to attach it to a poll).
Users can only see their own profile, and not those of other users.
The search function returns polls with a similar title to the query, but the poll cards are missing options, tags, and creator username.
The tag and friend lists at the top of the Following and Friend feeds respectively cannot be used to unfollow tags or unfriend users - this can only be done from poll cards.
If the server is stopped while a user is logged in, some information like followed tags and friends will remain in the browser's local storage and appear on poll feeds the next time it's started, even though the user is logged out. The user must log out before stopping the server to properly clear out the local storage.
If we (or you!) in the future decide to expand upon this project, we plan on implementing at least the following:
Users should be able to post polls just to their friends, and or to the whole public.
Users should be able to have a 2 way system whether or not they follow a person, and should decide to accept or deny someone's follow request.
As detailed above, users need to be notified when someone follows them, or when someone likes or comments on one of their polls.
Users should be able to use the search bar to search for other users. Right now user are only able to search for poll based on the poll title.
It would enhance the user experience to have a settings page where users could set notification settings, use dark mode, and save other various preferences.
We would like to have an email based forgot password system, where users can reset their password.
We would like to add test for the frontend. Right now we only have unit test for backend endpoint
- To start the unit test for backend run
cd backend && npm test
- Make sure to add your account credential in the
.env
file as the test will be using this account to test the endpoint that require authentication
We used a regular expression for email format verification on Signup from Stack Overflow: https://stackoverflow.com/questions/54916150/how-to-make-this-email-validation-regex-from-regular-expressions-info-work-in-ja
Our navigation bar is an altered version of this Material UI template:
https://mui.com/material-ui/react-app-bar/#app-bar-with-a-primary-search-field