Skip to content

Resilient-Labs/Sprint3-HitlistCohort

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

72 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation


HitList Pro by ResilientLabs

A sleek progress tracker for resilient job hunters πŸ‹πŸ½

GitHub last commit GitHub issues GitHub pull requests

Features β€’ Installation β€’ Updating β€’ APIs β€’ Contributing β€’ Credits β€’ License

Features

  • Users can signup and login to their accounts
  • Companies can be created using a form on the home page
  • A user can search all saved companies by company name, location, role, etc.

Installation

Getting Started

This app is a monorepo that houses both the frontend and backend of Hitlist Pro. To get the app running you will need to run the backend and frontend independantly. There are some pre-requisites for this project:

Node.js (at least version 18 or higher) Vite NVM (this is optional, but will be helpful. You can also use NPM)

Running the frontend

  1. Clone the repository onto your machine.

  2. Navigate to ./frontend.

  3. Run npm install . If you have an issue downloading the project dependencies, you may have an outdated node version. If you are using nvm, run nvm use 18.18.0. The required version of the project may change. Check the terminal to determine which version nvm should use.

  4. Run npm run dev. This will start the frontend.

  5. Navigate to http://localhost:5174/

To run the front end from the backend folder:

  1. In frontend folder: npm run build first to create minimized dist directory

  2. In frontend folder: cp -r dist ../backend to copy minimized dist folder from the frontend into backend

  3. Still In the frontend folder: npm run dev

  4. Then in the backend folder run npm run dev

Running the backend

  1. Run npm i. If you are using nvm make sure to run npm use [required node version for project]

  2. Navigate to ./backend

  3. Run npm run start

  4. Ensure the backend is at http://localhost:3001/


HitList Pages

Home Page: Home Page

Adding Hitlist: Adding Page

Contacts page: Contacts Page

Login Page: Login Page

Sign Up Page: Sign Up Page

Updating

Workspaces

This project used workspaces to manage dependencies within its monorepo. You can read more about how workspaces work here in NPM's documentation. The active workspaces in this project are listed within ./package.json and ./package-lock.json.

To create a new workspace you can run npm init -w [the path of your workspace]. I do not reccommend updating these files manually since a typo could really ruin your day and introduce bugs.


Contributing

PR Templates

When you are creating a PR for the project, make sure to fill out the PR template. The template exist within the ./github folder at the root of the project. If you make changes to this template, like all other changes, be sure to create a Pull Request. For more information on the reasoning behind PR templates, see This PR Template guide or checkout github's documentation. All PRs should be comparing the main branch to your feature branch. Here is an example of a PR description using the template: Documentation Initial Readme

To Squash or Not To Squash

Squash. After your PR is approved by our DEV OPs lead, you will want to squash merge your changes. Your commit history leading up to your PR is not needed for posterity. If you don't squash then this history will end up on the main branch. Feel free to keep that record on your machine or on that specific branch.

So how and why do I squash merge my changes?

Squash merging is usually an option listed below a PR after it is approved.

Squash Merge Button

That button gives github permission to Squash the git history associated with the feature branch that you are tying to merge into main.

That way the commit history under your feature branch is mot merged into the history of the main branch which can kind of clutter the project’s full history over time. See HitList Pro Commit History.

If every developer who pushed the changes to the main branch retained their commit history, this page would grow exponentially over time. This would make it more difficult to pin down specific feature changes and would add possibly unnecessary commits to our git history. A pro on the other hand is that the project would retain a high level of detail about all the commits that have made up the project. There are pros and cons to squashing.

If you want to squash merge without github, you can do a git squash locally before or after your PR is approved. After you squash (and rebase if you need to). Then push your changes and merger them to main. Here is an article that explains more about git squash and how to use it: Free code camp article on squash commits


APIs

Usage

Connect to the API using Postman or Insominia. The API lives at http://localhost:3001 when the server is running.

