Skip to content

lam-kelly/interclass

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Fritter Frontend

Congrats, you've completed the Fritter backend! Now it's time to make an interface that your users will be able to interact with in A6: Fritter frontend. Make sure to read this document fully as well, as it contains a lot of A6-specific info!

Starter Code

This starter code implements freets, feeds, and forms with no styling. The backend starter code for freets and users from the previous assignment (A5) is contained in the server folder. The frontend starter code is in the client folder and is implemented using the Vue framework.

The project is structured as follows:

  • api/index.ts sets up the backend database connection and Express server. This should actually be in the server folder, but it must be here due to a Vercel limitation.
  • server/ contains the backend starter code from A5 (with some changes)
    • freet/ contains files related to the Freet concept
    • user/ contains files related to the User concept
  • client/ contains the frontend starter code
    • App.vue is the root component of your application
    • main.ts is the entry point of your application, which initializes Vue
    • components/ contains the components of the frontend
      • Account/ contains the account settings page and the related forms
      • Freet/ contains the homepage and components related to Freets
      • Login/ contains the login/register page and the related forms
      • Common/ contains general form components that can be reused across different concepts
    • public/ contains base HTML files and static assets (like the default Fritter logo)
    • router.ts contains the Vue router
    • store.ts contains the Vuex store, which stores application state and persistent data

Installation

Make a copy of this repository under your personal GitHub account by clicking the Use this template button. Run npm install in your terminal to install local dependencies. Copy your .env file from A5 into the root directory of your new repo. Make sure you can run the starter code locally before proceeding.

Setup

To incorporate the backend you developed in A5 with our starter frontend, please move your files into the server folder. For example, note that whereas the freet and user folders were in the root directory of your backend, they have been now moved to server/freet and server/user, respectively.

We've made some updated to the A5 server starter code that we hope you can incorporate into your backend as well.

Please reference the full diff here for a complete list of changes.

A summary of the changes is provided below:

  • api/index.ts:
    • Add mongo store to track sessions
    • Remove old frontend from express server (remember to copy over lines importing your router.ts files)
  • freet/middleware.ts and user/middleware.ts:
    • change all contents of error: to strings to be easily printed out by the frontend
    • in isUsernameNotAlreadyInUse() of user/middleware.ts: bug fix related to changing password
  • freet/collection.ts:
    • update finding freets from an author to return in descending order for consistency with finding all freets
  • freet/router.ts
    • updated incorrect documentation for GET /api/freets
    • changed PUT to PATCH /api/freets/:freetid, to better follow REST API conventions
  • user/router.ts
    • add GET /api/users/session so the frontend can fetch info about the logged-in user
    • changed PUT to PATCH /api/users, to better follow REST API conventions
  • user/collection.ts
    • add typings to a updateOne() parameter to make TypeScript happy

Once you're done, test once more that you can run the project locally. Now you're ready to start developing your frontend interface for Fritter!

Running locally

Running locally requires a few extra npm scripts from package.json in comparison to A5.

  1. Run npm run serve, which compiles the frontend for hot-reloading with webpack and serves it at port 8080.
  2. Open a new terminal (with the original one still open) and run npm run dev to start the backend at port 3000.
  3. To view your website, connect to localhost:8080 (instead of port 3000) since the backend will no longer serve any HTML files.

Vue proxies any URL it can't resolve on the client side (at port 8080) to the server (to port 3030), which is why we can call API routes using relative URLs (such as fetch('/api/freets')). See client/vue.config.js and associated Vue CLI docs for more details.

Deployment to Vercel

We will be using Vercel to host a publicly accessible deployment of your application.

  1. Log in to Vercel and go to the project creation page and select Continue with GitHub.

  2. Find your frontend repository you just created and click Import. For the Framework Preset, choose Vue.js. In the Build and Output Settings section, toggle the override switch for Output Directory and set it to client/dist. In the Environment Variables section, add an entry where NAME is MONGO_SRV and VALUE is your MongoDB secret.

  3. Click Deploy and you will get a link like https://fritter-starter-abcd.vercel.app/ where you can access your site.

Vercel will automatically deploy the latest version of your code whenever a push is made to the main branch.

Using Vue

Working in Vue means working with Vue components. The starter code organizes components by the resultant tree structure of how the components are composed together.

Template

Every component takes advantage of an HTML-based template syntax, which is HTML code that binds the rendered DOM to the component data. Inside the template is where we can display specific form components like <CreateFreetForm />. We also take advantage of conditional rendering here to display different things to different users (such as signed in vs. signed out). For example, in client/components/Freet/Freets.vue in lines 5-23, we have:

<section v-if="$store.state.username">
  <header>
    <h2>Welcome @{{ $store.state.username }}</h2>
  </header>
  <CreateFreetForm />
</section>
<section v-else>
  <header>
    <h2>Welcome to Fritter!</h2>
  </header>
  <article>
    <h3>
      <router-link to="/login">
        Sign in
      </router-link>
      to create, edit, and delete freets.
    </h3>
  </article>
