Skip to content

wojciechszmelczerczyk/mern-notes-app

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Speech Notes App

Description

The project target is helping hearing impaired students during the lessons they attend. But potentially can be used by any student.

The project aims to introduce a speech to text system able to transcript the voice of the professor into a text on the screen of the student pc or mobile phone.

Students can record teacher voice by clicking a button which will transcrypt voice to text for their better understandment.

Registered user will have premium feature to store the transcriptions online and save them to a file (txt or pdf format).

Students will be able to change speech language.

Students will be able to search lessons by title they provide before transcryption to text.

Server side of application will be made in Node using Express framework.

Data will be saved to Mongo database.

Data will be cached in Redis cache.

Tests will be written in Cypress and Supertest.

Client side will be created in React using Tailwindcss framework.


Table of content

Techstack

  • React
  • Tailwindcss
  • TypeScript
  • Express
  • MongoDB
  • Mongoose
  • Redis
  • Jest
  • Supertest
  • Cypress

Requirements

  • install node
  • install mongo
  • install redis

Usage

Clone repository

git clone https://github.com/wojciechszmelczerczyk/express-notes-app.git

Navigate to project folder

cd /mern-notes-app

Install dependencies

npm i

Run app

Run React server

npm run client

Run Express REST API

npm run server

Run both servers concurrently

npm run app

Env setup

# Uri to mongo database
DB_URI=

# Speech key for Microsoft Cognitive Services
SPEECH_KEY=

# Speech region for Microsoft Cognitive Services
SPEECH_REGION=

# Port
PORT=

# Token for test purposes
JWT=

# Access token secret
ACCESS_TOKEN_SECRET=

# Refresh token secret
REFRESH_TOKEN_SECRET=

# Access token expiration time
ACCESS_TOKEN_EXP=

# Refresh token expiration time
REFRESH_TOKEN_EXP=

Architectures

App Architecture

App implements Rest API style architecture. App includes Speech-to-text API.

Database Architecture

Client routing

Endpoint Authenticated Component Description
/ * NoteListComponent Note list of current auth user
/register - RegisterComponent Register form
/login - LoginComponent Login form
/note/:id * NoteDetailsComponent Single note details, speech-to-text service
/createNote * CreateNoteComponent Set note title

API Endpoints

User:

Method Endpoint
GET /user
POST /user
POST /user/authenticate
GET /user/refresh-token

Note:

Method Endpoint
GET /note
POST /note
PUT /note/:id
GET /note/:id
DELETE /note/:id
POST /note/:id/file

Speech-to-text:

Method Endpoint
GET /api/get-speech-token

Tests

E2E

To run tests:

npm run e2e

Note

if note title is too short, prompt an error
it("if note title is too short, prompt an error", () => {
  localStorage.setItem("at", "");

  cy.visit("http://localhost:5000/createNote");

  cy.get('[data-cy="noteTitleInput"]').type("tes");

  cy.get('[data-cy="createNoteButton"]').click();

  cy.get('[data-cy="createNoteError"]').should(
    "contain",
    "Note title is too short."
  );
});
if note title is too long, prompt an error
it("if note title is too long, prompt an error", () => {
  localStorage.setItem("at", "");

  cy.visit("http://localhost:5000/createNote");

  cy.get('[data-cy="noteTitleInput"]').type("testtest1");

  cy.get('[data-cy="createNoteButton"]').click();

  cy.get('[data-cy="createNoteError"]').should(
    "contain",
    "Note title is too long."
  );
});

User