Technologies used

  • NodeJS This is a cross-platform runtime environment built on Chrome's V8 JavaScript engine used in running JavaScript codes on the server. It allows for installation and managing of dependencies and communication with databases.
  • ExpressJS This is a NodeJS web application framework.
  • MongoDB This is a free open source NOSQL document database with scalability and flexibility. Data are stored in flexible JSON-like documents.
  • Mongoose ODM This makes it easy to write MongoDB validation by providing a straight-forward, schema-based solution to model to application data.
  • Requests and Responses

    GET: /signup - Action: To create an account for the user.
    Status Response
    201
    { "user": {}, "message": "User registered successfully", "token": "string" }
    400
    { "message": "Email already in use" }
    500
    { "message": "Server error", "error": "string" }
    GET: /login - Action: To sign the user into the app.
    Status Response
    200
    { "message": "Login successful", "token": "string", "user": {} }
    400
    { "message": "Invalid email or password" }
    500
    { "message": "Server error", "error": "string" }
    GET: /companies - Action: To retrieve a list of companies and applications saved by the user.
    Status Response
    200
    { "companies": [{ "name": "string", "status": "string", "applicationUrl": "string", "notes": "string", "pointOfContacts": ["string"] }] }
    500
    { "error": "Internal Server Error" }
    PUT: /companies/:id - Action: To update the details of an existing company.

    Request Params: { "id": "string" }

    Status Response
    200
    { "companies": [{ "name": "string", "status": "string", "applicationUrl": "string", "notes": "string", "pointOfContacts": ["string"] }] }
    500
    { "error": "Internal Server Error" }
    GET: /all-contacts - Action: To retrieve an array of all the user's contacts.
    Status Response
    200
    { "allContacts": ["string"] }
    500
    { "error": "Internal Server Error" }

    Schemas

    Contact Schema (backend/models/contact.schema.js)

    const mongoose = require('mongoose');
    
    const contactSchema = new mongoose.Schema({
      role: String,
      email: String,
      linkedIn: String,
      company: String,  // Deprecated: This is being replaced by the reference to Company model below.
      company: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'Company'  // References the 'Company' model, establishing a relationship between Contact and Company
      },
      lastContactDate: Date,
    });
    
    const ContactModel = mongoose.model('Contact', contactSchema);
    
    module.exports = ContactModel;

    Contact Schema Fields

    • role: The role of the contact (e.g., Manager, Developer).
    • email: The contact's email address.
    • linkedIn: The contact's LinkedIn profile.
    • company: Deprecated. Previously used as a string field, now replaced with an ObjectId reference to the Company model.
    • company: The ObjectId referencing a company document from the Company model. This creates a relationship between the Contact and Company models.
    • lastContactDate: The most recent date when the contact was interacted with.

    Company Schema (backend/models/company.schema.js)

    const mongoose = require('mongoose');
    
    const companySchema = new mongoose.Schema({
      name: String,
      status: String,
      applicationUrl: String,
      notes: String,
      pointOfContacts: [String], // Deprecated: This is replaced by a reference to the Contact model below.
      pointOfContacts: [{
        type: mongoose.Schema.Types.ObjectId,
        ref: 'Contact'  // References the 'Contact' model, establishing a relationship between Company and Contact
      }],
    });
    
    const CompanyModel = mongoose.model('Company', companySchema);
    
    module.exports = CompanyModel;

    Contact Schema Fields

    • role: The role of the contact (e.g., Manager, Developer).
    • email: The contact's email address.
    • linkedIn: The contact's LinkedIn profile.
    • company: Deprecated. Previously used as a string field, now replaced with an ObjectId reference to the Company model.
    • company: The ObjectId referencing a company document from the Company model. This creates a relationship between the Contact and Company models.
    • lastContactDate: The most recent date when the contact was interacted with.

    How the Contact and Company Schemas Work Together

    The Contact and Company schemas are related through the company field in the Contact schema and the pointOfContacts field in the Company schema:

    • The company field in the Contact schema references the _id of a Company, establishing a connection between a contact and the company they work for or are associated with.
    • The pointOfContacts field in the Company schema is an array of ObjectId references to the Contact model, which holds the contacts associated with a specific company.

    This relationship allows you to easily retrieve information about a contact's associated company or find all contacts linked to a specific company.

    Example Use Case:

    • When querying for a Contact, you can populate the company field to get detailed information about the company the contact works for.
    • When querying for a Company, you can populate the pointOfContacts field to retrieve all contacts (e.g., employees or representatives) associated with that company.

    Adding SuperTest for Backend Testing

    Let's use the supertest package to help us write our tests for testing the API.

    Setting Up Environment Variables

    MONGO_URI='your_mongo_uri_here'
    TEST_MONGO_URI='your_test_mongo_uri_here'
    TEST_PORT=4000
    

    Installation

    npm install --save-dev supertest

    scripts in the (backend/package.json)

     "scripts": {
            "start": "cross-env NODE_ENV=production node server.js",
            "build-prod": "cd ../frontend/ && npm run build && cp -r dist ../backend/",
            "dev": "cross-env NODE_ENV=development node --watch server.js",
            "lint": "eslint --fix .",
            "format": "npx prettier --write \"**/*.{ts,js,md}\"",
            "check": "npx prettier --check \"**/*.{ts,js,md}\"",
            "test": "cross-env NODE_ENV=test node --test"
        },

    Writing a Basic Test

    Create a test file inside backend/tests/, e.g., sample-test.js:

    // Importing the test function and 'after' hook from 'node:test'
    const { test, after } = require('node:test');
    // Importing supertest, which allows us to send HTTP requests to our app
    const request = require('supertest');
    // Importing mongoose for database connection management
    const mongoose = require('mongoose');
    // Importing the Express app from the server file
    const app = require('../server');
    // This function runs after all tests are done
    after(async () => {
      // Close the MongoDB connection to avoid open connections
      await mongoose.connection.close();
    });
    test('GET /companies/ should return JSON', async () => {
      await request(app) // Send a GET request to the endpoint
        .get('/companies/')
        .expect(200) // Expect HTTP status 200 (OK)
        .expect('Content-Type', /application\/json/); // Expect JSON response
    });

    What it does:

    • Sends an HTTP GET request to /companies/ using supertest.
    • Verifies that the server responds with status 200.
    • Ensures that the Content-Type of the response is JSON (application/json).

    Running the Tests

    cd .. Sprint3-HitlistCohort and find what test file you want to test and then run:

    NODE_ENV=test node --test backend/tests/sample-test.js

    πŸ’ͺ Cypress Testing Guide

    This guide helps you get started with Cypress to run end-to-end tests on your Vite + Express application.


    πŸ“¦ Installation

    Install Cypress as a development dependency:

    npm install --save-dev cypress

    Then, install the Cypress binary:

    npx cypress install

    πŸš€ Running Cypress

    Start your frontend and backend servers in separate terminals:

    # Terminal 1 - Start Vite dev server
    npm run dev
    
    # Terminal 2 - Start Express backend
    node server.js or npm run dev

    Then, open the Cypress test runner:

    npx cypress open

    This will open the Cypress UI where you can select and run tests interactively.


    πŸ“‚ Folder Structure

    After first run, Cypress creates the following structure:

    cypress/
      e2e/
        login.cy.js      # Your test file
      support/
      fixtures/
      ...
    cypress.config.js     # Cypress config file
    

    ✍️ Writing a Test (Login Example)

    Create a test file in cypress/e2e/login.cy.js:

    describe('Login and Logout Flow', () => {
      it('logs in and logs out successfully', () => {
        cy.visit('http://localhost:5173'); // Vite app
    
        cy.get('input[name="email"]').type('testuser@example.com');
        cy.get('input[name="password"]').type('password123');
        cy.get('button[type="submit"]').click();
    
        cy.contains('Welcome, Test User');
        
        cy.get('button#logout').click();
    
        cy.contains('Login');
      });
    });

    πŸ” Running Tests Headlessly (CI or CLI)

    To run Cypress tests in the terminal without opening the UI:

    npx cypress run

    This will execute tests in headless mode using Electron by default.


    πŸ›  Tips

    • Make sure your test data (e.g. testuser@example.com) exists in your backend.
    • You can use cy.request() to seed data or log in via API.
    • Cypress automatically waits for elements β€” no need to use timeouts manually.

    ✨ Jest Testing Guide

    This guide will help you set up and use Jest for unit testing your JavaScript or React codebase.


    πŸ“¦ Installation

    Install Jest as a development dependency:

    npm install --save-dev jest

    If you're using Babel (e.g. with React or modern JS syntax), install these too:

    npm install --save-dev @babel/preset-env @babel/preset-react babel-jest

    Create a babel.config.js file:

    module.exports = {
      presets: ['@babel/preset-env', '@babel/preset-react'],
    };

    πŸ”’ Basic Configuration

    Add this to your package.json:

    "scripts": {
      "test": "jest"
    }

    Or create a jest.config.js:

    module.exports = {
      testEnvironment: 'jsdom',
      moduleFileExtensions: ['js', 'jsx'],
      transform: {
        '^.+\\.(js|jsx)$': 'babel-jest',
      },
    };

    πŸ“ Writing a Test

    Create a test file like sum.test.js:

    function sum(a, b) {
      return a + b;
    }
    
    test('adds 1 + 2 to equal 3', () => {
      expect(sum(1, 2)).toBe(3);
    });

    Run the test:

    npm test

    πŸ”Ή Testing React Components

    Example Button.test.jsx:

    import React from 'react';
    import { render, screen, fireEvent } from '@testing-library/react';
    import '@testing-library/jest-dom';
    import Button from './Button';
    
    test('calls onClick when clicked', () => {
      const handleClick = jest.fn();
      render(<Button onClick={handleClick}>Click Me</Button>);
      fireEvent.click(screen.getByText('Click Me'));
      expect(handleClick).toHaveBeenCalledTimes(1);
    });

    Install testing library if needed:

    npm install --save-dev @testing-library/react @testing-library/jest-dom

    πŸ” Watch Mode

    Run Jest in watch mode:

    npm test -- --watch

    🚧 Tips

    • Use .test.js or .spec.js suffixes for test files.
    • Mock functions with jest.fn().
    • Use describe() blocks to group related tests.
    • Snapshot testing is supported with expect(component).toMatchSnapshot().

    πŸ“… Running Coverage Report

    npm test -- --coverage

    This generates a coverage/ folder with HTML reports.


    Happy testing! ✨

    Should look like this:

    testing supertest img result

    Deploying with Fly.io

    Before you begin, ensure you have:

    Step 1: Log in to Fly.io

    Authenticate your Fly.io account:

    fly auth login

    Step 2: Initialize the Fly.io App

    Navigate to the backend directory and run:

    fly launch
    • Choose hitlist as the app name or create a new one.
    • Select bos (Boston) as the deployment region.
    • This command generates a fly.toml configuration file.

    Step 3: Deploy the App

    To deploy, run:

    fly deploy

    Once deployed, Fly.io will provide a public URL where the backend is accessible.

    Managing the Deployment

    View Logs:

    fly logs

    Restart the App:

    fly restart

    For the latest installation instructions, visit the official guide:
    πŸ‘‰ Flyctl Installation Guide

    Credits

    All credit goes to my son Cosmo. He wrote most of this. Pray that he never learns Cobalt - otherwise, its over. KingCosmo Behold! The face of genius.

    License

    This project is available for use under the MIT License.

    About

    No description, website, or topics provided.

    Resources

    Stars

    Watchers

    Forks

    Releases

    No releases published

    Packages

     
     
     

    Contributors

    Languages