</section>

Here, if store.state.username exists, we say Welcome @username. Otherwise, we say Welcome to Fritter! and give them a link to the login page. This is just one example of conditional rendering.

Components

Each .vue file also has script tag, which is where you can export the actual component.

The "top level" components displayed when you navigating to certain URLs (like /login or /account) are shown in client/router.ts. Within each of these components, we have:

  • name name of the component
  • components components that are used in this top level component, usually forms like LoginForm

The "lower level" components are the general form components that we have provided. These consist of:

  • name name of the component
  • mixins (sometimes) used to have reusable logic between components. In this case, mixins have components in client/components/common/ like BlockForm.
  • props (sometimes) properties that are passed from a parent component to child components as needed
  • data() stores data associated with this Vue instance
  • methods methods associated with the current component that can be used in it

Store

You may see that many components use this.$store. In client/store.ts, we have created a Vuex.Store that is used to our application state. We use mutations to change state. We call these mutations by doing this.$store.commit('[mutation]', [payload]). The payload is like an additional argument that could be used in our mutation. An example mutation:

setUsername(state, username) {
  /**
    * Update the stored username to the specified one.
    * @param username - new username to set
    */
  state.username = username;
}

This mutation is called a few times, such as in App.vue where it says this.$store.commit('setUsername', user ? user.username : null);. In this case, we are committing the value of our username, which can be accessed within the state as $store.state.username.

Routing

Routing on the server side means the server sending a response based on the URL path that the user is visiting. When we click on a link in a traditional server-rendered web app, the browser receives an HTML response from the server and reloads the entire page with the new HTML.

However, in a Single-Page Application (SPA) like the one we're developing, the client-side JavaScript can intercept the navigation, dynamically fetch new data, and update the current page without full page reloads. This typically results in a more snappy user experience, especially for use cases that are more like actual "applications", where the user is expected to perform many interactions over a long period of time.

In such SPAs, the "routing" is done on the client side, in the browser. A client-side router is responsible for managing the application's rendered view using browser APIs such as History API or the hashchange event. We use the Vue Router library for client-side routing, which is referenced in client/router.ts.

References

IMPORTANT: This starter code uses version 2 of Vue, not version 3! There are multiple significant breaking changes between the two versions, so please only consult documentation, StackOverflow questions, and other resources that reference Vue 2.

Here is a list of documentation you may want to consult while working with Vue:

API routes

POST /api/users/session - Sign in user

Body

  • username {string} - The user's username
  • password {string} - The user's password

Returns

  • A success message
  • An object with user's details (without password)

Throws

  • 403 if the user is already logged in
  • 400 if username or password is not in correct format format or missing in the req
  • 401 if the user login credentials are invalid

DELETE /api/users/session - Sign out user

Returns

  • A success message

Throws

  • 403 if user is not logged in

POST /api/users - Create an new user account

Body

  • username {string} - The user's username
  • password {string} - The user's password

Returns

  • A success message
  • An object with the created user's details (without password)

Throws

  • 403 if there is a user already logged in
  • 400 if username or password is in the wrong format
  • 409 if username is already in use

PUT /api/users - Update a user's profile

Body (no need to add fields that are not being changed)

  • username {string} - The user's username
  • password {string} - The user's password

Returns

  • A success message
  • An object with the update user details (without password)

Throws

  • 403 if the user is not logged in
  • 400 if username or password is in the wrong format
  • 409 if the username is already in use

DELETE /api/users - Delete user

Returns

  • A success message

Throws

  • 403 if the user is not logged in

GET /api/class/allclass - Get all classes

Returns

  • All classes that exist

GET /api/class/:classId? - Get a class given an ID

Returns

  • The class specified by the class ID

GET /api/class/teacher/:teacherId? - Get the class that a teacher belongs to

Returns

  • The class that has the specified teacher

Throws

  • 404 if the teacher ID is not valid

GET /api/class/student/:studentId? - Get the class that a student belongs to

Returns

  • The class that has the specified student

Throws

  • 404 if the student ID is not valid

POST /api/class - Create a new class

Returns

  • A success message

Throws

  • 403 if the user is not logged in or is not a teacher

DELETE /api/class/:classId? - Delete a class specified by the class ID

Returns

  • A success message

Throws

  • 403 if the user is not logged in or is not the teacher of the class
  • 404 if the class ID is not valid

PATCH /api/class/add/:classId? - Add a student to a class

Body

  • studentName {string} - The username of the student to be added

Returns

  • A success message

Throws

  • 403 if the user is not logged in or not the teacher of the class, or if the student is already in the class
  • 404 if the class ID is not valid
  • 400 if the student does not exist

PATCH /api/class/remove/:classId? - Remove a student from a class

Body

  • studentId {string} - The ID of the student to be removed

Returns

  • A success message

Throws

  • 403 if the user is not logged in or not the teacher of the class
  • 404 if the class ID is not valid
  • 400 if the student does not exist

GET /api/assignment/all - Get all assignments

Returns

  • The assignments that exist

