We will continue from HW2, the previous website.
This task's main goals are:
- Adding users to our website. Only logged-in users can create new notes, but guests can view them without adding/editing/deleting notes. Follow https://fullstackopen.com/en/part4/. For simplicity, the name, username, password, and email cannot be changed after user creation, and we don't support user deletion.
- End-to-end testing: you have to submit at least 5 Playwright tests, and this is an opportunity to practice test-driven design (see tip at the bottom of this file).
- Minimize front-end waiting time:
- Next.Js static site generation (https://nextjs.org/docs/pages/building-your-application/rendering/static-site-generation): The page HTML is generated when you run the next build.
- React prefetch: react will get pages in the background, and make them ready in a local cache.
- Submission is in pairs, but starting alone is better for practice.
- Coding: 70%, Questions: 30%.
- Your submitted git repo should be private, please make barashd@post.bgu.ac.il and nitzanlevy (github username) collaborators.
- Deadline: The official deadline is 19.7.24, but because of the exam period, we'll give an automatic extension to whoever needs it for exam preparation.
- Additionally, solve the theoretical questions.
- The ex3 forum is open for questions in Moodle.
- Git repository content:
- Aim for a minimal repository size that can be cloned and installed: most of the files in Github should be code and package dependencies (package.json).
- Don't submit node_modules dir, .next project dir, JSON creation script, .env file, or JSON files.
- If a specific use case is not described here, you can code it as you see fit.
- As before, You will submit HW3 will be submitted via a Github repository, using a tag called a3. git tagging
Recommendation about using an AI assistant: You can ask questions and read the answers but never copy them. Understand the details, but write the code yourself. If two people copy the same AI code, it will be considered plagiarism.
- We use a plagiarism detector.
- The person who copies and the person who was copied from are both responsible.
- Set your repository private, and don't share your code.
- Read about user administration and authentication at https://fullstackopen.com/en/part4/.
- Read about Bcrypt format: https://en.wikipedia.org/wiki/Bcrypt.
- Read about JSON Web Token: https://jwt.io/introduction.
- Read about testing using Playwright at: https://fullstackopen.com/en/part5/end_to_end_testing_playwright#playwright, https://playwright.dev/docs/writing-tests
- Often, errors are lost between try/catch blocks, frontend/backend communication and async functions do not print to the console.
- Breakpoints don't work in some code parts, such as UseEffect.
- use the backend middleware logger, console logs, and possibly more loggers, to see what's happening. It is sometimes printed to the vs.code console, and sometimes to Chrome's console.
- Like before, progress in small steps, and commit when the code is stable with meaningful commit messages. You can choose to commit only some files that have changed.
- Implementation steps:
- Move the frontend to a next.js structure, so it can pass
npm run build
. - Add Playwright testing library to your frontent project.(tests are under the tests library, see the example Playwright adds to your project)
- Write a single test: for example, log in a user/ verify that a logged-out user can't add new notes, this test should fail.
- Add the necessary code/schemas/mongo data to pass this test.
- add another test that fails,
- Move the frontend to a next.js structure, so it can pass
- Like before, the destination Mongodb will always contain at least one note.
{
id: number;
title: string;
author: {
name: string;
email: string;
} | null;
content: string;
};
- Add a collection name called
users
with schema:
{
name: string,
email: string,
username: string,
passwordHash: string
}
We need to store the password hashes, we'll use MongoDB to be aligned with full-stack open examples. (https://fullstackopen.com/en/part4/.)
For the next.js "build" to work correctly, the folder structure should be updated. (https://nextjs.org/docs/getting-started/project-structure#pages-routing-conventions) Notice: This is only the front end. The backend which uses express stays the same. (It could be added to the next.js project, but here we use it externally.)
One option:
src
├── components
├── consts.tsx
├── contexts
├── css
├── pages
│ └── index.tsx
└── utils
index.tsx is a "page" in next.js terms, and contains the topmost component called App: don't put other components under the pages directory, move them to components instead.
To check that it works in dev mode, use npm run dev
.
To check that it works in test mode, use npm run build
(checks structure, typescript syntax, and prepares SSR), and then npm run start
. We won't test for typescript syntax errors in the npm run build
.
Read: https://nextjs.org/docs/pages/building-your-application/data-fetching/get-static-props Update the App component to get props during build time:
const App = ({ prop1,prop2... }) => {}
It gets props at build time:
export async function getStaticProps() {
return {
props: {
prop1,prop2...
}
};
}
- Each note's
Edit
/Delete
buttons, are shown only if a user is logged in, and his name is the same as the name of the note's author: assumed unique. add new note
is only shown if a user is logged in, and the appropriate user fields are used in the new note.
In react, implement the following caching mechanism:
- We'll have 5 (=pagination size) pre-fetched pages.
- The pages we will store are the pages that are visible in the current page pagination bar (for example, the current page is 5, and the cached pages are 3,4,5,6,7).
- When moving to a new page:
- If the current page is in the cache, it will be loaded from there.
- Regardless if it was loaded from the cache or not, in the background, using an async function, we will update the cache as needed: for example, when moving from page 5 to 6, only one page should be fetched.
- It's ok to have 5 fetches of 10 notes each, instead of one fetch of 50 notes; it's ok to use the existing 10-notes fetching API.
- Examples:
- The first page should be in the cache after build, and not be brought when the user first enters the website.
- Moving from page 1 to 2 should not fetch new data to the cache.
-
Submit 5 Playwright e2e tests under the "test" folder (parallel to the "src" folder), in the following format:
test('...', () => {...})
And they should pass the
npx playwright test
. https://nextjs.org/docs/pages/building-your-application/testing/playwright, https://playwright.dev/docs/writing-tests Assume the tests run on an empty database. Run the tests Sequentially, to prevent read-write race conditions: https://playwright.dev/docs/test-parallel#disable-parallelism
- Create User form: The homepage will contain a create user form with HTML name attribute "create_user_form": (Directly in the home page)
- field text:
Name
, html name attribute "create_user_form_name". - field text:
Email
, HTML name attribute "create_user_form_email". Email doesn't need to be verified. - field text:
Username
, HTML name attribute "create_user_form_username". - field text:
Password
, html name attribute "create_user_form_password". - button text:
Create User
, html name attribute "create_user_form_create_user".
- field text:
- Login form: The homepage will contain a login form with html name attribute "login_form": (Directly in the home page)
- field text:
Username
, HTML name attribute "login_form_username". - field text:
Password
, html name attribute "login_form_password". - button text:
Login
, html name attribute "login_form_login". - Like in Full Stack Open, part 4, the accepted token will be saved in a React state and sent as an 'Authorization' header on the following API requests.
- comment: it's considered less secure to have the javascript access the token. It's good enough for this exercise.
- field text:
- Logout button: The homepage will contain a logout button with html name attribute "logout". The logout will delete the token from the state.
- Comment: it should also be added to a blacklist on the server, so it won't be used later. In this exercise, we won't use blacklist.
- Add a new note for a logged-in user, should have the correct name and the email in the note.
Like in part4, implement:
- Post a request to '/users', to create a new user with the required fields from Mongo (assuming the username and name are unique, you don't need to verify).
- Post a request to '/login', to login using a username and a password, return a signed token, and use a SECRET environment variable from
.env
. returns{token,name,email}
.
Read and use the following error codes: HTTP response status codes In addition to HW2 codes (see "backend error handling - reminder from HW2"):
- 401: Unauthorized: the user is unknown, and needs to authenticate to get a response or incorrect credentials.
- 403: Forbidden: user is known, and does not have access rights to the content.
- The logger should still log to "log.txt" as before, the:
- time
- HTTP request method. (e.g, "GET")
- request target path ("/notes")
- request body.
git clone <your_submitted_github_repo>
cd <cloned_dir>
git checkout a3
npm install
from thefrontend
dir andbackend
dir (a package.json file should exist in both)- Copy a
.env
file into thebackend
and 'frontend' dir. node index.js
from thebackend
dir (configured to default port 3001).- run your tests from the
frontend
dir:npx playwright test
, and check they all pass. (It starts the next.js server usingnpm run dev
, configurable in frontend/playwright.config.ts). npm run build && npm run start
from thefrontend
dir (configured to default port 3000). The build/start enables static server-side generation.- Run tests (our tests) based on 'test requirements hw3'.
-
This exercise is an opportunity to practice test-driven design.
-
The three laws of TDD: (Taken from "Clean Code"/ Robert C. Martin, Chapter 9, unit tests)
- You may not write exercise code until you have written a failing test.
- You may not write more of a test than is sufficient to fail.
- You may not write more exercise code than is sufficient to pass the currently failing test.
-
How to write a single test? Use the "given-when-then" convention:
- given that a user is in the database, and a correct auth request is made, then success is expected.
- given an existing note ID, a delete request is sent, then the post should no longer be in the database.
- Unknown route/note number: 404
- Generic error response, cannot delete/update note: 500
- Missing fields in the request: 400.
- Success codes:
- Saved note: 201.
- Deleted note: 204.
- Other: 200.
- Each note should be of class name "note". (And not post)
- A note must get the unique ID from the database and use it as the html id attribute.
- Pagination buttons:
- Navigation buttons should be with html name attribute "first", "previous", "next", "last".
- Page buttons should be with the html name attribute "page-<target_page_number>"
-
Each note should have an
Edit
/Delete
button. -
Edit button: we will only test the body of the edited note, (content field).
- name attribute: "edit-<note_id>" html name attributes. For example, "edit-1".
- When clicked, a text input should be rendered:
- with the same input as the note.
- It should be editable
- with name "text_input-<note_id>"
- with "save" button "text_input_save-<note_id>"
- On click, the default values which are not the content, should match the "Note" schema, so a click will save the new note to Mongo without issues.
- with "cancel" button "text_input_cancel-<note_id>"
-
Delete button:
- name "delete-<note_id>" html name attributes. For example, "delete-1".
-
Add new note
button: we will only test the body of the new note, (content field).- button name "add_new_note". html name attributes.
- When clicked, React should render a text input:
- It should be editable
- with name "text_input_new_note"
- with "save" button "text_input_save_new_note"
- with "cancel" button "text_input_cancel_new_note"
-
There should be a global "theme" button that changes between two styles: dark and light.
- name attribute: "change_theme".
- You're free to implement any style change, as long as it appears visually on the UI.
-
Data transfer: 10 notes at a time, (frontend-backend, backend-atlas, see Mongoose pagination API, similarly to hw2).