Skip to content

Commit

Permalink
basic functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
dj-eong committed Apr 19, 2024
0 parents commit 4259409
Show file tree
Hide file tree
Showing 36 changed files with 5,792 additions and 0 deletions.
45 changes: 45 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"use strict";

/** Express app for jobly. */

const express = require("express");
const cors = require("cors");

const { NotFoundError } = require("./expressError");

const { authenticateJWT } = require("./middleware/auth");
const authRoutes = require("./routes/auth");
const companiesRoutes = require("./routes/companies");
const usersRoutes = require("./routes/users");

const morgan = require("morgan");

const app = express();

app.use(cors());
app.use(express.json());
app.use(morgan("tiny"));
app.use(authenticateJWT);

app.use("/auth", authRoutes);
app.use("/companies", companiesRoutes);
app.use("/users", usersRoutes);


/** Handle 404 errors -- this matches everything */
app.use(function (req, res, next) {
return next(new NotFoundError());
});

/** Generic error handler; anything unhandled goes here. */
app.use(function (err, req, res, next) {
if (process.env.NODE_ENV !== "test") console.error(err.stack);
const status = err.status || 500;
const message = err.message;

return res.status(status).json({
error: { message, status },
});
});

module.exports = app;
21 changes: 21 additions & 0 deletions app.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const request = require("supertest");

const app = require("./app");
const db = require("./db");


test("not found for site 404", async function () {
const resp = await request(app).get("/no-such-path");
expect(resp.statusCode).toEqual(404);
});

test("not found for site 404 (test stack print)", async function () {
process.env.NODE_ENV = "";
const resp = await request(app).get("/no-such-path");
expect(resp.statusCode).toEqual(404);
delete process.env.NODE_ENV;
});

afterAll(function () {
db.end();
});
36 changes: 36 additions & 0 deletions config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"use strict";

/** Shared config for application; can be required many places. */

require("dotenv").config();
require("colors");

const SECRET_KEY = process.env.SECRET_KEY || "secret-dev";

const PORT = +process.env.PORT || 3001;

// Use dev database, testing database, or via env var, production database
function getDatabaseUri() {
return (process.env.NODE_ENV === "test")
? "postgresql:///jobly_test"
: process.env.DATABASE_URL || "postgresql:///jobly";
}

// Speed up bcrypt during tests, since the algorithm safety isn't being tested
//
// WJB: Evaluate in 2021 if this should be increased to 13 for non-test use
const BCRYPT_WORK_FACTOR = process.env.NODE_ENV === "test" ? 1 : 12;

console.log("Jobly Config:".green);
console.log("SECRET_KEY:".yellow, SECRET_KEY);
console.log("PORT:".yellow, PORT.toString());
console.log("BCRYPT_WORK_FACTOR".yellow, BCRYPT_WORK_FACTOR);
console.log("Database:".yellow, getDatabaseUri());
console.log("---");

module.exports = {
SECRET_KEY,
PORT,
BCRYPT_WORK_FACTOR,
getDatabaseUri,
};
27 changes: 27 additions & 0 deletions config.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"use strict";

describe("config can come from env", function () {
test("works", function() {
process.env.SECRET_KEY = "abc";
process.env.PORT = "5000";
process.env.DATABASE_URL = "other";
process.env.NODE_ENV = "other";

const config = require("./config");
expect(config.SECRET_KEY).toEqual("abc");
expect(config.PORT).toEqual(5000);
expect(config.getDatabaseUri()).toEqual("other");
expect(config.BCRYPT_WORK_FACTOR).toEqual(12);

delete process.env.SECRET_KEY;
delete process.env.PORT;
delete process.env.BCRYPT_WORK_FACTOR;
delete process.env.DATABASE_URL;

expect(config.getDatabaseUri()).toEqual("jobly");
process.env.NODE_ENV = "test";

expect(config.getDatabaseUri()).toEqual("jobly_test");
});
})

23 changes: 23 additions & 0 deletions db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"use strict";
/** Database setup for jobly. */
const { Client } = require("pg");
const { getDatabaseUri } = require("./config");

let db;

if (process.env.NODE_ENV === "production") {
db = new Client({
connectionString: getDatabaseUri(),
ssl: {
rejectUnauthorized: false
}
});
} else {
db = new Client({
connectionString: getDatabaseUri()
});
}

db.connect();