GET /api/assignment/:assignmentId? - Get an assignment given its ID

Returns

  • The assignment specified by the ID

Throws

  • 404 if the assignment does not exist

POST /api/assignment - Create a new assignment

Body

  • assignmentName {string} - The name of the assignment to be added

Returns

  • A success message

Throws

  • 403 if the user is not logged in or is not a teacher

PATCH /api/assignment/:assignmentId? - Update an assignment with a problem

Body

  • newProblem {string} - The ID of the problem to be added

Returns

  • A success message

Throws

  • 403 if the user is not logged in or is not a teacher

DELETE /api/assignment/:assignmentId? - Delete an assignment

Returns

  • A success message

Throws

  • 403 if the user is not logged in or is not the teacher who made the assignment
  • 404 if the assignment ID is not valid

GET /api/competition - Get the competition the logged in user is in

Returns

  • A success message
  • null if user is not in a competition

Throws

  • 403 if the user is not logged in

POST /api/competition - Create a competition

Body

  • name {string} - The name of the competition

Returns

  • The created competition

Throws

  • 403 if the user is not logged in
  • 403 if the user is not a teacher
  • 403 if the user is currently in an active competition
  • 400 if the name is empty or a stream of empty spaces
  • 403 if teacher does not have a class

PATCH /api/competition/:competitionId/join - Join a competition

Body

  • classId {string} - The ID of the class to add

Returns

  • The joined competition

Throws

  • 403 if the user is not logged in
  • 404 if competitionId is invalid
  • 403 If competition has ended
  • 404 if classId is invalid
  • 403 If the user is not the teacher of the class
  • 400 if the class is already in the competition

PATCH /api/competition/:competitionId/leave - Leave a competition

Body

  • classId {string} - The ID of the class to add

Returns

  • The competition that user left

Throws

  • 403 if the user is not logged in
  • 404 if competitionId is invalid
  • 403 If competition has ended
  • 404 if classId is invalid
  • 403 If the user is not the teacher of the class
  • 400 if the class is not already in the competition

PATCH /api/competition/:competitionId/addAssignment - Add an assignment

Body

  • assignmentId {string} - The ID of the assignment to add

Returns

  • The competition that you're adding an assignment to

Throws

  • 403 if the user is not logged in
  • 404 if competitionId is invalid
  • 403 If competition has ended
  • 404 if assignmentId is invalid
  • 403 If the user is not the teacher of the class
  • 400 if the class is already in the competition

PATCH /api/competition/:competitionId/removeAssignment - Remove an assignment

Body

  • assignmentId {string} - The ID of the assignment to remove

Returns

  • The competition that you're removing an assignment from

Throws

  • 403 if the user is not logged in
  • 404 if competitionId is invalid
  • 403 If competition has ended
  • 404 if assignmentId is invalid
  • 403 If the user is not the teacher of the class

PATCH /api/competition/:competitionId/end - End a competition

Returns

  • The competition to end

Throws

  • 403 if the user is not logged in
  • 404 if competitionId is invalid
  • 403 If competition has ended
  • 403 If the user is not the teacher of the class

DELETE /api/competition/:competitionId - Delete a competition

Returns

  • A success message

Throws

  • 403 if the user is not logged in
  • 404 if competitionId is invalid
  • 403 If the user is not the teacher of the class

GET /api/problem/:problemId - Get the specified problem

Returns

  • The problem with the specified ID

Throws

  • 404 if the problem does not exist
  • 400 if the problem ID is empty

POST /api/problem - Create a new problem for an assignment

Body

  • question {string} - The problem statement
  • answerChoices {string[]} - The answer choices
  • answer {string} - The answer to the problem statement
  • pointValue {Number} - The amount of points the problem is worth

Returns

  • An object with the problem's details

Throws

  • 400 if the problem is not valid (empty field, duplicate answer choices)
  • 403 if the answer does not match one of the answer choices

DELETE /api/problem/:problemId - Delete a problem

Returns

  • None

Throws

  • 404 if the problem does not exist
  • 400 if the problem ID is empty

PATCH /api/problem/:problemId?/problemDetails - Update the details of a problem

Body

  • question {string} - The problem statement
  • answerChoices {string[]} - The answer choices
  • answer {string} - The answer to the problem statement
  • pointValue {Number} - The amount of points the problem is worth

Returns

  • The updated problem

Throws

  • 404 if the problem does not exist
  • 400 if the problem ID is empty or if the problem is not valid (empty field, duplicate answer choices)
  • 403 if the answer does not match one of the answer choices

PATCH /api/problem/:problemId?/addStudent - Update the solvers or workers of a problem

Body

  • problemId {string} - The ID of the problem being updated
  • isSolver {string} - True if logged in user solved the problem
  • isWorker {string} - True if the logged in user attempted the problem

Returns

  • The updated problem

Throws

  • 404 if the problem does not exist
  • 400 if the problem ID is empty
  • 403 if the user is not logged in

Releases

No releases published

Packages

No packages published

Contributors 3

  •  
  •  
  •  

Languages