Skip to content

Job Hunting Management App built by Develop Me students for Develop Me students.

License

Notifications You must be signed in to change notification settings

develop-me/bagajob-API

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

99 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BagaJob - The Definitive Job Hunting Management App

Code of Conduct

Available here: https://github.com/develop-me/bagajob/wiki/A-Code-of-Conduct-for-Open-Source-Projects

Back-end Architecture - Laravel

Collaborating:

Never commit directly to the master branch. Create a new feature branch from the development branch, and make a pull request for a team-mate to review and merge.

Getting Started:

The Vagrant Box set up to use Laravel's Homestead image. To get started:

  1. Clone this repo and cd into the new directory

  2. In your new directory, run composer install

  3. Run vendor/bin/homestead make

  4. Copy the .env.example file to a new .env file: cp .env.example .env

  5. Update passwords in your .env file

    • The admin user is controlled by these environment variables and will be created when the database is seeded
      • ADMIN_USER_NAME="Bagajob Admin"
      • ADMIN_USER_EMAIL=[email protected]
      • ADMIN_USER_PASSWORD= *****
    • If you need email, set these api keys as well, they can be accessed from the bagajob mailjet account
      • MAILJET_APIKEY= *****
      • MAILJET_APISECRET= *****
  6. Run vagrant up

  7. ssh to the virtual machine: vagrant ssh

  8. Navigate to new code folder: cd code

  9. Run the database migrations: artisan migrate

  10. Seed the database with example data: artisan db:seed

    • UsersTableSeeder.php adds the admin user
    • JobsTableSeeder.php adds 10 Users, each with 10 jobs associated.
    • AppNoteSeeder.php adds 2 application notes to each job
    • InterviewSeeder.php adds 2 interviews to each job
  11. Create the Passport authentication keys: php artisan passport:install

Visit http://homestead.test on Mac or http://localhost:8000 on Windows:

Default view on Start

Troubleshooting:

Q: After running a database refresh/seed authentication is broken?

A: Run artisan passport:install to create the personal access client keys again

Q: When I run composer install I get a memory error

A:

First, make sure you're only running this on your local machine and NOT the Vagrant box.

Second, run this command to increase your memory limit, you can use -1 to make it unlimited if neccessary. php -d memory_limit=-1 $(which composer) update

Also see this page: https://getcomposer.org/doc/articles/troubleshooting.md#memory-limit-errors

Q: When I visit http://homestead.test on Mac or http://localhost:8000 on Windows, I get an Error 500: Internal Server Error

A:

Reload the vagrant box and provision it:

vagrant reload --provision

Generate a new app key:

php artisan key:generate

Q: When I run vagrant up, I get the following error:

Vagrant was unable to mount VirtualBox shared folders. This is usually
because the filesystem "vboxsf" is not available. This filesystem is
made available via the VirtualBox Guest Additions and kernel module.
Please verify that these guest additions are properly installed in the
guest. This is not a bug in Vagrant and is usually caused by a faulty
Vagrant box. For context, the command attempted was:

mount -t vboxsf -o dmode=777,fmode=666,uid=1000,gid=1000 var_www /var/www

The error output from the command was:

: No such device

A:

First, install the vagrant-vbguest plugin:

vagrant plugin install vagrant-vbguest

Next, initialise the plugin:

vagrant vbguest


Deployment

Hosting

  • The bagajob API lives on AWS, using the [email protected] account as the login user.
  • Server: EC2 t2.micro
  • Region: eu-west-2 (london)
  • OS: Ubuntu 20.04
  • AMI: ami-05c424d59413a2876 (UbuntuServer 20.04LTS(HVM), SSDVolumeType)
  • Software:
    • Nginx: HTTP server software
    • MySQL: database management system
    • PHP: programming language
    • Git: file management
    • Composer: PHP package manager
    • SSH Keys: for authentication

SSH Access (for deployment)

  • To access the EC2 instance you'll need an ssh keypair, which you likely already have from the DevelopMe course. Run tail ~/.ssh/id_rsa.pub at the command prompt on your machine and provide the output to Kieran/Nik.

  • Someone with access to the EC2 instance will then add this to ~/.ssh/authorized_keys on the server to grant you ssh access.

  • Add the below entry to your ssh configuration file, replace <alias> with anything you want and <your-server-key> with the name of the .pem file you have at ~/.ssh/