module.exports = db;
53 changes: 53 additions & 0 deletions expressError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/** ExpressError extends normal JS error so we can
* add a status when we make an instance of it.
*
* The error-handling middleware will return this.
*/

class ExpressError extends Error {
constructor(message, status) {
super();
this.message = message;
this.status = status;
}
}

/** 404 NOT FOUND error. */

class NotFoundError extends ExpressError {
constructor(message = "Not Found") {
super(message, 404);
}
}

/** 401 UNAUTHORIZED error. */

class UnauthorizedError extends ExpressError {
constructor(message = "Unauthorized") {
super(message, 401);
}
}

/** 400 BAD REQUEST error. */

class BadRequestError extends ExpressError {
constructor(message = "Bad Request") {
super(message, 400);
}
}

/** 403 BAD REQUEST error. */

class ForbiddenError extends ExpressError {
constructor(message = "Bad Request") {
super(message, 403);
}
}

module.exports = {
ExpressError,
NotFoundError,
UnauthorizedError,
BadRequestError,
ForbiddenError,
};
20 changes: 20 additions & 0 deletions helpers/sql.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const { BadRequestError } = require("../expressError");

// THIS NEEDS SOME GREAT DOCUMENTATION.

function sqlForPartialUpdate(dataToUpdate, jsToSql) {
const keys = Object.keys(dataToUpdate);
if (keys.length === 0) throw new BadRequestError("No data");

// {firstName: 'Aliya', age: 32} => ['"first_name"=$1', '"age"=$2']
const cols = keys.map((colName, idx) =>
`"${jsToSql[colName] || colName}"=$${idx + 1}`,
);

return {
setCols: cols.join(", "),
values: Object.values(dataToUpdate),
};
}

module.exports = { sqlForPartialUpdate };
18 changes: 18 additions & 0 deletions helpers/tokens.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const jwt = require("jsonwebtoken");
const { SECRET_KEY } = require("../config");

/** return signed JWT from user data. */

function createToken(user) {
console.assert(user.isAdmin !== undefined,
"createToken passed user without isAdmin property");

let payload = {
username: user.username,
isAdmin: user.isAdmin || false,
};

return jwt.sign(payload, SECRET_KEY);
}

module.exports = { createToken };
36 changes: 36 additions & 0 deletions helpers/tokens.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const jwt = require("jsonwebtoken");
const { createToken } = require("./tokens");
const { SECRET_KEY } = require("../config");

describe("createToken", function () {
test("works: not admin", function () {
const token = createToken({ username: "test", is_admin: false });
const payload = jwt.verify(token, SECRET_KEY);
expect(payload).toEqual({
iat: expect.any(Number),
username: "test",
isAdmin: false,
});
});

test("works: admin", function () {
const token = createToken({ username: "test", isAdmin: true });
const payload = jwt.verify(token, SECRET_KEY);
expect(payload).toEqual({
iat: expect.any(Number),
username: "test",
isAdmin: true,
});
});

test("works: default no admin", function () {
// given the security risk if this didn't work, checking this specifically
const token = createToken({ username: "test" });
const payload = jwt.verify(token, SECRET_KEY);
expect(payload).toEqual({
iat: expect.any(Number),
username: "test",
isAdmin: false,
});
});
});
34 changes: 34 additions & 0 deletions jobly-schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
CREATE TABLE companies (
handle VARCHAR(25) PRIMARY KEY CHECK (handle = lower(handle)),
name TEXT UNIQUE NOT NULL,
num_employees INTEGER CHECK (num_employees >= 0),
description TEXT NOT NULL,
logo_url TEXT
);

CREATE TABLE users (
username VARCHAR(25) PRIMARY KEY,
password TEXT NOT NULL,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
email TEXT NOT NULL
CHECK (position('@' IN email) > 1),
is_admin BOOLEAN NOT NULL DEFAULT FALSE
);

CREATE TABLE jobs (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
salary INTEGER CHECK (salary >= 0),
equity NUMERIC CHECK (equity <= 1.0),
company_handle VARCHAR(25) NOT NULL
REFERENCES companies ON DELETE CASCADE
);

CREATE TABLE applications (
username VARCHAR(25)
REFERENCES users ON DELETE CASCADE,
job_id INTEGER
REFERENCES jobs ON DELETE CASCADE,
PRIMARY KEY (username, job_id)
);
Loading

0 comments on commit 4259409

Please sign in to comment.