[
  {
    "path": ".gitignore",
    "content": "node_modules\n.env"
  },
  {
    "path": "README.md",
    "content": "# CodeGig\r\n\r\n> Simple Job find app for coders. This app uses Node, Express, Sequalize (Postgres) and Handlebars.\r\n\r\n## Quick Start\r\n\r\n``` bash\r\n# Install dependencies\r\nnpm install\r\n\r\n# Serve on localhost:5000\r\nnpm start\r\n# Dev Server (Nodemon)\r\nnpm run dev\r\n```\r\n\r\n## App Info\r\n\r\n### Author\r\n\r\nBrad Traversy\r\n[Traversy Media](http://www.traversymedia.com)\r\n\r\n### Version\r\n\r\n1.0.0\r\n\r\n### License\r\n\r\nThis project is licensed under the MIT License\r\n"
  },
  {
    "path": "app.js",
    "content": "const express = require('express');\nconst exphbs = require('express-handlebars');\nconst bodyParser = require('body-parser');\nconst path = require('path');\n\n// Database\nconst db = require('./config/database');\n\n// Test DB\ndb.authenticate()\n  .then(() => console.log('Database connected...'))\n  .catch(err => console.log('Error: ' + err))\n\nconst app = express();\n\n// Handlebars\napp.engine('handlebars', exphbs({ defaultLayout: 'main' }));\napp.set('view engine', 'handlebars');\n\n// Body Parser\napp.use(express.urlencoded({ extended: false }));\n\n// Set static folder\napp.use(express.static(path.join(__dirname, 'public')));\n\n// Index route\napp.get('/', (req, res) => res.render('index', { layout: 'landing' }));\n\n// Gig routes\napp.use('/gigs', require('./routes/gigs'));\n\nconst PORT = process.env.PORT || 5000;\n\napp.listen(PORT, console.log(`Server started on port ${PORT}`));"
  },
  {
    "path": "config/database.js",
    "content": "const Sequelize = require('sequelize');\nconst dotenv = require('dotenv');\n\ndotenv.config();\n\nmodule.exports =  new Sequelize(process.env.DATABASE_URL, {\n  host: 'localhost',\n  dialect: 'postgres',\n  operatorsAliases: false,\n\n  pool: {\n    max: 5,\n    min: 0,\n    acquire: 30000,\n    idle: 10000\n  },\n});"
  },
  {
    "path": "models/Gig.js",
    "content": "const Sequelize = require('sequelize');\nconst db = require('../config/database');\n\nconst Gig = db.define('gig', {\n  title: {\n    type: Sequelize.STRING\n  },\n  technologies: {\n    type: Sequelize.STRING\n  },\n  description: {\n    type: Sequelize.STRING\n  },\n  budget: {\n    type: Sequelize.STRING\n  },\n  contact_email: {\n    type: Sequelize.STRING\n  }\n});\n\nGig.sync().then(() => {\n  console.log('table created');\n});\nmodule.exports = Gig;"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"codegig\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Simple job find app for coders\",\n  \"main\": \"app.js\",\n  \"scripts\": {\n    \"start\": \"node app.js\",\n    \"dev\": \"nodemon app.js\"\n  },\n  \"author\": \"Brad Traversy\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"dotenv\": \"^8.1.0\",\n    \"express\": \"^4.16.4\",\n    \"express-handlebars\": \"^3.0.0\",\n    \"pg\": \"^7.7.1\",\n    \"pg-hstore\": \"^2.3.2\",\n    \"sequelize\": \"^4.44.3\"\n  },\n  \"devDependencies\": {\n    \"nodemon\": \"^1.18.8\"\n  }\n}\n"
  },
  {
    "path": "public/css/style.css",
    "content": "@import url('https://fonts.googleapis.com/css?family=Lato');\r\n\r\n:root {\r\n  --primary-color: #568c9b;\r\n  --primary-hover-color: #64a5b7;\r\n  --bg-color: #333;\r\n  --box-shadow: 3px 4px 12px rgba(0, 0, 0, 0.7);\r\n}\r\n\r\n* {\r\n  box-sizing: border-box;\r\n}\r\n\r\nbody {\r\n  font-family: 'Lato', sans-serif;\r\n  margin: 0;\r\n  color: #333;\r\n  background: #f4f4f4;\r\n}\r\n\r\nul {\r\n  list-style: none;\r\n  padding: 0;\r\n}\r\n\r\na {\r\n  color: #fff;\r\n  text-decoration: none;\r\n}\r\n\r\na:hover {\r\n  color: var(--primary-color);\r\n}\r\n\r\n.container {\r\n  max-width: 960px;\r\n  padding: 1rem 4rem;\r\n  margin: auto;\r\n  overflow: hidden;\r\n}\r\n\r\n.error {\r\n  padding: 5px;\r\n  border: #777 dotted 1px;\r\n  margin-bottom: 15px;\r\n}\r\n\r\n/* Header */\r\nheader {\r\n  display: flex;\r\n  position: absolute;\r\n  top: 0;\r\n  left: 0;\r\n  width: 100%;\r\n  justify-content: space-between;\r\n  align-items: center;\r\n  padding: 1rem 6rem;\r\n}\r\n\r\nheader nav ul {\r\n  display: flex;\r\n}\r\n\r\nheader nav li {\r\n  margin: 0 1rem;\r\n}\r\n\r\n/* Login Button Spacing */\r\n/*header nav li:last-child {\r\n  margin-left: -5px;\r\n}*/\r\n\r\nheader.inner {\r\n  background: var(--bg-color);\r\n  border-bottom: 4px solid var(--primary-color);\r\n  position: relative;\r\n  box-shadow: var(--box-shadow);\r\n}\r\n\r\n/* Buttons */\r\n.btn {\r\n  color: #fff;\r\n  padding: 0.6rem;\r\n  border: 1px solid #ccc;\r\n  transition: all 0.7s;\r\n}\r\n\r\n.btn:hover {\r\n  background: var(--primary-color);\r\n  border: 1px solid var(--primary-color);\r\n  color: #fff;\r\n}\r\n\r\n.btn-reverse {\r\n  background: var(--primary-color);\r\n  border: 1px solid var(--primary-color);\r\n}\r\n\r\n.btn-reverse:hover {\r\n  background: var(--primary-hover-color);\r\n  border: 1px solid var(--primary-hover-color);\r\n}\r\n\r\n/* Home Search */\r\n.search-wrap {\r\n  background: url('../img/showcase.jpg') no-repeat center center fixed;\r\n  background-size: cover;\r\n  height: 100vh;\r\n  width: 100%;\r\n  padding: 1.3rem 3rem;\r\n  display: flex;\r\n  flex-direction: column;\r\n  justify-content: center;\r\n  align-items: center;\r\n}\r\n\r\n.search-wrap h1 {\r\n  font-size: 3rem;\r\n  font-weight: 800;\r\n  color: #fff;\r\n  margin: 0 0 1.5rem;\r\n  text-align: center;\r\n}\r\n\r\n.search-form input[type='submit'] {\r\n  background: var(--primary-color);\r\n  border: 1px solid var(--primary-color);\r\n  color: #fff;\r\n  padding: 0 2rem;\r\n  cursor: pointer;\r\n  transition: all 0.8s;\r\n}\r\n\r\n.search-form input[type='submit']:hover {\r\n  background: var(--primary-hover-color);\r\n  border: 1px solid var(--primary-hover-color);\r\n}\r\n\r\n.search-form {\r\n  display: flex;\r\n  width: 600px;\r\n  box-shadow: var(--box-shadow);\r\n}\r\n\r\n.search-form i {\r\n  color: #333;\r\n}\r\n\r\n/* Everything in the search form */\r\n.search-form > * {\r\n  border: 0;\r\n  padding: 0 0 0 10px;\r\n  background: #fff;\r\n  line-height: 50px;\r\n  font-size: 1rem;\r\n  border-radius: 0;\r\n  outline: 0;\r\n}\r\n\r\ninput[type='search'] {\r\n  flex-basis: 600px;\r\n}\r\n\r\n/* Gigs */\r\n.gig {\r\n  background: var(--bg-color);\r\n  border-bottom: 4px solid var(--primary-color);\r\n  color: #fff;\r\n  padding: 1rem;\r\n  margin-bottom: 1rem;\r\n  box-shadow: var(--box-shadow);\r\n}\r\n\r\n.gig ul {\r\n  list-style: none;\r\n  display: flex;\r\n}\r\n\r\n.gig li {\r\n  margin-right: 0.5rem;\r\n  padding: 0.6rem;\r\n}\r\n\r\n.gig .tech span {\r\n  color: var(--primary-color);\r\n}\r\n\r\n/* Form */\r\n.form-wrap {\r\n  margin: auto;\r\n  background: var(--bg-color);\r\n  color: #fff;\r\n  padding: 1rem 3rem 3rem;\r\n  margin-top: 3rem;\r\n  border-bottom: 4px solid var(--primary-color);\r\n  box-shadow: var(--box-shadow);\r\n}\r\n\r\n.form-wrap.reg-form,\r\n.form-wrap.login-form {\r\n  width: 60%;\r\n}\r\n\r\n.form-wrap h1,\r\n.form-wrap h2,\r\n.form-wrap p {\r\n  text-align: center;\r\n}\r\n\r\n.form-wrap .btn {\r\n  margin-top: 1rem;\r\n  display: block;\r\n  width: 100%;\r\n  text-align: center;\r\n  font-size: 18px;\r\n}\r\n\r\nlabel {\r\n  display: block;\r\n  margin-bottom: 0.5rem;\r\n}\r\n\r\n.input-box {\r\n  padding: 0.5rem;\r\n  font-size: 18px;\r\n  width: 100%;\r\n  margin-bottom: 1.2rem;\r\n}\r\n\r\n/* Tablets */\r\n@media (max-width: 800px) {\r\n  .container {\r\n    padding: 1rem 2rem;\r\n  }\r\n\r\n  header {\r\n    flex-direction: column;\r\n    padding: 0.3rem !important;\r\n  }\r\n\r\n  .search-form {\r\n    width: 100%;\r\n  }\r\n\r\n  input[type='search'] {\r\n    flex-basis: 100%;\r\n  }\r\n\r\n  .search-wrap h1 {\r\n    font-size: 2rem;\r\n  }\r\n\r\n  .search-wrap {\r\n    padding: 2.3rem;\r\n  }\r\n\r\n  .gig ul {\r\n    flex-direction: column;\r\n  }\r\n\r\n  .gig .btn {\r\n    display: block;\r\n    margin-top: 1rem;\r\n    text-align: center;\r\n  }\r\n\r\n  .form-wrap.reg-form,\r\n  .form-wrap.login-form {\r\n    width: 80%;\r\n  }\r\n}\r\n\r\n/* Smartphones */\r\n@media (max-width: 500px) {\r\n  .container {\r\n    padding: 1rem;\r\n  }\r\n\r\n  header nav li {\r\n    margin: 0 10px;\r\n  }\r\n\r\n  .search-form {\r\n    display: flex;\r\n    flex-direction: column;\r\n  }\r\n\r\n  input[type='search'] {\r\n    flex-basis: 0;\r\n  }\r\n\r\n  .search-form i {\r\n    display: none;\r\n  }\r\n\r\n  .form-wrap {\r\n    padding: 1rem 2rem 2rem;\r\n  }\r\n\r\n  .form-wrap.reg-form,\r\n  .form-wrap.login-form {\r\n    width: 100%;\r\n  }\r\n}\r\n"
  },
  {
    "path": "routes/gigs.js",
    "content": "const express = require('express');\nconst router = express.Router();\nconst db = require('../config/database');\nconst Gig = require('../models/Gig');\nconst Sequelize = require('sequelize');\nconst Op = Sequelize.Op;\n\n// Get gig list\nrouter.get('/', (req, res) => \n  Gig.findAll()\n    .then(gigs => res.render('gigs', {\n        gigs\n      }))\n    .catch(err => res.render('error', {error: err})));\n\n// Display add gig form\nrouter.get('/add', (req, res) => res.render('add'));\n\n// Add a gig\nrouter.post('/add', (req, res) => {\n  let { title, technologies, budget, description, contact_email } = req.body;\n  let errors = [];\n\n  // Validate Fields\n  if(!title) {\n    errors.push({ text: 'Please add a title' });\n  }\n  if(!technologies) {\n    errors.push({ text: 'Please add some technologies' });\n  }\n  if(!description) {\n    errors.push({ text: 'Please add a description' });\n  }\n  if(!contact_email) {\n    errors.push({ text: 'Please add a contact email' });\n  }\n\n  // Check for errors\n  if(errors.length > 0) {\n    res.render('add', {\n      errors,\n      title, \n      technologies, \n      budget, \n      description, \n      contact_email\n    });\n  } else {\n    if(!budget) {\n      budget = 'Unknown';\n    } else {\n      budget = `$${budget}`;\n    }\n\n    // Make lowercase and remove space after comma\n    technologies = technologies.toLowerCase().replace(/,[ ]+/g, ',');\n\n    // Insert into table\n    Gig.create({\n      title,\n      technologies,\n      description,\n      budget,\n      contact_email\n    })\n      .then(gig => res.redirect('/gigs'))\n      .catch(err => res.render('error', {error:err.message}))\n  }\n});\n\n// Search for gigs\nrouter.get('/search', (req, res) => {\n  let { term } = req.query;\n\n  // Make lowercase\n  term = term.toLowerCase();\n\n  Gig.findAll({ where: { technologies: { [Op.like]: '%' + term + '%' } } })\n    .then(gigs => res.render('gigs', { gigs }))\n    .catch(err => res.render('error', {error: err}));\n});\n\nmodule.exports = router;"
  },
  {
    "path": "views/add.handlebars",
    "content": "<section id=\"add\" class=\"container\">\n    <div class=\"form-wrap\">\n      <h1>Add A Gig</h1>\n      <p>Your contact email will be shared with registered users to apply to your gig</p>\n      {{#each errors}}\n        <div class=\"error\">\n          <p>{{text}}</p>\n        </div>\n      {{/each}}\n      <form action=\"/gigs/add\" method=\"POST\">\n        <div class=\"input-group\">\n          <label for=\"title\">Gig Title</label>\n          <input type=\"text\" name=\"title\" id=\"title\" class=\"input-box\" placeholder=\"eg. Small Wordpress website, React developer\"\n            maxlength=\"100\" value={{title}}>\n        </div>\n        <div class=\"input-group\">\n          <label for=\"technologies\">Technologies Needed</label>\n          <input type=\"text\" name=\"technologies\" id=\"technologies\" class=\"input-box\" placeholder=\"eg. javascript, react, PHP\"\n          maxlength=\"100\" value={{technologies}}>\n        </div>\n        <div class=\"input-group\">\n          <label for=\"budget\">Budget (Leave blank for unknown)</label>\n          <input type=\"number\" name=\"budget\" id=\"budget\" class=\"input-box\" placeholder=\"eg. 500, 5000, 10000\" value={{budget}}>\n        </div>\n        <div class=\"input-group\">\n          <label for=\"description\">Gig Description</label>\n          <textarea name=\"description\" id=\"description\" class=\"input-box\" placeholder=\"Describe the details of the gig\"\n            rows=\"10\">{{description}}</textarea>\n        </div>\n        <div class=\"input-group\">\n          <label for=\"budget\">Contact Email</label>\n          <input type=\"email\" name=\"contact_email\" id=\"contactemail\" class=\"input-box\" placeholder=\"Enter an email\" value={{contact_email}}>\n        </div>\n        <input type=\"submit\" value=\"Add Gig\" class=\"btn btn-reverse\">\n      </form>\n    </div>\n  </section>"
  },
  {
    "path": "views/error.handlebars",
    "content": "<section id=\"gigs\" class=\"container\">\n  <h1> An error occured </h1>\n  <br><br>\n  <h2>{{error}}</h2>\n</section>"
  },
  {
    "path": "views/gigs.handlebars",
    "content": "<section id=\"gigs\" class=\"container\">\n    <h1>All Gigs</h1>\n\n    {{#each gigs}}\n      <div class=\"gig\">\n      <h3>{{title}}</h3>\n      <p>{{description}}</p>\n      <ul>\n        <li>Budget: {{budget}}</li>\n        <li><a href=\"mailto:{{contact_email}}\" class=\"btn btn-reverse\">Apply Now</a></li>\n      </ul>\n      <div class=\"tech\">\n        <small>Technologies Needed: <span>{{technologies}}</span></small>\n      </div>\n    </div>\n    {{else}}\n      <p>No gigs available</p>\n    {{/each}}\n    \n  </section>"
  },
  {
    "path": "views/index.handlebars",
    "content": "<section id=\"search\" class=\"search-wrap\">\n    <h1>Find A Coding Gig</h1>\n    <form action=\"/gigs/search\" class=\"search-form\">\n      <i class=\"fas fa-search\"></i>\n      <input type=\"search\" name=\"term\" placeholder=\"Javascript, PHP, Rails, etc...\">\n      <input type=\"submit\" value=\"Search\">\n    </form>\n  </section>"
  },
  {
    "path": "views/layouts/landing.handlebars",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n  <link rel=\"stylesheet\" href=\"https://use.fontawesome.com/releases/v5.3.1/css/all.css\" integrity=\"sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU\"\n    crossorigin=\"anonymous\">\n\n  <link rel=\"stylesheet\" href=\"/css/style.css\">\n  <title>CodeGig</title>\n</head>\n\n<body>\n  <header>\n    <h2><a href=\"/\"><i class=\"fas fa-code\"></i>\n        CodeGig</a></h2>\n    <nav>\n      <ul>\n        <li>\n          <a href=\"/\">Home</a>\n        </li>\n        <li>\n          <a href=\"/gigs\">All Gigs</a>\n        </li>\n        <li>\n          <a href=\"/gigs/add\">Add Gig</a>\n        </li>\n      </ul>\n    </nav>\n  </header>\n\n  {{{body}}}\n</body>\n\n</html>"
  },
  {
    "path": "views/layouts/main.handlebars",
    "content": " <!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n  <link rel=\"stylesheet\" href=\"https://use.fontawesome.com/releases/v5.3.1/css/all.css\" integrity=\"sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU\"\n    crossorigin=\"anonymous\">\n\n\n  <link rel=\"stylesheet\" href=\"/css/style.css\">\n  <title>CodeGig</title>\n</head>\n\n<body>\n  <header class=\"inner\">\n    <h2><a href=\"/\"><i class=\"fas fa-code\"></i>\n        CodeGig</a></h2>\n    <nav>\n      <ul>\n        <li>\n          <a href=\"/\">Home</a>\n        </li>\n        <li>\n          <a href=\"/gigs\">All Gigs</a>\n        </li>\n        <li>\n          <a href=\"/gigs/add\">Add Gig</a>\n        </li>   \n      </ul>\n    </nav>\n  </header>\n\n  <div class=\"container\">\n    {{{body}}}\n  </div>\n</body>\n\n</html>"
  }
]