code ~/.ssh/config

Host <alias> ec2-3-8-127-159.eu-west-2.compute.amazonaws.com
  HostName ec2-3-8-127-159.eu-west-2.compute.amazonaws.com 
  User ubuntu
  IdentityFile ~/.ssh/<your-server-key>.pem
  IdentitiesOnly yes

Code Deployment (Capistrano)

Updating

  • Updating the code on the EC2 instance is a breeze with capistrano
  1. Install bundle
  • On your machine run gem install bundler
  • Make sure you have an up to date copy of the repo that contains Gemfile, cd to the project directory and run bundle install
  1. Make sure your recent changes are tested and synced with the master branch (usually through merging a pull request)
  2. Run bundle exec cap production deploy
  • This will make capistrano do the following:
    1. Pull down the latest version of the repo to the server
    2. Copy the files into a new timestamped releases directory
    3. Link any shared files (.env, the storage directory)
    4. Run composer install
    5. Link the current directory to the latest

For more information check out Chapter 25 - Capistrano in the DevelopMe Notes

Rolling Back

If you deploy a site and then realise the code wasn’t quite ready, you can go back to the previous version by running:

bundle exec cap production deploy:rollback

Bagajob API

General

All requests should:

  • For non-production use the basename https://homestead.test/api/
  • For production use the basename https://bagajob-api.developme.space/api/
  • Be sent with the Accept: application/json header.

End points:

Registration and Login

  • POST /register
  • POST /login
  • POST /reset-password-without-token
  • POST /reset-password-with-token

Users

  • PATCH /user/:userId
  • DELETE /user/:userId

Jobs

  • GET /user/:userId/jobs
  • POST /user/:userId/jobs
  • GET /user/:userId/jobs/:jobId
  • PATCH /user/:userId/jobs/:jobId
  • DELETE /user/:userId/jobs/:jobId

Interviews

  • GET /user/:userId/jobs/:jobId/interviews
  • POST /user/:userId/jobs/:jobId/interviews
  • GET /user/:userId/jobs/:jobId/interviews/:interviewId
  • PATCH /user/:userId/jobs/:jobId/interviews/:interviewId
  • DELETE /user/:userId/jobs/:jobId/interviews/:interviewId

Application Notes

  • GET /user/:userId/jobs/:jobId/app-notes
  • POST /user/:userId/jobs/:jobId/app-notes
  • GET /user/:userId/jobs/:jobId/app-notes/:appNoteId
  • PATCH /user/:userId/jobs/:jobId/app-notes/:appNoteId
  • DELETE /user/:userId/jobs/:jobId/app-notes/:appNoteId

Register User - POST /register

Request

{
    "name": "<user name>", // REQ, full name
    "email": "<email>", // REQ, valid email, not the same as another in the database
    "password": "<password>" // REQ, password
}

Responses

Success
{  
    "success": {
        "token": "<token>"
    },
    "user": {
        "id": "<ID>",
        "name": "<user name>",
        "email": "<email>",
        "created_at": "2020-09-01 14:22:46"
    }
}
Failures
  • Missing name
  • Missing email
  • Missing password
  • Duplicate User (email must be unqiue)
{
    "message": "The given data was invalid.",
    "errors": {
        "name": [
            "A name is required to create a user account"
        ],
        "email": [
            "A email is required to create a user account"
        ],
        "password": [
            "A password is required to create a user account"
        ],
        "email": [
            "A user account exists already with this email"
        ],
    }
}

Login User - POST /login

Request

  • NOTE: username maps to email in this case
{
    "username": "<user email>", // REQ, valid user email
    "password": "<password>" // REQ, password
}

Responses

Success
{
    "token_type": "Bearer",
    "expires_in": 31536000,
    "access_token": "<token>",
    "refresh_token": "<token>",
    "user": {
        "id": 20,
        "name": "<user name>",
        "email": "<email>",
        "created_at": "2020-09-01 14:22:46"
    }
}
Failures
  • Missing username/password
{
    "error": "invalid_request",
    "error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed.",
    "hint": "Check the `< missing >` parameter",
    "message": "<same as error_description>"
}
  • Invalid username/password
{
    "error": "invalid_grant",
    "error_description": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.",
    "hint": "",
    "message": "<same as error_description>"
}

