From a5ca8e88cc08068391deafa0a83df0bd69645891 Mon Sep 17 00:00:00 2001 From: dlanoronald123 Date: Mon, 12 Jul 2021 22:15:22 +0800 Subject: [PATCH 01/97] Added models --- Gemfile | 1 + Gemfile.lock | 20 +- app/models/review.rb | 2 + app/models/tour.rb | 2 + app/models/tourist_tour.rb | 2 + app/models/travel_transaction.rb | 2 + app/models/user.rb | 6 + app/views/layouts/application.html.erb | 8 + config/environments/development.rb | 1 + config/environments/production.rb | 2 +- config/initializers/devise.rb | 311 ++++++++++++++++++ config/locales/devise.en.yml | 65 ++++ config/routes.rb | 1 + config/storage.yml | 14 +- config/webpacker.yml | 2 +- ...te_active_storage_tables.active_storage.rb | 27 ++ .../20210712140502_devise_create_users.rb | 59 ++++ db/migrate/20210712140709_create_tours.rb | 13 + .../20210712140806_create_tourist_tours.rb | 13 + ...210712140856_create_travel_transactions.rb | 10 + db/migrate/20210712140925_create_reviews.rb | 11 + db/schema.rb | 93 +++++- 22 files changed, 653 insertions(+), 12 deletions(-) create mode 100644 app/models/review.rb create mode 100644 app/models/tour.rb create mode 100644 app/models/tourist_tour.rb create mode 100644 app/models/travel_transaction.rb create mode 100644 app/models/user.rb create mode 100644 config/initializers/devise.rb create mode 100644 config/locales/devise.en.yml create mode 100644 db/migrate/20210712140032_create_active_storage_tables.active_storage.rb create mode 100644 db/migrate/20210712140502_devise_create_users.rb create mode 100644 db/migrate/20210712140709_create_tours.rb create mode 100644 db/migrate/20210712140806_create_tourist_tours.rb create mode 100644 db/migrate/20210712140856_create_travel_transactions.rb create mode 100644 db/migrate/20210712140925_create_reviews.rb diff --git a/Gemfile b/Gemfile index ba5697f15..06e4a3af2 100644 --- a/Gemfile +++ b/Gemfile @@ -15,6 +15,7 @@ gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] gem 'devise' gem 'hamlit-rails' +gem "aws-sdk-s3", require: false group :development, :test do gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] diff --git a/Gemfile.lock b/Gemfile.lock index b425d0d31..7cca9bba3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -59,6 +59,22 @@ GEM addressable (2.7.0) public_suffix (>= 2.0.2, < 5.0) ast (2.4.2) + aws-eventstream (1.1.1) + aws-partitions (1.476.0) + aws-sdk-core (3.116.0) + aws-eventstream (~> 1, >= 1.0.2) + aws-partitions (~> 1, >= 1.239.0) + aws-sigv4 (~> 1.1) + jmespath (~> 1.0) + aws-sdk-kms (1.44.0) + aws-sdk-core (~> 3, >= 3.112.0) + aws-sigv4 (~> 1.1) + aws-sdk-s3 (1.96.1) + aws-sdk-core (~> 3, >= 3.112.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.1) + aws-sigv4 (1.2.4) + aws-eventstream (~> 1, >= 1.0.2) bcrypt (3.1.16) bindex (0.8.1) bootsnap (1.7.3) @@ -100,6 +116,7 @@ GEM concurrent-ruby (~> 1.0) jbuilder (2.11.2) activesupport (>= 5.0.0) + jmespath (1.4.0) listen (3.5.1) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) @@ -114,8 +131,6 @@ GEM minitest (5.14.4) msgpack (1.4.2) nio4r (2.5.7) - nokogiri (1.11.3-x86_64-darwin) - racc (~> 1.4) nokogiri (1.11.3-x86_64-linux) racc (~> 1.4) orm_adapter (0.5.0) @@ -262,6 +277,7 @@ PLATFORMS x86_64-linux DEPENDENCIES + aws-sdk-s3 bootsnap (>= 1.4.2) byebug database_rewinder diff --git a/app/models/review.rb b/app/models/review.rb new file mode 100644 index 000000000..b2ca4935e --- /dev/null +++ b/app/models/review.rb @@ -0,0 +1,2 @@ +class Review < ApplicationRecord +end diff --git a/app/models/tour.rb b/app/models/tour.rb new file mode 100644 index 000000000..29bd7d141 --- /dev/null +++ b/app/models/tour.rb @@ -0,0 +1,2 @@ +class Tour < ApplicationRecord +end diff --git a/app/models/tourist_tour.rb b/app/models/tourist_tour.rb new file mode 100644 index 000000000..a16e4aaef --- /dev/null +++ b/app/models/tourist_tour.rb @@ -0,0 +1,2 @@ +class TouristTour < ApplicationRecord +end diff --git a/app/models/travel_transaction.rb b/app/models/travel_transaction.rb new file mode 100644 index 000000000..38ca04fc4 --- /dev/null +++ b/app/models/travel_transaction.rb @@ -0,0 +1,2 @@ +class TravelTransaction < ApplicationRecord +end diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 000000000..47567994e --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,6 @@ +class User < ApplicationRecord + # Include default devise modules. Others available are: + # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable + devise :database_authenticatable, :registerable, + :recoverable, :rememberable, :validatable +end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 73221bbe8..d6cef184a 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -10,6 +10,14 @@ + + <% if notice %> +

<%= notice %>

+ <% end %> + <% if alert %> +

<%= alert %>

+ <% end %> + <%= yield %> diff --git a/config/environments/development.rb b/config/environments/development.rb index 66df51f6f..f77c59e83 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -59,4 +59,5 @@ # Use an evented file watcher to asynchronously detect changes in source code, # routes, locales, etc. This feature depends on the listen gem. config.file_watcher = ActiveSupport::EventedFileUpdateChecker + config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } end diff --git a/config/environments/production.rb b/config/environments/production.rb index 69afea11b..77ddb50bd 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -36,7 +36,7 @@ # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX # Store uploaded files on the local file system (see config/storage.yml for options). - config.active_storage.service = :local + config.active_storage.service = :amazon # Mount Action Cable outside main process or domain. # config.action_cable.mount_path = nil diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb new file mode 100644 index 000000000..a98ad2613 --- /dev/null +++ b/config/initializers/devise.rb @@ -0,0 +1,311 @@ +# frozen_string_literal: true + +# Assuming you have not yet modified this file, each configuration option below +# is set to its default value. Note that some are commented out while others +# are not: uncommented lines are intended to protect your configuration from +# breaking changes in upgrades (i.e., in the event that future versions of +# Devise change the default values for those options). +# +# Use this hook to configure devise mailer, warden hooks and so forth. +# Many of these configuration options can be set straight in your model. +Devise.setup do |config| + # The secret key used by Devise. Devise uses this key to generate + # random tokens. Changing this key will render invalid all existing + # confirmation, reset password and unlock tokens in the database. + # Devise will use the `secret_key_base` as its `secret_key` + # by default. You can change it below and use your own secret key. + # config.secret_key = '45ea502616488c88616e1b5a578ac46a7151aff872f47c7b3cbb2607445ba0a52726baef340953c1d820aa821edfe0045231489f19042c94a033536a6e717ef6' + + # ==> Controller configuration + # Configure the parent class to the devise controllers. + # config.parent_controller = 'DeviseController' + + # ==> Mailer Configuration + # Configure the e-mail address which will be shown in Devise::Mailer, + # note that it will be overwritten if you use your own mailer class + # with default "from" parameter. + config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com' + + # Configure the class responsible to send e-mails. + # config.mailer = 'Devise::Mailer' + + # Configure the parent class responsible to send e-mails. + # config.parent_mailer = 'ActionMailer::Base' + + # ==> ORM configuration + # Load and configure the ORM. Supports :active_record (default) and + # :mongoid (bson_ext recommended) by default. Other ORMs may be + # available as additional gems. + require 'devise/orm/active_record' + + # ==> Configuration for any authentication mechanism + # Configure which keys are used when authenticating a user. The default is + # just :email. You can configure it to use [:username, :subdomain], so for + # authenticating a user, both parameters are required. Remember that those + # parameters are used only when authenticating and not when retrieving from + # session. If you need permissions, you should implement that in a before filter. + # You can also supply a hash where the value is a boolean determining whether + # or not authentication should be aborted when the value is not present. + # config.authentication_keys = [:email] + + # Configure parameters from the request object used for authentication. Each entry + # given should be a request method and it will automatically be passed to the + # find_for_authentication method and considered in your model lookup. For instance, + # if you set :request_keys to [:subdomain], :subdomain will be used on authentication. + # The same considerations mentioned for authentication_keys also apply to request_keys. + # config.request_keys = [] + + # Configure which authentication keys should be case-insensitive. + # These keys will be downcased upon creating or modifying a user and when used + # to authenticate or find a user. Default is :email. + config.case_insensitive_keys = [:email] + + # Configure which authentication keys should have whitespace stripped. + # These keys will have whitespace before and after removed upon creating or + # modifying a user and when used to authenticate or find a user. Default is :email. + config.strip_whitespace_keys = [:email] + + # Tell if authentication through request.params is enabled. True by default. + # It can be set to an array that will enable params authentication only for the + # given strategies, for example, `config.params_authenticatable = [:database]` will + # enable it only for database (email + password) authentication. + # config.params_authenticatable = true + + # Tell if authentication through HTTP Auth is enabled. False by default. + # It can be set to an array that will enable http authentication only for the + # given strategies, for example, `config.http_authenticatable = [:database]` will + # enable it only for database authentication. + # For API-only applications to support authentication "out-of-the-box", you will likely want to + # enable this with :database unless you are using a custom strategy. + # The supported strategies are: + # :database = Support basic authentication with authentication key + password + # config.http_authenticatable = false + + # If 401 status code should be returned for AJAX requests. True by default. + # config.http_authenticatable_on_xhr = true + + # The realm used in Http Basic Authentication. 'Application' by default. + # config.http_authentication_realm = 'Application' + + # It will change confirmation, password recovery and other workflows + # to behave the same regardless if the e-mail provided was right or wrong. + # Does not affect registerable. + # config.paranoid = true + + # By default Devise will store the user in session. You can skip storage for + # particular strategies by setting this option. + # Notice that if you are skipping storage for all authentication paths, you + # may want to disable generating routes to Devise's sessions controller by + # passing skip: :sessions to `devise_for` in your config/routes.rb + config.skip_session_storage = [:http_auth] + + # By default, Devise cleans up the CSRF token on authentication to + # avoid CSRF token fixation attacks. This means that, when using AJAX + # requests for sign in and sign up, you need to get a new CSRF token + # from the server. You can disable this option at your own risk. + # config.clean_up_csrf_token_on_authentication = true + + # When false, Devise will not attempt to reload routes on eager load. + # This can reduce the time taken to boot the app but if your application + # requires the Devise mappings to be loaded during boot time the application + # won't boot properly. + # config.reload_routes = true + + # ==> Configuration for :database_authenticatable + # For bcrypt, this is the cost for hashing the password and defaults to 12. If + # using other algorithms, it sets how many times you want the password to be hashed. + # The number of stretches used for generating the hashed password are stored + # with the hashed password. This allows you to change the stretches without + # invalidating existing passwords. + # + # Limiting the stretches to just one in testing will increase the performance of + # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use + # a value less than 10 in other environments. Note that, for bcrypt (the default + # algorithm), the cost increases exponentially with the number of stretches (e.g. + # a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation). + config.stretches = Rails.env.test? ? 1 : 12 + + # Set up a pepper to generate the hashed password. + # config.pepper = 'e7dbc2411fdbdc86605f235229e3cea5c762a3eb163746e28a81e38e452f71e9b26cd5dc66e421eea949b8784f0221aa8946192f1609bb6fdf4b4388c0f1f234' + + # Send a notification to the original email when the user's email is changed. + # config.send_email_changed_notification = false + + # Send a notification email when the user's password is changed. + # config.send_password_change_notification = false + + # ==> Configuration for :confirmable + # A period that the user is allowed to access the website even without + # confirming their account. For instance, if set to 2.days, the user will be + # able to access the website for two days without confirming their account, + # access will be blocked just in the third day. + # You can also set it to nil, which will allow the user to access the website + # without confirming their account. + # Default is 0.days, meaning the user cannot access the website without + # confirming their account. + # config.allow_unconfirmed_access_for = 2.days + + # A period that the user is allowed to confirm their account before their + # token becomes invalid. For example, if set to 3.days, the user can confirm + # their account within 3 days after the mail was sent, but on the fourth day + # their account can't be confirmed with the token any more. + # Default is nil, meaning there is no restriction on how long a user can take + # before confirming their account. + # config.confirm_within = 3.days + + # If true, requires any email changes to be confirmed (exactly the same way as + # initial account confirmation) to be applied. Requires additional unconfirmed_email + # db field (see migrations). Until confirmed, new email is stored in + # unconfirmed_email column, and copied to email column on successful confirmation. + config.reconfirmable = true + + # Defines which key will be used when confirming an account + # config.confirmation_keys = [:email] + + # ==> Configuration for :rememberable + # The time the user will be remembered without asking for credentials again. + # config.remember_for = 2.weeks + + # Invalidates all the remember me tokens when the user signs out. + config.expire_all_remember_me_on_sign_out = true + + # If true, extends the user's remember period when remembered via cookie. + # config.extend_remember_period = false + + # Options to be passed to the created cookie. For instance, you can set + # secure: true in order to force SSL only cookies. + # config.rememberable_options = {} + + # ==> Configuration for :validatable + # Range for password length. + config.password_length = 6..128 + + # Email regex used to validate email formats. It simply asserts that + # one (and only one) @ exists in the given string. This is mainly + # to give user feedback and not to assert the e-mail validity. + config.email_regexp = /\A[^@\s]+@[^@\s]+\z/ + + # ==> Configuration for :timeoutable + # The time you want to timeout the user session without activity. After this + # time the user will be asked for credentials again. Default is 30 minutes. + # config.timeout_in = 30.minutes + + # ==> Configuration for :lockable + # Defines which strategy will be used to lock an account. + # :failed_attempts = Locks an account after a number of failed attempts to sign in. + # :none = No lock strategy. You should handle locking by yourself. + # config.lock_strategy = :failed_attempts + + # Defines which key will be used when locking and unlocking an account + # config.unlock_keys = [:email] + + # Defines which strategy will be used to unlock an account. + # :email = Sends an unlock link to the user email + # :time = Re-enables login after a certain amount of time (see :unlock_in below) + # :both = Enables both strategies + # :none = No unlock strategy. You should handle unlocking by yourself. + # config.unlock_strategy = :both + + # Number of authentication tries before locking an account if lock_strategy + # is failed attempts. + # config.maximum_attempts = 20 + + # Time interval to unlock the account if :time is enabled as unlock_strategy. + # config.unlock_in = 1.hour + + # Warn on the last attempt before the account is locked. + # config.last_attempt_warning = true + + # ==> Configuration for :recoverable + # + # Defines which key will be used when recovering the password for an account + # config.reset_password_keys = [:email] + + # Time interval you can reset your password with a reset password key. + # Don't put a too small interval or your users won't have the time to + # change their passwords. + config.reset_password_within = 6.hours + + # When set to false, does not sign a user in automatically after their password is + # reset. Defaults to true, so a user is signed in automatically after a reset. + # config.sign_in_after_reset_password = true + + # ==> Configuration for :encryptable + # Allow you to use another hashing or encryption algorithm besides bcrypt (default). + # You can use :sha1, :sha512 or algorithms from others authentication tools as + # :clearance_sha1, :authlogic_sha512 (then you should set stretches above to 20 + # for default behavior) and :restful_authentication_sha1 (then you should set + # stretches to 10, and copy REST_AUTH_SITE_KEY to pepper). + # + # Require the `devise-encryptable` gem when using anything other than bcrypt + # config.encryptor = :sha512 + + # ==> Scopes configuration + # Turn scoped views on. Before rendering "sessions/new", it will first check for + # "users/sessions/new". It's turned off by default because it's slower if you + # are using only default views. + # config.scoped_views = false + + # Configure the default scope given to Warden. By default it's the first + # devise role declared in your routes (usually :user). + # config.default_scope = :user + + # Set this configuration to false if you want /users/sign_out to sign out + # only the current scope. By default, Devise signs out all scopes. + # config.sign_out_all_scopes = true + + # ==> Navigation configuration + # Lists the formats that should be treated as navigational. Formats like + # :html, should redirect to the sign in page when the user does not have + # access, but formats like :xml or :json, should return 401. + # + # If you have any extra navigational formats, like :iphone or :mobile, you + # should add them to the navigational formats lists. + # + # The "*/*" below is required to match Internet Explorer requests. + # config.navigational_formats = ['*/*', :html] + + # The default HTTP method used to sign out a resource. Default is :delete. + config.sign_out_via = :delete + + # ==> OmniAuth + # Add a new OmniAuth provider. Check the wiki for more information on setting + # up on your models and hooks. + # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo' + + # ==> Warden configuration + # If you want to use other strategies, that are not supported by Devise, or + # change the failure app, you can configure them inside the config.warden block. + # + # config.warden do |manager| + # manager.intercept_401 = false + # manager.default_strategies(scope: :user).unshift :some_external_strategy + # end + + # ==> Mountable engine configurations + # When using Devise inside an engine, let's call it `MyEngine`, and this engine + # is mountable, there are some extra configurations to be taken into account. + # The following options are available, assuming the engine is mounted as: + # + # mount MyEngine, at: '/my_engine' + # + # The router that invoked `devise_for`, in the example above, would be: + # config.router_name = :my_engine + # + # When using OmniAuth, Devise cannot automatically set OmniAuth path, + # so you need to do it manually. For the users scope, it would be: + # config.omniauth_path_prefix = '/my_engine/users/auth' + + # ==> Turbolinks configuration + # If your app is using Turbolinks, Turbolinks::Controller needs to be included to make redirection work correctly: + # + # ActiveSupport.on_load(:devise_failure_app) do + # include Turbolinks::Controller + # end + + # ==> Configuration for :registerable + + # When set to false, does not sign a user in automatically after their password is + # changed. Defaults to true, so a user is signed in automatically after changing a password. + # config.sign_in_after_change_password = true +end diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml new file mode 100644 index 000000000..ab1f07060 --- /dev/null +++ b/config/locales/devise.en.yml @@ -0,0 +1,65 @@ +# Additional translations at https://github.com/heartcombo/devise/wiki/I18n + +en: + devise: + confirmations: + confirmed: "Your email address has been successfully confirmed." + send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." + send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." + failure: + already_authenticated: "You are already signed in." + inactive: "Your account is not activated yet." + invalid: "Invalid %{authentication_keys} or password." + locked: "Your account is locked." + last_attempt: "You have one more attempt before your account is locked." + not_found_in_database: "Invalid %{authentication_keys} or password." + timeout: "Your session expired. Please sign in again to continue." + unauthenticated: "You need to sign in or sign up before continuing." + unconfirmed: "You have to confirm your email address before continuing." + mailer: + confirmation_instructions: + subject: "Confirmation instructions" + reset_password_instructions: + subject: "Reset password instructions" + unlock_instructions: + subject: "Unlock instructions" + email_changed: + subject: "Email Changed" + password_change: + subject: "Password Changed" + omniauth_callbacks: + failure: "Could not authenticate you from %{kind} because \"%{reason}\"." + success: "Successfully authenticated from %{kind} account." + passwords: + no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." + send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." + send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." + updated: "Your password has been changed successfully. You are now signed in." + updated_not_active: "Your password has been changed successfully." + registrations: + destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon." + signed_up: "Welcome! You have signed up successfully." + signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." + signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." + signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account." + update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirmation link to confirm your new email address." + updated: "Your account has been updated successfully." + updated_but_not_signed_in: "Your account has been updated successfully, but since your password was changed, you need to sign in again" + sessions: + signed_in: "Signed in successfully." + signed_out: "Signed out successfully." + already_signed_out: "Signed out successfully." + unlocks: + send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes." + send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes." + unlocked: "Your account has been unlocked successfully. Please sign in to continue." + errors: + messages: + already_confirmed: "was already confirmed, please try signing in" + confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" + expired: "has expired, please request a new one" + not_found: "not found" + not_locked: "was not locked" + not_saved: + one: "1 error prohibited this %{resource} from being saved:" + other: "%{count} errors prohibited this %{resource} from being saved:" diff --git a/config/routes.rb b/config/routes.rb index c06383a17..54b04d773 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,3 +1,4 @@ Rails.application.routes.draw do + devise_for :users # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html end diff --git a/config/storage.yml b/config/storage.yml index d32f76e8f..ecc7b0ad9 100644 --- a/config/storage.yml +++ b/config/storage.yml @@ -7,12 +7,12 @@ local: root: <%= Rails.root.join("storage") %> # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) -# amazon: -# service: S3 -# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> -# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> -# region: us-east-1 -# bucket: your_own_bucket +amazon: + service: S3 + access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> + secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> + region: ap-southeast-1 + bucket: travel-go # Remember not to checkin your GCS keyfile to a repository # google: @@ -31,4 +31,4 @@ local: # mirror: # service: Mirror # primary: local -# mirrors: [ amazon, google, microsoft ] +# mirrors: [ amazon, google, microsoft ] \ No newline at end of file diff --git a/config/webpacker.yml b/config/webpacker.yml index 8581ac047..79711bc8d 100644 --- a/config/webpacker.yml +++ b/config/webpacker.yml @@ -53,7 +53,7 @@ development: compile: true # Verifies that correct packages and versions are installed by inspecting package.json, yarn.lock, and node_modules - check_yarn_integrity: true + check_yarn_integrity: false # Reference: https://webpack.js.org/configuration/dev-server/ dev_server: diff --git a/db/migrate/20210712140032_create_active_storage_tables.active_storage.rb b/db/migrate/20210712140032_create_active_storage_tables.active_storage.rb new file mode 100644 index 000000000..0b2ce257c --- /dev/null +++ b/db/migrate/20210712140032_create_active_storage_tables.active_storage.rb @@ -0,0 +1,27 @@ +# This migration comes from active_storage (originally 20170806125915) +class CreateActiveStorageTables < ActiveRecord::Migration[5.2] + def change + create_table :active_storage_blobs do |t| + t.string :key, null: false + t.string :filename, null: false + t.string :content_type + t.text :metadata + t.bigint :byte_size, null: false + t.string :checksum, null: false + t.datetime :created_at, null: false + + t.index [ :key ], unique: true + end + + create_table :active_storage_attachments do |t| + t.string :name, null: false + t.references :record, null: false, polymorphic: true, index: false + t.references :blob, null: false + + t.datetime :created_at, null: false + + t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true + t.foreign_key :active_storage_blobs, column: :blob_id + end + end +end diff --git a/db/migrate/20210712140502_devise_create_users.rb b/db/migrate/20210712140502_devise_create_users.rb new file mode 100644 index 000000000..a655fc42c --- /dev/null +++ b/db/migrate/20210712140502_devise_create_users.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +class DeviseCreateUsers < ActiveRecord::Migration[6.0] + def change + create_table :users do |t| + ## Database authenticatable + t.string :email, null: false, default: "" + t.string :encrypted_password, null: false, default: "" + t.integer :contact_number + t.string :address + t.string :type + ## Tourist + t.string :first_name + t.string :middle_name + t.string :last_name + t.date :birth_date + + ##Travel Agency + t.string :agency_name + t.decimal :average_rating + t.bigint :verified_by + t.boolean :active, default: true + + + ## Recoverable + t.string :reset_password_token + t.datetime :reset_password_sent_at + + ## Rememberable + t.datetime :remember_created_at + + ## Trackable + # t.integer :sign_in_count, default: 0, null: false + # t.datetime :current_sign_in_at + # t.datetime :last_sign_in_at + # t.string :current_sign_in_ip + # t.string :last_sign_in_ip + + ## Confirmable + # t.string :confirmation_token + # t.datetime :confirmed_at + # t.datetime :confirmation_sent_at + # t.string :unconfirmed_email # Only if using reconfirmable + + ## Lockable + # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts + # t.string :unlock_token # Only if unlock strategy is :email or :both + # t.datetime :locked_at + + + t.timestamps null: false + end + + add_index :users, :email, unique: true + add_index :users, :reset_password_token, unique: true + # add_index :users, :confirmation_token, unique: true + # add_index :users, :unlock_token, unique: true + end +end diff --git a/db/migrate/20210712140709_create_tours.rb b/db/migrate/20210712140709_create_tours.rb new file mode 100644 index 000000000..4626fcb0a --- /dev/null +++ b/db/migrate/20210712140709_create_tours.rb @@ -0,0 +1,13 @@ +class CreateTours < ActiveRecord::Migration[6.0] + def change + create_table :tours do |t| + t.belongs_to :agency + t.string :name + t.decimal :price + t.string :location + t.integer :duration + t.boolean :active, default: true + t.timestamps + end + end +end diff --git a/db/migrate/20210712140806_create_tourist_tours.rb b/db/migrate/20210712140806_create_tourist_tours.rb new file mode 100644 index 000000000..7998c80a2 --- /dev/null +++ b/db/migrate/20210712140806_create_tourist_tours.rb @@ -0,0 +1,13 @@ +class CreateTouristTours < ActiveRecord::Migration[6.0] + def change + create_table :tourist_tours do |t| + t.belongs_to :tourist + t.belongs_to :tour + t.integer :guest_quantity + t.decimal :amount_bought + t.date :start_date + t.date :end_date + t.timestamps + end + end +end diff --git a/db/migrate/20210712140856_create_travel_transactions.rb b/db/migrate/20210712140856_create_travel_transactions.rb new file mode 100644 index 000000000..cf0ab7747 --- /dev/null +++ b/db/migrate/20210712140856_create_travel_transactions.rb @@ -0,0 +1,10 @@ +class CreateTravelTransactions < ActiveRecord::Migration[6.0] + def change + create_table :travel_transactions do |t| + t.belongs_to :tourist_tour + t.belongs_to :agency + t.decimal :total_price + t.timestamps + end + end +end diff --git a/db/migrate/20210712140925_create_reviews.rb b/db/migrate/20210712140925_create_reviews.rb new file mode 100644 index 000000000..341478814 --- /dev/null +++ b/db/migrate/20210712140925_create_reviews.rb @@ -0,0 +1,11 @@ +class CreateReviews < ActiveRecord::Migration[6.0] + def change + create_table :reviews do |t| + t.belongs_to :agency + t.belongs_to :tourist + t.text :review + t.decimal :rating + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index b10373ba6..44ecfccc3 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,9 +10,100 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 0) do +ActiveRecord::Schema.define(version: 2021_07_12_140925) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "active_storage_attachments", force: :cascade do |t| + t.string "name", null: false + t.string "record_type", null: false + t.bigint "record_id", null: false + t.bigint "blob_id", null: false + t.datetime "created_at", null: false + t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" + t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true + end + + create_table "active_storage_blobs", force: :cascade do |t| + t.string "key", null: false + t.string "filename", null: false + t.string "content_type" + t.text "metadata" + t.bigint "byte_size", null: false + t.string "checksum", null: false + t.datetime "created_at", null: false + t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true + end + + create_table "reviews", force: :cascade do |t| + t.bigint "agency_id" + t.bigint "tourist_id" + t.text "review" + t.decimal "rating" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["agency_id"], name: "index_reviews_on_agency_id" + t.index ["tourist_id"], name: "index_reviews_on_tourist_id" + end + + create_table "tourist_tours", force: :cascade do |t| + t.bigint "tourist_id" + t.bigint "tour_id" + t.integer "guest_quantity" + t.decimal "amount_bought" + t.date "start_date" + t.date "end_date" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["tour_id"], name: "index_tourist_tours_on_tour_id" + t.index ["tourist_id"], name: "index_tourist_tours_on_tourist_id" + end + + create_table "tours", force: :cascade do |t| + t.bigint "agency_id" + t.string "name" + t.decimal "price" + t.string "location" + t.integer "duration" + t.boolean "active", default: true + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["agency_id"], name: "index_tours_on_agency_id" + end + + create_table "travel_transactions", force: :cascade do |t| + t.bigint "tourist_tour_id" + t.bigint "agency_id" + t.decimal "total_price" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["agency_id"], name: "index_travel_transactions_on_agency_id" + t.index ["tourist_tour_id"], name: "index_travel_transactions_on_tourist_tour_id" + end + + create_table "users", force: :cascade do |t| + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false + t.integer "contact_number" + t.string "address" + t.string "type" + t.string "first_name" + t.string "middle_name" + t.string "last_name" + t.date "birth_date" + t.string "agency_name" + t.decimal "average_rating" + t.bigint "verified_by" + t.boolean "active", default: true + t.string "reset_password_token" + t.datetime "reset_password_sent_at" + t.datetime "remember_created_at" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["email"], name: "index_users_on_email", unique: true + t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true + end + + add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" end From 1cbc9b50cf8da1213602da4a01605467fce8ab4c Mon Sep 17 00:00:00 2001 From: dlanoronald123 Date: Mon, 12 Jul 2021 22:32:05 +0800 Subject: [PATCH 02/97] Update model validations --- app/models/agency.rb | 6 ++++++ app/models/review.rb | 2 ++ app/models/tour.rb | 5 +++++ app/models/tourist.rb | 8 ++++++++ app/models/tourist_tour.rb | 4 ++++ app/models/travel_transaction.rb | 2 ++ app/models/user.rb | 2 ++ 7 files changed, 29 insertions(+) create mode 100644 app/models/agency.rb create mode 100644 app/models/tourist.rb diff --git a/app/models/agency.rb b/app/models/agency.rb new file mode 100644 index 000000000..74bd5c2ea --- /dev/null +++ b/app/models/agency.rb @@ -0,0 +1,6 @@ +class Agency < User + has_many :tours, dependent: :destroy + has_many :travel_transactions, dependent: :destroy + + has_many :reviews, dependent: :destroy +end diff --git a/app/models/review.rb b/app/models/review.rb index b2ca4935e..d327321d9 100644 --- a/app/models/review.rb +++ b/app/models/review.rb @@ -1,2 +1,4 @@ class Review < ApplicationRecord + belongs_to :tourist + belongs_to :agency end diff --git a/app/models/tour.rb b/app/models/tour.rb index 29bd7d141..45bd91e17 100644 --- a/app/models/tour.rb +++ b/app/models/tour.rb @@ -1,2 +1,7 @@ class Tour < ApplicationRecord + belongs_to :agency + has_many :tourist_tours, dependent: :destroy + has_many :tourists, through: :tourist_tours + + has_many :travel_transactions, through: :tourist_tours end diff --git a/app/models/tourist.rb b/app/models/tourist.rb new file mode 100644 index 000000000..c5b33d015 --- /dev/null +++ b/app/models/tourist.rb @@ -0,0 +1,8 @@ +class Tourist < User + has_many :tourist_tours, dependent: :destroy + has_many :tours, through: :tourist_tours + + has_many :travel_transactions, through: :tourist_tours + + has_many :reviews, dependent: :destroy +end diff --git a/app/models/tourist_tour.rb b/app/models/tourist_tour.rb index a16e4aaef..d6d0c82ed 100644 --- a/app/models/tourist_tour.rb +++ b/app/models/tourist_tour.rb @@ -1,2 +1,6 @@ class TouristTour < ApplicationRecord + belongs_to :tour + belongs_to :tourist + + has_one :travel_transaction, dependent: :destroy end diff --git a/app/models/travel_transaction.rb b/app/models/travel_transaction.rb index 38ca04fc4..ebe4d37b7 100644 --- a/app/models/travel_transaction.rb +++ b/app/models/travel_transaction.rb @@ -1,2 +1,4 @@ class TravelTransaction < ApplicationRecord + belongs_to :tourist_tour + belongs_to :agency end diff --git a/app/models/user.rb b/app/models/user.rb index 47567994e..474eb5705 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -3,4 +3,6 @@ class User < ApplicationRecord # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable + + has_one_attached :profile_pic end From 075aa3d74aa2f0ee6262ea45e4e886aa58dfc27b Mon Sep 17 00:00:00 2001 From: dlanoronald123 Date: Mon, 12 Jul 2021 22:43:29 +0800 Subject: [PATCH 03/97] Added Bootstrap for styling --- app/assets/stylesheets/application.css | 2 ++ app/assets/stylesheets/custom.css.scss | 1 + app/assets/stylesheets/home.scss | 3 +++ app/controllers/home_controller.rb | 3 +++ app/helpers/home_helper.rb | 2 ++ app/javascript/packs/application.js | 3 +++ app/views/home/index.html.erb | 37 ++++++++++++++++++++++++++ config/routes.rb | 1 + config/webpack/environment.js | 7 ++++- package.json | 3 +++ yarn.lock | 15 +++++++++++ 11 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 app/assets/stylesheets/custom.css.scss create mode 100644 app/assets/stylesheets/home.scss create mode 100644 app/controllers/home_controller.rb create mode 100644 app/helpers/home_helper.rb create mode 100644 app/views/home/index.html.erb diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index d05ea0f51..f3b601fc0 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -10,6 +10,8 @@ * files in this directory. Styles in this file should be added after the last require_* statement. * It is generally better to create a new file per style scope. * + + *= require bootstrap *= require_tree . *= require_self */ diff --git a/app/assets/stylesheets/custom.css.scss b/app/assets/stylesheets/custom.css.scss new file mode 100644 index 000000000..1f21cf8a4 --- /dev/null +++ b/app/assets/stylesheets/custom.css.scss @@ -0,0 +1 @@ +@import 'bootstrap/dist/css/bootstrap'; diff --git a/app/assets/stylesheets/home.scss b/app/assets/stylesheets/home.scss new file mode 100644 index 000000000..072f44eea --- /dev/null +++ b/app/assets/stylesheets/home.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the home controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: https://sass-lang.com/ diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb new file mode 100644 index 000000000..6d3fe90f7 --- /dev/null +++ b/app/controllers/home_controller.rb @@ -0,0 +1,3 @@ +class HomeController < ApplicationController + def index; end +end diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb new file mode 100644 index 000000000..23de56ac6 --- /dev/null +++ b/app/helpers/home_helper.rb @@ -0,0 +1,2 @@ +module HomeHelper +end diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 9cd55d4b9..0f4ac5574 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -15,3 +15,6 @@ require("channels") // // const images = require.context('../images', true) // const imagePath = (name) => images(name, true) + +import "bootstrap" + \ No newline at end of file diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb new file mode 100644 index 000000000..d55acf142 --- /dev/null +++ b/app/views/home/index.html.erb @@ -0,0 +1,37 @@ +

