diff --git a/Gemfile b/Gemfile index 64ba8dc..c9cc5a9 100644 --- a/Gemfile +++ b/Gemfile @@ -40,9 +40,12 @@ end gem 'database_cleaner' gem 'devise-jwt' gem 'factory_bot_rails' +gem 'jsonapi-serializer', '~> 2.2' gem 'paranoia', '~> 2.5' gem 'rack-cors' gem 'rswag' +gem 'rswag-api' +gem 'rswag-ui' gem 'shoulda-matchers' - -gem 'jsonapi-serializer', '~> 2.2' +gem 'swagger-docs' +gem 'swagger-ui_rails' diff --git a/Gemfile.lock b/Gemfile.lock index f7dd74f..8d970f1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -261,6 +261,10 @@ GEM shoulda-matchers (5.3.0) activesupport (>= 5.2.0) stringio (3.1.0) + swagger-docs (0.2.9) + activesupport (>= 3) + rails (>= 3) + swagger-ui_rails (0.1.7) thor (1.3.0) timeout (0.4.1) tzinfo (2.0.6) @@ -301,7 +305,11 @@ DEPENDENCIES rails-controller-testing rspec-rails rswag + rswag-api + rswag-ui shoulda-matchers + swagger-docs + swagger-ui_rails tzinfo-data RUBY VERSION diff --git a/README.md b/README.md index 0a8e642..57c1a9c 100644 --- a/README.md +++ b/README.md @@ -7,26 +7,26 @@ # 📗 Table of Contents -- [📖 About the Project](#about-project) - - [🛠 Built With](#built-with) - - [Tech Stack](#tech-stack) - - [Key Features](#key-features) -- [💻 Getting Started](#getting-started) - - [Setup](#setup) - - [Prerequisites](#prerequisites) - - [Install](#install) - - [Usage](#usage) - - [Run tests](#run-tests) - - [Frontend Repository](#frontend-repository) - - [ERD Image](#erd-image) - - [API Documentation](#api-documentation) -- [👥 Authors](#authors) -- [🔭 Future Features](#future-features) -- [🤝 Contributing](#contributing) -- [⭐️ Show your support](#support) -- [🙏 Acknowledgements](#acknowledgements) -- [❓ FAQ (OPTIONAL)](#faq) -- [📝 License](#license) +- [📗 Table of Contents](#-table-of-contents) +- [📖 vehicle booking app ](#-vehicle-booking-app-) + - [🛠 Built With ](#-built-with-) + - [Tech Stack ](#tech-stack-) + - [Key Features ](#key-features-) + - [💻 Getting Started ](#-getting-started-) + - [Prerequisites](#prerequisites) + - [Setup](#setup) + - [Install](#install) + - [Frontend Repository](#frontend-repository) + - [ERD Image](#erd-image) + - [API Documentation](#api-documentation) + - [Usage](#usage) + - [Run tests](#run-tests) + - [👥 Authors ](#-authors-) + - [🔭 Future Features ](#-future-features-) + - [🤝 Contributing ](#-contributing-) + - [⭐️ Show your support ](#️-show-your-support-) + - [🙏 Acknowledgments ](#-acknowledgments-) + - [📝 License ](#-license-) # 📖 vehicle booking app @@ -75,35 +75,55 @@ In order to run this project you need: Use the following URL to clone this project: - https://github.com/tan12082001/Vehicle-Booking-App-Backend.git + ``` + git clone https://github.com/tan12082001/Vehicle-Booking-App-Backend.git + + ``` ### Install Open the terminal in the root directory of the project and run the following command to install all dependencies. - bundle install + - bundle install + - secret_base_key generation + In terminal: + * rm credentials.yml.enc + * $ EDITOR="mate --wait" bin/rails credentials:edit + + - configure database + In terminal: + * run `rails db:create` + * run `db:migrate` + + - Rspec + In terminal: + * bundle exec rails db:schema:load RAILS_ENV=test ### Frontend Repository Reference the [Frontend Repository](https://github.com/tan12082001/Vehicle-Booking-App-Frontend.git) for the corresponding frontend. ### ERD Image -Add an ERD image to visualize the database schema. -## follow the below link to access -(https://drawsql.app/teams/wineshuga/diagrams/book-appointment) +[ERD image to visualize the database schema](https://drawsql.app/teams/wineshuga/diagrams/book-appointment) ### API Documentation -Reference the API documentation for details on how to interact with the API. +For details on how to interact with the API, view the API documentation. Follow the steps below: + + - run server `rails s` + - open in browser `http://localhost:4000/api-docs` ### Usage -To start the development server, run the following command then navigate to `localhost:3000` in your browser. +To start the development server, run the following command then navigate to `localhost:4000` in your browser. rails s -Open [http://localhost:3000](http://localhost:3000/api/random_greeting) to view it in your browser. +Open [http://localhost:4000](http://localhost:4000) to view it in your browser. ### Run tests -There are no tests to run. +To run tests, use the following command: +``` + rspec +```
@@ -121,11 +141,12 @@ There are no tests to run. - GitHub: [@tan12082001](https://github.com/tan12082001) -👤 **Winnie uzochukwu** +👤 **Nweneary Uzochukwu Winnie** -- GitHub: [@winnie](https://github.com/Wineshuga) +- GitHub: [@wineshuga](https://github.com/Wineshuga) +- LinkedIn: [LinkedIn](https://linkedin.com/in/wineshuga) -👤 **Winnie uzochukwu** +👤 **Bolaji Toyib** - GitHub: [@toyybi bolaji](https://github.com/Simpleshaikh1) diff --git a/config/initializers/rswag_api.rb b/config/initializers/rswag_api.rb new file mode 100644 index 0000000..c4462b2 --- /dev/null +++ b/config/initializers/rswag_api.rb @@ -0,0 +1,14 @@ +Rswag::Api.configure do |c| + + # Specify a root folder where Swagger JSON files are located + # This is used by the Swagger middleware to serve requests for API descriptions + # NOTE: If you're using rswag-specs to generate Swagger, you'll need to ensure + # that it's configured to generate files in the same folder + c.openapi_root = Rails.root.to_s + '/swagger' + + # Inject a lambda function to alter the returned Swagger prior to serialization + # The function will have access to the rack env for the current request + # For example, you could leverage this to dynamically assign the "host" property + # + #c.swagger_filter = lambda { |swagger, env| swagger['host'] = env['HTTP_HOST'] } +end diff --git a/config/initializers/rswag_ui.rb b/config/initializers/rswag_ui.rb new file mode 100644 index 0000000..1d6151b --- /dev/null +++ b/config/initializers/rswag_ui.rb @@ -0,0 +1,16 @@ +Rswag::Ui.configure do |c| + + # List the Swagger endpoints that you want to be documented through the + # swagger-ui. The first parameter is the path (absolute or relative to the UI + # host) to the corresponding endpoint and the second is a title that will be + # displayed in the document selector. + # NOTE: If you're using rspec-api to expose Swagger files + # (under openapi_root) as JSON or YAML endpoints, then the list below should + # correspond to the relative paths for those endpoints. + + c.swagger_endpoint '/api-docs/v1/swagger.yaml', 'API V1 Docs' + + # Add Basic Auth in case your API is private + # c.basic_auth_enabled = true + # c.basic_auth_credentials 'username', 'password' +end diff --git a/config/routes.rb b/config/routes.rb index b5a88e2..e701c98 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,7 @@ Rails.application.routes.draw do + mount Rswag::Ui::Engine => '/api-docs' + mount Rswag::Api::Engine => '/api-docs' + namespace :api do devise_for :users, controllers: { @@ -7,7 +10,9 @@ } resources :cars do - delete 'destroy', on: :member, to: 'cars#destroy' + member do + delete 'destroy' + end end get 'all_cars', to: 'cars#index' diff --git a/spec/examples.txt b/spec/examples.txt index ab3c962..7998359 100644 --- a/spec/examples.txt +++ b/spec/examples.txt @@ -1,22 +1,22 @@ example_id | status | run_time | ---------------------------------------------------- | ------ | --------------- | -./spec/models/car_spec.rb[1:1:1] | passed | 0.00587 seconds | -./spec/models/car_spec.rb[1:1:2] | passed | 0.14159 seconds | -./spec/models/my_reservation_spec.rb[1:1:1] | passed | 0.00397 seconds | -./spec/models/my_reservation_spec.rb[1:1:2] | passed | 0.0029 seconds | -./spec/models/user_spec.rb[1:1:1] | passed | 0.00157 seconds | -./spec/models/user_spec.rb[1:1:2] | passed | 0.002 seconds | -./spec/requests/api/cars_spec.rb[1:1:1] | passed | 0.05168 seconds | -./spec/requests/api/cars_spec.rb[1:2:1:1] | passed | 2.02 seconds | -./spec/requests/api/cars_spec.rb[1:2:2:1] | passed | 1.78 seconds | -./spec/requests/api/cars_spec.rb[1:3:1:1] | passed | 0.02911 seconds | -./spec/requests/api/cars_spec.rb[1:3:2:1] | passed | 0.02891 seconds | -./spec/requests/api/cars_spec.rb[1:4:1:1] | passed | 0.02337 seconds | -./spec/requests/api/cars_spec.rb[1:4:2:1] | passed | 0.55206 seconds | -./spec/requests/api/my_reservations_spec.rb[1:1:1] | passed | 0.02825 seconds | -./spec/requests/api/my_reservations_spec.rb[1:2:1:1] | passed | 0.0311 seconds | -./spec/requests/api/my_reservations_spec.rb[1:2:2:1] | passed | 0.05047 seconds | -./spec/requests/api/registrations_spec.rb[1:1:1:1] | passed | 0.01067 seconds | -./spec/requests/api/registrations_spec.rb[1:1:2:1] | passed | 0.10001 seconds | -./spec/requests/api/sessions_spec.rb[1:1:1:1] | passed | 0.02346 seconds | -./spec/requests/api/sessions_spec.rb[1:1:2:1] | passed | 0.01105 seconds | +./spec/models/car_spec.rb[1:1:1] | passed | 0.00344 seconds | +./spec/models/car_spec.rb[1:1:2] | passed | 0.0022 seconds | +./spec/models/my_reservation_spec.rb[1:1:1] | passed | 0.00311 seconds | +./spec/models/my_reservation_spec.rb[1:1:2] | passed | 0.00317 seconds | +./spec/models/user_spec.rb[1:1:1] | passed | 0.00516 seconds | +./spec/models/user_spec.rb[1:1:2] | passed | 0.00459 seconds | +./spec/requests/api/cars_spec.rb[1:1:1] | passed | 0.02357 seconds | +./spec/requests/api/cars_spec.rb[1:2:1:1] | passed | 0.0264 seconds | +./spec/requests/api/cars_spec.rb[1:2:2:1] | passed | 0.54128 seconds | +./spec/requests/api/cars_spec.rb[1:3:1:1] | passed | 0.08633 seconds | +./spec/requests/api/cars_spec.rb[1:3:2:1] | passed | 0.05279 seconds | +./spec/requests/api/cars_spec.rb[1:4:1:1] | passed | 0.02828 seconds | +./spec/requests/api/cars_spec.rb[1:4:2:1] | passed | 0.58217 seconds | +./spec/requests/api/my_reservations_spec.rb[1:1:1] | passed | 0.02292 seconds | +./spec/requests/api/my_reservations_spec.rb[1:2:1:1] | passed | 0.07591 seconds | +./spec/requests/api/my_reservations_spec.rb[1:2:2:1] | passed | 0.03623 seconds | +./spec/requests/api/registrations_spec.rb[1:1:1:1] | passed | 0.01678 seconds | +./spec/requests/api/registrations_spec.rb[1:1:2:1] | passed | 0.01791 seconds | +./spec/requests/api/sessions_spec.rb[1:1:1:1] | passed | 0.23151 seconds | +./spec/requests/api/sessions_spec.rb[1:1:2:1] | passed | 0.01566 seconds | diff --git a/spec/swagger_helper.rb b/spec/swagger_helper.rb index cb54786..b69e8ad 100644 --- a/spec/swagger_helper.rb +++ b/spec/swagger_helper.rb @@ -10,7 +10,7 @@ # When you run the 'rswag:specs:swaggerize' rake task, the complete Swagger will # be generated at the provided relative path under openapi_root # By default, the operations defined in spec files are added to the first - # document below. You can override this behavior by adding a openapi_spec tag to the + # document below. You can override this behavior by adding a openapi_spec tag to theA # the root example_group in your specs, e.g. describe '...', openapi_spec: 'v2/swagger.json' config.openapi_specs = { 'v1/swagger.yaml' => { @@ -25,7 +25,7 @@ url: 'https://{defaultHost}', variables: { defaultHost: { - default: 'www.example.com' + default: 'localhost:4000' } } } diff --git a/swagger/v1/swagger.yaml b/swagger/v1/swagger.yaml new file mode 100644 index 0000000..b1d2d8d --- /dev/null +++ b/swagger/v1/swagger.yaml @@ -0,0 +1,418 @@ +openapi: 3.0.1 +info: + title: Cabooky API + description: |- + To use the authorized endpoints: + 1. Register a new user + 2. Log in with the registered user + 3. Copy the token string in the response body + 4. Click "Authorize [padlock-icon]", paste it in the "value" field, then click "Authorize". + version: v1 +servers: + - url: http://localhost:4000 + description: Sandbox server for testing +components: + schemas: + User: + type: object + properties: + id: + type: integer + username: + type: string + email: + type: string + password: + type: string + required: + - id + - username + - email + - password + + Reservation: + type: object + properties: + id: + type: integer + date: + type: string + format: date + city: + type: string + car_id: + type: integer + user_id: + type: integer + required: + - id + - date + - city + - car_id + - user_id + + Car: + type: object + properties: + id: + type: integer + name: + type: string + description: + type: string + image: + type: string + pricePerHr: + type: integer + seating_capacity: + type: integer + rental_duration: + type: integer + required: + - id + - name + - description + - image + - pricePerHr + - seating_capacity + - rental_duration + + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + +paths: + "/api/users": + post: + summary: Creates a new user + tags: + - Users + responses: + '200': + description: Signup successful + content: + application/json: + schema: + type: object + properties: + message: + type: string + user: + type: object + properties: + id: + type: integer + username: + type: string + email: + type: string + password: + type: string + required: + - id + - username + - email + - password + '422': + description: Username can't be blank + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + user[username]: + type: string + user[email]: + type: string + user[password]: + type: string + required: + - user[username] + - user[email] + - user[password] + description: Username, email, and password are needed to create a new user. + + "/api/users/sign_in": + post: + summary: Logs a user in + tags: + - Users + responses: + '200': + description: user logged in successfully + content: + application/json: + schema: + type: object + properties: + message: + type: string + username: + type: string + token: + type: string + '401': + description: user not registered + content: + application/json: + schema: + type: object + properties: + message: + type: string + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + session[username]: + type: string + session[password]: + type: string + required: + - session[username] + - session[password] + description: A username of an existing user needed to sign in. + + "/api/my_reservations": + get: + summary: Retrieves all reservations for the current user + security: + - bearerAuth: [] + tags: + - Reservations + responses: + '200': + description: successful + content: + application/json: + schema: + type: object + properties: + reservation: + type: object + properties: + id: + type: integer + date: + type: string + format: date + city: + type: string + car_id: + type: integer + user_id: + type: integer + required: + - id + - date + - city + - car_id + - user_id + + "/api/new_reserve": + post: + parameters: + - name: car + in: query + description: Car ID + required: true + schema: + type: integer + summary: Creates a new reservation + security: + - bearerAuth: [] + tags: + - Reservations + responses: + '201': + description: successful + content: + application/json: + schema: + type: object + properties: + success: + type: string + '422': + description: reservation not created + content: + application/json: + schema: + type: object + properties: + errors: + type: array + items: + type: string + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + reservation[date]: + description: YYYY-MM-DD + type: string + format: date + reservation[city]: + type: string + required: + - reservation[date] + - reservation[city] + example: + $ref: '#components/examples/reservation' + + "/api/all_cars": + get: + summary: Retrieves all cars + security: + - bearerAuth: [] + tags: + - Cars + responses: + '200': + description: successful + content: + application/json: + schema: + type: object + properties: + cars: + type: array + items: + type: object + properties: + id: + type: integer + name: + type: string + description: + type: string + image: + type: string + pricePerHr: + type: integer + seating_capacity: + type: integer + rental_duration: + type: integer + required: + - id + - name + - description + - image + - pricePerHr + - seating_capacity + - rental_duration + success: + type: string + + "/api/cars/{id}": + parameters: + - name: id + in: path + description: Car ID + required: true + schema: + type: integer + get: + summary: Retrieves a specific car + security: + - bearerAuth: [] + tags: + - Cars + responses: + '200': + description: successful + content: + application/json: + schema: + type: object + properties: + car: + type: array + items: + type: object + properties: + id: + type: integer + name: + type: string + description: + type: string + image: + type: string + pricePerHr: + type: integer + seating_capacity: + type: integer + rental_duration: + type: integer + required: + - id + - name + - description + - image + - pricePerHr + - seating_capacity + - rental_duration + + delete: + summary: Deletes a car + security: + - bearerAuth: [] + tags: + - Cars + responses: + '201': + description: car deleted + content: + application/json: + schema: + type: object + properties: + success: + type: string + '404': + description: 'Car Not Found' + content: + application/json: + schema: + type: object + properties: + error: + type: string + + "/api/car/new_car": + post: + summary: Creates a new car + security: + - bearerAuth: [] + tags: + - Cars + responses: + '201': + description: successful + content: + application/json: + schema: + type: object + properties: + error: + type: string + '422': + description: 'Car not created' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' diff --git a/test/channels/application_cable/connection_test.rb b/test/channels/application_cable/connection_test.rb deleted file mode 100644 index f925925..0000000 --- a/test/channels/application_cable/connection_test.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'test_helper' - -module ApplicationCable - class ConnectionTest < ActionCable::Connection::TestCase - # test "connects with cookies" do - # cookies.signed[:user_id] = 42 - # - # connect - # - # assert_equal connection.user_id, "42" - # end - end -end diff --git a/test/controllers/.keep b/test/controllers/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/test/controllers/users/sessions_controller_test.rb b/test/controllers/users/sessions_controller_test.rb deleted file mode 100644 index 4e81373..0000000 --- a/test/controllers/users/sessions_controller_test.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'test_helper' - -class Users::SessionsController < Devise::SessionsController - before_action :configure_sign_in_params, only: [:create] - - def create - self.resource = warden.authenticate!(auth_options) - sign_in(resource_name, resource) - yield resource if block_given? - respond_with resource, location: after_sign_in_path_for(resource) - end - - protected - - def configure_sign_in_params - devise_parameter_sanitizer.permit(:sign_in, keys: [:username]) - end -end diff --git a/test/fixtures/files/.keep b/test/fixtures/files/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/test/integration/.keep b/test/integration/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/test/mailers/.keep b/test/mailers/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/test/test_helper.rb b/test/test_helper.rb deleted file mode 100644 index d4229f9..0000000 --- a/test/test_helper.rb +++ /dev/null @@ -1,15 +0,0 @@ -ENV['RAILS_ENV'] ||= 'test' -require_relative '../config/environment' -require 'rails/test_help' - -module ActiveSupport - class TestCase - # Run tests in parallel with specified workers - parallelize(workers: :number_of_processors, with: :threads) - - # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. - fixtures :all - - # Add more helper methods to be used by all tests here... - end -end