Not Logged In/Unauthenticated

  • If the frontend tries to make a request to any of the non-authentication workflow related routes without the proper Bearer Token, they will receive this response:
{
    "message": "Unauthenticated."
}

Forgot Password / Password Reset Routes

POST /reset-password-without-token

Request

{
    "email": "<email>", // REQ, valid user email
}

Responses

Success
{
    "message": "Reset Password email successfully sent"
}
Failure
  • No email supplied/invalid email format
{
    "message": "The given data was invalid.",
    "errors": {
        "email": [
            "An email is required to reset the password" // "A valid email must be used to reset the password"
        ]
    }
}
  • User does not exist with that email
{
    "message": "An account does not exist with this email address"
}
  • Email failed
{
    "message": "An error has occured sending the reset password email, please try again",
    "error": "<error message>"
}

POST /reset-password-with-token

Request

{
    "email": "<email>", // REQ, valid user email
    "password": "<new password>", // REQ, new password
    "token": "<reset token>", // REQ, valid reset token from email
}

Responses

Success
{
    "message": "Password Reset Successful",
    "user": {
        "id": 21,
        "name": "<name>",
        "email": "<email>",
        "created_at": "2020-09-03 11:16:06"
    },
    "token": "<bearer token>"
}
Failure
  • Similar email, password, token validation as before (required)
  • Invalid token, or they've already reset their password
{
    "message": "Password token invalid, please submit a new password reset request"
}

Update User Account - PATCH /user/{user id}

Request

{
    "email": "<email>", // OPT, valid user email, not duplicate
    "name": "<user name>" // OPT, string
}

Responses

Success
  • Returns the user object
{
    "user": {
        "id": 23,
        "name": "<name>",
        "email": "<email>",
        "created_at": "2020-09-04 14:15:57"
    }
}
Failures
  • Trying to update a different user than you are logged in as
{
    "message": "You cannot edit a user other than your own"
}
  • Trying to update to a Duplicate email in the database
{
    "message": "The given data was invalid.",
    "errors": {
        "email": [
            "A user account exists already with this email"
        ]
    }
}
  • Incorrect format(s) name or email
{
    "message": "The given data was invalid.",
    "errors": {
        "email": [
            "The email must be a valid email address."
        ]
    }
}

DELETE User Account - DELETE /user/{user id}

Responses

Success
  • Returns a 204 - No Content response
Failures
  • Trying to delete a user other than your own
{
    "message": "You cannot delete a user other than your own"
}
  • Trying to delete a user that does not exist
{
    "message": "No query results for model [App\\User] <ID>"
}

Jobs

GET /user/:userId/jobs

Returns a subset of information from all of the specified user's jobs as JSON:

{
    "data": [
        {
            "id": 21,
            "title": "Architect",
            "company": "Halvorson, Runolfsson and Gutmann",
            "active": 1,
            "stage": "1"
        },
        {
            "id": 22,
            "title": "Tire Builder",
            "company": "Spinka-Lubowitz",
            "active": 1,
            "stage": "1"
        },
    ]
}

Does not return interviews or notes

POST /user/:userId/jobs

Request

Adds a job to the database. The below is the minimum required JSON, all other fields optional.

{
    "title" : "Senior Java Developer",
    "company" : "Green Software Inc.",
    "stage" : 1,
    "active": 1
}
Response

The newly created job with an Id (see GET request below)

GET /user/:userId/jobs/:jobId

Returns an individual job as JSON object where :jobId is the job ID

{
    "data": {
        "id": 21,
        "title": "Rental Clerk",
        "company": "Sauer, Witting and Osinski",
        "active": 1, // boolean (1 - true, 0 - false)
        "location": "Bergemouth",
        "salary": "56010.00",
        "closing_date": "2021-05-22 22:05:29",
        "date_applied": null,
        "description": "Dicta dolorem aut id porro ut porro sit. Expedita nemo vel natus eos ipsa quasi. Deleniti placeat non qui quibusdam adipisci amet et at.",
        "stage": "1",
        "cv": "CV_June_2020.pdf", // string of the filename, not actually storing files at this time
        "cover_letter": "fullstack_cover_July_2020.pdf", // string of the filename, not actually storing files at this time
        "interviews": ["..."],
        "application_notes": ["..."]
    }
}

