Skip to content

Latest commit

 

History

History
321 lines (272 loc) · 7.99 KB

RoadMap.md

File metadata and controls

321 lines (272 loc) · 7.99 KB

Backend Road Map

General Task List

  • Update schema.
    • Add more meta-data
    • blog models?
  • Implement Company endpoints.
  • Update all endpoints to Company schema.
  • Update and expand all tests.
    • mock database and implement automated testing on github.
  • Add token validation to all endpoints.
  • Ensure frontend only receives data they should have access to.
    • Applicant can see their own applications and offers but not other users.
    • Applicant can't see other users applications and offers on company and listings.
    • Clients can see applications and offers on their company and listings but not others.
  • Standardise the code layout, and error responses across all endpoints.

Future tasks

  • Pagination of results, return max 100, with response headers for current and max pages.

    http://localhost:12345/listings/?page=2&limit=10
    let page = req.query.page;
    let limit = req.query.limit;
    https://nodejs.org/api/querystring.html
    https://stackabuse.com/get-query-strings-and-parameters-in-express-js/
    
  • Sort/filter results, perhaps goes with search.

    http://localhost:1234/listings/?sortBy=name&orderBy=asc&includes=requirements&includesValue=javascript
    
  • Search Implementation.

  • User verification through email, to verify email and reset password.

  • Email notifications for users and clients for applications and offers.

  • Storage for images, pdf and other documentation for applications?.

  • Blog feed, with posts, reactions, and comments.

Additional ideas and requirements?

????

Code Standardisation

Middleware

Try to add reusable validation, typically update/delete use identical user verification on each route.

Example middleware for user endpoint.

import { findUserById } from "../../../utilities/findUser.js";
import { verifyToken } from "../../../utilities/jsonWebToken.js";

export const validateUser = async function (req, res, next) {
  const id = req.params.id;
  const user = await findUserById(id);

  if (!user) {
    return res.status(401).json({ message: "User does not exist." });
  }

  const token = req.headers.authorization;
  let readyToken = token;
  if (!token) {
    return res.status(401).json({ message: "No authorization header provided." });
  } else if (token.includes("Bearer")) {
    readyToken = token.slice(7);
  }

  let verified;
  if (readyToken != undefined) {
    verified = await verifyToken(readyToken);

    if (!verified) {
      return res.status(401).json({ message: "Invalid authorization token provided, please re-log." });
    }
  }

  //Throw 401 error if user isn't the correct user
  if (verified.role !== "admin") {
    if (verified.id != id) {
      return res.status(401).json({ message: "You do not have authorization to delete this user." });
    }
  }

  next();
};

This is then insert before the controller that handles the request in the route.js

// PUT /users/:id
usersRouter.put("/:id", validateUser, async (req, res) => {
  try {
    const { status, data } = await handleUpdate(req);
    res.status(status).json(data);
  } catch (error) {
    console.log(error);
    res.status(500).json({ ...error, message: "Internal server error" });
  }
});

// DELETE /users/:id
usersRouter.delete("/:id", validateUser, async (req, res) => {
  try {
    const { status, data } = await handleDelete(req);
    res.status(status).json(data);
  } catch (error) {
    console.log(error);
    res.status(500).json({ ...error, message: "Internal server error" });
  }
});

File layout

Route
  |- route.js
  |- route.test.js
  |- routeSwaggerDocs.js
  |--- Controllers
  |      |-create.js
  |      |-read.js
  |      |-update.js
  |      |-delete.js
  |--- Middleware
        |-tokenVerification.js
        |-additionalChecks.js

Errors

// unexpected errors in catch blocks return errors with message.
res.status(500).json({
  ...error,
  message: "Internal server error",
});

// Expected errors with user or developer friendly messages.
res.status(400).json({
  message: "Bad Request, skills must be in an array.",
});

//  using express validator
const errors = validationResult(req);
if (!errors.isEmpty()) {
  return res.status(400).json({
    message: `${errors.array()[0].msg} in ${errors.array()[0].param}${errors.array()[1] ? " and " + errors.array()[1].param : ""} field(s)`,
  });
}

Possible Schema Updates

User model expansion

If we choose to add things like language, education and work experience we may want additional end points for create/update and delete for these.

model User {
  id           String         @id @default(uuid())
  email        String         @unique
  title        String?
  firstName    String
  lastName     String
  password     String
  profile      String?
  salt         String?
  about        String?
  avatar       String?
  // users git, port and linkedin
  github       String?
  portfolio    String?
  linkedin     String?
  createdAt    DateTime       @default(now())
  updatedAt    DateTime       @updatedAt
  role         UserRole       @default(Applicant)
  // users skills, experience, and education
  skills       String[]       @default([])
  languages    UserLanguage[]
  experience   Experience[]
  education    Education[]
  applications Application[]
  offers       Offer[]
  company      Company?       @relation(fields: [companyId], references: [id])
  companyId    String?
  created      DateTime       @default(now())
  updated      DateTime       @updatedAt
}

enum UserRole {
  Applicant
  Client
  Admin
}

// work experience
model Experience {
  id               String   @id @default(uuid())
  user             User     @relation(fields: [userId], references: [id])
  userId           String
  company          String
  location         String
  start            String
  end              String?
  current          Boolean
  title            String
  description      String?
  responsibilities String[]
}

// education
model Education {
  id          String   @id @default(uuid())
  user        User     @relation(fields: [userId], references: [id])
  userId      String
  institution  String
  location    String
  start       String
  end         String?
  current     Boolean
  course      String
  description String?
  skills      String[]
}

// users languages
model UserLanguage {
  id       String        @id @default(uuid())
  language String
  level    LanguageLevel
  user     User          @relation(fields: [userId], references: [id])
  userId   String
}

// beginner, elementary, lower intermediate, upper intermediate, advanced, fluent.
enum LanguageLevel {
  A1
  A2
  B1
  B2
  C1
  C2
}

Listings model

model Listing {
  id               String         @id @default(uuid())
  title            String
  type             EmploymentType
  // possibly multiple locations to work at?
  location         String[]
  locationtype     LocationType
  tags             String[]
  description      String
  //possible additional optional data
  responsibilities String?
  salary           String?
  positions        Int?
  incentives       String?
  requirements     String[]
  deadline         DateTime
  created          DateTime       @default(now())
  updated          DateTime       @updatedAt
  applications     Application[]
  offer            Offer[]
  company          Company        @relation(fields: [companyId], references: [id])
  companyId        String
  listingsState    ListingState   @default(Active)
}

enum ListingState {
  Active
  Ended
}

enum EmploymentType {
  Fulltime
  Parttime
  Contract
  Internship
}

enum LocationType {
  Onsite
  Hybrid
  Remote
}

Additional meta data on company

model Company {
  id           String        @id @default(uuid())
  name         String        @unique
  admin        User[]
  sector       String
  logo         String
  banner       String?
  phone        String
  email        String
  locations    String[]
  about        String
  website      String
  applications Application[]
  offers       Offer[]
  listings     Listing[]
  created      DateTime      @default(now())
  updated      DateTime      @updatedAt
}