hello

+ \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 54b04d773..1bab170f5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,5 @@ Rails.application.routes.draw do devise_for :users # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html + root "home#index" end diff --git a/config/webpack/environment.js b/config/webpack/environment.js index d16d9af74..3cf52851b 100644 --- a/config/webpack/environment.js +++ b/config/webpack/environment.js @@ -1,3 +1,8 @@ const { environment } = require('@rails/webpacker') - +const webpack = require("webpack") +environment.plugins.append("Provide", new webpack.ProvidePlugin({ + $: 'jquery', + jQuery: 'jquery', + Popper: ['popper.js', 'default'] +})) module.exports = environment diff --git a/package.json b/package.json index b56a1d18e..afb6cfcb2 100644 --- a/package.json +++ b/package.json @@ -2,10 +2,13 @@ "name": "rails_project", "private": true, "dependencies": { + "@popperjs/core": "^2.9.2", "@rails/actioncable": "^6.0.0", "@rails/activestorage": "^6.0.0", "@rails/ujs": "^6.0.0", "@rails/webpacker": "4.3.0", + "bootstrap": "^5.0.2", + "jquery": "^3.6.0", "turbolinks": "^5.2.0" }, "version": "0.1.0", diff --git a/yarn.lock b/yarn.lock index 641111eee..960c86088 100644 --- a/yarn.lock +++ b/yarn.lock @@ -836,6 +836,11 @@ mkdirp "^1.0.4" rimraf "^3.0.2" +"@popperjs/core@^2.9.2": + version "2.9.2" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.2.tgz#adea7b6953cbb34651766b0548468e743c6a2353" + integrity sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q== + "@rails/actioncable@^6.0.0": version "6.1.1" resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-6.1.1.tgz#e980b2ea1c62cae17bcddad37deb6ba88d498b63" @@ -1489,6 +1494,11 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= +bootstrap@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.0.2.tgz#aff23d5e0e03c31255ad437530ee6556e78e728e" + integrity sha512-1Ge963tyEQWJJ+8qtXFU6wgmAVj9gweEjibUdbmcCEYsn38tVwRk8107rk2vzt6cfQcRr3SlZ8aQBqaD8aqf+Q== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -4018,6 +4028,11 @@ jest-worker@^25.4.0: merge-stream "^2.0.0" supports-color "^7.0.0" +jquery@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.0.tgz#c72a09f15c1bdce142f49dbf1170bdf8adac2470" + integrity sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw== + js-base64@^2.1.8: version "2.6.4" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4" From e5ab8644ac4a723cbbe1c695d5e1d296e64dc339 Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Fri, 16 Jul 2021 15:16:13 +0800 Subject: [PATCH 04/97] add approved column to users table --- db/migrate/20210716071248_add_column_to_users.rb | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 db/migrate/20210716071248_add_column_to_users.rb diff --git a/db/migrate/20210716071248_add_column_to_users.rb b/db/migrate/20210716071248_add_column_to_users.rb new file mode 100644 index 000000000..c4c60c2c3 --- /dev/null +++ b/db/migrate/20210716071248_add_column_to_users.rb @@ -0,0 +1,5 @@ +class AddColumnToUsers < ActiveRecord::Migration[6.0] + def change + add_column :users, :approved, :boolean, default: false + end +end From aea760e19418ec852c724bbc47013d188e1e9c7c Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Fri, 16 Jul 2021 15:42:26 +0800 Subject: [PATCH 05/97] add chat room model and table --- db/migrate/20210716071208_create_chat_room_users.rb | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 db/migrate/20210716071208_create_chat_room_users.rb diff --git a/db/migrate/20210716071208_create_chat_room_users.rb b/db/migrate/20210716071208_create_chat_room_users.rb new file mode 100644 index 000000000..015af7120 --- /dev/null +++ b/db/migrate/20210716071208_create_chat_room_users.rb @@ -0,0 +1,9 @@ +class CreateChatRoomUsers < ActiveRecord::Migration[6.0] + def change + create_table :chat_room_users do |t| + t.belongs_to :user + t.belongs_to :chat_room + t.timestamps + end + end +end From f05d2d0490ecba650be735d7eb93d112fd1ea870 Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Fri, 16 Jul 2021 15:53:31 +0800 Subject: [PATCH 06/97] add action text cable for rich text area --- ...071040_create_action_text_tables.action_text.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 db/migrate/20210716071040_create_action_text_tables.action_text.rb diff --git a/db/migrate/20210716071040_create_action_text_tables.action_text.rb b/db/migrate/20210716071040_create_action_text_tables.action_text.rb new file mode 100644 index 000000000..5dfbd1561 --- /dev/null +++ b/db/migrate/20210716071040_create_action_text_tables.action_text.rb @@ -0,0 +1,14 @@ +# This migration comes from action_text (originally 20180528164100) +class CreateActionTextTables < ActiveRecord::Migration[6.0] + def change + create_table :action_text_rich_texts do |t| + t.string :name, null: false + t.text :body, size: :long + t.references :record, null: false, polymorphic: true, index: false + + t.timestamps + + t.index [ :record_type, :record_id, :name ], name: "index_action_text_rich_texts_uniqueness", unique: true + end + end +end From a0c30c515a7c7c3df290bd6f6d23c09064409c67 Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Fri, 16 Jul 2021 15:57:57 +0800 Subject: [PATCH 07/97] add chat rooms and messages --- db/migrate/20210716070806_create_chat_rooms.rb | 7 +++++++ db/migrate/20210716070910_create_messages.rb | 10 ++++++++++ 2 files changed, 17 insertions(+) create mode 100644 db/migrate/20210716070806_create_chat_rooms.rb create mode 100644 db/migrate/20210716070910_create_messages.rb diff --git a/db/migrate/20210716070806_create_chat_rooms.rb b/db/migrate/20210716070806_create_chat_rooms.rb new file mode 100644 index 000000000..a75fccfd5 --- /dev/null +++ b/db/migrate/20210716070806_create_chat_rooms.rb @@ -0,0 +1,7 @@ +class CreateChatRooms < ActiveRecord::Migration[6.0] + def change + create_table :chat_rooms do |t| + t.timestamps + end + end +end diff --git a/db/migrate/20210716070910_create_messages.rb b/db/migrate/20210716070910_create_messages.rb new file mode 100644 index 000000000..3a626712a --- /dev/null +++ b/db/migrate/20210716070910_create_messages.rb @@ -0,0 +1,10 @@ +class CreateMessages < ActiveRecord::Migration[6.0] + def change + create_table :messages do |t| + t.belongs_to :chat_room + t.belongs_to :user + t.text :body + t.timestamps + end + end +end From 26b4300d4157c657403f409acad50534885983cb Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Fri, 16 Jul 2021 15:58:29 +0800 Subject: [PATCH 08/97] create active storage for photo upload --- app/views/active_storage/blobs/_blob.html.erb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 app/views/active_storage/blobs/_blob.html.erb diff --git a/app/views/active_storage/blobs/_blob.html.erb b/app/views/active_storage/blobs/_blob.html.erb new file mode 100644 index 000000000..49ba357dd --- /dev/null +++ b/app/views/active_storage/blobs/_blob.html.erb @@ -0,0 +1,14 @@ +
attachment--<%= blob.filename.extension %>"> + <% if blob.representable? %> + <%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %> + <% end %> + +
+ <% if caption = blob.try(:caption) %> + <%= caption %> + <% else %> + <%= blob.filename %> + <%= number_to_human_size blob.byte_size %> + <% end %> +
+
From ed9f785058384d18935b9f3e26ed72b126994ef3 Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Fri, 16 Jul 2021 15:58:46 +0800 Subject: [PATCH 09/97] generate message model --- app/models/message.rb | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 app/models/message.rb diff --git a/app/models/message.rb b/app/models/message.rb new file mode 100644 index 000000000..28bdd9d60 --- /dev/null +++ b/app/models/message.rb @@ -0,0 +1,2 @@ +class Message < ApplicationRecord +end From 80f7167dc944b923fe1a3098a0c0203f18251576 Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Fri, 16 Jul 2021 15:59:04 +0800 Subject: [PATCH 10/97] generate chat_room model --- app/models/chat_room_user.rb | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 app/models/chat_room_user.rb diff --git a/app/models/chat_room_user.rb b/app/models/chat_room_user.rb new file mode 100644 index 000000000..d4c214eaf --- /dev/null +++ b/app/models/chat_room_user.rb @@ -0,0 +1,2 @@ +class ChatRoomUser < ApplicationRecord +end From 1afffa13290585872f61936d77a2913d1df96e07 Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Fri, 16 Jul 2021 15:59:20 +0800 Subject: [PATCH 11/97] generate chat_room model --- app/models/chat_room.rb | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 app/models/chat_room.rb diff --git a/app/models/chat_room.rb b/app/models/chat_room.rb new file mode 100644 index 000000000..2e8a6b852 --- /dev/null +++ b/app/models/chat_room.rb @@ -0,0 +1,2 @@ +class ChatRoom < ApplicationRecord +end From 69bf0a05283c79ee71f219db9e3534620a22cd09 Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Fri, 16 Jul 2021 15:59:43 +0800 Subject: [PATCH 12/97] add action text for rich text area --- app/assets/stylesheets/actiontext.scss | 36 ++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 app/assets/stylesheets/actiontext.scss diff --git a/app/assets/stylesheets/actiontext.scss b/app/assets/stylesheets/actiontext.scss new file mode 100644 index 000000000..7cb26e74a --- /dev/null +++ b/app/assets/stylesheets/actiontext.scss @@ -0,0 +1,36 @@ +// +// Provides a drop-in pointer for the default Trix stylesheet that will format the toolbar and +// the trix-editor content (whether displayed or under editing). Feel free to incorporate this +// inclusion directly in any other asset bundle and remove this file. +// +//= require trix/dist/trix + +// We need to override trix.css’s image gallery styles to accommodate the +// element we wrap around attachments. Otherwise, +// images in galleries will be squished by the max-width: 33%; rule. +.trix-content { + .attachment-gallery { + > action-text-attachment, + > .attachment { + flex: 1 0 33%; + padding: 0 0.5em; + max-width: 33%; + } + + &.attachment-gallery--2, + &.attachment-gallery--4 { + > action-text-attachment, + > .attachment { + flex-basis: 50%; + max-width: 50%; + } + } + } + + action-text-attachment { + .attachment { + padding: 0 !important; + max-width: 100% !important; + } + } +} From cb060204204256850da4432b65e06cd986ebeb95 Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Fri, 16 Jul 2021 15:59:55 +0800 Subject: [PATCH 13/97] generate tests --- Gemfile.lock | 5 ++++ app/javascript/packs/application.js | 4 ++- db/schema.rb | 37 +++++++++++++++++++++++- package.json | 2 ++ test/fixtures/action_text/rich_texts.yml | 4 +++ yarn.lock | 12 ++++++++ 6 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/action_text/rich_texts.yml diff --git a/Gemfile.lock b/Gemfile.lock index 7cca9bba3..433a9d32e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -128,9 +128,13 @@ GEM marcel (1.0.1) method_source (1.0.0) mini_mime (1.1.0) + mini_portile2 (2.5.3) minitest (5.14.4) msgpack (1.4.2) nio4r (2.5.7) + nokogiri (1.11.3) + mini_portile2 (~> 2.5.0) + racc (~> 1.4) nokogiri (1.11.3-x86_64-linux) racc (~> 1.4) orm_adapter (0.5.0) @@ -274,6 +278,7 @@ GEM zeitwerk (2.4.2) PLATFORMS + ruby x86_64-linux DEPENDENCIES diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 0f4ac5574..3c37cc78c 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -17,4 +17,6 @@ require("channels") // const imagePath = (name) => images(name, true) import "bootstrap" - \ No newline at end of file + +require("trix") +require("@rails/actiontext") \ No newline at end of file diff --git a/db/schema.rb b/db/schema.rb index 44ecfccc3..e384006ba 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,11 +10,21 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_07_12_140925) do +ActiveRecord::Schema.define(version: 2021_07_16_071248) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "action_text_rich_texts", force: :cascade do |t| + t.string "name", null: false + t.text "body" + t.string "record_type", null: false + t.bigint "record_id", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["record_type", "record_id", "name"], name: "index_action_text_rich_texts_uniqueness", unique: true + end + create_table "active_storage_attachments", force: :cascade do |t| t.string "name", null: false t.string "record_type", null: false @@ -36,6 +46,30 @@ t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true end + create_table "chat_room_users", force: :cascade do |t| + t.bigint "user_id" + t.bigint "chat_room_id" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["chat_room_id"], name: "index_chat_room_users_on_chat_room_id" + t.index ["user_id"], name: "index_chat_room_users_on_user_id" + end + + create_table "chat_rooms", force: :cascade do |t| + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + end + + create_table "messages", force: :cascade do |t| + t.bigint "chat_room_id" + t.bigint "user_id" + t.text "body" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["chat_room_id"], name: "index_messages_on_chat_room_id" + t.index ["user_id"], name: "index_messages_on_user_id" + end + create_table "reviews", force: :cascade do |t| t.bigint "agency_id" t.bigint "tourist_id" @@ -101,6 +135,7 @@ t.datetime "remember_created_at" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false + t.boolean "approved", default: false t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end diff --git a/package.json b/package.json index afb6cfcb2..dbe838d5f 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,13 @@ "dependencies": { "@popperjs/core": "^2.9.2", "@rails/actioncable": "^6.0.0", + "@rails/actiontext": "^6.0.3-6", "@rails/activestorage": "^6.0.0", "@rails/ujs": "^6.0.0", "@rails/webpacker": "4.3.0", "bootstrap": "^5.0.2", "jquery": "^3.6.0", + "trix": "^1.2.0", "turbolinks": "^5.2.0" }, "version": "0.1.0", diff --git a/test/fixtures/action_text/rich_texts.yml b/test/fixtures/action_text/rich_texts.yml new file mode 100644 index 000000000..8b371ea60 --- /dev/null +++ b/test/fixtures/action_text/rich_texts.yml @@ -0,0 +1,4 @@ +# one: +# record: name_of_fixture (ClassOfFixture) +# name: content +# body:

In a million stars!

diff --git a/yarn.lock b/yarn.lock index 960c86088..bfdad4242 100644 --- a/yarn.lock +++ b/yarn.lock @@ -846,6 +846,13 @@ resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-6.1.1.tgz#e980b2ea1c62cae17bcddad37deb6ba88d498b63" integrity sha512-A8toxvSuzK/wX9uMwyV6T1EYzlqzIJNrO8XiNCSNsmKresdX2cbAjxFdMu9haaPJPjl3aC2FKRJeVCUWVtLPAQ== +"@rails/actiontext@^6.0.3-6": + version "6.1.4" + resolved "https://registry.yarnpkg.com/@rails/actiontext/-/actiontext-6.1.4.tgz#ed8c7d2b68d66205301f4538ce65d04c48031f6b" + integrity sha512-KG+VCofmfZAhW1x1xFxMC2sZtvwbx+XX0Y+dzeBFjKU1jZMM0RNytJf5EiuHtD9fL71zz6/nMT7FjwOFenSA7Q== + dependencies: + "@rails/activestorage" "^6.0.0" + "@rails/activestorage@^6.0.0": version "6.1.1" resolved "https://registry.yarnpkg.com/@rails/activestorage/-/activestorage-6.1.1.tgz#c7e9c849ab9487c9914bd0bb447467cb4520bc56" @@ -7112,6 +7119,11 @@ trim-newlines@^1.0.0: resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= +trix@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/trix/-/trix-1.3.1.tgz#ccce8d9e72bf0fe70c8c019ff558c70266f8d857" + integrity sha512-BbH6mb6gk+AV4f2as38mP6Ucc1LE3OD6XxkZnAgPIduWXYtvg2mI3cZhIZSLqmMh9OITEpOBCCk88IVmyjU7bA== + "true-case-path@^1.0.2": version "1.0.3" resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.3.tgz#f813b5a8c86b40da59606722b144e3225799f47d" From 101de21cee34a7d0e3bfa8162023254a24dc2c2d Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Fri, 16 Jul 2021 16:02:48 +0800 Subject: [PATCH 14/97] add gems: image_processing and mini_magick --- Gemfile | 2 ++ Gemfile.lock | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/Gemfile b/Gemfile index 06e4a3af2..2b9b4834a 100644 --- a/Gemfile +++ b/Gemfile @@ -13,9 +13,11 @@ gem 'jbuilder', '~> 2.7' gem 'bootsnap', '>= 1.4.2', require: false gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] +gem 'image_processing' gem 'devise' gem 'hamlit-rails' gem "aws-sdk-s3", require: false +gem "mini_magick" group :development, :test do gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] diff --git a/Gemfile.lock b/Gemfile.lock index 433a9d32e..6fc02a586 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -114,6 +114,9 @@ GEM hashdiff (1.0.1) i18n (1.8.10) concurrent-ruby (~> 1.0) + image_processing (1.12.1) + mini_magick (>= 4.9.5, < 5) + ruby-vips (>= 2.0.17, < 3) jbuilder (2.11.2) activesupport (>= 5.0.0) jmespath (1.4.0) @@ -127,6 +130,7 @@ GEM mini_mime (>= 0.1.1) marcel (1.0.1) method_source (1.0.0) + mini_magick (4.11.0) mini_mime (1.1.0) mini_portile2 (2.5.3) minitest (5.14.4) @@ -223,6 +227,8 @@ GEM rubocop (~> 1.0) rubocop-ast (>= 1.1.0) ruby-progressbar (1.11.0) + ruby-vips (2.1.2) + ffi (~> 1.12) sass-rails (6.0.0) sassc-rails (~> 2.1, >= 2.1.1) sassc (2.4.0) @@ -289,8 +295,10 @@ DEPENDENCIES devise factory_bot_rails hamlit-rails + image_processing jbuilder (~> 2.7) listen (~> 3.2) + mini_magick pg puma (~> 4.1) rails (~> 6.0.3, >= 6.0.3.4) From 853207e49056bd978deccd20a2d3ac1df87f50cb Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Fri, 16 Jul 2021 16:46:20 +0800 Subject: [PATCH 15/97] add associations to all models and added admin model inherited from devise user --- app/models/admin.rb | 7 +++++++ app/models/chat_room.rb | 7 +++++++ app/models/chat_room_user.rb | 2 ++ app/models/message.rb | 2 ++ app/models/tour.rb | 2 ++ app/models/tourist.rb | 6 ++++++ app/models/user.rb | 22 ++++++++++++++++++++++ 7 files changed, 48 insertions(+) create mode 100644 app/models/admin.rb diff --git a/app/models/admin.rb b/app/models/admin.rb new file mode 100644 index 000000000..1f886ac72 --- /dev/null +++ b/app/models/admin.rb @@ -0,0 +1,7 @@ +class Admin < User + after_create :set_approved_to_true + + def set_approved_to_true + self.update(approved: true) + end +end \ No newline at end of file diff --git a/app/models/chat_room.rb b/app/models/chat_room.rb index 2e8a6b852..37e659d1d 100644 --- a/app/models/chat_room.rb +++ b/app/models/chat_room.rb @@ -1,2 +1,9 @@ class ChatRoom < ApplicationRecord + has_many :messages + has_many :chat_room_users + has_many :users, through: :chat_room_users + + def chat_mate(current_user) + users.reject{|user| user == current_user}.first + end end diff --git a/app/models/chat_room_user.rb b/app/models/chat_room_user.rb index d4c214eaf..fb5115e74 100644 --- a/app/models/chat_room_user.rb +++ b/app/models/chat_room_user.rb @@ -1,2 +1,4 @@ class ChatRoomUser < ApplicationRecord + belongs_to :user + belongs_to :chat_room end diff --git a/app/models/message.rb b/app/models/message.rb index 28bdd9d60..d5b63e5ca 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -1,2 +1,4 @@ class Message < ApplicationRecord + belongs_to :chat_room + belongs_to :user end diff --git a/app/models/tour.rb b/app/models/tour.rb index 45bd91e17..006cf17d1 100644 --- a/app/models/tour.rb +++ b/app/models/tour.rb @@ -4,4 +4,6 @@ class Tour < ApplicationRecord has_many :tourists, through: :tourist_tours has_many :travel_transactions, through: :tourist_tours + has_rich_text :details + has_many_attached :images end diff --git a/app/models/tourist.rb b/app/models/tourist.rb index c5b33d015..dad9cb549 100644 --- a/app/models/tourist.rb +++ b/app/models/tourist.rb @@ -5,4 +5,10 @@ class Tourist < User has_many :travel_transactions, through: :tourist_tours has_many :reviews, dependent: :destroy + + after_create :set_approved_to_true + + def set_approved_to_true + self.update(approved: true) + end end diff --git a/app/models/user.rb b/app/models/user.rb index 474eb5705..1ef3f2cb0 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -5,4 +5,26 @@ class User < ApplicationRecord :recoverable, :rememberable, :validatable has_one_attached :profile_pic + has_one_attached :cover_pic + has_many :messages + + def chat_room(user) + chat_rooms.select { |chat_room| chat_room.users.include?(user)}.first + end + def tourist? + type == "Tourist" + end + def agency? + type == "Agency" + end + def admin? + type == "Admin" + end + def username + if self.agency? + agency_name + else + "#{first_name} #{last_name}" + end + end end From b3e5caca2d1ad33f3714dc991c464f42bc9362a5 Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Fri, 16 Jul 2021 16:57:41 +0800 Subject: [PATCH 16/97] add dependent value to all model that has has_many --- app/models/admin.rb | 10 +++++----- app/models/agency.rb | 6 +++--- app/models/chat_room.rb | 12 ++++++------ app/models/chat_room_user.rb | 4 ++-- app/models/message.rb | 4 ++-- app/models/tour.rb | 2 +- app/models/tourist.rb | 10 +++++----- app/models/tourist_tour.rb | 2 +- app/models/user.rb | 16 ++++++++++------ 9 files changed, 35 insertions(+), 31 deletions(-) diff --git a/app/models/admin.rb b/app/models/admin.rb index 1f886ac72..9bba62b29 100644 --- a/app/models/admin.rb +++ b/app/models/admin.rb @@ -1,7 +1,7 @@ class Admin < User - after_create :set_approved_to_true + after_create :set_approved_to_true - def set_approved_to_true - self.update(approved: true) - end -end \ No newline at end of file + def set_approved_to_true + update(approved: true) + end +end diff --git a/app/models/agency.rb b/app/models/agency.rb index 74bd5c2ea..ed135c19f 100644 --- a/app/models/agency.rb +++ b/app/models/agency.rb @@ -1,6 +1,6 @@ class Agency < User - has_many :tours, dependent: :destroy - has_many :travel_transactions, dependent: :destroy + has_many :tours, dependent: :nullify + has_many :travel_transactions, dependent: :nullify - has_many :reviews, dependent: :destroy + has_many :reviews, dependent: :nullify end diff --git a/app/models/chat_room.rb b/app/models/chat_room.rb index 37e659d1d..e0be869c5 100644 --- a/app/models/chat_room.rb +++ b/app/models/chat_room.rb @@ -1,9 +1,9 @@ class ChatRoom < ApplicationRecord - has_many :messages - has_many :chat_room_users - has_many :users, through: :chat_room_users + has_many :messages, dependent: :nullify + has_many :chat_room_users, dependent: :nullify + has_many :users, through: :chat_room_users - def chat_mate(current_user) - users.reject{|user| user == current_user}.first - end + def chat_mate(current_user) + users.reject { |user| user == current_user }.first + end end diff --git a/app/models/chat_room_user.rb b/app/models/chat_room_user.rb index fb5115e74..a50db186f 100644 --- a/app/models/chat_room_user.rb +++ b/app/models/chat_room_user.rb @@ -1,4 +1,4 @@ class ChatRoomUser < ApplicationRecord - belongs_to :user - belongs_to :chat_room + belongs_to :user + belongs_to :chat_room end diff --git a/app/models/message.rb b/app/models/message.rb index d5b63e5ca..eda9ddec7 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -1,4 +1,4 @@ class Message < ApplicationRecord - belongs_to :chat_room - belongs_to :user + belongs_to :chat_room + belongs_to :user end diff --git a/app/models/tour.rb b/app/models/tour.rb index 006cf17d1..6ed8af13f 100644 --- a/app/models/tour.rb +++ b/app/models/tour.rb @@ -1,6 +1,6 @@ class Tour < ApplicationRecord belongs_to :agency - has_many :tourist_tours, dependent: :destroy + has_many :tourist_tours, dependent: :nullify has_many :tourists, through: :tourist_tours has_many :travel_transactions, through: :tourist_tours diff --git a/app/models/tourist.rb b/app/models/tourist.rb index dad9cb549..1dbee3353 100644 --- a/app/models/tourist.rb +++ b/app/models/tourist.rb @@ -1,14 +1,14 @@ class Tourist < User - has_many :tourist_tours, dependent: :destroy + has_many :tourist_tours, dependent: :nullify has_many :tours, through: :tourist_tours has_many :travel_transactions, through: :tourist_tours - has_many :reviews, dependent: :destroy + has_many :reviews, dependent: :nullify after_create :set_approved_to_true - def set_approved_to_true - self.update(approved: true) - end + def set_approved_to_true + update(approved: true) + end end diff --git a/app/models/tourist_tour.rb b/app/models/tourist_tour.rb index d6d0c82ed..88c1a0d57 100644 --- a/app/models/tourist_tour.rb +++ b/app/models/tourist_tour.rb @@ -2,5 +2,5 @@ class TouristTour < ApplicationRecord belongs_to :tour belongs_to :tourist - has_one :travel_transaction, dependent: :destroy + has_one :travel_transaction, dependent: :nullify end diff --git a/app/models/user.rb b/app/models/user.rb index 1ef3f2cb0..fbdd31de6 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -6,22 +6,26 @@ class User < ApplicationRecord has_one_attached :profile_pic has_one_attached :cover_pic - has_many :messages + has_many :messages, dependent: :nullify def chat_room(user) - chat_rooms.select { |chat_room| chat_room.users.include?(user)}.first + chat_rooms.select { |chat_room| chat_room.users.include?(user) }.first end + def tourist? - type == "Tourist" + type == 'Tourist' end + def agency? - type == "Agency" + type == 'Agency' end + def admin? - type == "Admin" + type == 'Admin' end + def username - if self.agency? + if agency? agency_name else "#{first_name} #{last_name}" From 6e120dd905af6b0f73e05c1c2e7f64c157e7140c Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Fri, 16 Jul 2021 22:54:28 +0800 Subject: [PATCH 17/97] add integration helpers for rspec sign in --- spec/rails_helper.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 69a565b8e..3108cead0 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -22,6 +22,7 @@ config.infer_spec_type_from_file_location! config.filter_rails_from_backtrace! + config.include Devise::Test::IntegrationHelpers config.include FactoryBot::Syntax::Methods config.before(:suite) do From cd16cc1999c1a27bc4acaf3344b59126c59ea8c4 Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Fri, 16 Jul 2021 22:55:16 +0800 Subject: [PATCH 18/97] add resources and devise routes --- config/routes.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index 1bab170f5..5c8bb3ea4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,10 @@ Rails.application.routes.draw do - devise_for :users + devise_for :tourists + devise_for :agencies + devise_for :admins, skip: :registration # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html root "home#index" + + resources :admins + resources :agencies end From 39033337d6702e16d47800e80d6384f9184bf7b2 Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Fri, 16 Jul 2021 22:56:25 +0800 Subject: [PATCH 19/97] generate controller and rspec test for admins --- app/controllers/admins_controller.rb | 40 ++++++++++++++++++++++++ spec/requests/admins_controllers_spec.rb | 38 ++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 app/controllers/admins_controller.rb create mode 100644 spec/requests/admins_controllers_spec.rb diff --git a/app/controllers/admins_controller.rb b/app/controllers/admins_controller.rb new file mode 100644 index 000000000..c81619d1b --- /dev/null +++ b/app/controllers/admins_controller.rb @@ -0,0 +1,40 @@ +class AdminsController < ApplicationController + before_action :authenticate_admin! + + def index + @users = User.where(type: %w[Tourist Agency], approved: true).all + end + + def show + @admin = Admin.find(params[:id]) + end + + def new + @admin = Admin.new + end + + def create + @admin = Admin.new(admin_params) + if @admin.valid? && @admin.save + redirect_to admins_path, notice: 'Successfully Created Admin!' + else + redirect_back fall_back_location: new_admins_path, alert: @admin.errors_full_messages.first + end + end + + def update + @admin = Admin.find(params[:id]) + @admin.update(admin_params) + if @admin.valid? && @admin.save + redirect_to admin_path(@admin), notice: 'Successfully Created Admin!' + else + redirect_back fall_back_location: edit_admin_path, alert: @admin.errors_full_messages.first + end + end + + private + + def admin_params + params.require(:admin).permit(:email, :first_name, :last_name, :birth_date, :password, :password_confirmation, :cover_pic, :profile_pic) + end +end diff --git a/spec/requests/admins_controllers_spec.rb b/spec/requests/admins_controllers_spec.rb new file mode 100644 index 000000000..8250c0603 --- /dev/null +++ b/spec/requests/admins_controllers_spec.rb @@ -0,0 +1,38 @@ +require 'rails_helper' + +RSpec.describe 'AdminsControllers', type: :request do + let!(:admin) { Admin.create(email: 'admin@email.com', first_name: 'Admin', last_name: 'Last', password: '1234567') } + + before do + sign_in(admin) + end + + context 'when GET admins' do + it 'shows the index page' do + get admins_path + expect(response).to have_http_status(:ok) + end + + it 'shows the selected admin' do + get admin_path(admin) + expect(response).to have_http_status(:ok) + end + + it 'shows the new admin template' do + get new_admin_path + expect(response).to have_http_status(:ok) + end + end + + context 'when POST and PATCH admins' do + it 'creates a new admin' do + post admins_path, params: { admin: { email: 'admin2@email.com', first_name: 'Admin', last_name: 'Last', birth_date: Date.new(1999, 10, 10), address: 'Kyoto, Japan', password: '1234567' } } + expect(response).to redirect_to(admins_path) + end + + it 'updates a new admin' do + patch admin_path(admin), params: { admin: { email: 'admin4@email.com', first_name: 'Admin2', last_name: 'Last', birth_date: Date.new(1999, 10, 10), address: 'Kyoto, Japan', password: '1234567' } } + expect(response).to redirect_to(admin_path(admin)) + end + end +end From 853531d5500c65537921a53b09121c63183967bb Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Fri, 16 Jul 2021 23:00:31 +0800 Subject: [PATCH 20/97] add devise views --- app/views/devise/confirmations/new.html.erb | 16 +++++++ .../mailer/confirmation_instructions.html.erb | 5 +++ .../devise/mailer/email_changed.html.erb | 7 +++ .../devise/mailer/password_change.html.erb | 3 ++ .../reset_password_instructions.html.erb | 8 ++++ .../mailer/unlock_instructions.html.erb | 7 +++ app/views/devise/passwords/edit.html.erb | 25 +++++++++++ app/views/devise/passwords/new.html.erb | 16 +++++++ app/views/devise/registrations/edit.html.erb | 43 +++++++++++++++++++ app/views/devise/registrations/new.html.erb | 29 +++++++++++++ app/views/devise/sessions/new.html.erb | 28 ++++++++++++ .../devise/shared/_error_messages.html.erb | 15 +++++++ app/views/devise/shared/_links.html.erb | 25 +++++++++++ app/views/devise/unlocks/new.html.erb | 16 +++++++ 14 files changed, 243 insertions(+) create mode 100644 app/views/devise/confirmations/new.html.erb create mode 100644 app/views/devise/mailer/confirmation_instructions.html.erb create mode 100644 app/views/devise/mailer/email_changed.html.erb create mode 100644 app/views/devise/mailer/password_change.html.erb create mode 100644 app/views/devise/mailer/reset_password_instructions.html.erb create mode 100644 app/views/devise/mailer/unlock_instructions.html.erb create mode 100644 app/views/devise/passwords/edit.html.erb create mode 100644 app/views/devise/passwords/new.html.erb create mode 100644 app/views/devise/registrations/edit.html.erb create mode 100644 app/views/devise/registrations/new.html.erb create mode 100644 app/views/devise/sessions/new.html.erb create mode 100644 app/views/devise/shared/_error_messages.html.erb create mode 100644 app/views/devise/shared/_links.html.erb create mode 100644 app/views/devise/unlocks/new.html.erb diff --git a/app/views/devise/confirmations/new.html.erb b/app/views/devise/confirmations/new.html.erb new file mode 100644 index 000000000..b12dd0cbe --- /dev/null +++ b/app/views/devise/confirmations/new.html.erb @@ -0,0 +1,16 @@ +

Resend confirmation instructions

+ +<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %> +
+ +
+ <%= f.submit "Resend confirmation instructions" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb new file mode 100644 index 000000000..dc55f64f6 --- /dev/null +++ b/app/views/devise/mailer/confirmation_instructions.html.erb @@ -0,0 +1,5 @@ +

Welcome <%= @email %>!

+ +

You can confirm your account email through the link below:

+ +

<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>

diff --git a/app/views/devise/mailer/email_changed.html.erb b/app/views/devise/mailer/email_changed.html.erb new file mode 100644 index 000000000..32f4ba803 --- /dev/null +++ b/app/views/devise/mailer/email_changed.html.erb @@ -0,0 +1,7 @@ +

Hello <%= @email %>!

+ +<% if @resource.try(:unconfirmed_email?) %> +

We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.

+<% else %> +

We're contacting you to notify you that your email has been changed to <%= @resource.email %>.

+<% end %> diff --git a/app/views/devise/mailer/password_change.html.erb b/app/views/devise/mailer/password_change.html.erb new file mode 100644 index 000000000..b41daf476 --- /dev/null +++ b/app/views/devise/mailer/password_change.html.erb @@ -0,0 +1,3 @@ +

Hello <%= @resource.email %>!

+ +

We're contacting you to notify you that your password has been changed.

diff --git a/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb new file mode 100644 index 000000000..f667dc12f --- /dev/null +++ b/app/views/devise/mailer/reset_password_instructions.html.erb @@ -0,0 +1,8 @@ +

Hello <%= @resource.email %>!

+ +

Someone has requested a link to change your password. You can do this through the link below.

+ +

<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

+ +

If you didn't request this, please ignore this email.

+

Your password won't change until you access the link above and create a new one.

diff --git a/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb new file mode 100644 index 000000000..41e148bf2 --- /dev/null +++ b/app/views/devise/mailer/unlock_instructions.html.erb @@ -0,0 +1,7 @@ +

Hello <%= @resource.email %>!

+ +

Your account has been locked due to an excessive number of unsuccessful sign in attempts.

+ +

Click the link below to unlock your account:

+ +

<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>

diff --git a/app/views/devise/passwords/edit.html.erb b/app/views/devise/passwords/edit.html.erb new file mode 100644 index 000000000..5fbb9ff0a --- /dev/null +++ b/app/views/devise/passwords/edit.html.erb @@ -0,0 +1,25 @@ +

Change your password

+ +<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + <%= f.hidden_field :reset_password_token %> + +
+ <%= f.label :password, "New password" %>
+ <% if @minimum_password_length %> + (<%= @minimum_password_length %> characters minimum)
+ <% end %> + <%= f.password_field :password, autofocus: true, autocomplete: "new-password" %> +
+ +
+ <%= f.label :password_confirmation, "Confirm new password" %>
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %> +
+ +
+ <%= f.submit "Change my password" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/passwords/new.html.erb b/app/views/devise/passwords/new.html.erb new file mode 100644 index 000000000..9b486b81b --- /dev/null +++ b/app/views/devise/passwords/new.html.erb @@ -0,0 +1,16 @@ +

Forgot your password?

+ +<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ +
+ <%= f.submit "Send me reset password instructions" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb new file mode 100644 index 000000000..38d95b85a --- /dev/null +++ b/app/views/devise/registrations/edit.html.erb @@ -0,0 +1,43 @@ +

Edit <%= resource_name.to_s.humanize %>