PATCH /user/:userId/jobs/:jobId

Request

JSON with fields to update

Response

The updated job

DELETE /user/:userId/jobs/:jobId

Response

204 No Content

Interviews

GET /user/:userId/jobs/:jobId/interviews

Returns all of the specified job's interviews as JSON:

{
    "data": [
        {
            "id": 41,
            "job_id": 21,
            "interview_date": "2019-12-04 00:00:00",
            "format": "video_call",
            "interviewer": "Prof. Ozella Stark II",
            "notes": "Quis aut commodi nam id consectetur. Et veniam magnam et incidunt est officiis magnam consequatur."
        },
        {
            "id": 42,
            "job_id": 21,
            "interview_date": "2008-07-14 00:00:00",
            "format": "online_testing",
            "interviewer": "Leonardo Bayer DVM",
            "notes": "Voluptatem numquam ea a quaerat sunt. Laudantium aperiam aut pariatur perferendis nisi possimus."
        }
    ]
}

POST /user/:userId/jobs/:jobId/interviews

Request
{
    "interview_date": "2020-09-09", // REQ, date
    "format": "telephone", // REQ, set - 'online_testing','telephone', 'video_call', 'in_person'
    "interviewer": "Johnny", // OPT, string, max 250
    "notes": "Twas the night before xmas and all through the house" // OPT, string, max 500
}

Response

The newly created interview as JSON

GET /user/:userId/jobs/:jobId/interviews/:interviewId

Return 1 interview with the specified :interviewId

{
    "data": {
        "id": 42,
        "job_id": 21,
        "interview_date": "2008-07-14 00:00:00",
        "format": "online_testing",
        "interviewer": "Leonardo Bayer DVM",
        "notes": "Voluptatem numquam ea a quaerat sunt. Laudantium aperiam aut pariatur perferendis nisi possimus."
    }
}

PATCH /user/:userId/jobs/:jobId/interviews/:interviewId

Request

JSON with fields to update

Response

The updated interview

DELETE /user/:userId/jobs/:jobId/interviews/:interviewId

Response

204 No Content

Application Notes

GET /user/:userId/jobs/:jobId/app-notes

Returns all of the specified job's application notes as JSON:

{
    "data": [
        {
            "id": 41,
            "job_id": 21,
            "date": "1998-06-17",
            "data": "Illo tempora sequi ea quos. Et ut praesentium aut. Perspiciatis voluptas natus nisi similique. Architecto animi unde eum doloribus voluptatum in."
        },
        {
            "id": 42,
            "job_id": 21,
            "date": "2008-02-01",
            "data": "Est totam magnam ratione non ut ut qui. Sequi quia exercitationem ratione iure ullam et in et. Consequatur qui qui enim."
        }
    ]
}

POST /user/:userId/jobs/:jobId/app-notes

Request format
{
    "date": "2020-10-10", // REQ, date
    "data": "xmas and all through the house" // REQ, string, max 500
}

Response

The newly created application notes as JSON

GET /user/:userId/jobs/:jobId/app-notes/:appNoteId

Return 1 applicaiton note with the specified :appNoteId

{
    "data": {
        "id": 42,
        "job_id": 21,
        "date": "2008-02-01",
        "data": "Est totam magnam ratione non ut ut qui. Sequi quia exercitationem ratione iure ullam et in et. Consequatur qui qui enim."
    }
}

PATCH /user/:userId/jobs/:jobId/app-notes/:appNoteId

Request

JSON with fields to update

Response

The updated interview

DELETE /user/:userId/jobs/:jobId/app-notes/:appNoteId

Response

204 No Content


Laravel

Documentation

Laravel has the most extensive and thorough documentation and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework.

Laravel is accessible, powerful, and provides tools required for large, robust applications.


Scotch Box

Documentation