if user credentials are incorrect, prompt error
it("if user credentials are incorrect, prompt error", () => {
  cy.visit("http://localhost:5000/login");

  cy.get('[data-cy="emailInput"]').type("user2404gmail.com");

  cy.get('[data-cy="passwordInput"]').type("test404");

  cy.get('[data-cy="userBtn"]').click();

  cy.get("[data-cy='emailError']").should(
    "contain",
    "Please enter a valid email"
  );
});
if user credentials are correct, redirect to note list page
it("if user credentials are correct, redirect to note list page", () => {
  cy.visit("http://localhost:5000/login");

  cy.get('[data-cy="emailInput"]').type("user2@gmail.com");

  cy.get('[data-cy="passwordInput"]').type("test123");

  cy.get('[data-cy="userBtn"]').click();

  cy.location().should((loc) => {
    expect(loc.href).to.eq("http://localhost:5000/");
  });
});

API

To run tests:

npm run api

GET /note

when jwt doesn't exists
test("when jwt doesn't exists", async () => {
  const notes = await request(app).get("/note");

  expect(notes.status).toEqual(401);
  expect(notes.body.jwt_error).toEqual("Jwt doesn't exists");
});
when jwt is incorrect
test("when jwt is incorrect", async () => {
  const notes = await request(app)
    .get("/note")
    .set("Authorization", "Bearer ssss");

  expect(notes.status).toEqual(403);
  expect(notes.body.error).toEqual("jwt malformed");
});
when jwt is expired
test("when jwt is expired", async () => {
  const notes = await request(app)
    .get("/note")
    .set("Authorization", `Bearer ${jwt};`);

  expect(notes.status).toEqual(403);
  expect(notes.body.error).toEqual("invalid token");
});

POST /note

when jwt doesn't exists
test("when jwt doesn't exists", async () => {
  const newNote = await request(app)
    .post("/note")
    .send({ title: "new note added in jest", content: "" });
  expect(newNote.status).toBe(403);
  expect(newNote.body.error).toBe("Jwt doesn't exist");
});
when jwt is incorrect
test("when jwt is incorrect", async () => {
  const newNote = await request(app)
    .post("/note")
    .send({ title: "new note added in jest", content: "" })
    .set("Authorization", "Bearer falseToken");

  expect(newNote.status).toBe(403);
  expect(newNote.body.error).toBe("jwt malformed");
});
when jwt is expired
test("when jwt is expired", async () => {
  const newNote = await request(app)
    .post("/note")
    .send({ title: "just next note added in jest", content: "" })
    .set("Authorization", `Bearer ${jwt}`);

  expect(newNote.status).toBe(201);
  expect(newNote.body.title).toBe("just next note added in jest");
});

POST /user

when credentials are correct
test("when credentials are correct", async () => {
  let user = {
    email: "testuser@gmail.com",
    password: "testpassword123",
  };

  const newUser = await request(app).post("/user").send(user);

  expect(newUser.body.email).toEqual(user.email);
  expect(newUser.status).toEqual(200);
});
when credentials are incorrect
test("when credentials are incorrect", async () => {
  let user = {
    email: "testuser",
    password: "test",
  };

  const newUser = await request(app).post("/user").send(user);

  expect(newUser.body.errors).toBeTruthy();
  expect(newUser.status).toEqual(400);
});

POST /user/authenticate

when credentials are correct
test("when credentials are correct", async () => {
  let user = {
    email: "testuser@gmail.com",
    password: "testpassword123",
  };

  const newUser = await request(app).post("/user/authenticate").send(user);

  expect(newUser.body.token).toBeTruthy();
  expect(newUser.status).toEqual(201);
});
when credentials don't match user in db
test("when credentials don't match user in db", async () => {
  let user = {
    email: "testuser",
    password: "test",
  };

  const newUser = await request(app).post("/user/authenticate").send(user);

  expect(newUser.body.error).toBeTruthy();
  expect(newUser.status).toEqual(400);
});
when credentials are incorrect
test("when credentials are incorrect", async () => {
  let user = {
    email: "testuser2@gmail.com",
    password: "test1234",
  };

  const newUser = await request(app).post("/user/authenticate").send(user);

  expect(newUser.body.error).toBeTruthy();
  expect(newUser.status).toEqual(400);
});