+ +<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ + <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> +
Currently waiting confirmation for: <%= resource.unconfirmed_email %>
+ <% end %> + +
+ <%= f.label :password %> (leave blank if you don't want to change it)
+ <%= f.password_field :password, autocomplete: "new-password" %> + <% if @minimum_password_length %> +
+ <%= @minimum_password_length %> characters minimum + <% end %> +
+ +
+ <%= f.label :password_confirmation %>
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %> +
+ +
+ <%= f.label :current_password %> (we need your current password to confirm your changes)
+ <%= f.password_field :current_password, autocomplete: "current-password" %> +
+ +
+ <%= f.submit "Update" %> +
+<% end %> + +

Cancel my account

+ +

Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %>

+ +<%= link_to "Back", :back %> diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb new file mode 100644 index 000000000..d655b66f6 --- /dev/null +++ b/app/views/devise/registrations/new.html.erb @@ -0,0 +1,29 @@ +

Sign up

+ +<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ +
+ <%= f.label :password %> + <% if @minimum_password_length %> + (<%= @minimum_password_length %> characters minimum) + <% end %>
+ <%= f.password_field :password, autocomplete: "new-password" %> +
+ +
+ <%= f.label :password_confirmation %>
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %> +
+ +
+ <%= f.submit "Sign up" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb new file mode 100644 index 000000000..b91601b4a --- /dev/null +++ b/app/views/devise/sessions/new.html.erb @@ -0,0 +1,28 @@ +

Log in

+ +<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ +
+ <%= f.label :password %>
+ <%= f.password_field :password, autocomplete: "current-password" %> +
+ + <% if devise_mapping.rememberable? %> +
+ <%= f.check_box :remember_me %> + <%= f.label :remember_me %> +
+ <% end %> + +
+ <%= f.submit "Log in" %> +
+<% end %> +<% if current_page?(new_tourist_session_path || new_agency_session_path) %> + <%= render "devise/shared/links" %> +<% end %> + diff --git a/app/views/devise/shared/_error_messages.html.erb b/app/views/devise/shared/_error_messages.html.erb new file mode 100644 index 000000000..ba7ab8870 --- /dev/null +++ b/app/views/devise/shared/_error_messages.html.erb @@ -0,0 +1,15 @@ +<% if resource.errors.any? %> +
+

+ <%= I18n.t("errors.messages.not_saved", + count: resource.errors.count, + resource: resource.class.model_name.human.downcase) + %> +

+
    + <% resource.errors.full_messages.each do |message| %> +
  • <%= message %>
  • + <% end %> +
+
+<% end %> diff --git a/app/views/devise/shared/_links.html.erb b/app/views/devise/shared/_links.html.erb new file mode 100644 index 000000000..084af701c --- /dev/null +++ b/app/views/devise/shared/_links.html.erb @@ -0,0 +1,25 @@ +<%- if controller_name != 'sessions' %> + <%= link_to "Log in", new_session_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.registerable? && controller_name != 'registrations' %> + <%= link_to "Sign up", new_registration_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> + <%= link_to "Forgot your password?", new_password_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> + <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> + <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.omniauthable? %> + <%- resource_class.omniauth_providers.each do |provider| %> + <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %>
+ <% end %> +<% end %> diff --git a/app/views/devise/unlocks/new.html.erb b/app/views/devise/unlocks/new.html.erb new file mode 100644 index 000000000..ffc34de8d --- /dev/null +++ b/app/views/devise/unlocks/new.html.erb @@ -0,0 +1,16 @@ +

Resend unlock instructions

+ +<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ +
+ <%= f.submit "Resend unlock instructions" %> +
+<% end %> + +<%= render "devise/shared/links" %> From ef5e70f9e1176c25c4a694d38c680a849c592abe Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Fri, 16 Jul 2021 23:01:08 +0800 Subject: [PATCH 21/97] generate admin views --- app/views/admins/edit.html.erb | 0 app/views/admins/index.html.erb | 25 ++++++++++ app/views/admins/new.html.erb | 46 ++++++++++++++++++ app/views/admins/show.html.erb | 85 +++++++++++++++++++++++++++++++++ 4 files changed, 156 insertions(+) create mode 100644 app/views/admins/edit.html.erb create mode 100644 app/views/admins/index.html.erb create mode 100644 app/views/admins/new.html.erb create mode 100644 app/views/admins/show.html.erb diff --git a/app/views/admins/edit.html.erb b/app/views/admins/edit.html.erb new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/admins/index.html.erb b/app/views/admins/index.html.erb new file mode 100644 index 000000000..31490931d --- /dev/null +++ b/app/views/admins/index.html.erb @@ -0,0 +1,25 @@ +

+ + + + + + + + + + + <% @users.each do |user|%> + + <% if user.type == 'Tourist' %> + + <%elsif user.type == 'Agency'%> + + <% end %> + + + + + <%end%> + +
UserEmailTypeAddress
<%= link_to "#{ user.first_name} #{ user.last_name}", tourist_path(user)%><%= link_to "#{ user.agency_name}", agency_path(user)%><%= user.type%><%= user.email%><%= user.address%>
\ No newline at end of file diff --git a/app/views/admins/new.html.erb b/app/views/admins/new.html.erb new file mode 100644 index 000000000..46d845daf --- /dev/null +++ b/app/views/admins/new.html.erb @@ -0,0 +1,46 @@ + +<%= render 'shared/errors', obj: @admin %> +
+
+
+

Create Admin

+
+ <%= form_with scope: :admin, model: @admin, local: true do |f| %> +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email",class:"form-control" %> +
+
+ <%= f.label :first_name%>
+ <%= f.text_field :first_name, class:"form-control"%> +
+
+ <%= f.label :last_name%>
+ <%= f.text_field :last_name, class:"form-control"%> +
+
+ <%= f.label :birth_date%>
+ <%= f.date_field :birth_date, class:"form-control"%> +
+ +
+ <%= f.label :password %>
+ <% if @minimum_password_length %> + (<%= @minimum_password_length %> characters minimum) + <% end %>
+ <%= f.password_field :password, autocomplete: "new-password",placeholder:"Password",class:"form-control" %> +
+
+ <%= f.label :password_confirmation %>
+ <%= f.password_field :password_confirmation, autocomplete: "new-password",placeholder:"Confirm Password",class:"form-control" %> +
+
+
+ <%= f.submit "Sign up",class:"btn btn-dark w-100 font-weight-bold mt-2" %> +
+
+ <% end %> +
+
+
+
\ No newline at end of file diff --git a/app/views/admins/show.html.erb b/app/views/admins/show.html.erb new file mode 100644 index 000000000..64bc9e8d5 --- /dev/null +++ b/app/views/admins/show.html.erb @@ -0,0 +1,85 @@ +
+
+
+
+ <%if @admin.cover_pic.attached?%> + + <%else%> + + <%end%> +

+
+
+
+ <%if @admin.profile_pic.attached?%> + + <%else%> + + <%end%> + +
+

<%= @admin.first_name%> <%=@admin.last_name%>

+

Tourist

+ <% if @admin == current_admin%> + <%= button_to 'Edit Profile', edit_admin_path,class:"btn btn-info ", method: :get%> + <% end %> +
+
+
+
+
+
+

+
+
+
+
First Name
+
+
+ <%= @admin.first_name%> +
+
+
+
+
+
Last Name
+
+
+ <%=@admin.last_name%> +
+
+
+
+
+
Email
+
+
+ <%= @admin.email%> +
+
+
+
+
+
Birth Date
+
+
+ <%= @admin&.birth_date&.strftime('%b %d, %Y')%> +
+
+
+
+
+
Address
+
+
+ <%= @admin.address%> +
+
+
+ +
+
+
+
+
+
\ No newline at end of file From e49bf36bdf25fa9fd6fea7fa892c8a0fc89c72d6 Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Fri, 16 Jul 2021 23:01:41 +0800 Subject: [PATCH 22/97] add nav bar and error partials --- app/views/shared/_errors.html.erb | 15 ++++++ app/views/shared/_nav_bar.html.erb | 82 ++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 app/views/shared/_errors.html.erb create mode 100644 app/views/shared/_nav_bar.html.erb diff --git a/app/views/shared/_errors.html.erb b/app/views/shared/_errors.html.erb new file mode 100644 index 000000000..5c84e5f83 --- /dev/null +++ b/app/views/shared/_errors.html.erb @@ -0,0 +1,15 @@ +<% if obj.errors.any? %> +
+ +
+ <%end%> \ No newline at end of file diff --git a/app/views/shared/_nav_bar.html.erb b/app/views/shared/_nav_bar.html.erb new file mode 100644 index 000000000..cab8fe438 --- /dev/null +++ b/app/views/shared/_nav_bar.html.erb @@ -0,0 +1,82 @@ + + + + + + + \ No newline at end of file From e359fe235a58835549bbb7603c2cc8a92148db47 Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Fri, 16 Jul 2021 23:03:39 +0800 Subject: [PATCH 23/97] add scss and helper admin --- app/assets/stylesheets/admins.scss | 3 +++ app/helpers/admins_helper.rb | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 app/assets/stylesheets/admins.scss create mode 100644 app/helpers/admins_helper.rb diff --git a/app/assets/stylesheets/admins.scss b/app/assets/stylesheets/admins.scss new file mode 100644 index 000000000..7d9911c54 --- /dev/null +++ b/app/assets/stylesheets/admins.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the admins controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: https://sass-lang.com/ diff --git a/app/helpers/admins_helper.rb b/app/helpers/admins_helper.rb new file mode 100644 index 000000000..d4f7b3486 --- /dev/null +++ b/app/helpers/admins_helper.rb @@ -0,0 +1,2 @@ +module AdminsHelper +end From 7a08f52cc7af256f32126785cfbdd8efcdf450a6 Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Fri, 16 Jul 2021 23:17:51 +0800 Subject: [PATCH 24/97] add render errors on application html --- app/controllers/application_controller.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 09705d12a..49179d6ec 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,2 +1,11 @@ class ApplicationController < ActionController::Base + before_action :configure_permitted_parameters, if: :devise_controller? + devise_group :user, contains: %i[tourist agency admin] + + protected + + def configure_permitted_parameters + devise_parameter_sanitizer.permit(:sign_up, keys: %i[type username first_name last_name address birth_date agency_name approved]) + devise_parameter_sanitizer.permit(:account_update, keys: %i[first_name last_name profile_pic cover_pic address birth_date agency_name approved]) + end end From 3b835e03b9c5ddf9bccdf94b47977baf388531d7 Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Fri, 16 Jul 2021 23:18:28 +0800 Subject: [PATCH 25/97] add home page --- app/views/home/index.html.erb | 37 ----------------------------------- 1 file changed, 37 deletions(-) diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb index d55acf142..e69de29bb 100644 --- a/app/views/home/index.html.erb +++ b/app/views/home/index.html.erb @@ -1,37 +0,0 @@ -

hello

- \ No newline at end of file From 5baa7b9d091c76695a5469caa67bc9039a1abb5b Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Fri, 16 Jul 2021 23:19:11 +0800 Subject: [PATCH 26/97] add errors on application page --- app/views/layouts/application.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index d6cef184a..09f2c16bc 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -17,7 +17,7 @@ <% if alert %>

<%= alert %>

<% end %> - + <%= render 'shared/nav_bar'%> <%= yield %> From ec2cac3fd6ab9bf4a99d91ddbc8f1987362a75ea Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Fri, 16 Jul 2021 23:54:49 +0800 Subject: [PATCH 27/97] add credentials --- config/credentials.yml.enc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc index 13bec8b12..4a6168be1 100644 --- a/config/credentials.yml.enc +++ b/config/credentials.yml.enc @@ -1 +1 @@ -vGGT60HbIwlvjrOSj8QkGzQ+EqBcuWQJziz4siEGjSykZwqibwIc5BZChF2C3wBvxcuXygOObUvRGWcp+B58qkXoCUjwueUUhnkq4Utn0QcjV/5rmsVdVuLC2V7ZmxT/IKgviPzOa1Iz7PN7tBrqJTZoi6KrqYYYWRnXSvC0lLDkdqoKG35CEKRcdLtt385m4rK3JWWQXOVJXLSlQhCANW0XvXvZEJtm8E0LCwUx98SXoV8WRO//B4i7CUv56H61daxxSb9A940DcRkDR6dJLDOAyCJbuOGgsyoX3R4ybiK4xNzS9XXLmiUyXfdRxeAJAeB0Bthc4+8tymmiEd5Nlo8U5mwAN8U149b/5wBbw4vKMPkdNxCLMygLjC84rbMtJTrxCsdRHjQ+YbCrxirhEFQNHM2XyUoJ06WD--RYah7W4NWRhsH2Sf--TLLM8vHkIpbAHY802vtuIA== \ No newline at end of file +vGGT60HbIwlvjrOSj8QkGzQ+EqBcuWQJziz4siEGjSykZwqibwIc5BZChF2C3wBvxcuXygOObUvRGWcp+B58qkXoCUjwueUUhnkq4Utn0QcjV/5rmsVdVuLC2V7ZmxT/IKgviPzOa1Iz7PN7tBrqJTZoi6KrqYYYWRnXSvC0lLDkdqoKG35CEKRcdLtt385m4rK3JWWQXOVJXLSlQhCANW0XvXvZEJtm8E0LCwUx98SXoV8WRO//B4i7CUv56H61daxxSb9A940DcRkDR6dJLDOAyCJbuOGgsyoX3R4ybiK4xNzS9XXLmiUyXfdRxeAJAeB0Bthc4+8tymmiEd5Nlo8U5mwAN8U149b/5wBbw4vKMPkdNxCLMygLjC84rbMtJTrxCsdRHjQ+YbCrxirhEFQNHM2XyUoJ06WD--RYah7W4NWRhsH2Sf--TLLM8vHkIpbAHY802vtuIA== \ No newline at end of file From 392c1a1e8560dc1d2b3fda3e3116c2f41190797c Mon Sep 17 00:00:00 2001 From: dlanoronald123 Date: Sat, 17 Jul 2021 00:10:43 +0800 Subject: [PATCH 28/97] Add master key --- config/credentials.yml.enc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc index 13bec8b12..1b515e511 100644 --- a/config/credentials.yml.enc +++ b/config/credentials.yml.enc @@ -1 +1 @@ -vGGT60HbIwlvjrOSj8QkGzQ+EqBcuWQJziz4siEGjSykZwqibwIc5BZChF2C3wBvxcuXygOObUvRGWcp+B58qkXoCUjwueUUhnkq4Utn0QcjV/5rmsVdVuLC2V7ZmxT/IKgviPzOa1Iz7PN7tBrqJTZoi6KrqYYYWRnXSvC0lLDkdqoKG35CEKRcdLtt385m4rK3JWWQXOVJXLSlQhCANW0XvXvZEJtm8E0LCwUx98SXoV8WRO//B4i7CUv56H61daxxSb9A940DcRkDR6dJLDOAyCJbuOGgsyoX3R4ybiK4xNzS9XXLmiUyXfdRxeAJAeB0Bthc4+8tymmiEd5Nlo8U5mwAN8U149b/5wBbw4vKMPkdNxCLMygLjC84rbMtJTrxCsdRHjQ+YbCrxirhEFQNHM2XyUoJ06WD--RYah7W4NWRhsH2Sf--TLLM8vHkIpbAHY802vtuIA== \ No newline at end of file +AZ3fFcqNhWle6CsRYYEqJ0FHLysDnNoKALoTnGMlkfUa7r2I4tA1Mdg/ALqucRVPjGNFNXQr53DE8tgBcJYs3mMnU8mFfeJ/cRVahPdOp5UnC9uJCHNBkYJ9Wg/f0w5HLxBFfAwynh91jRXT2zkfEj/hWZWR42XxLltEXTXTbRrCf0B5Fhuz4tOqqn7gLLLEL3s6C6cZwS/55tQaK+W+qq8yeoLzBaMCsUVcFXs7G8azKAx79yWsleQhFarojdYVIFX/dFbKKGGKLVyGzuceoYD0nrMhwD8q9gWPS1bIPK9Ca/1SxozL3Y1v8vivFiXa7bbn8Mh6bM9sM8uWJiz8CLOvCu8sBomZqKr2rLxkD8n4EqSfpG6PHTiCN2y8OSx+Vh0W2Z0nY6mbdDv8T3bSrCbclZkcVxGPukm6xItBM3l52W12naEI60afhgXPHDvbrIUCuAkoOkqzOWjpWgB/mZzC6L77--nzV6P8zKT1o+V7Xt--6gKQGBneywoG6HaWeYy/Qg== \ No newline at end of file From 84eade329e9b2a9f86cec130b36909d60df0cc80 Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Sat, 17 Jul 2021 00:19:25 +0800 Subject: [PATCH 29/97] fixed credentials error --- config/credentials.yml.enc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc index aae7c80f5..1b515e511 100644 --- a/config/credentials.yml.enc +++ b/config/credentials.yml.enc @@ -1 +1 @@ -AZ3fFcqNhWle6CsRYYEqJ0FHLysDnNoKALoTnGMlkfUa7r2I4tA1Mdg/ALqucRVPjGNFNXQr53DE8tgBcJYs3mMnU8mFfeJ/cRVahPdOp5UnC9uJCHNBkYJ9Wg/f0w5HLxBFfAwynh91jRXT2zkfEj/hWZWR42XxLltEXTXTbRrCf0B5Fhuz4tOqqn7gLLLEL3s6C6cZwS/55tQaK+W+qq8yeoLzBaMCsUVcFXs7G8azKAx79yWsleQhFarojdYVIFX/dFbKKGGKLVyGzuceoYD0nrMhwD8q9gWPS1bIPK9Ca/1SxozL3Y1v8vivFiXa7bbn8Mh6bM9sM8uWJiz8CLOvCu8sBomZqKr2rLxkD8n4EqSfpG6PHTiCN2y8OSx+Vh0W2Z0nY6mbdDv8T3bSrCbclZkcVxGPukm6xItBM3l52W12naEI60afhgXPHDvbrIUCuAkoOkqzOWjpWgB/mZzC6L77--nzV6P8zKT1o+V7Xt--6gKQGBneywoG6HaWeYy/Qg== +AZ3fFcqNhWle6CsRYYEqJ0FHLysDnNoKALoTnGMlkfUa7r2I4tA1Mdg/ALqucRVPjGNFNXQr53DE8tgBcJYs3mMnU8mFfeJ/cRVahPdOp5UnC9uJCHNBkYJ9Wg/f0w5HLxBFfAwynh91jRXT2zkfEj/hWZWR42XxLltEXTXTbRrCf0B5Fhuz4tOqqn7gLLLEL3s6C6cZwS/55tQaK+W+qq8yeoLzBaMCsUVcFXs7G8azKAx79yWsleQhFarojdYVIFX/dFbKKGGKLVyGzuceoYD0nrMhwD8q9gWPS1bIPK9Ca/1SxozL3Y1v8vivFiXa7bbn8Mh6bM9sM8uWJiz8CLOvCu8sBomZqKr2rLxkD8n4EqSfpG6PHTiCN2y8OSx+Vh0W2Z0nY6mbdDv8T3bSrCbclZkcVxGPukm6xItBM3l52W12naEI60afhgXPHDvbrIUCuAkoOkqzOWjpWgB/mZzC6L77--nzV6P8zKT1o+V7Xt--6gKQGBneywoG6HaWeYy/Qg== \ No newline at end of file From e571824d3c696c1ab79d225866c74da1ca7f34dd Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Sat, 17 Jul 2021 10:16:41 +0800 Subject: [PATCH 30/97] add required resources for controllers --- config/routes.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index 5c8bb3ea4..fa511424a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -6,5 +6,9 @@ root "home#index" resources :admins - resources :agencies + resources :agencies, only: [:index, :show] + resources :tourists, only: :show + resources :tours, except: [:destroy] + resources :tourist_tours, except: [:new, :edit, :update] + resources :travel_transactions, only: [:index] end From b78d34baa673447e67036e63d4d245b14b1ac0a9 Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Sat, 17 Jul 2021 10:17:41 +0800 Subject: [PATCH 31/97] add agency controller and views with rspec tests --- app/assets/stylesheets/agencies.scss | 3 + app/controllers/agencies_controller.rb | 14 ++++ app/views/agencies/index.html.erb | 19 +++++ app/views/agencies/show.html.erb | 84 ++++++++++++++++++++++ spec/requests/agencies_controllers_spec.rb | 20 ++++++ 5 files changed, 140 insertions(+) create mode 100644 app/assets/stylesheets/agencies.scss create mode 100644 app/controllers/agencies_controller.rb create mode 100644 app/views/agencies/index.html.erb create mode 100644 app/views/agencies/show.html.erb create mode 100644 spec/requests/agencies_controllers_spec.rb diff --git a/app/assets/stylesheets/agencies.scss b/app/assets/stylesheets/agencies.scss new file mode 100644 index 000000000..14ec7d6e6 --- /dev/null +++ b/app/assets/stylesheets/agencies.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the agencies controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: https://sass-lang.com/ diff --git a/app/controllers/agencies_controller.rb b/app/controllers/agencies_controller.rb new file mode 100644 index 000000000..354a3ea3b --- /dev/null +++ b/app/controllers/agencies_controller.rb @@ -0,0 +1,14 @@ +class AgenciesController < ApplicationController + def index + if tourist_signed_in? + @agencies = Agency.all + elsif admin_signed_in? + @agencies = Agency.where(approved: false) + end + end + + def show + @agency = Agency.find(params[:id]) + @tours = @agency.tours + end +end diff --git a/app/views/agencies/index.html.erb b/app/views/agencies/index.html.erb new file mode 100644 index 000000000..2c935b933 --- /dev/null +++ b/app/views/agencies/index.html.erb @@ -0,0 +1,19 @@ +

+ + + + + + + + + + <% @agencies.each do |agency|%> + + + + + + <%end%> + +
AgencyAgency EmailAddress
<%= link_to agency.agency_name, agency_path(agency)%><%= agency.email%><%= agency.address%>
\ No newline at end of file diff --git a/app/views/agencies/show.html.erb b/app/views/agencies/show.html.erb new file mode 100644 index 000000000..177a87ea4 --- /dev/null +++ b/app/views/agencies/show.html.erb @@ -0,0 +1,84 @@ + +
+
+
+
+ <%if @agency.cover_pic.attached?%> + + <%else%> + + <%end%> + +

+
+
+
+ <%if @agency.profile_pic.attached?%> + + <%else%> + + <%end%> +
+

<%= @agency.agency_name%>

+

Agency

+ <% if agency_signed_in?%> + <%= button_to 'Edit Profile', edit_agency_registration_path,class:"btn btn-info ", method: :get%> + <% end %> +
+
+
+
+
+
+

+
+
+
+
Name
+
+
+ <%= @agency.agency_name%> +
+
+
+
+
+
Email
+
+
+ <%= @agency.email%> +
+
+
+
+
+
Address
+
+
+ <%= @agency.address%> +
+
+
+
+
+
Packages Created
+
+
+ <%= @agency.tours.count%> +
+
+
+
+
+
Rating
+
+
+ <%#= @agency.address%> Very Good +
+
+
+
+
+
+
+
diff --git a/spec/requests/agencies_controllers_spec.rb b/spec/requests/agencies_controllers_spec.rb new file mode 100644 index 000000000..3f74ef184 --- /dev/null +++ b/spec/requests/agencies_controllers_spec.rb @@ -0,0 +1,20 @@ +require 'rails_helper' + +RSpec.describe 'AgenciesControllers', type: :request do + let!(:agency) { Agency.create(email: 'agency@email.com', agency_name: 'AgencyOne', password: '1234567') } + let!(:admin) { Admin.create(email: 'admin@email.com', first_name: 'Admin', last_name: 'Last', password: '1234567') } + + before { sign_in(admin) } + + context 'when GET agencies' do + it 'gets all agencies' do + get agencies_path + expect(response).to have_http_status(:ok) + end + + it 'shows a specific agency' do + get agency_path(agency) + expect(response).to have_http_status(:ok) + end + end +end From 2d25dfb8841caf661b812e310be054bb6247ec87 Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Sat, 17 Jul 2021 10:18:10 +0800 Subject: [PATCH 32/97] add tourists controller and views with rspec tests --- app/assets/stylesheets/tourists.scss | 3 + app/controllers/tourists_controller.rb | 7 ++ app/helpers/agencies_helper.rb | 2 + app/helpers/tourists_helper.rb | 2 + app/views/tourists/show.html.erb | 83 ++++++++++++++++++++++ spec/requests/tourists_controllers_spec.rb | 14 ++++ 6 files changed, 111 insertions(+) create mode 100644 app/assets/stylesheets/tourists.scss create mode 100644 app/controllers/tourists_controller.rb create mode 100644 app/helpers/agencies_helper.rb create mode 100644 app/helpers/tourists_helper.rb create mode 100644 app/views/tourists/show.html.erb create mode 100644 spec/requests/tourists_controllers_spec.rb diff --git a/app/assets/stylesheets/tourists.scss b/app/assets/stylesheets/tourists.scss new file mode 100644 index 000000000..ef4f9ab1b --- /dev/null +++ b/app/assets/stylesheets/tourists.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the tourists controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: https://sass-lang.com/ diff --git a/app/controllers/tourists_controller.rb b/app/controllers/tourists_controller.rb new file mode 100644 index 000000000..ec207fc82 --- /dev/null +++ b/app/controllers/tourists_controller.rb @@ -0,0 +1,7 @@ +class TouristsController < ApplicationController + before_action :authenticate_user! + + def show + @tourist = Tourist.find(params[:id]) + end +end diff --git a/app/helpers/agencies_helper.rb b/app/helpers/agencies_helper.rb new file mode 100644 index 000000000..9d3751428 --- /dev/null +++ b/app/helpers/agencies_helper.rb @@ -0,0 +1,2 @@ +module AgenciesHelper +end diff --git a/app/helpers/tourists_helper.rb b/app/helpers/tourists_helper.rb new file mode 100644 index 000000000..f70b4ffb5 --- /dev/null +++ b/app/helpers/tourists_helper.rb @@ -0,0 +1,2 @@ +module TouristsHelper +end diff --git a/app/views/tourists/show.html.erb b/app/views/tourists/show.html.erb new file mode 100644 index 000000000..9acbff724 --- /dev/null +++ b/app/views/tourists/show.html.erb @@ -0,0 +1,83 @@ +
+
+
+
+ <%if @tourist.cover_pic.attached?%> + + <%else%> + + <%end%> +

+
+
+
+ <%if @tourist.profile_pic.attached?%> + + <%else%> + + <%end%> + +
+

<%= @tourist.first_name%> <%=@tourist.last_name%>

+

Tourist

+ <% if @tourist == current_tourist%> + <%= button_to 'Edit Profile', edit_tourist_registration_path,class:"btn btn-info ", method: :get%> + <% end %> +
+
+
+
+
+
+

+
+
+
+
First Name
+
+
+ <%= @tourist.first_name%> +
+
+
+
+
+
Last Name
+
+
+ <%=@tourist.last_name%> +
+
+
+
+
+
Email
+
+
+ <%= @tourist.email%> +
+
+
+
+
+
Birth Date
+
+
+ <%= @tourist&.birth_date&.strftime('%b %d, %Y')%> +
+
+
+
+
+
Address
+
+
+ <%= @tourist.address%> +
+
+
+
+
+
+
+
diff --git a/spec/requests/tourists_controllers_spec.rb b/spec/requests/tourists_controllers_spec.rb new file mode 100644 index 000000000..61c9ffb38 --- /dev/null +++ b/spec/requests/tourists_controllers_spec.rb @@ -0,0 +1,14 @@ +require 'rails_helper' + +RSpec.describe 'TouristsControllers', type: :request do + let!(:tourist) { Tourist.create(email: 'tourist@email.com', first_name: 'TouristOne', last_name: 'Tourist', address: 'Kyoto, Japan', birth_date: Date.new(1999, 8, 10), password: '1234567') } + + before { sign_in(tourist) } + + context 'when GET tourists' do + it 'works! (now write some real specs)' do + get tourist_path(tourist) + expect(response).to have_http_status(:ok) + end + end +end From c7bcf206825f1182511d42854ef8eb69761ce642 Mon Sep 17 00:00:00 2001 From: JC Date: Sat, 17 Jul 2021 11:35:41 +0800 Subject: [PATCH 33/97] added models --- .gitignore | 1 + app/models/review.rb | 2 ++ app/models/tour.rb | 5 +++ app/models/tourist_tour.rb | 3 ++ app/models/travel_transaction.rb | 2 ++ spec/models/admin_spec.rb | 4 +++ spec/models/agency_spec.rb | 4 +++ spec/models/chat_room_spec.rb | 20 ++++++++++++ spec/models/chat_room_user_spec.rb | 15 +++++++++ spec/models/message_spec.rb | 16 ++++++++++ spec/models/review_spec.rb | 22 +++++++++++++ spec/models/tour_spec.rb | 44 ++++++++++++++++++++++++++ spec/models/tourist_spec.rb | 4 +++ spec/models/tourist_tour_spec.rb | 15 +++++++++ spec/models/travel_transaction_spec.rb | 21 ++++++++++++ 15 files changed, 178 insertions(+) create mode 100644 spec/models/admin_spec.rb create mode 100644 spec/models/agency_spec.rb create mode 100644 spec/models/chat_room_spec.rb create mode 100644 spec/models/chat_room_user_spec.rb create mode 100644 spec/models/message_spec.rb create mode 100644 spec/models/review_spec.rb create mode 100644 spec/models/tour_spec.rb create mode 100644 spec/models/tourist_spec.rb create mode 100644 spec/models/tourist_tour_spec.rb create mode 100644 spec/models/travel_transaction_spec.rb diff --git a/.gitignore b/.gitignore index aef65f16f..46baf15fe 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ # Ignore master key for decrypting credentials and more. /config/master.key +/config/database.yml /public/packs /public/packs-test diff --git a/app/models/review.rb b/app/models/review.rb index d327321d9..21d3cbcd1 100644 --- a/app/models/review.rb +++ b/app/models/review.rb @@ -1,4 +1,6 @@ class Review < ApplicationRecord + validates :rating, presence: true, numericality: { greater_than_or_equal_to: 0 } + belongs_to :tourist belongs_to :agency end diff --git a/app/models/tour.rb b/app/models/tour.rb index 6ed8af13f..f61f431e9 100644 --- a/app/models/tour.rb +++ b/app/models/tour.rb @@ -1,4 +1,9 @@ class Tour < ApplicationRecord + validates :price, presence: true, numericality: { greater_than_or_equal_to: 0 } + validates :name, presence: true, length: { minimum: 1 } + validates :location, presence: true, length: { minimum: 1 } + validates :duration, presence: true, numericality: { greater_than_or_equal_to: 1 } + belongs_to :agency has_many :tourist_tours, dependent: :nullify has_many :tourists, through: :tourist_tours diff --git a/app/models/tourist_tour.rb b/app/models/tourist_tour.rb index 88c1a0d57..bcea3f2c1 100644 --- a/app/models/tourist_tour.rb +++ b/app/models/tourist_tour.rb @@ -1,4 +1,7 @@ class TouristTour < ApplicationRecord + validates :guest_quantity, presence: true, numericality: { greater_than_or_equal_to: 1 } + validates :amount_bought, presence: true, numericality: { greater_than_or_equal_to: 0 } + belongs_to :tour belongs_to :tourist diff --git a/app/models/travel_transaction.rb b/app/models/travel_transaction.rb index ebe4d37b7..a707a6365 100644 --- a/app/models/travel_transaction.rb +++ b/app/models/travel_transaction.rb @@ -1,4 +1,6 @@ class TravelTransaction < ApplicationRecord + validates :total_price, presence: true, numericality: { greater_than_or_equal_to: 1 } + belongs_to :tourist_tour belongs_to :agency end diff --git a/spec/models/admin_spec.rb b/spec/models/admin_spec.rb new file mode 100644 index 000000000..b91008e17 --- /dev/null +++ b/spec/models/admin_spec.rb @@ -0,0 +1,4 @@ +require 'rails_helper' + +RSpec.describe Admin, type: :model do +end diff --git a/spec/models/agency_spec.rb b/spec/models/agency_spec.rb new file mode 100644 index 000000000..38dca50b7 --- /dev/null +++ b/spec/models/agency_spec.rb @@ -0,0 +1,4 @@ +require 'rails_helper' + +RSpec.describe Agency, type: :model do +end diff --git a/spec/models/chat_room_spec.rb b/spec/models/chat_room_spec.rb new file mode 100644 index 000000000..41dda655c --- /dev/null +++ b/spec/models/chat_room_spec.rb @@ -0,0 +1,20 @@ +require 'rails_helper' + +RSpec.describe ChatRoom, type: :model do + let!(:chat_room) { described_class.new } + + + context 'with validations of relationship' do + it 'belongs to messages' do + expect(chat_room).to have_many(:messages) + end + + it 'belongs to a chat room users' do + expect(chat_room).to have_many(:chat_room_users) + end + + it 'belongs to a users' do + expect(chat_room).to have_many(:users) + end + end +end diff --git a/spec/models/chat_room_user_spec.rb b/spec/models/chat_room_user_spec.rb new file mode 100644 index 000000000..ff888877f --- /dev/null +++ b/spec/models/chat_room_user_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +RSpec.describe ChatRoomUser, type: :model do + let!(:chat_room_user) { described_class.new } + + context 'with validations of relationship' do + it 'belongs to tourist tour' do + expect(chat_room_user).to belong_to(:user) + end + + it 'belongs to a agency' do + expect(chat_room_user).to belong_to(:chat_room) + end + end +end diff --git a/spec/models/message_spec.rb b/spec/models/message_spec.rb new file mode 100644 index 000000000..c94076f2c --- /dev/null +++ b/spec/models/message_spec.rb @@ -0,0 +1,16 @@ +require 'rails_helper' + +RSpec.describe Message, type: :model do + let!(:message) { described_class.new } + + + context 'with validations of relationship' do + it 'belongs to chat room' do + expect(message).to belong_to(:chat_room) + end + + it 'belongs to a user' do + expect(message).to belong_to(:user) + end + end +end diff --git a/spec/models/review_spec.rb b/spec/models/review_spec.rb new file mode 100644 index 000000000..ee7de3b06 --- /dev/null +++ b/spec/models/review_spec.rb @@ -0,0 +1,22 @@ +require 'rails_helper' + +RSpec.describe Review, type: :model do + let!(:review) { described_class.new } + + + context 'with validations' do + it 'must have a valid rating' do + expect(review).to validate_numericality_of(:rating).is_greater_than_or_equal_to(0) + end + end + + context 'with validations of relationship' do + it 'belongs to agency' do + expect(review).to belong_to(:agency) + end + + it 'belongs to a tourist' do + expect(review).to belong_to(:tourist) + end + end +end diff --git a/spec/models/tour_spec.rb b/spec/models/tour_spec.rb new file mode 100644 index 000000000..c05b88aca --- /dev/null +++ b/spec/models/tour_spec.rb @@ -0,0 +1,44 @@ +require 'rails_helper' + +RSpec.describe Tour, type: :model do + let!(:tour) { described_class.new } + + context 'with validations' do + it 'is not valid without name' do + name = "haha" + expect(tour).to validate_presence_of(:name) + end + + it 'must have atlease 1 character' do + expect(tour).to validate_length_of(:name) + .is_at_least(1) + .on(:create) + end + + it 'is not valid without location' do + expect(tour).to validate_presence_of(:location) + end + + it 'must have at least 1 character' do + expect(tour).to validate_length_of(:location) + .is_at_least(1) + .on(:create) + end + + it 'must have a valid price' do + expect(tour).to validate_numericality_of(:price).is_greater_than_or_equal_to(0) + end + + it 'must have a valid duration' do + expect(tour).to validate_numericality_of(:duration).is_greater_than_or_equal_to(1) + end + + end + + context 'with validations of relationship' do + it 'belongs to agency' do + expect(tour).to belong_to(:agency) + end + + end +end diff --git a/spec/models/tourist_spec.rb b/spec/models/tourist_spec.rb new file mode 100644 index 000000000..d93bb4d6b --- /dev/null +++ b/spec/models/tourist_spec.rb @@ -0,0 +1,4 @@ +require 'rails_helper' + +RSpec.describe Tourist, type: :model do +end diff --git a/spec/models/tourist_tour_spec.rb b/spec/models/tourist_tour_spec.rb new file mode 100644 index 000000000..6ac39ca8f --- /dev/null +++ b/spec/models/tourist_tour_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +RSpec.describe TouristTour, type: :model do + let!(:tourist_tour) { described_class.new } + + context 'with validations' do + it 'must have a valid guest quantity' do + expect(tourist_tour).to validate_numericality_of(:guest_quantity).is_greater_than_or_equal_to(1) + end + + it 'must have a valid amount bought' do + expect(tourist_tour).to validate_numericality_of(:amount_bought).is_greater_than_or_equal_to(0) + end + end +end diff --git a/spec/models/travel_transaction_spec.rb b/spec/models/travel_transaction_spec.rb new file mode 100644 index 000000000..8de15be20 --- /dev/null +++ b/spec/models/travel_transaction_spec.rb @@ -0,0 +1,21 @@ +require 'rails_helper' + +RSpec.describe TravelTransaction, type: :model do + let!(:travel_transaction) { described_class.new } + + context 'with validations' do + it 'must have a valid amount bought' do + expect(travel_transaction).to validate_numericality_of(:total_price).is_greater_than_or_equal_to(1) + end + end + + context 'with validations of relationship' do + it 'belongs to tourist tour' do + expect(travel_transaction).to belong_to(:tourist_tour) + end + + it 'belongs to a agency' do + expect(travel_transaction).to belong_to(:agency) + end + end +end From 51e5ea43f8d612377ce0ae118fbc7a62f1770e23 Mon Sep 17 00:00:00 2001 From: JC Date: Sat, 17 Jul 2021 11:54:37 +0800 Subject: [PATCH 34/97] added changes in rspec models --- spec/models/admin_spec.rb | 4 ---- spec/models/agency_spec.rb | 4 ---- spec/models/chat_room_spec.rb | 1 - spec/models/message_spec.rb | 1 - spec/models/review_spec.rb | 1 - spec/models/tour_spec.rb | 3 --- spec/models/tourist_spec.rb | 4 ---- 7 files changed, 18 deletions(-) delete mode 100644 spec/models/admin_spec.rb delete mode 100644 spec/models/agency_spec.rb delete mode 100644 spec/models/tourist_spec.rb diff --git a/spec/models/admin_spec.rb b/spec/models/admin_spec.rb deleted file mode 100644 index b91008e17..000000000 --- a/spec/models/admin_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe Admin, type: :model do -end diff --git a/spec/models/agency_spec.rb b/spec/models/agency_spec.rb deleted file mode 100644 index 38dca50b7..000000000 --- a/spec/models/agency_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe Agency, type: :model do -end diff --git a/spec/models/chat_room_spec.rb b/spec/models/chat_room_spec.rb index 41dda655c..6f48ed43b 100644 --- a/spec/models/chat_room_spec.rb +++ b/spec/models/chat_room_spec.rb @@ -3,7 +3,6 @@ RSpec.describe ChatRoom, type: :model do let!(:chat_room) { described_class.new } - context 'with validations of relationship' do it 'belongs to messages' do expect(chat_room).to have_many(:messages) diff --git a/spec/models/message_spec.rb b/spec/models/message_spec.rb index c94076f2c..f96091a4a 100644 --- a/spec/models/message_spec.rb +++ b/spec/models/message_spec.rb @@ -3,7 +3,6 @@ RSpec.describe Message, type: :model do let!(:message) { described_class.new } - context 'with validations of relationship' do it 'belongs to chat room' do expect(message).to belong_to(:chat_room) diff --git a/spec/models/review_spec.rb b/spec/models/review_spec.rb index ee7de3b06..f78c523f4 100644 --- a/spec/models/review_spec.rb +++ b/spec/models/review_spec.rb @@ -3,7 +3,6 @@ RSpec.describe Review, type: :model do let!(:review) { described_class.new } - context 'with validations' do it 'must have a valid rating' do expect(review).to validate_numericality_of(:rating).is_greater_than_or_equal_to(0) diff --git a/spec/models/tour_spec.rb b/spec/models/tour_spec.rb index c05b88aca..587e85499 100644 --- a/spec/models/tour_spec.rb +++ b/spec/models/tour_spec.rb @@ -5,7 +5,6 @@ context 'with validations' do it 'is not valid without name' do - name = "haha" expect(tour).to validate_presence_of(:name) end @@ -32,13 +31,11 @@ it 'must have a valid duration' do expect(tour).to validate_numericality_of(:duration).is_greater_than_or_equal_to(1) end - end context 'with validations of relationship' do it 'belongs to agency' do expect(tour).to belong_to(:agency) end - end end diff --git a/spec/models/tourist_spec.rb b/spec/models/tourist_spec.rb deleted file mode 100644 index d93bb4d6b..000000000 --- a/spec/models/tourist_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe Tourist, type: :model do -end From a2caf7b88a0df644829a4bc3fe23fc08335304b8 Mon Sep 17 00:00:00 2001 From: dlanoronald123 Date: Sat, 17 Jul 2021 12:23:48 +0800 Subject: [PATCH 35/97] Add styling to welcome page --- app/assets/stylesheets/application.css | 177 +++++++++++++++++++++++++ app/views/home/index.html.erb | 86 ++++++++++++ app/views/layouts/application.html.erb | 41 +++++- 3 files changed, 302 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index f3b601fc0..22cf39854 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -15,3 +15,180 @@ *= require_tree . *= require_self */ + html { + scroll-behavior: smooth; + } + + *{ + font-family: 'Roboto', sans-serif; + margin: 0; + padding: 0; + box-sizing: border-box; + } + +.user-card-full { + overflow: hidden +} + +.card { + border-radius: 5px; + -webkit-box-shadow: 0 1px 20px 0 rgba(69, 90, 100, 0.08); + box-shadow: 0 1px 20px 0 rgba(69, 90, 100, 0.08); + border: none; + margin-bottom: 30px +} + +.user-card-full .user-profile { + border-radius: 5px 0 0 5px +} + +@media only screen and (min-width: 1400px) { + p { + font-size: 14px + } +} + + +#cont{ + width: auto; + height: 500px; +} + +a{ + text-decoration: none +} + +.Package{ + width: 300px; +} + +.Address{ + width: 600px; +} + +#image{ + height:100vh; + +} + + +#editImage{ + width: 400px; + height:210px; + object-fit: cover +} + +#ProfilePic{ + width:150px; + height:150px; + border-radius: 50%; +} + +#form{ + width: 500px; +} + +/* FEATURES */ + + .box { + position: relative; + width: 300px; + border: 1px solid black; + border-radius: 10px; + padding: 90px 15px 15px; + background: linear-gradient(to bottom, rgb(255, 255, 255), rgb(112, 116, 111));; + box-shadow: 0 15px 45px rgba(0,0,0,0.4); + } + + .box:before{ + content: ""; + position: absolute; + border-radius: 10px; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgb(79, 133, 19); + transform: scaleY(0); + transform-origin: top; + transition: transform 0.5s; + } + + .box:hover:before{ + transform: scaleY(1); + transform-origin: bottom; + transition: transform 0.5s; + } + + .box h2{ + position: absolute; + left: 40px; + top: 40px; + font-size: 2.5em; + z-index: 1; + opacity: 0.2; + transition: .5s; + color:rgb(56, 36, 0); + font-family: 'Oswald', sans-serif; + } + + .box:hover h2{ + color:#ffffff; + opacity: 1; + transform: translateY(-20px); + font-family: 'Oswald', sans-serif; + } + + + .box h3{ + position: relative; + font-size: 1.3em; + z-index: 2; + opacity: 0.8; + transition: .5s; + color:rgb(0, 0, 0); + } + + .box:hover h3{ + color:#ffffff; + } + + + .box p{ + position: relative; + z-index: 2; + opacity: 0.8; + transition: .5s; + color:rgb(0, 0, 0); + font-size: 1rem; + } + + .box:hover p{ + color:#ffffff; + } + + +/* NAVBAR */ + +.carousel-content { + position: absolute; + bottom: 35%; + left: 10%; + z-index: 20; + color: white; + text-shadow: 0 1px 2px rgba(0,0,0,.6); +} + +.carousel-content h1 { + font-family: 'Oswald', sans-serif; + font-size: 4rem; + -webkit-text-stroke-width: 1px; + -webkit-text-stroke-color: black; +} + + +.carousel-inner{ + background-position: center; + background-repeat: no-repeat; + background-size: cover; + } diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb index e69de29bb..5e7356b9a 100644 --- a/app/views/home/index.html.erb +++ b/app/views/home/index.html.erb @@ -0,0 +1,86 @@ + + + + + +
+
+
Card title
+

+ Some quick example text to build on the card title and make up the bulk + of the card's content. +

+ Button +
+
+ + +
+

SERVICES OFFERED

+
+
+
+
+
+

AMAZING TOURS

+

+

There are dozens of tours to choose from, while organizing a trip, and we’ll make sure your tour includes everything you need.

+
+
+
+
+

GREAT PRICES

+

+

All our tours and excursions are available at really affordable prices so you can always pick a great destination.

+
+
+
+
+

BEST SERVICES

+

+

Our aim is to provide you with the tour service of the top quality, and we'll do our best to find the suitable tour for you.

+
+
+
+
+ diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 09f2c16bc..d182c64ef 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -1,12 +1,31 @@ - - Rails Project + + + + + + + + + + + + + + + + + + + TravelZilla + <%= csrf_meta_tags %> <%= csp_meta_tag %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> + @@ -19,5 +38,23 @@ <% end %> <%= render 'shared/nav_bar'%> <%= yield %> + + + + + + + + + + + + + From e8f6075251207a8a0a02e12bbfbcce56e4e04297 Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Sat, 17 Jul 2021 12:36:29 +0800 Subject: [PATCH 36/97] add .where(approved: true) to Agency when tourist_signed_in --- app/controllers/agencies_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/agencies_controller.rb b/app/controllers/agencies_controller.rb index 354a3ea3b..a9b5c1712 100644 --- a/app/controllers/agencies_controller.rb +++ b/app/controllers/agencies_controller.rb @@ -1,7 +1,7 @@ class AgenciesController < ApplicationController def index if tourist_signed_in? - @agencies = Agency.all + @agencies = Agency.where(approved: true) elsif admin_signed_in? @agencies = Agency.where(approved: false) end From 4e8a6614dfd3c135e525f374e69057f74cde2674 Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Sat, 17 Jul 2021 12:36:38 +0800 Subject: [PATCH 37/97] modify views --- app/views/devise/registrations/edit.html.erb | 117 +++++++++++++------ app/views/devise/registrations/new.html.erb | 88 +++++++++----- app/views/devise/sessions/new.html.erb | 57 +++++---- app/views/devise/shared/_links.html.erb | 2 +- 4 files changed, 176 insertions(+), 88 deletions(-) diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb index 38d95b85a..e5eb08567 100644 --- a/app/views/devise/registrations/edit.html.erb +++ b/app/views/devise/registrations/edit.html.erb @@ -1,43 +1,84 @@ -

Edit <%= resource_name.to_s.humanize %>

- <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> - <%= render "devise/shared/error_messages", resource: resource %> - -
- <%= f.label :email %>
- <%= f.email_field :email, autofocus: true, autocomplete: "email" %> -
- - <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> -
Currently waiting confirmation for: <%= resource.unconfirmed_email %>
- <% end %> - -
- <%= f.label :password %> (leave blank if you don't want to change it)
- <%= f.password_field :password, autocomplete: "new-password" %> - <% if @minimum_password_length %> -
- <%= @minimum_password_length %> characters minimum - <% end %> -
+<%= render "devise/shared/error_messages", resource: resource %> -
- <%= f.label :password_confirmation %>
- <%= f.password_field :password_confirmation, autocomplete: "new-password" %> -
- -
- <%= f.label :current_password %> (we need your current password to confirm your changes)
- <%= f.password_field :current_password, autocomplete: "current-password" %> -
+
+
+
+

Edit <%= resource_name.to_s.humanize %>

+
+
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email",placeholder:"Email",class:"form-control" %> +
+ <%if current_page?(edit_tourist_registration_path)%> +
+ <%= f.label :first_name%>
+ <%= f.text_field :first_name, class:"form-control"%> +
+
+ <%= f.label :last_name%>
+ <%= f.text_field :last_name, class:"form-control"%> +
+
+ <%= f.label :birth_date%>
+ <%= f.date_field :birth_date, class:"form-control"%> +
+ <% elsif current_page?(edit_agency_registration_path) %> + <%= f.label :agency_name%>
+ <%= f.text_field :agency_name, class:"form-control"%> + <%end%> +
+ <%= f.label :address%>
+ <%= f.text_field :address, class:"form-control"%> +
-
- <%= f.submit "Update" %> +
+ <%= f.label :password %>
+ <% if @minimum_password_length %> + (<%= @minimum_password_length %> characters minimum) + <% end %>
+ <%= f.password_field :password, autocomplete: "new-password",placeholder:"Password",class:"form-control" %> +
+
+ <%= f.label :password_confirmation %>
+ <%= f.password_field :password_confirmation, autocomplete: "new-password",placeholder:"Confirm Password",class:"form-control" %> +
+
+ <%= f.label :current_password %>
+ <%= f.password_field :current_password, autocomplete: "current-password",placeholder:"Current Password",class:"form-control" %> +
+
+
+
+
+
+ <%= f.label "Cover Photo"%> + <%= f.file_field :cover_pic%> +
+ <%if current_user.cover_pic.attached?%> + + <%else%> + + <%end%> +
+ <%= f.label "Profile Photo"%> + <%= f.file_field :profile_pic%> +
+ <%if current_user.profile_pic.attached?%> + + <%else%> + + <%end%> +
+
+ <%= f.submit "Update",class:"btn btn-success w-80 font-weight-bold mt-4 mx-2" %> +
+
+
+
+
<% end %> - -

Cancel my account

- -

Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %>

- -<%= link_to "Back", :back %> +
+<%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete, class:"btn btn-danger w-80" %>

+
diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb index d655b66f6..658bd08c8 100644 --- a/app/views/devise/registrations/new.html.erb +++ b/app/views/devise/registrations/new.html.erb @@ -1,29 +1,63 @@ -

Sign up

+ +<%= render 'shared/errors', obj: resource %> +
+
+
+ <% if current_page?(new_tourist_registration_path) %> + + <% elsif current_page?( new_agency_registration_path) %> + + <%end %> +
+
+

Sign Up

+
+ <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email",class:"form-control" %> +
+ <%if current_page?(new_tourist_registration_path)%> +
+ <%= f.label :first_name%>
+ <%= f.text_field :first_name, class:"form-control"%> +
+
+ <%= f.label :last_name%>
+ <%= f.text_field :last_name, class:"form-control"%> +
+
+ <%= f.label :birth_date%>
+ <%= f.date_field :birth_date, class:"form-control"%> +
+ <% elsif current_page?(new_agency_registration_path) %> + <%= f.label :agency_name%>
+ <%= f.text_field :agency_name, class:"form-control"%> + <%end%> +
+ <%= f.label :address%>
+ <%= f.text_field :address, class:"form-control"%> +
-<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> - <%= render "devise/shared/error_messages", resource: resource %> - -
- <%= f.label :email %>
- <%= f.email_field :email, autofocus: true, autocomplete: "email" %> -
- -
- <%= f.label :password %> - <% if @minimum_password_length %> - (<%= @minimum_password_length %> characters minimum) - <% end %>
- <%= f.password_field :password, autocomplete: "new-password" %> -
- -
- <%= f.label :password_confirmation %>
- <%= f.password_field :password_confirmation, autocomplete: "new-password" %> -
- -
- <%= f.submit "Sign up" %> +
+ <%= f.label :password %>
+ <% if @minimum_password_length %> + (<%= @minimum_password_length %> characters minimum) + <% end %>
+ <%= f.password_field :password, autocomplete: "new-password",placeholder:"Password",class:"form-control" %> +
+
+ <%= f.label :password_confirmation %>
+ <%= f.password_field :password_confirmation, autocomplete: "new-password",placeholder:"Confirm Password",class:"form-control" %> +
+
+
+ <%= f.submit "Sign up",class:"btn btn-dark w-100 font-weight-bold mt-2" %> +
+
+ <% end %> + <%= render "devise/shared/links" %> +
+
-<% end %> - -<%= render "devise/shared/links" %> +
\ No newline at end of file diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb index b91601b4a..58f5d0774 100644 --- a/app/views/devise/sessions/new.html.erb +++ b/app/views/devise/sessions/new.html.erb @@ -1,28 +1,41 @@ -

Log in

- -<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> -
- <%= f.label :email %>
- <%= f.email_field :email, autofocus: true, autocomplete: "email" %> -
- -
- <%= f.label :password %>
- <%= f.password_field :password, autocomplete: "current-password" %> -
- - <% if devise_mapping.rememberable? %> + +
+
+
+ <% if resource_name == :tourist %> + + <% elsif resource_name == :agency %> + + <%end %> +
+
+

Login

+
+ <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email",placeholder:"Email",class:"form-control" %> +
+
+ <%= f.label :password %>
+ <%= f.password_field :password, autocomplete: "current-password",placeholder:"Password",class:"form-control" %> +
+
+ <% if devise_mapping.rememberable? %>
<%= f.check_box :remember_me %> <%= f.label :remember_me %>
<% end %> - -
- <%= f.submit "Log in" %> +
+
+
+ <%= f.submit "Log in",class:"btn btn-dark w-100 font-weight-bold mt-2" %> +
+
+ <% end %> + <%= render "devise/shared/links" %> +
+
-<% end %> -<% if current_page?(new_tourist_session_path || new_agency_session_path) %> - <%= render "devise/shared/links" %> -<% end %> - +
diff --git a/app/views/devise/shared/_links.html.erb b/app/views/devise/shared/_links.html.erb index 084af701c..823c58838 100644 --- a/app/views/devise/shared/_links.html.erb +++ b/app/views/devise/shared/_links.html.erb @@ -2,7 +2,7 @@ <%= link_to "Log in", new_session_path(resource_name) %>
<% end %> -<%- if devise_mapping.registerable? && controller_name != 'registrations' %> +<%- if devise_mapping.registerable? && controller_name != 'registrations' && resource_name != :admin %> <%= link_to "Sign up", new_registration_path(resource_name) %>
<% end %> From 309d01e37aaafb0e98c84fbec35a30d102e64e2f Mon Sep 17 00:00:00 2001 From: Eileenandrea Date: Sat, 17 Jul 2021 21:24:44 +0800 Subject: [PATCH 38/97] modify routes add tourist_tour routes --- config/routes.rb | 4 ++++ db/schema.rb | 4 ++-- spec/rails_helper.rb | 3 +-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index fa511424a..545c0ed9f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,7 +2,9 @@ devise_for :tourists devise_for :agencies devise_for :admins, skip: :registration + # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html + root "home#index" resources :admins @@ -11,4 +13,6 @@ resources :tours, except: [:destroy] resources :tourist_tours, except: [:new, :edit, :update] resources :travel_transactions, only: [:index] + get '/tourist_tours/new/:tour_id', to: 'tourist_tours#new', as: 'new_tourist_tour' + end diff --git a/db/schema.rb b/db/schema.rb index e384006ba..b85b15c78 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_07_16_071248) do +ActiveRecord::Schema.define(version: 2021_07_17_023742) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -119,7 +119,7 @@ create_table "users", force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false - t.integer "contact_number" + t.string "contact_number" t.string "address" t.string "type" t.string "first_name" diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 3108cead0..115f2d25f 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -21,8 +21,7 @@ config.use_transactional_fixtures = false config.infer_spec_type_from_file_location! config.filter_rails_from_backtrace! - - config.include Devise::Test::IntegrationHelpers + config.include Devise::Test::IntegrationHelpers config.include FactoryBot::Syntax::Methods config.before(:suite) do From 10a34309a2e643d54a3d1f6d648cdb79614fa688 Mon Sep 17 00:00:00 2001 From: Eileenandrea Date: Sat, 17 Jul 2021 21:27:25 +0800 Subject: [PATCH 39/97] adding test factory_bot --- test/factories/admins.rb | 10 ++++++++++ test/factories/agencies.rb | 17 +++++++++++++++++ test/factories/tourist_tours.rb | 10 ++++++++++ test/factories/tourists.rb | 13 +++++++++++++ test/factories/tours.rb | 9 +++++++++ 5 files changed, 59 insertions(+) create mode 100644 test/factories/admins.rb create mode 100644 test/factories/agencies.rb create mode 100644 test/factories/tourist_tours.rb create mode 100644 test/factories/tourists.rb create mode 100644 test/factories/tours.rb diff --git a/test/factories/admins.rb b/test/factories/admins.rb new file mode 100644 index 000000000..34a7c0735 --- /dev/null +++ b/test/factories/admins.rb @@ -0,0 +1,10 @@ +FactoryBot.define do + factory :admin do + email { "#{first_name.gsub(' ', '')}@email.com" } + password { 'secure123' } + address { 'Antipolo City' } + approved { true } + first_name { 'admin' } + type { 'Admin' } + end +end diff --git a/test/factories/agencies.rb b/test/factories/agencies.rb new file mode 100644 index 000000000..058bb9cde --- /dev/null +++ b/test/factories/agencies.rb @@ -0,0 +1,17 @@ +FactoryBot.define do + factory :agency do + email { "#{agency_name.gsub(' ', '')}@email.com" } + password { 'secure123' } + address { 'Antipolo City' } + agency_name { 'Tourist Travel Tours' } + type { 'Agency' } + contact_number { '9307176311' } + average_rating { 5 } + + trait :approved do + approved { true } + end + + factory :approved_agency, traits: [:approved] + end +end diff --git a/test/factories/tourist_tours.rb b/test/factories/tourist_tours.rb new file mode 100644 index 000000000..4b43a9848 --- /dev/null +++ b/test/factories/tourist_tours.rb @@ -0,0 +1,10 @@ +FactoryBot.define do + factory :tourist_tour do + guest_quantity { 2 } + amount_bought { 7998 } + start_date { '2021-07-16' } + end_date { '2021-07-19' } + tourist + tour + end +end diff --git a/test/factories/tourists.rb b/test/factories/tourists.rb new file mode 100644 index 000000000..3152e23b6 --- /dev/null +++ b/test/factories/tourists.rb @@ -0,0 +1,13 @@ +FactoryBot.define do + factory :tourist do + email { "#{first_name.gsub(' ', '')}@email.com" } + password { 'secure123' } + address { 'Antipolo City' } + approved { true } + first_name { 'Tourist' } + middle_name { 'New' } + last_name { 'Gala' } + birth_date { '2006-01-01' } + type { 'Tourist' } + end +end diff --git a/test/factories/tours.rb b/test/factories/tours.rb new file mode 100644 index 000000000..56dcf2082 --- /dev/null +++ b/test/factories/tours.rb @@ -0,0 +1,9 @@ +FactoryBot.define do + factory :tour do + name { 'Trip to Boracay 3 days and 2 nights' } + price { 3999 } + location { 'Boracay' } + duration { 3 } + agency + end +end From 20e8c4632f96afacf054dd05d61f3f32b2ae4a1e Mon Sep 17 00:00:00 2001 From: Eileenandrea Date: Sat, 17 Jul 2021 21:28:17 +0800 Subject: [PATCH 40/97] tourist tour controllers and rspec request test --- app/assets/stylesheets/tourist_tours.scss | 3 + app/controllers/tourist_tours_controller.rb | 33 ++++++++ app/helpers/tourist_tours_helper.rb | 2 + .../tourist_tours_controllers_spec.rb | 84 +++++++++++++++++++ 4 files changed, 122 insertions(+) create mode 100644 app/assets/stylesheets/tourist_tours.scss create mode 100644 app/controllers/tourist_tours_controller.rb create mode 100644 app/helpers/tourist_tours_helper.rb create mode 100644 spec/requests/tourist_tours_controllers_spec.rb diff --git a/app/assets/stylesheets/tourist_tours.scss b/app/assets/stylesheets/tourist_tours.scss new file mode 100644 index 000000000..986156029 --- /dev/null +++ b/app/assets/stylesheets/tourist_tours.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the tourist_tours controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: https://sass-lang.com/ diff --git a/app/controllers/tourist_tours_controller.rb b/app/controllers/tourist_tours_controller.rb new file mode 100644 index 000000000..1e4fa439a --- /dev/null +++ b/app/controllers/tourist_tours_controller.rb @@ -0,0 +1,33 @@ +class TouristToursController < ApplicationController + before_action :authenticate_tourist! + def index + @tourist_tours = current_tourist.tourist_tours.all + end + + def show + @tourist_tour = current_tourist.tourist_tours.find(params[:id]) + end + + def new + @tour = Tour.find(params[:tour_id]) + @tourist_tour = current_tourist.tourist_tours.build + end + + def create + @tourist_tour = current_tourist.tourist_tours.build(tourist_tour_params) + @tourist_tour.save + if @tourist_tour.save + TravelTransaction.create(tourist_tour_id: @tourist_tour.id, agency_id: @tourist_tour.tour.agency.id, total_price: @tourist_tour.amount_bought) + redirect_to tourist_tours_path, notice: "Successfully bought #{@tourist_tour.tour.name} package!" + + else + redirect_to tours_path, alert: @tourist_tour.errors.full_messages.first + end + end + + private + + def tourist_tour_params + params.require(:tourist_tour).permit(:tour_id, :guest_quantity, :amount_bought, :start_date, :end_date) + end +end diff --git a/app/helpers/tourist_tours_helper.rb b/app/helpers/tourist_tours_helper.rb new file mode 100644 index 000000000..754c2fa73 --- /dev/null +++ b/app/helpers/tourist_tours_helper.rb @@ -0,0 +1,2 @@ +module TouristToursHelper +end diff --git a/spec/requests/tourist_tours_controllers_spec.rb b/spec/requests/tourist_tours_controllers_spec.rb new file mode 100644 index 000000000..47d8bb825 --- /dev/null +++ b/spec/requests/tourist_tours_controllers_spec.rb @@ -0,0 +1,84 @@ +require 'rails_helper' + +RSpec.describe 'TouristToursControllers', type: :request do + let!(:admin) { create(:admin) } + let!(:tourist) { create(:tourist) } + let!(:agency) { create(:approved_agency) } + let!(:tour) { create(:tour, agency: agency) } + let!(:tourist_tour) { create(:tourist_tour, tourist: tourist, tour: tour) } + + describe 'GET tourist_tour index path for different users' do + it 'returns a redirect response if not logged_in ' do + get tourist_tours_path + expect(response).to have_http_status(:found) + end + + it 'returns a success response if current_user is tourist ' do + sign_in(tourist) + get tourist_tours_path + expect(response).to have_http_status(:ok) + end + + it 'returns a redirect response if current_user is agency ' do + sign_in(agency) + get tourist_tours_path + expect(response).to have_http_status(:found) + end + + it 'returns a redirect response if current_user is admin ' do + sign_in(admin) + get tourist_tours_path + expect(response).to have_http_status(:found) + end + end + + describe 'GET /tourist_tours/new/:tour_id path (new tourist_tour) for different users' do + it 'returns a redirect response if not logged_in ' do + get new_tourist_tour_path(tour) + expect(response).to have_http_status(:found) + end + + it 'returns a redirect response if current_user is agency ' do + sign_in(agency) + get new_tourist_tour_path(tour) + expect(response).to have_http_status(:found) + end + + it 'returns a redirect response if current_user is admin ' do + sign_in(admin) + get new_tourist_tour_path(tour) + expect(response).to have_http_status(:found) + end + + it 'returns a success response if current_user is tourist ' do + sign_in(tourist) + get new_tourist_tour_path(tour) + expect(response).to have_http_status(:ok) + end + end + + describe 'GET /tourist_tours/:id path (show tourist tour) for different users' do + it 'returns a redirect response if not logged_in ' do + get tourist_tours_path(tourist_tour) + expect(response).to have_http_status(:unauthorized) + end + + it 'returns a redirect response if current_user is agency ' do + sign_in(agency) + get tourist_tours_path(tourist_tour) + expect(response).to have_http_status(:unauthorized) + end + + it 'returns a redirect response if current_user is admin ' do + sign_in(admin) + get tourist_tours_path(tourist_tour) + expect(response).to have_http_status(:unauthorized) + end + + it 'returns a success response if current_user is tourist ' do + sign_in(tourist) + get tourist_tours_path(tourist_tour) + expect(response).to have_http_status(:ok) + end + end +end From fca7c4927165374fd143825f2587cc23e0a1bb8e Mon Sep 17 00:00:00 2001 From: Eileenandrea Date: Sat, 17 Jul 2021 21:31:01 +0800 Subject: [PATCH 41/97] changing contact number from integer to string due to error in long number --- db/migrate/20210717023742_change_contact_type.rb | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 db/migrate/20210717023742_change_contact_type.rb diff --git a/db/migrate/20210717023742_change_contact_type.rb b/db/migrate/20210717023742_change_contact_type.rb new file mode 100644 index 000000000..82d942297 --- /dev/null +++ b/db/migrate/20210717023742_change_contact_type.rb @@ -0,0 +1,5 @@ +class ChangeContactType < ActiveRecord::Migration[6.0] + def change + change_column :users, :contact_number, :string + end +end From 5fd0dee2b7e6c03ed4875b87fb314f6ee3a1f45a Mon Sep 17 00:00:00 2001 From: Eileenandrea Date: Sat, 17 Jul 2021 21:54:00 +0800 Subject: [PATCH 42/97] adding tourist_tours view --- app/javascript/packs/tourist_tour_new.js | 22 +++++++++++ app/views/tourist_tours/index.html.erb | 32 ++++++++++++++++ app/views/tourist_tours/new.html.erb | 48 ++++++++++++++++++++++++ app/views/tourist_tours/show.html.erb | 15 ++++++++ 4 files changed, 117 insertions(+) create mode 100644 app/javascript/packs/tourist_tour_new.js create mode 100644 app/views/tourist_tours/index.html.erb create mode 100644 app/views/tourist_tours/new.html.erb create mode 100644 app/views/tourist_tours/show.html.erb diff --git a/app/javascript/packs/tourist_tour_new.js b/app/javascript/packs/tourist_tour_new.js new file mode 100644 index 000000000..830cc4e0a --- /dev/null +++ b/app/javascript/packs/tourist_tour_new.js @@ -0,0 +1,22 @@ +const { start } = require("@popperjs/core") + +// For Guest quantity and Total price +$("#guest_quantity").on("input", function(){ + $("#total_price").val($("#guest_quantity").val() * Number($("#tour-price").attr('data-price'))) +}) + + +// For Date and Duration + +$("#start-date").on("input", function(){ + let duration = Number($("#tour-duration").attr('data-duration')) + let start_date = new Date($("#start-date").val()) + console.log(duration) + console.log($("#start-date").val()) + let end_date = new Date(start_date.setDate(new Date($("#start-date").val()).getDate() + duration)) + let new_month = `${end_date.getMonth()+1 < 9 ? '0' : ''}${end_date.getMonth() + 1}` + let new_day = `${end_date.getDate() < 9 ? '0': ''}${end_date.getDate()}` + + $("#end-date").val(`${end_date.getFullYear()}-${new_month}-${new_day}`) + console.log(`${end_date.getFullYear()}-${new_month}-${new_day}`) +}) \ No newline at end of file diff --git a/app/views/tourist_tours/index.html.erb b/app/views/tourist_tours/index.html.erb new file mode 100644 index 000000000..283fbdc41 --- /dev/null +++ b/app/views/tourist_tours/index.html.erb @@ -0,0 +1,32 @@ +<% if @tourist_tours.count == 0 %> + +
+ <%= image_tag("journey-animate.svg")%> + +
+ +
+<%= link_to 'Travel Now', tours_path, class:"h1 text-secondary"%> +
+ +<%else%> + +

+
+<%@tourist_tours.each do |tourist_tour|%> +
+ <%if tourist_tour.tour.images.attached?%> + + <%end%> +
+

<%= tourist_tour.tour.name%>

+
Total Amount: <%= tourist_tour.amount_bought%>
+
Start Date: <%= tourist_tour.start_date%>
+
End Date: <%= tourist_tour.end_date%>
+ <%= link_to 'Details', tourist_tour_path(tourist_tour),class:"btn btn-secondary", method: :get%> +
+
+<%end%> +
+ +<%end%> \ No newline at end of file diff --git a/app/views/tourist_tours/new.html.erb b/app/views/tourist_tours/new.html.erb new file mode 100644 index 000000000..0345e1f2d --- /dev/null +++ b/app/views/tourist_tours/new.html.erb @@ -0,0 +1,48 @@ +
+
+

<%=@tour.name%>

+ <% if @tour.images.attached?%> + <%@tour.images.each do |image|%> + ... + <%end%> + <%end%> +
+
+
+

<%=@tour.location%>

+

>Price: <%=@tour.price%>

+

>Duration: <%=@tour.duration%> day/s

+

Details:

+

<%=@tour.details%>

+
+ +
+ <%= form_with scope: :tourist_tour, url: tourist_tours_path, local: true do |f|%> +
+ <%= f.label :guest_quantity%>
+ <%= f.number_field :guest_quantity, id: 'guest_quantity'%> +
+ +
+ <%= f.label 'Total Price'%>
+ <%= f.number_field :amount_bought, readonly: true, id: 'total_price', min: 1%> +
+ +
+ <%= f.label :start_date%>
+ <%= f.date_field :start_date, id: 'start-date'%> +
+ +
+ <%= f.label :end_date%>
+ <%= f.date_field :end_date, readonly: true, id: 'end-date'%> +
+ + <%= f.hidden_field :tour_id, value: @tour.id%> + <%= f.submit 'Avail'%> + <%end%> +
+
+
+
+ <%= javascript_pack_tag 'tourist_tour_new' %> \ No newline at end of file diff --git a/app/views/tourist_tours/show.html.erb b/app/views/tourist_tours/show.html.erb new file mode 100644 index 000000000..0c40e40f2 --- /dev/null +++ b/app/views/tourist_tours/show.html.erb @@ -0,0 +1,15 @@ +
+
+
+ <%if @tourist_tour.tour.images.attached?%> + <%@tourist_tour.tour.images.each do |img|%> + + <% end %> + <%end%> +
+
<%= @tourist_tour.tour.name%>
+

<%= @tourist_tour.tour.details.body%>

+ +
+
+
\ No newline at end of file From 19b8488d383b30adfa5b9d6c4d8762ee508b6911 Mon Sep 17 00:00:00 2001 From: Eileenandrea Date: Sat, 17 Jul 2021 22:20:14 +0800 Subject: [PATCH 43/97] adding image to assets --- app/assets/images/journey-animate.svg | 1 + app/assets/images/upload_image.png | Bin 0 -> 112867 bytes 2 files changed, 1 insertion(+) create mode 100644 app/assets/images/journey-animate.svg create mode 100644 app/assets/images/upload_image.png diff --git a/app/assets/images/journey-animate.svg b/app/assets/images/journey-animate.svg new file mode 100644 index 000000000..229ddbde1 --- /dev/null +++ b/app/assets/images/journey-animate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/upload_image.png b/app/assets/images/upload_image.png new file mode 100644 index 0000000000000000000000000000000000000000..1c65a01aa4c0dc8aa755219d485bc32dfbbbcd89 GIT binary patch literal 112867 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 B*Sr7# literal 0 HcmV?d00001 From 2b38747f3390b2e95315580fe5d498f1ddf5aebe Mon Sep 17 00:00:00 2001 From: Eileenandrea Date: Sat, 17 Jul 2021 23:14:06 +0800 Subject: [PATCH 44/97] travel_transaction factory_bot --- test/factories/travel_transactions.rb | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 test/factories/travel_transactions.rb diff --git a/test/factories/travel_transactions.rb b/test/factories/travel_transactions.rb new file mode 100644 index 000000000..0162d275f --- /dev/null +++ b/test/factories/travel_transactions.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :travel_transaction do + total_price {19995} + tourist_tour + agency + end +end From 2a6533272132c438fd9ea17c424b1910e33575ee Mon Sep 17 00:00:00 2001 From: Eileenandrea Date: Sat, 17 Jul 2021 23:15:25 +0800 Subject: [PATCH 45/97] travel_transaction rspec test --- .../tourist_tours_controllers_spec.rb | 2 +- .../travel_transactions_controllers_spec.rb | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 spec/requests/travel_transactions_controllers_spec.rb diff --git a/spec/requests/tourist_tours_controllers_spec.rb b/spec/requests/tourist_tours_controllers_spec.rb index 47d8bb825..46467830d 100644 --- a/spec/requests/tourist_tours_controllers_spec.rb +++ b/spec/requests/tourist_tours_controllers_spec.rb @@ -7,7 +7,7 @@ let!(:tour) { create(:tour, agency: agency) } let!(:tourist_tour) { create(:tourist_tour, tourist: tourist, tour: tour) } - describe 'GET tourist_tour index path for different users' do + describe 'GET tourist_tour index path request response for different users' do it 'returns a redirect response if not logged_in ' do get tourist_tours_path expect(response).to have_http_status(:found) diff --git a/spec/requests/travel_transactions_controllers_spec.rb b/spec/requests/travel_transactions_controllers_spec.rb new file mode 100644 index 000000000..c9b0d3b9f --- /dev/null +++ b/spec/requests/travel_transactions_controllers_spec.rb @@ -0,0 +1,36 @@ +require 'rails_helper' + +RSpec.describe "TravelTransactionsControllers", type: :request do + let!(:admin) { create(:admin) } + let!(:tourist) { create(:tourist) } + let!(:agency) { create(:approved_agency) } + let!(:tour) { create(:tour, agency: agency) } + let!(:tourist_tour) { create(:tourist_tour, tourist: tourist, tour: tour) } + let!(:travel_transaction) { create(:travel_transaction, tourist_tour: tourist_tour, agency: agency) } + + describe 'GET /travel_transactions index path request response for different users' do + it 'returns a redirect response if not logged_in ' do + get travel_transactions_path + expect(response).to have_http_status(:found) + end + + it 'returns a success response if current_user is tourist ' do + sign_in(tourist) + get travel_transactions_path + expect(response).to have_http_status(:ok) + end + + it 'returns a success response if current_user is agency ' do + sign_in(agency) + get travel_transactions_path + expect(response).to have_http_status(:ok) + end + + it 'returns a success response if current_user is admin ' do + sign_in(admin) + get travel_transactions_path + expect(response).to have_http_status(:ok) + end + end + +end From d51178bb7b3fc4f9ce881061b88d894bc6040f5a Mon Sep 17 00:00:00 2001 From: Eileenandrea Date: Sat, 17 Jul 2021 23:15:57 +0800 Subject: [PATCH 46/97] travel_transaction views --- .../stylesheets/travel_transactions.scss | 3 ++ app/helpers/travel_transactions_helper.rb | 2 ++ app/views/travel_transactions/index.html.erb | 29 +++++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 app/assets/stylesheets/travel_transactions.scss create mode 100644 app/helpers/travel_transactions_helper.rb create mode 100644 app/views/travel_transactions/index.html.erb diff --git a/app/assets/stylesheets/travel_transactions.scss b/app/assets/stylesheets/travel_transactions.scss new file mode 100644 index 000000000..2c55d7199 --- /dev/null +++ b/app/assets/stylesheets/travel_transactions.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the travel_transactions controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: https://sass-lang.com/ diff --git a/app/helpers/travel_transactions_helper.rb b/app/helpers/travel_transactions_helper.rb new file mode 100644 index 000000000..53d4c1519 --- /dev/null +++ b/app/helpers/travel_transactions_helper.rb @@ -0,0 +1,2 @@ +module TravelTransactionsHelper +end diff --git a/app/views/travel_transactions/index.html.erb b/app/views/travel_transactions/index.html.erb new file mode 100644 index 000000000..3a085657b --- /dev/null +++ b/app/views/travel_transactions/index.html.erb @@ -0,0 +1,29 @@ +

+ + + + + + + + + + + + + <% @travel_transactions.each do |transaction|%> + + + + + + <%if !transaction.tourist_tour.start_date.nil?%> + + <%end%> + <%if !transaction.tourist_tour.end_date.nil?%> + + <%end%> + + <%end%> + +
Package NameTouristAgencyDate AvailedStart DateEnd Date
<%= transaction.tourist_tour.tour.name%><%= transaction.tourist_tour.tourist.email%><%= transaction.agency.email%><%= transaction.created_at.strftime('%b %d, %Y')%><%= transaction.tourist_tour.start_date.strftime('%b %d, %Y')%><%= transaction.tourist_tour.end_date.strftime('%b %d, %Y')%>
\ No newline at end of file From d3f7d47905facab7e7e5ffdcae0645f381a3ed84 Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Sat, 17 Jul 2021 23:16:04 +0800 Subject: [PATCH 47/97] add image_preview.js --- app/javascript/packs/image_preview.js | 34 +++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 app/javascript/packs/image_preview.js diff --git a/app/javascript/packs/image_preview.js b/app/javascript/packs/image_preview.js new file mode 100644 index 000000000..4a4224393 --- /dev/null +++ b/app/javascript/packs/image_preview.js @@ -0,0 +1,34 @@ +$(function () { + const output = document.getElementById('img_prev'); + // Multiple images preview in browser + var imagesPreview = function(input, placeToInsertImagePreview) { + $(placeToInsertImagePreview).empty() + if (input.files) { + var filesAmount = input.files.length; + + for (i = 0; i < filesAmount; i++) { + var reader = new FileReader(); + + reader.onload = function(event) { + $($.parseHTML('')).attr('src', event.target.result).addClass('tour_img').appendTo(placeToInsertImagePreview); + } + + reader.readAsDataURL(input.files[i]); + } + } + + }; + + $('#tour_images').on('change', function() { + imagesPreview(this, 'div.images-container'); + }); + $('#images-prev').on("click", function (event) { + $('img.active').removeClass('active') + $(event.target).closest("img").toggleClass("active"); + console.log(output); + output.src = $(event.target).closest("img").attr('src') + }); + + + +}); \ No newline at end of file From b5e10977b51fca8b0c09a5cc2fb9c2876148e600 Mon Sep 17 00:00:00 2001 From: Eileenandrea Date: Sat, 17 Jul 2021 23:16:12 +0800 Subject: [PATCH 48/97] travel_transaction controller --- app/controllers/travel_transactions_controller.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 app/controllers/travel_transactions_controller.rb diff --git a/app/controllers/travel_transactions_controller.rb b/app/controllers/travel_transactions_controller.rb new file mode 100644 index 000000000..407cf0699 --- /dev/null +++ b/app/controllers/travel_transactions_controller.rb @@ -0,0 +1,11 @@ +class TravelTransactionsController < ApplicationController + before_action :authenticate_user! + + def index + if tourist_signed_in? || agency_signed_in? + @travel_transactions = current_user.travel_transactions + elsif admin_signed_in? + @travel_transactions = TravelTransaction.all + end + end +end From 502dee8ff3cf274ed16f543bbdc3fdd187f6cc21 Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Sat, 17 Jul 2021 23:16:27 +0800 Subject: [PATCH 49/97] add --- app/assets/stylesheets/tours.scss | 3 ++ app/controllers/tours_controller.rb | 45 +++++++++++++++++++++++ app/helpers/tours_helper.rb | 2 ++ app/views/tours/index.html.erb | 34 ++++++++++++++++++ app/views/tours/new.html.erb | 42 ++++++++++++++++++++++ app/views/tours/show.html.erb | 48 +++++++++++++++++++++++++ spec/requests/tours_controllers_spec.rb | 37 +++++++++++++++++++ 7 files changed, 211 insertions(+) create mode 100644 app/assets/stylesheets/tours.scss create mode 100644 app/controllers/tours_controller.rb create mode 100644 app/helpers/tours_helper.rb create mode 100644 app/views/tours/index.html.erb create mode 100644 app/views/tours/new.html.erb create mode 100644 app/views/tours/show.html.erb create mode 100644 spec/requests/tours_controllers_spec.rb diff --git a/app/assets/stylesheets/tours.scss b/app/assets/stylesheets/tours.scss new file mode 100644 index 000000000..a7517cb6e --- /dev/null +++ b/app/assets/stylesheets/tours.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the tours controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: https://sass-lang.com/ diff --git a/app/controllers/tours_controller.rb b/app/controllers/tours_controller.rb new file mode 100644 index 000000000..e53007178 --- /dev/null +++ b/app/controllers/tours_controller.rb @@ -0,0 +1,45 @@ +class ToursController < ApplicationController + before_action :authenticate_agency!, except: %i[index show] + + def index + if tourist_signed_in? + @tours = Tour.all + elsif agency_signed_in? + @tours = current_agency.tours.all + end + end + + def show + @tour = Tour.find(params[:id]) + end + + def new + @tour = current_agency.tours.build + end + + def create + @tour = current_agency.tours.build(tour_params) + if @tour.valid? + @tour.save + redirect_to tours_path, notice: "Successfully created #{@tour.name} package" + else + redirect_back fallback_location: new_tour_path, alert: @tour.errors.full_messages.first + end + end + + def update + @tour = Tour.find(params[:id]) + if @tour.update(tour_params) + flash[:notice] = 'tour was updated successfully.' + redirect_to tour_path(@tour) + else + render 'edit' + end + end + + private + + def tour_params + params.require(:tour).permit(:name, :price, :location, :duration, :details, images: []) + end +end diff --git a/app/helpers/tours_helper.rb b/app/helpers/tours_helper.rb new file mode 100644 index 000000000..b430fec50 --- /dev/null +++ b/app/helpers/tours_helper.rb @@ -0,0 +1,2 @@ +module ToursHelper +end diff --git a/app/views/tours/index.html.erb b/app/views/tours/index.html.erb new file mode 100644 index 000000000..8db123eba --- /dev/null +++ b/app/views/tours/index.html.erb @@ -0,0 +1,34 @@ +<% if @tours.count == 0 %> + +
+ <%= image_tag("journey-animate.svg")%> + +
+ +
+<%= link_to 'Create Tour', new_tour_path, class:"h1 text-secondary"%> +
+ +<%else%> +

+
+<% @tours.each do |tour|%> +
+ <% if tour.images.attached?%> + ... + <%end%> +
+

Name: <%= tour.name%>

+
Price: <%= tour.price%>
+
Location: <%= tour.location%>
+ <%if tourist_signed_in?%> + <%= link_to 'Show Package', tour_path(tour.id),class:"btn btn-primary"%> + <%else%> + <%= link_to 'Show', tour_path(tour),class:"btn btn-primary"%> + <%end%> +
+
+<%end%> +
+ +<%end%> diff --git a/app/views/tours/new.html.erb b/app/views/tours/new.html.erb new file mode 100644 index 000000000..37abf7e49 --- /dev/null +++ b/app/views/tours/new.html.erb @@ -0,0 +1,42 @@ +
+
+
+

Create a new Tour

+ <%= render 'shared/errors', obj: @tour %> +
+ <%= form_with scope: :tour, url: tours_path, local:true do |f|%> +
+ <%= f.text_field :name, autofocus: true,placeholder:"Package Name",class:"form-control" %> +
+
+ <%= f.number_field :price,placeholder:"Price",class:"form-control" %> +
+
+ <%= f.text_field :location, placeholder: "Tour location" ,class:"form-control" %> +
+
+ <%= f.number_field :duration, step:1, placeholder: "Duration" ,class:"form-control" %> +
+
+ +
+
+
+ <%= f.label :"Select Hero Image",:class=>"form-label"%> + <%= f.file_field :images, multiple: true,:class=>"form-control form-control-lg image-upload"%> +
+
+
+ <%= f.label :"Tour Details", :class=>"form-label"%>
+ <%= f.rich_text_area :details, wrap: "hard", placeholder: "Tour Details here" ,:class=>"form-control blog-body"%> +
+
+ <%= link_to 'Cancel',tours_path, class:'btn btn-danger' %> + <%= f.submit "Post",class:"btn btn-success mx-3" %> +
+ <% end %> +
+
+
+
+<%= javascript_pack_tag 'image_preview' %> \ No newline at end of file diff --git a/app/views/tours/show.html.erb b/app/views/tours/show.html.erb new file mode 100644 index 000000000..631695942 --- /dev/null +++ b/app/views/tours/show.html.erb @@ -0,0 +1,48 @@ +
+
+

<%=@tour.name%>

+ <%= link_to 'Edit', edit_tour_path, class:"nav-link"%> +
+ +
+

Location: <%=@tour.location%>

+

Price: <%=@tour.price%>

+

Duration: <%=@tour.duration%>

+

Details:

+

<%=@tour.details%>

+
+ <%if user_signed_in? && current_user&.type != 'Agency' %> + <%=link_to chat_user_path(user_id: "#{@tour.agency.id}"), method: :post ,class: 'btn btn-primary' do%> + Message Me + <%end%> + <%end%> +
\ No newline at end of file diff --git a/spec/requests/tours_controllers_spec.rb b/spec/requests/tours_controllers_spec.rb new file mode 100644 index 000000000..a238f768c --- /dev/null +++ b/spec/requests/tours_controllers_spec.rb @@ -0,0 +1,37 @@ +require 'rails_helper' + +RSpec.describe 'ToursControllers', type: :request do + let!(:agency) { create(:approved_agency) } + let!(:tour) { create(:tour, agency: agency) } + + before { sign_in(agency) } + + context 'when GET in tours' do + it 'gets all active tours' do + get tours_path + expect(response).to have_http_status(:ok) + end + + it 'gets the specifc tour' do + get tour_path(tour) + expect(response).to have_http_status(:ok) + end + + it 'gets the new tour template' do + get new_tour_path + expect(response).to have_http_status(:ok) + end + end + + context 'when POST and PATCH tours' do + it 'posts a new tour' do + post tours_path, params: { tour: { name: 'Boracay', location: 'Boracay', duration: 3, price: 300, agency: agency } } + expect(response).to redirect_to(tours_path) + end + + it 'updates a tour' do + patch tour_path(tour), params: { tour: { name: 'Boracay', location: 'Boracay', duration: 3, price: 300, agency: agency } } + expect(response).to redirect_to(tour_path) + end + end +end From a1ec9fbf9b23e7ebee30a14cf357670d0a722826 Mon Sep 17 00:00:00 2001 From: Eileenandrea Date: Sat, 17 Jul 2021 23:22:46 +0800 Subject: [PATCH 50/97] rubocop fix --- app/controllers/travel_transactions_controller.rb | 10 +++++----- spec/requests/travel_transactions_controllers_spec.rb | 8 +++----- test/factories/agencies.rb | 2 +- test/factories/travel_transactions.rb | 2 +- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/app/controllers/travel_transactions_controller.rb b/app/controllers/travel_transactions_controller.rb index 407cf0699..a91e5cb55 100644 --- a/app/controllers/travel_transactions_controller.rb +++ b/app/controllers/travel_transactions_controller.rb @@ -2,10 +2,10 @@ class TravelTransactionsController < ApplicationController before_action :authenticate_user! def index - if tourist_signed_in? || agency_signed_in? - @travel_transactions = current_user.travel_transactions - elsif admin_signed_in? - @travel_transactions = TravelTransaction.all - end + if tourist_signed_in? || agency_signed_in? + @travel_transactions = current_user.travel_transactions + elsif admin_signed_in? + @travel_transactions = TravelTransaction.all + end end end diff --git a/spec/requests/travel_transactions_controllers_spec.rb b/spec/requests/travel_transactions_controllers_spec.rb index c9b0d3b9f..d463ff125 100644 --- a/spec/requests/travel_transactions_controllers_spec.rb +++ b/spec/requests/travel_transactions_controllers_spec.rb @@ -1,12 +1,11 @@ require 'rails_helper' -RSpec.describe "TravelTransactionsControllers", type: :request do +RSpec.describe 'TravelTransactionsControllers', type: :request do let!(:admin) { create(:admin) } let!(:tourist) { create(:tourist) } let!(:agency) { create(:approved_agency) } - let!(:tour) { create(:tour, agency: agency) } - let!(:tourist_tour) { create(:tourist_tour, tourist: tourist, tour: tour) } - let!(:travel_transaction) { create(:travel_transaction, tourist_tour: tourist_tour, agency: agency) } + let!(:tourist_tour) { create(:tourist_tour, tourist: tourist) } + let(:travel_transaction) { create(:travel_transaction, tourist_tour: tourist_tour, agency: agency) } describe 'GET /travel_transactions index path request response for different users' do it 'returns a redirect response if not logged_in ' do @@ -32,5 +31,4 @@ expect(response).to have_http_status(:ok) end end - end diff --git a/test/factories/agencies.rb b/test/factories/agencies.rb index 058bb9cde..a6d6d6a0f 100644 --- a/test/factories/agencies.rb +++ b/test/factories/agencies.rb @@ -1,6 +1,6 @@ FactoryBot.define do factory :agency do - email { "#{agency_name.gsub(' ', '')}@email.com" } + email { "#{agency_name.gsub(' ', '')}#{rand(1..100)}@email.com" } password { 'secure123' } address { 'Antipolo City' } agency_name { 'Tourist Travel Tours' } diff --git a/test/factories/travel_transactions.rb b/test/factories/travel_transactions.rb index 0162d275f..ec288ca50 100644 --- a/test/factories/travel_transactions.rb +++ b/test/factories/travel_transactions.rb @@ -1,6 +1,6 @@ FactoryBot.define do factory :travel_transaction do - total_price {19995} + total_price { 19_995 } tourist_tour agency end From 38c395d1ba360883aa5a79dcbe1ce8c7d1289753 Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Sat, 17 Jul 2021 23:44:55 +0800 Subject: [PATCH 51/97] modify home and tour views --- app/controllers/home_controller.rb | 8 ++++++ app/views/tours/edit.html.erb | 42 ++++++++++++++++++++++++++++++ app/views/tours/new.html.erb | 2 +- 3 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 app/views/tours/edit.html.erb diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index 6d3fe90f7..e33fd785d 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -1,3 +1,11 @@ class HomeController < ApplicationController + before_action :authenticate_user! + before_action :redirect def index; end + + private + + def redirect + redirect_to tours_path, notice: 'Succesfully Signed-in!' if agency_signed_in? + end end diff --git a/app/views/tours/edit.html.erb b/app/views/tours/edit.html.erb new file mode 100644 index 000000000..980a82b4e --- /dev/null +++ b/app/views/tours/edit.html.erb @@ -0,0 +1,42 @@ +
+
+
+

Create a new Tour

+ <%= render 'shared/errors', obj: @tour %> +
+ <%= form_with scope: :tour, model: @tour,method: :patch, local:true do |f|%> +
+ <%= f.text_field :name, autofocus: true,placeholder:"Package Name",class:"form-control" %> +
+
+ <%= f.number_field :price, step: 0.001,placeholder:"Price",class:"form-control" %> +
+
+ <%= f.text_field :location, placeholder: "Tour location" ,class:"form-control" %> +
+
+ <%= f.number_field :duration, step:1, placeholder: "Duration" ,class:"form-control" %> +
+
+ +
+
+
+ <%= f.label :"Select Hero Image",:class=>"form-label"%> + <%= f.file_field :images, multiple: true,:class=>"form-control form-control-lg image-upload"%> +
+
+
+ <%= f.label :"Tour Details", :class=>"form-label"%>
+ <%= f.rich_text_area :details, wrap: "hard", placeholder: "Tour Details here" ,:class=>"form-control blog-body"%> +
+
+ <%= link_to 'Cancel',tours_path, class:'btn btn-danger' %> + <%= f.submit "Update",class:"btn btn-success mx-3" %> +
+ <% end %> +
+
+
+
+<%= javascript_pack_tag 'image_preview' %> \ No newline at end of file diff --git a/app/views/tours/new.html.erb b/app/views/tours/new.html.erb index 37abf7e49..7ec95d262 100644 --- a/app/views/tours/new.html.erb +++ b/app/views/tours/new.html.erb @@ -9,7 +9,7 @@ <%= f.text_field :name, autofocus: true,placeholder:"Package Name",class:"form-control" %>
- <%= f.number_field :price,placeholder:"Price",class:"form-control" %> + <%= f.number_field :price, step: 0.001,placeholder:"Price",class:"form-control" %>
<%= f.text_field :location, placeholder: "Tour location" ,class:"form-control" %> From cfdec634426ae27fd96970fe8bcbd4a156ac9a4a Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Sat, 17 Jul 2021 23:45:11 +0800 Subject: [PATCH 52/97] add update test for tour controller --- app/controllers/tours_controller.rb | 4 ++++ spec/requests/tours_controllers_spec.rb | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/app/controllers/tours_controller.rb b/app/controllers/tours_controller.rb index e53007178..fbf53066d 100644 --- a/app/controllers/tours_controller.rb +++ b/app/controllers/tours_controller.rb @@ -27,6 +27,10 @@ def create end end + def edit + @tour = Tour.find(params[:id]) + end + def update @tour = Tour.find(params[:id]) if @tour.update(tour_params) diff --git a/spec/requests/tours_controllers_spec.rb b/spec/requests/tours_controllers_spec.rb index a238f768c..f9f4bfb7d 100644 --- a/spec/requests/tours_controllers_spec.rb +++ b/spec/requests/tours_controllers_spec.rb @@ -21,6 +21,11 @@ get new_tour_path expect(response).to have_http_status(:ok) end + + it 'gets the edit tour template' do + get edit_tour_path(tour) + expect(response).to have_http_status(:ok) + end end context 'when POST and PATCH tours' do From f21b96acafc73cf465ca14275b7639475d2e67ef Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Sun, 18 Jul 2021 01:00:27 +0800 Subject: [PATCH 53/97] add edit method for agencies resources --- config/routes.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index 545c0ed9f..6f9ce9781 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -8,7 +8,7 @@ root "home#index" resources :admins - resources :agencies, only: [:index, :show] + resources :agencies, only: [:index, :show, :update] resources :tourists, only: :show resources :tours, except: [:destroy] resources :tourist_tours, except: [:new, :edit, :update] From a3bf7c6f03cb919c60d5ebda2db324ed06436998 Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Sun, 18 Jul 2021 01:00:46 +0800 Subject: [PATCH 54/97] modify agency email --- test/factories/agencies.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/factories/agencies.rb b/test/factories/agencies.rb index a6d6d6a0f..c2241c63e 100644 --- a/test/factories/agencies.rb +++ b/test/factories/agencies.rb @@ -1,6 +1,6 @@ FactoryBot.define do factory :agency do - email { "#{agency_name.gsub(' ', '')}#{rand(1..100)}@email.com" } + sequence(:email) { |n| "#{agency_name.gsub(' ', '')}#{n}@email.com" } password { 'secure123' } address { 'Antipolo City' } agency_name { 'Tourist Travel Tours' } From d313e8be07b70e4cec5518758e0abb8a913006dd Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Sun, 18 Jul 2021 01:01:11 +0800 Subject: [PATCH 55/97] add authentication for user approved column --- app/models/user.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/models/user.rb b/app/models/user.rb index fbdd31de6..257f3fc2f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -31,4 +31,12 @@ def username "#{first_name} #{last_name}" end end + + def active_for_authentication? + super && approved + end + + def inactive_message + approved ? super : :not_approved + end end From 9efe86180b935ab7f8c61f7d5cb0623f5a4b6ead Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Sun, 18 Jul 2021 01:01:39 +0800 Subject: [PATCH 56/97] add agency update method --- app/controllers/agencies_controller.rb | 16 ++++++++++++++++ spec/requests/agencies_controllers_spec.rb | 9 ++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/app/controllers/agencies_controller.rb b/app/controllers/agencies_controller.rb index a9b5c1712..542b8673e 100644 --- a/app/controllers/agencies_controller.rb +++ b/app/controllers/agencies_controller.rb @@ -11,4 +11,20 @@ def show @agency = Agency.find(params[:id]) @tours = @agency.tours end + + def update + @agency = Agency.find(params[:id]) + if @agency.valid? && @agency.update(agency_params) + @agency.save + redirect_to admins_path, notice: 'Agency approved!' + else + redirect_back fallback_location: agencies_path, alert: @agency.errors.first + end + end + + private + + def agency_params + params.require(:agency).permit(:verified_by, :approved) + end end diff --git a/spec/requests/agencies_controllers_spec.rb b/spec/requests/agencies_controllers_spec.rb index 3f74ef184..764601dcb 100644 --- a/spec/requests/agencies_controllers_spec.rb +++ b/spec/requests/agencies_controllers_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' RSpec.describe 'AgenciesControllers', type: :request do - let!(:agency) { Agency.create(email: 'agency@email.com', agency_name: 'AgencyOne', password: '1234567') } + let!(:agency) { Agency.create(email: 'agency@email.com', agency_name: 'AgencyOne', address: 'address', password: '1234567') } let!(:admin) { Admin.create(email: 'admin@email.com', first_name: 'Admin', last_name: 'Last', password: '1234567') } before { sign_in(admin) } @@ -17,4 +17,11 @@ expect(response).to have_http_status(:ok) end end + + context 'when PATCH agencies' do + it 'updates a specific agency' do + patch agency_path(agency), params: { agency: { email: 'agency5@email.com', agency_name: 'AgencyOne', password: '1234567', verified_by: admin.id } } + expect(response).to redirect_to(admins_path) + end + end end From 0176669999d6adcc9268c4515e0619d0bd152432 Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Sun, 18 Jul 2021 01:02:07 +0800 Subject: [PATCH 57/97] add verified by --- app/views/agencies/show.html.erb | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/app/views/agencies/show.html.erb b/app/views/agencies/show.html.erb index 177a87ea4..a933f3bb8 100644 --- a/app/views/agencies/show.html.erb +++ b/app/views/agencies/show.html.erb @@ -67,8 +67,8 @@ <%= @agency.tours.count%>
-
-
+
+
Rating
@@ -76,6 +76,17 @@ <%#= @agency.address%> Very Good
+ <% if @agency.verified_by %> +
+
+
+
Verified By:
+
+
+ <%= Admin.find(@agency.verified_by).email%> +
+
+ <% end %>
From 84128b52508b1f459ac5780e3ee8ff3271cd7373 Mon Sep 17 00:00:00 2001 From: Leif Bersamina Date: Sun, 18 Jul 2021 01:02:18 +0800 Subject: [PATCH 58/97] add approve button --- app/views/agencies/index.html.erb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/views/agencies/index.html.erb b/app/views/agencies/index.html.erb index 2c935b933..bc5cb6fbf 100644 --- a/app/views/agencies/index.html.erb +++ b/app/views/agencies/index.html.erb @@ -5,6 +5,10 @@ Agency Agency Email Address + <% if admin_signed_in? %> + Action + <% end %> + @@ -13,6 +17,9 @@ <%= link_to agency.agency_name, agency_path(agency)%> <%= agency.email%> <%= agency.address%> + <% if admin_signed_in? %> + <%= button_to 'Approve', agency_path(agency), params: {agency: { approved: true, verified_by: current_admin.id }}, method: :patch, local: true%> + <% end %> <%end%> From 716c9124262124356378e847a0b3d3c78a413dba Mon Sep 17 00:00:00 2001 From: Eileenandrea Date: Sun, 18 Jul 2021 16:20:12 +0800 Subject: [PATCH 59/97] edit tourist factories to make email more dynamic --- test/factories/tourists.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/factories/tourists.rb b/test/factories/tourists.rb index 3152e23b6..0d1aa4d5d 100644 --- a/test/factories/tourists.rb +++ b/test/factories/tourists.rb @@ -1,6 +1,6 @@ FactoryBot.define do factory :tourist do - email { "#{first_name.gsub(' ', '')}@email.com" } + sequence(:email) { |n| "#{first_name.gsub(' ', '')}#{n}@email.com" } password { 'secure123' } address { 'Antipolo City' } approved { true } From e8c5c09ae1c2e681070ff21581b7c89e5b510d21 Mon Sep 17 00:00:00 2001 From: Eileenandrea Date: Sun, 18 Jul 2021 16:21:38 +0800 Subject: [PATCH 60/97] chat room rspec request test --- spec/requests/chat_rooms_controllers_spec.rb | 45 ++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 spec/requests/chat_rooms_controllers_spec.rb diff --git a/spec/requests/chat_rooms_controllers_spec.rb b/spec/requests/chat_rooms_controllers_spec.rb new file mode 100644 index 000000000..5fc142cb2 --- /dev/null +++ b/spec/requests/chat_rooms_controllers_spec.rb @@ -0,0 +1,45 @@ +require 'rails_helper' + +RSpec.describe 'ChatRoomsControllers', type: :request do + let!(:tourist) { create(:tourist) } + let!(:tourist2) { create(:tourist) } + let!(:agency) { create(:approved_agency) } + let!(:chat_room) { ChatRoom.create } + + before do + chat_room.users << tourist + chat_room.users << agency + end + + describe 'GET chat_rooms#index path' do + it 'returns a redirect response if not logged_in' do + get chat_rooms_path + expect(response).to have_http_status(:found) + end + + it 'returns a success response if logged_in' do + sign_in(tourist) + get chat_rooms_path + expect(response).to have_http_status(:ok) + end + end + + describe 'GET /chat_rooms/:id show path request response for different users' do + it 'returns a redirect response if not logged_in' do + get chat_room_path(chat_room) + expect(response).to have_http_status(:found) + end + + it 'returns a success response if logged_in user owns the chatroom' do + sign_in(tourist) + get chat_room_path(chat_room) + expect(response).to have_http_status(:ok) + end + + it 'returns a redirect response if logged_in user is not the owener of the chatroom' do + sign_in(tourist2) + get chat_room_path(chat_room) + expect(response).to have_http_status(:found) + end + end +end From 2be09c545e2291cda4c9f3110e49e7eee79264c7 Mon Sep 17 00:00:00 2001 From: Eileenandrea Date: Sun, 18 Jul 2021 16:22:48 +0800 Subject: [PATCH 61/97] edit tourist, agency and chat room model to fix association issue --- app/models/agency.rb | 2 ++ app/models/chat_room_user.rb | 2 +- app/models/tourist.rb | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/models/agency.rb b/app/models/agency.rb index ed135c19f..6fe728d6f 100644 --- a/app/models/agency.rb +++ b/app/models/agency.rb @@ -3,4 +3,6 @@ class Agency < User has_many :travel_transactions, dependent: :nullify has_many :reviews, dependent: :nullify + has_many :chat_room_users, dependent: :nullify, foreign_key: 'user_id', inverse_of: :user + has_many :chat_rooms, through: :chat_room_users end diff --git a/app/models/chat_room_user.rb b/app/models/chat_room_user.rb index a50db186f..b3dac6b32 100644 --- a/app/models/chat_room_user.rb +++ b/app/models/chat_room_user.rb @@ -1,4 +1,4 @@ class ChatRoomUser < ApplicationRecord - belongs_to :user + belongs_to :user, class_name: 'User' belongs_to :chat_room end diff --git a/app/models/tourist.rb b/app/models/tourist.rb index 1dbee3353..def35e7e0 100644 --- a/app/models/tourist.rb +++ b/app/models/tourist.rb @@ -3,8 +3,9 @@ class Tourist < User has_many :tours, through: :tourist_tours has_many :travel_transactions, through: :tourist_tours - has_many :reviews, dependent: :nullify + has_many :chat_room_users, dependent: :nullify, foreign_key: 'user_id', inverse_of: :user + has_many :chat_rooms, through: :chat_room_users after_create :set_approved_to_true From 47c70d0003ef300edf385c46733fb032cd1efc9c Mon Sep 17 00:00:00 2001 From: Eileenandrea Date: Sun, 18 Jul 2021 16:23:23 +0800 Subject: [PATCH 62/97] chat_room and messages controller --- app/controllers/chat_rooms_controller.rb | 26 ++++++++++++++++++++++++ app/controllers/messages_controller.rb | 19 +++++++++++++++++ app/helpers/chat_rooms_helper.rb | 2 ++ app/helpers/messages_helper.rb | 2 ++ 4 files changed, 49 insertions(+) create mode 100644 app/controllers/chat_rooms_controller.rb create mode 100644 app/controllers/messages_controller.rb create mode 100644 app/helpers/chat_rooms_helper.rb create mode 100644 app/helpers/messages_helper.rb diff --git a/app/controllers/chat_rooms_controller.rb b/app/controllers/chat_rooms_controller.rb new file mode 100644 index 000000000..ed6b42ce1 --- /dev/null +++ b/app/controllers/chat_rooms_controller.rb @@ -0,0 +1,26 @@ +class ChatRoomsController < ApplicationController + before_action :authenticate_user! + before_action :set_room, only: %i[show] + def chat_user + user = User.find(params[:user_id]) + ChatRoom.create(users: [current_user, user]) unless current_user.chat_room(user) + redirect_to chat_rooms_path + end + + def show + @rooms = current_user.chat_rooms + render 'index' + end + + def index + @rooms = current_user.chat_rooms + end + + private + + # Use callbacks to share common setup or constraints between actions. + def set_room + @room = current_user.chat_rooms.find_by(id: params[:id]) + redirect_to chat_rooms_path unless @room + end +end diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb new file mode 100644 index 000000000..a53cb8994 --- /dev/null +++ b/app/controllers/messages_controller.rb @@ -0,0 +1,19 @@ +class MessagesController < ApplicationController + def create + @message = Message.new(message_params) + @message.user = current_user + @message.save + mine = ApplicationController.render(partial: 'messages/mine', locals: { message: @message }) + theirs = ApplicationController.render(partial: 'messages/theirs', locals: { message: @message }) + message = @message + ActionCable.server.broadcast "room_channel_#{message.chat_room_id}", { mine: mine, theirs: theirs, message: message } + end + + private + + # Use callbacks to share common setup or constraints between actions. + # Only allow a list of trusted parameters through. + def message_params + params.require(:message).permit(:body, :user_id, :chat_room_id) + end +end diff --git a/app/helpers/chat_rooms_helper.rb b/app/helpers/chat_rooms_helper.rb new file mode 100644 index 000000000..4d46c38db --- /dev/null +++ b/app/helpers/chat_rooms_helper.rb @@ -0,0 +1,2 @@ +module ChatRoomsHelper +end diff --git a/app/helpers/messages_helper.rb b/app/helpers/messages_helper.rb new file mode 100644 index 000000000..f1bca9f6c --- /dev/null +++ b/app/helpers/messages_helper.rb @@ -0,0 +1,2 @@ +module MessagesHelper +end From 87176db2d10b7908f1d528e339bbf7dfe834a3be Mon Sep 17 00:00:00 2001 From: Eileenandrea Date: Sun, 18 Jul 2021 16:23:47 +0800 Subject: [PATCH 63/97] room channel --- app/channels/room_channel.rb | 9 +++++++ app/javascript/channels/room_channel.js | 33 +++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 app/channels/room_channel.rb create mode 100644 app/javascript/channels/room_channel.js diff --git a/app/channels/room_channel.rb b/app/channels/room_channel.rb new file mode 100644 index 000000000..b2de416c1 --- /dev/null +++ b/app/channels/room_channel.rb @@ -0,0 +1,9 @@ +class RoomChannel < ApplicationCable::Channel + def subscribed + stream_from "room_channel_#{params[:room_id]}" + end + + def unsubscribed + # Any cleanup needed when channel is unsubscribed + end +end diff --git a/app/javascript/channels/room_channel.js b/app/javascript/channels/room_channel.js new file mode 100644 index 000000000..cf12cb0f6 --- /dev/null +++ b/app/javascript/channels/room_channel.js @@ -0,0 +1,33 @@ +import consumer from "./consumer" + +document.addEventListener('turbolinks:load', () => { + const room_id = $('#room-id').attr('data-room-id'); + consumer.subscriptions.create({ channel: "RoomChannel", room_id: room_id }, { + connected() { + console.log(`Connected to Channel ${room_id}`) + // Called when the subscription is ready for use on the server + }, + + disconnected() { + // Called when the subscription has been terminated by the server + }, + + received(data) { + console.log(room_id) + const user_id = $('#user-id').attr('data-user-id'); + let html; + console.log(user_id); + console.log(data.message.user_id); + if (user_id == data.message.user_id) { + html = data.mine + } + else{ + html = data.theirs + } + if (room_id == data.message.chat_room_id) { + $(`#message-container-${data.message.chat_room_id}`).append(html); + } + + } + }); +}) \ No newline at end of file From 7c6850aaaa858261959b47481a58f10f25fa9c80 Mon Sep 17 00:00:00 2001 From: Eileenandrea Date: Sun, 18 Jul 2021 16:24:10 +0800 Subject: [PATCH 64/97] chat rooms and messages views --- app/assets/stylesheets/chat_rooms.scss | 56 ++++++++++++++++++++++++++ app/assets/stylesheets/messages.scss | 3 ++ app/views/chat_rooms/index.html.erb | 41 +++++++++++++++++++ app/views/messages/_form.html.erb | 23 +++++++++++ app/views/messages/_message.html.erb | 11 +++++ app/views/messages/_mine.html.erb | 11 +++++ app/views/messages/_theirs.html.erb | 11 +++++ 7 files changed, 156 insertions(+) create mode 100644 app/assets/stylesheets/chat_rooms.scss create mode 100644 app/assets/stylesheets/messages.scss create mode 100644 app/views/chat_rooms/index.html.erb create mode 100644 app/views/messages/_form.html.erb create mode 100644 app/views/messages/_message.html.erb create mode 100644 app/views/messages/_mine.html.erb create mode 100644 app/views/messages/_theirs.html.erb diff --git a/app/assets/stylesheets/chat_rooms.scss b/app/assets/stylesheets/chat_rooms.scss new file mode 100644 index 000000000..4e02426fa --- /dev/null +++ b/app/assets/stylesheets/chat_rooms.scss @@ -0,0 +1,56 @@ +// Place all the styles related to the chat_rooms controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: https://sass-lang.com/ + +.chat-room { + min-height: 80vh; + overflow-x: hidden; + .message{ + display:inline-block; + width: 100%; + .content-container{ + width: max-content; + .content{ + background-color: #eee; + padding: 10px 30px 10px 15px; + border-radius: 15px; + } + } + &.me{ + .content-container{ + float: right; + .content{ + background-color: #0d6efd; + color: white; + } + } + } + + } + .author{ + font-size: 0.6rem; + color: #777777; + margin-left: 10px; + } + .chat-box{ + position: absolute; + bottom: 0; + padding-bottom: 20px; + width: calc(100%-15px) + + input{ + height: 45px !important; + font-size: 18px; + padding: 8px; + } + } + .btn{ + height: 45px !important; + } + +} + +.message-container{ + height: 65vh; + overflow-y: scroll; +} \ No newline at end of file diff --git a/app/assets/stylesheets/messages.scss b/app/assets/stylesheets/messages.scss new file mode 100644 index 000000000..a3fdc8d23 --- /dev/null +++ b/app/assets/stylesheets/messages.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the messages controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: https://sass-lang.com/ diff --git a/app/views/chat_rooms/index.html.erb b/app/views/chat_rooms/index.html.erb new file mode 100644 index 000000000..5f03edf2e --- /dev/null +++ b/app/views/chat_rooms/index.html.erb @@ -0,0 +1,41 @@ +
>
+
>
+
+
+
+

Hello, <%=current_user&.first_name%>

+ <% @rooms.each do |room| %> + <%= link_to room do%> +
+
+ <%if room.chat_mate(current_user).profile_pic.attached?%> + + <%end%> + <%=room.chat_mate(current_user).username%> +
+
+ <%end%> + <%end%> +
+
+ <% if @room.present?%> +
+ +
+ <% @room.messages.each do |message|%> + <%=render 'messages/message', message: message%> + <%end%> +
+
+ <%= render 'messages/form', message: Message.new, room: @room %> +
+
+ <%end%> +
+
\ No newline at end of file diff --git a/app/views/messages/_form.html.erb b/app/views/messages/_form.html.erb new file mode 100644 index 000000000..ce14ed75c --- /dev/null +++ b/app/views/messages/_form.html.erb @@ -0,0 +1,23 @@ +<%= form_for(message, html:{class:"row gx-3 ui reply form", role: "form"}, url: messages_path, remote: true) do |form| %> + <% if message.errors.any? %> +
+

<%= pluralize(message.errors.count, "error") %> prohibited this message from being saved:

+ +
    + <% message.errors.each do |error| %> +
  • <%= error.full_message %>
  • + <% end %> +
+
+ <% end %> + + <%= form.hidden_field :chat_room_id, value: room.id %> + +
+ <%= form.text_field :body, placeholder: 'Type your message here...', class: 'form-control' %> +
+ +
+ <%= form.submit "Send", class: 'btn btn-primary' %> +
+<% end %> diff --git a/app/views/messages/_message.html.erb b/app/views/messages/_message.html.erb new file mode 100644 index 000000000..22e07f272 --- /dev/null +++ b/app/views/messages/_message.html.erb @@ -0,0 +1,11 @@ +
+
+
+ <%=message.body%> +
+
+ <%=message.user.username%> +
+ +
+
\ No newline at end of file diff --git a/app/views/messages/_mine.html.erb b/app/views/messages/_mine.html.erb new file mode 100644 index 000000000..3edfb1183 --- /dev/null +++ b/app/views/messages/_mine.html.erb @@ -0,0 +1,11 @@ +
+
+
+ <%=message.body%> +
+
+ <%=message.user.username%> +
+ +
+
\ No newline at end of file diff --git a/app/views/messages/_theirs.html.erb b/app/views/messages/_theirs.html.erb new file mode 100644 index 000000000..14df3f828 --- /dev/null +++ b/app/views/messages/_theirs.html.erb @@ -0,0 +1,11 @@ +
+
+
+ <%=message.body%> +
+
+ <%=message.user.username%> +
+ +
+
\ No newline at end of file From 5779e76eec4257b8245d8579697b5d558ee69dbb Mon Sep 17 00:00:00 2001 From: Eileenandrea Date: Sun, 18 Jul 2021 16:24:35 +0800 Subject: [PATCH 65/97] adding chat_room routes --- config/routes.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/routes.rb b/config/routes.rb index 6f9ce9781..286522b3c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -13,6 +13,9 @@ resources :tours, except: [:destroy] resources :tourist_tours, except: [:new, :edit, :update] resources :travel_transactions, only: [:index] + resources :chat_rooms, only: [:index, :show] + resources :messages, only: [:create] + post 'chat_user', to: 'chat_rooms#chat_user', as: 'chat_user' get '/tourist_tours/new/:tour_id', to: 'tourist_tours#new', as: 'new_tourist_tour' end From 1906452df8e55f166e07a69f659d009bc41de5f1 Mon Sep 17 00:00:00 2001 From: Ronald Magno <74632509+dlanoronald123@users.noreply.github.com> Date: Sun, 18 Jul 2021 20:54:24 +0800 Subject: [PATCH 66/97] Added favicon --- app/assets/images/favicon.ico | Bin 0 -> 1150 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 app/assets/images/favicon.ico diff --git a/app/assets/images/favicon.ico b/app/assets/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..be60b97398110ae237135f6126db211e7a9c458f GIT binary patch literal 1150 zcmZvbc}!GS6vpqk#5Bsx8$iU=1r&z2&B7~7XRyw&6j|mmLQonj{Lv^9wW;C~NdytQ zikN`d(x_Vl09&CKBW#&zPSU#^@|;>KKOo(!-P3$C9)@k3+sw6+Ne00JcmR>}m`d z?Al{_*x4`{11;>YVBJfKSGOt2_gJEKf3_m_U94IE{ozO5jR%LW?Cl_eL<>8>> zX-)uD#V)MMjKcc7DAcWX-91{9o^+_v%M^Ji`n|OBuT!;#(PYODNOE3;UAzhd zR>Vu~SSY@aTxACQiUql{1_xT`1D*7KML?ciKv~B08x@5!XXiVumONDm`V}+Ml*L-iLS zP0EJXmSyyyt5#mezl4|id4ufsfFl%Rek<}_5m@xrS|ljv;I=m-Q}JNw!V-9ub;yw; z>7Ky#cL_Ub7_Bt~3gW|QHg8g$JiZmWb3V;09BTsE_@tr_3HB}|M;B1-My?+zx0!TY zujNSQ`yc5tj(<^%W$sxU8@SDuFIvd#Ex+X&*-9;mZ6&{_CX%2B%58OF676UC}1 zN{oE6I$NUnVZ1(rT-l5d-gTpD-EVl?aRCeFr;^{?LiYv2cn5h5>SHR|pXNxGN4!5i zi#(e1*i0 Date: Sun, 18 Jul 2021 21:32:09 +0800 Subject: [PATCH 67/97] Add font awesome gem --- Gemfile | 1 + Gemfile.lock | 3 +++ 2 files changed, 4 insertions(+) diff --git a/Gemfile b/Gemfile index 2b9b4834a..2c3d72d04 100644 --- a/Gemfile +++ b/Gemfile @@ -18,6 +18,7 @@ gem 'devise' gem 'hamlit-rails' gem "aws-sdk-s3", require: false gem "mini_magick" +gem "font-awesome-rails" group :development, :test do gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] diff --git a/Gemfile.lock b/Gemfile.lock index 6fc02a586..9c32c467b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -100,6 +100,8 @@ GEM factory_bot (~> 6.1.0) railties (>= 5.0.0) ffi (1.15.0) + font-awesome-rails (4.7.0.7) + railties (>= 3.2, < 7) globalid (0.4.2) activesupport (>= 4.2.0) hamlit (2.15.0) @@ -294,6 +296,7 @@ DEPENDENCIES database_rewinder devise factory_bot_rails + font-awesome-rails hamlit-rails image_processing jbuilder (~> 2.7) From de3ff4a1c1be931f2b62e47271ba2772e0ca7420 Mon Sep 17 00:00:00 2001 From: dlanoronald123 Date: Sun, 18 Jul 2021 21:32:58 +0800 Subject: [PATCH 68/97] Update home page --- app/assets/stylesheets/application.css | 183 +++++++++++++++++++++++- app/views/home/index.html.erb | 190 ++++++++++++++++++++----- 2 files changed, 337 insertions(+), 36 deletions(-) diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 22cf39854..b953f8a0a 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -12,6 +12,7 @@ * *= require bootstrap + *= require font-awesome *= require_tree . *= require_self */ @@ -68,9 +69,12 @@ a{ #image{ height:100vh; - } +#cover{ + height:30vh; + max-width:100%; +} #editImage{ width: 400px; @@ -167,16 +171,24 @@ a{ color:#ffffff; } + + #services{ + font-family: 'PT Sans Narrow', sans-serif; + font-size: 4rem; + } + + .feature{ + background-image: linear-gradient(to bottom, rgba(39, 38, 38, 0), rgb(34, 33, 33)); + } /* NAVBAR */ .carousel-content { position: absolute; - bottom: 35%; + bottom: 30%; left: 10%; z-index: 20; color: white; - text-shadow: 0 1px 2px rgba(0,0,0,.6); } .carousel-content h1 { @@ -192,3 +204,168 @@ a{ background-repeat: no-repeat; background-size: cover; } + +#card{ + height:30vh; + max-width:100%; +} + +/* PARALLAX */ + +#parallax { + /* The image used */ + background-image: url("https://images.unsplash.com/photo-1610552050890-fe99536c2615?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1382&q=80"); + + /* Set a specific height */ + min-height: 400px; + + /* Create the parallax scrolling effect */ + background-attachment: fixed; + background-position: center; + background-repeat: no-repeat; + background-size: cover; +} + +#caption{ + font-family: 'PT Sans Narrow', sans-serif; + font-size: 4rem; + color:rgb(0, 0, 0); + text-shadow: 2px 2px #ffffff; +} + + + +/* TEAM SECTION */ + +.our-team-section { + position: relative; + padding-top: 40px; + padding-bottom: 40px; + background-image: linear-gradient(to bottom, rgba(39, 38, 38, 0), rgb(34, 33, 33)); +} + +.our-team-section:before { + position: absolute; + top: -0; + left: 0; + content: " "; + background: url(img/service-section-bottom.png); + background-size: 100% 100px; + width: 100%; + height: 100px; + float: left; + z-index: 99; +} +.our-team { + padding: 30px 0 40px; + background: #f9f9f9; + text-align: center; + overflow: hidden; + position: relative; + border-bottom: 5px solid #00325a; +} +.our-team:hover{ + border-bottom: 5px solid #2f2f2f; +} + +.our-team .pic{ + display: inline-block; + width: 130px; + height: 130px; + margin-bottom: 50px; + z-index: 1; + position: relative; +} +.our-team .pic:before { + content: ""; + width: 100%; + height: 100%; + border-radius: 50%; + background: #00325a; + position: absolute; + bottom: 135%; + right: 0; + left: 0; + opacity: 1; + transform: scale(3); + transition: all 0.3s linear 0s; +} +.our-team:hover .pic:before{ +height: 100%; + background: #2f2f2f; +} +.our-team .pic:after { + content: ""; + width: 100%; + height: 100%; + border-radius: 50%; + background: #ffffff00; + position: absolute; + top: 0; + left: 0; + z-index: 1; + transition: all 0.3s linear 0s; +} +.our-team:hover .pic:after{ + background: #7ab92d; +} +.our-team .pic img { + width: 100%; + height: auto; + border-radius: 50%; + transform: scale(1); + transition: all 0.9s ease 0s; + box-shadow: 0 0 0 14px #f7f5ec; + transform: scale(0.7); + position: relative; + z-index: 2; +} +.our-team:hover .pic img{ + box-shadow: 0 0 0 14px #f7f5ec; + transform: scale(0.7); +} +.our-team .team-content{ margin-bottom: 30px; } +.our-team .title{ + font-size: 22px; + font-weight: 700; + color: #4e5052; + letter-spacing: 1px; + text-transform: capitalize; + margin-bottom: 5px; +} +.our-team .post{ + display: block; + font-size: 15px; + color: #4e5052; + text-transform:capitalize; +} +.our-team .social{ + width: 100%; + padding: 0; + margin: 0; + background: #2f2f2f; + position: absolute; + bottom: -100px; + left: 0; + transition: all 0.5s ease 0s; +} +.our-team:hover .social{ bottom: 0; } +.our-team .social li{ display: inline-block; } +.our-team .social li a{ + display: block; + padding: 10px; + font-size: 17px; + color: #fff; + transition: all 0.3s ease 0s; +} +.our-team .social li a:hover{ + color: #2f2f2f; + background: #f7f5ec; +} +@media only screen and (max-width: 990px){ + .our-team{ margin-bottom: 30px; } +} + +#links{ + color:white; +} diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb index 5e7356b9a..3e668ca53 100644 --- a/app/views/home/index.html.erb +++ b/app/views/home/index.html.erb @@ -9,6 +9,7 @@