forest_liana 5.3.2 → 6.0.0.pre.beta.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/forest_liana/actions_controller.rb +8 -1
  3. data/app/controllers/forest_liana/application_controller.rb +1 -7
  4. data/app/controllers/forest_liana/authentication_controller.rb +122 -0
  5. data/app/controllers/forest_liana/base_controller.rb +4 -0
  6. data/app/controllers/forest_liana/resources_controller.rb +14 -17
  7. data/app/controllers/forest_liana/router.rb +2 -2
  8. data/app/controllers/forest_liana/sessions_controller.rb +1 -1
  9. data/app/controllers/forest_liana/smart_actions_controller.rb +10 -5
  10. data/app/controllers/forest_liana/stats_controller.rb +5 -5
  11. data/app/helpers/forest_liana/adapter_helper.rb +1 -1
  12. data/app/serializers/forest_liana/schema_serializer.rb +2 -2
  13. data/app/services/forest_liana/apimap_sorter.rb +1 -1
  14. data/app/services/forest_liana/authentication.rb +59 -0
  15. data/app/services/forest_liana/authorization_getter.rb +12 -20
  16. data/app/services/forest_liana/forest_api_requester.rb +14 -5
  17. data/app/services/forest_liana/ip_whitelist_checker.rb +1 -1
  18. data/app/services/forest_liana/login_handler.rb +3 -11
  19. data/app/services/forest_liana/oidc_client_manager.rb +34 -0
  20. data/app/services/forest_liana/oidc_configuration_retriever.rb +12 -0
  21. data/app/services/forest_liana/oidc_dynamic_client_registrator.rb +67 -0
  22. data/app/services/forest_liana/permissions_checker.rb +117 -56
  23. data/app/services/forest_liana/permissions_formatter.rb +52 -0
  24. data/app/services/forest_liana/permissions_getter.rb +52 -17
  25. data/app/services/forest_liana/query_stat_getter.rb +5 -5
  26. data/app/services/forest_liana/scope_validator.rb +8 -7
  27. data/app/services/forest_liana/token.rb +27 -0
  28. data/app/services/forest_liana/utils/beta_schema_utils.rb +13 -0
  29. data/config/initializers/error-messages.rb +20 -0
  30. data/config/routes.rb +5 -0
  31. data/lib/forest_liana.rb +1 -0
  32. data/lib/forest_liana/bootstrapper.rb +1 -1
  33. data/lib/forest_liana/collection.rb +2 -2
  34. data/lib/forest_liana/engine.rb +9 -0
  35. data/lib/forest_liana/json_printer.rb +1 -1
  36. data/lib/forest_liana/version.rb +1 -1
  37. data/spec/dummy/app/assets/config/manifest.js +1 -0
  38. data/spec/dummy/config/application.rb +1 -1
  39. data/spec/dummy/config/initializers/forest_liana.rb +1 -0
  40. data/spec/dummy/db/migrate/20190226172951_create_user.rb +1 -1
  41. data/spec/dummy/db/migrate/20190226173051_create_isle.rb +1 -1
  42. data/spec/dummy/db/migrate/20190226174951_create_tree.rb +1 -1
  43. data/spec/dummy/db/migrate/20190716130830_add_age_to_tree.rb +1 -1
  44. data/spec/dummy/db/migrate/20190716135241_add_type_to_user.rb +1 -1
  45. data/spec/dummy/db/schema.rb +18 -20
  46. data/spec/requests/actions_controller_spec.rb +46 -11
  47. data/spec/requests/authentications_spec.rb +105 -0
  48. data/spec/requests/resources_spec.rb +4 -4
  49. data/spec/requests/sessions_spec.rb +53 -0
  50. data/spec/services/forest_liana/permissions_checker_acl_disabled_spec.rb +711 -0
  51. data/spec/services/forest_liana/permissions_checker_acl_enabled_spec.rb +831 -0
  52. data/spec/services/forest_liana/permissions_formatter_spec.rb +222 -0
  53. data/spec/services/forest_liana/permissions_getter_spec.rb +83 -0
  54. data/spec/spec_helper.rb +3 -0
  55. data/test/dummy/app/assets/config/manifest.js +1 -0
  56. data/test/dummy/config/application.rb +1 -1
  57. data/test/dummy/db/migrate/20150608130516_create_date_field.rb +1 -1
  58. data/test/dummy/db/migrate/20150608131430_create_integer_field.rb +1 -1
  59. data/test/dummy/db/migrate/20150608131603_create_decimal_field.rb +1 -1
  60. data/test/dummy/db/migrate/20150608131610_create_float_field.rb +1 -1
  61. data/test/dummy/db/migrate/20150608132159_create_boolean_field.rb +1 -1
  62. data/test/dummy/db/migrate/20150608132621_create_string_field.rb +1 -1
  63. data/test/dummy/db/migrate/20150608133038_create_belongs_to_field.rb +1 -1
  64. data/test/dummy/db/migrate/20150608133044_create_has_one_field.rb +1 -1
  65. data/test/dummy/db/migrate/20150608150016_create_has_many_field.rb +1 -1
  66. data/test/dummy/db/migrate/20150609114636_create_belongs_to_class_name_field.rb +1 -1
  67. data/test/dummy/db/migrate/20150612112520_create_has_and_belongs_to_many_field.rb +1 -1
  68. data/test/dummy/db/migrate/20150616150629_create_polymorphic_field.rb +1 -1
  69. data/test/dummy/db/migrate/20150623115554_create_has_many_class_name_field.rb +1 -1
  70. data/test/dummy/db/migrate/20150814081918_create_has_many_through_field.rb +1 -1
  71. data/test/dummy/db/migrate/20160627172810_create_owner.rb +1 -1
  72. data/test/dummy/db/migrate/20160627172951_create_tree.rb +1 -1
  73. data/test/dummy/db/migrate/20160628173505_add_timestamps.rb +1 -1
  74. data/test/dummy/db/migrate/20170614141921_create_serialize_field.rb +1 -1
  75. data/test/dummy/db/migrate/20181111162121_create_references_table.rb +1 -1
  76. metadata +71 -4
@@ -0,0 +1,52 @@
1
+ module ForestLiana
2
+ class PermissionsFormatter
3
+ class << PermissionsFormatter
4
+ # Convert old format permissions to unify PermissionsGetter code
5
+ def convert_to_new_format(permissions, rendering_id)
6
+ permissions_new_format = Hash.new
7
+ permissions_new_format['collections'] = Hash.new
8
+ permissions_new_format['renderings'] = Hash.new
9
+ permissions_new_format['renderings'][rendering_id] = Hash.new
10
+ permissions.keys.each { |collection_name|
11
+ permissions_new_format['collections'][collection_name] = {
12
+ 'collection' => convert_collection_permissions_to_new_format(permissions[collection_name]['collection']),
13
+ 'actions' => convert_actions_permissions_to_new_format(permissions[collection_name]['actions'])
14
+ }
15
+
16
+ permissions_new_format['renderings'][rendering_id][collection_name] = { 'scope' => permissions[collection_name]['scope'] }
17
+ }
18
+
19
+ permissions_new_format
20
+ end
21
+
22
+ def convert_collection_permissions_to_new_format(collection_permissions)
23
+ {
24
+ 'browseEnabled' => collection_permissions['list'] || collection_permissions['searchToEdit'],
25
+ 'readEnabled' => collection_permissions['show'],
26
+ 'addEnabled' => collection_permissions['create'],
27
+ 'editEnabled' => collection_permissions['update'],
28
+ 'deleteEnabled' => collection_permissions['delete'],
29
+ 'exportEnabled' => collection_permissions['export']
30
+ }
31
+ end
32
+
33
+ def convert_actions_permissions_to_new_format(actions_permissions)
34
+ return nil unless actions_permissions
35
+
36
+ actions_permissions_new_format = Hash.new
37
+
38
+ actions_permissions.keys.each { |action_name|
39
+ allowed = actions_permissions[action_name]['allowed']
40
+ users = actions_permissions[action_name]['users']
41
+
42
+ actions_permissions_new_format[action_name] = Hash.new
43
+ actions_permissions_new_format[action_name] = {
44
+ 'triggerEnabled' => allowed && (users.nil? || users)
45
+ }
46
+ }
47
+
48
+ actions_permissions_new_format
49
+ end
50
+ end
51
+ end
52
+ end
@@ -1,25 +1,60 @@
1
1
  module ForestLiana
2
2
  class PermissionsGetter
3
- def initialize(rendering_id)
4
- @route = "/liana/v2/permissions"
5
- @rendering_id = rendering_id
6
- end
3
+ class << PermissionsGetter
4
+ def get_permissions_api_route
5
+ '/liana/v3/permissions'
6
+ end
7
+
8
+ # Permission format example:
9
+ # collections => {
10
+ # {model_name} => {
11
+ # collection => {
12
+ # browseEnabled => true,
13
+ # readEnabled => true,
14
+ # editEnabled => true,
15
+ # addEnabled => true,
16
+ # deleteEnabled => true,
17
+ # exportEnabled => true,
18
+ # },
19
+ # actions => {
20
+ # {action_name} => {
21
+ # triggerEnabled => true,
22
+ # },
23
+ # },
24
+ # },
25
+ # },
26
+ # rederings => {
27
+ # {rendering_id} => {
28
+ # {collection_id} => {
29
+ # scope => {
30
+ # dynamicScopesValues => {},
31
+ # filter => {}
32
+ # }
33
+ # }
34
+ # }
35
+ # }
36
+ # }
37
+ # With `rendering_specific_only` this returns only the permissions related data specific to the provided rendering
38
+ # For now this only includes scopes
39
+ def get_permissions_for_rendering(rendering_id, rendering_specific_only: false)
40
+ begin
41
+ query_parameters = { 'renderingId' => rendering_id }
42
+ query_parameters['renderingSpecificOnly'] = rendering_specific_only if rendering_specific_only
7
43
 
8
- def perform
9
- begin
10
- query_parameters = { 'renderingId' => @rendering_id }
11
- response = ForestLiana::ForestApiRequester.get(@route, query: query_parameters)
44
+ api_route = get_permissions_api_route
45
+ response = ForestLiana::ForestApiRequester.get(api_route, query: query_parameters)
12
46
 
13
- if response.is_a?(Net::HTTPOK)
14
- JSON.parse(response.body)
15
- else
16
- raise "Forest API returned an #{ForestLiana::Errors::HTTPErrorHelper.format(response)}"
47
+ if response.is_a?(Net::HTTPOK)
48
+ JSON.parse(response.body)
49
+ else
50
+ raise "Forest API returned an #{ForestLiana::Errors::HTTPErrorHelper.format(response)}"
51
+ end
52
+ rescue => exception
53
+ FOREST_LOGGER.error 'Cannot retrieve the permissions from the Forest server.'
54
+ FOREST_LOGGER.error 'Which was caused by:'
55
+ ForestLiana::Errors::ExceptionHelper.recursively_print(exception, margin: ' ', is_error: true)
56
+ nil
17
57
  end
18
- rescue => exception
19
- FOREST_LOGGER.error 'Cannot retrieve the permissions from the Forest server.'
20
- FOREST_LOGGER.error 'Which was caused by:'
21
- ForestLiana::Errors::ExceptionHelper.recursively_print(exception, margin: ' ', is_error: true)
22
- nil
23
58
  end
24
59
  end
25
60
  end
@@ -2,11 +2,11 @@ module ForestLiana
2
2
  class QueryStatGetter
3
3
  attr_accessor :record
4
4
 
5
- CHART_TYPE_VALUE = 'Value';
6
- CHART_TYPE_PIE = 'Pie';
7
- CHART_TYPE_LINE = 'Line';
8
- CHART_TYPE_LEADERBOARD = 'Leaderboard';
9
- CHART_TYPE_OBJECTIVE = 'Objective';
5
+ CHART_TYPE_VALUE = 'Value'
6
+ CHART_TYPE_PIE = 'Pie'
7
+ CHART_TYPE_LINE = 'Line'
8
+ CHART_TYPE_LEADERBOARD = 'Leaderboard'
9
+ CHART_TYPE_OBJECTIVE = 'Objective'
10
10
 
11
11
  def initialize(params)
12
12
  @params = params
@@ -32,9 +32,10 @@ module ForestLiana
32
32
  def compute_condition_filters_from_scope(user_id)
33
33
  computed_condition_filters = @scope_filters.clone
34
34
  computed_condition_filters['conditions'].each do |condition|
35
- if condition.include?('value') &&
36
- !condition['value'].nil? &&
37
- condition['value'].start_with?('$') &&
35
+ if condition.include?('value') &&
36
+ !condition['value'].nil? &&
37
+ condition['value'].instance_of?(String) &&
38
+ condition['value'].start_with?('$') &&
38
39
  @users_variable_values.include?(user_id)
39
40
  condition['value'] = @users_variable_values[user_id][condition['value']]
40
41
  end
@@ -51,9 +52,9 @@ module ForestLiana
51
52
  ensure_valid_aggregation(node)
52
53
 
53
54
  return is_scope_condition?(node) unless node['aggregator']
54
-
55
+
55
56
  # NOTICE: Remove conditions that are not from the scope
56
- filtered_conditions = node['conditions'].map { |condition|
57
+ filtered_conditions = node['conditions'].map { |condition|
57
58
  search_scope_aggregation(condition)
58
59
  }.select { |condition|
59
60
  condition
@@ -61,7 +62,7 @@ module ForestLiana
61
62
 
62
63
  # NOTICE: If there is only one condition filter left and its current aggregator is
63
64
  # an "and", this condition filter is the searched scope
64
- if (filtered_conditions.length == 1 &&
65
+ if (filtered_conditions.length == 1 &&
65
66
  filtered_conditions.first.is_a?(Hash) &&
66
67
  filtered_conditions.first.include?(:aggregator) &&
67
68
  node['aggregator'] == 'and')
@@ -70,7 +71,7 @@ module ForestLiana
70
71
 
71
72
  # NOTICE: Otherwise, validate if the current node is the scope and return nil
72
73
  # if it's not
73
- return (filtered_conditions.length == @scope_filters['conditions'].length &&
74
+ return (filtered_conditions.length == @scope_filters['conditions'].length &&
74
75
  node['aggregator'] == @scope_filters['aggregator']) ?
75
76
  { aggregator: node['aggregator'], conditions: filtered_conditions } :
76
77
  nil
@@ -0,0 +1,27 @@
1
+ EXPIRATION_IN_SECONDS = 14.days
2
+
3
+ module ForestLiana
4
+ class Token
5
+ REGEX_COOKIE_SESSION_TOKEN = /forest_session_token=([^;]*)/;
6
+
7
+ def self.expiration_in_days
8
+ Time.current + EXPIRATION_IN_SECONDS
9
+ end
10
+
11
+ def self.expiration_in_seconds
12
+ return Time.now.to_i + EXPIRATION_IN_SECONDS
13
+ end
14
+
15
+ def self.create_token(user, rendering_id)
16
+ return JWT.encode({
17
+ id: user['id'],
18
+ email: user['email'],
19
+ first_name: user['first_name'],
20
+ last_name: user['last_name'],
21
+ team: user['teams'][0],
22
+ rendering_id: rendering_id,
23
+ exp: expiration_in_seconds()
24
+ }, ForestLiana.auth_secret, 'HS256')
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ module ForestLiana
2
+ module Utils
3
+ class BetaSchemaUtils
4
+ def self.find_action_from_endpoint(collection_name, endpoint, http_method)
5
+ collection = ForestLiana.apimap.find { |collection| collection.name.to_s == collection_name }
6
+
7
+ return nil unless collection
8
+
9
+ collection.actions.find { |action| action.endpoint == endpoint && action.http_method == http_method }
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ module ForestLiana
2
+ MESSAGES = {
3
+ CONFIGURATION: {
4
+ AUTH_SECRET_MISSING: "Your Forest authSecret seems to be missing. Can you check that you properly set a Forest authSecret in the Forest initializer?",
5
+ },
6
+ SERVER_TRANSACTION: {
7
+ SECRET_AND_RENDERINGID_INCONSISTENT: "Cannot retrieve the project you're trying to unlock. The envSecret and renderingId seems to be missing or inconsistent.",
8
+ SERVER_DOWN: "Cannot retrieve the data from the Forest server. Forest API seems to be down right now.",
9
+ SECRET_NOT_FOUND: "Cannot retrieve the data from the Forest server. Can you check that you properly copied the Forest envSecret in the Liana initializer?",
10
+ UNEXPECTED: "Cannot retrieve the data from the Forest server. An error occured in Forest API.",
11
+ INVALID_STATE_MISSING: "Invalid response from the authentication server: the state parameter is missing",
12
+ INVALID_STATE_FORMAT: "Invalid response from the authentication server: the state parameter is not at the right format",
13
+ INVALID_STATE_RENDERING_ID: "Invalid response from the authentication server: the state does not contain a renderingId",
14
+ MISSING_RENDERING_ID: "Authentication request must contain a renderingId",
15
+ INVALID_RENDERING_ID: "The parameter renderingId is not valid",
16
+ REGISTRATION_FAILED: "The registration to the authentication API failed, response: ",
17
+ OIDC_CONFIGURATION_RETRIEVAL_FAILED: "Failed to retrieve the provider's configuration.",
18
+ }
19
+ }
20
+ end
@@ -4,6 +4,11 @@ ForestLiana::Engine.routes.draw do
4
4
  # Onboarding
5
5
  get '/' => 'apimaps#index'
6
6
 
7
+ # Authentication
8
+ post 'authentication' => 'authentication#start_authentication'
9
+ get 'authentication/callback' => 'authentication#authentication_callback'
10
+ post 'authentication/logout' => 'authentication#logout'
11
+
7
12
  # Session
8
13
  post 'sessions' => 'sessions#create_with_password'
9
14
  post 'sessions-google' => 'sessions#create_with_google'
@@ -16,6 +16,7 @@ module ForestLiana
16
16
 
17
17
  mattr_accessor :env_secret
18
18
  mattr_accessor :auth_secret
19
+ mattr_accessor :application_url
19
20
  mattr_accessor :integrations
20
21
  mattr_accessor :apimap
21
22
  mattr_accessor :allowed_users
@@ -640,7 +640,7 @@ module ForestLiana
640
640
  end
641
641
 
642
642
  def forest_url
643
- ENV['FOREST_URL'] || 'https://api.forestadmin.com';
643
+ ENV['FOREST_URL'] || 'https://api.forestadmin.com'
644
644
  end
645
645
 
646
646
  def database_type
@@ -148,7 +148,7 @@ module ForestLiana::Collection
148
148
  # TODO: Remove once lianas prior to 2.0.0 are not supported anymore.
149
149
  model = ForestLiana.models.find { |collection| collection.try(:table_name) == collection_name.to_s }
150
150
  if model
151
- collection_name_new = ForestLiana.name_for(model);
151
+ collection_name_new = ForestLiana.name_for(model)
152
152
  FOREST_LOGGER.warn "DEPRECATION WARNING: Collection names are now based on the models " \
153
153
  "names. Please rename the collection '#{collection_name.to_s}' of your Forest " \
154
154
  "customisation in '#{collection_name_new}'."
@@ -158,7 +158,7 @@ module ForestLiana::Collection
158
158
  # TODO: Remove once lianas prior to 2.0.0 are not supported anymore.
159
159
  model = ForestLiana.names_old_overriden.invert[collection_name.to_s]
160
160
  if model
161
- collection_name_new = ForestLiana.name_for(model);
161
+ collection_name_new = ForestLiana.name_for(model)
162
162
  FOREST_LOGGER.warn "DEPRECATION WARNING: Collection names are now based on the models " \
163
163
  "names. Please rename the collection '#{collection_name.to_s}' of your Forest " \
164
164
  "customisation in '#{collection_name_new}'."
@@ -16,8 +16,17 @@ module ForestLiana
16
16
  begin
17
17
  rack_cors_class = Rack::Cors
18
18
  rack_cors_class = 'Rack::Cors' if Rails::VERSION::MAJOR < 5
19
+ null_regex = Regexp.new(/\Anull\z/)
19
20
 
20
21
  config.middleware.insert_before 0, rack_cors_class do
22
+ allow do
23
+ hostnames = [null_regex, 'localhost:4200', /\A.*\.forestadmin\.com\z/]
24
+ hostnames += ENV['CORS_ORIGINS'].split(',') if ENV['CORS_ORIGINS']
25
+
26
+ origins hostnames
27
+ resource ForestLiana::AuthenticationController::PUBLIC_ROUTES[1], headers: :any, methods: :any, credentials: true, max_age: 86400 # NOTICE: 1 day
28
+ end
29
+
21
30
  allow do
22
31
  hostnames = ['localhost:4200', /\A.*\.forestadmin\.com\z/]
23
32
  hostnames += ENV['CORS_ORIGINS'].split(',') if ENV['CORS_ORIGINS']
@@ -20,7 +20,7 @@ module ForestLiana
20
20
  result << ", "
21
21
  end
22
22
 
23
- result << pretty_print(item, is_primary_value ? "#{indentation} " : indentation);
23
+ result << pretty_print(item, is_primary_value ? "#{indentation} " : indentation)
24
24
  end
25
25
 
26
26
  result << "\n#{indentation}" if is_primary_value && !is_small
@@ -1,3 +1,3 @@
1
1
  module ForestLiana
2
- VERSION = "5.3.2"
2
+ VERSION = "6.0.0-beta.3"
3
3
  end
@@ -20,7 +20,7 @@ module Dummy
20
20
  # config.i18n.default_locale = :de
21
21
 
22
22
  # Do not swallow errors in after_commit/after_rollback callbacks.
23
- config.active_record.raise_in_transactional_callbacks = true
23
+ # config.active_record.raise_in_transactional_callbacks = true
24
24
  end
25
25
  end
26
26
 
@@ -1,2 +1,3 @@
1
1
  ForestLiana.env_secret = 'env_secret_test'
2
2
  ForestLiana.auth_secret = 'auth_secret_test'
3
+ ForestLiana.application_url = 'http://localhost:3000'
@@ -1,4 +1,4 @@
1
- class CreateUser < ActiveRecord::Migration
1
+ class CreateUser < ActiveRecord::Migration[4.2]
2
2
  def change
3
3
  create_table :users do |t|
4
4
  t.string :name
@@ -1,4 +1,4 @@
1
- class CreateIsle < ActiveRecord::Migration
1
+ class CreateIsle < ActiveRecord::Migration[4.2]
2
2
  def change
3
3
  create_table :isle do |t|
4
4
  t.string :name
@@ -1,4 +1,4 @@
1
- class CreateTree < ActiveRecord::Migration
1
+ class CreateTree < ActiveRecord::Migration[4.2]
2
2
  def change
3
3
  create_table :trees do |t|
4
4
  t.string :name
@@ -1,4 +1,4 @@
1
- class AddAgeToTree < ActiveRecord::Migration
1
+ class AddAgeToTree < ActiveRecord::Migration[4.2]
2
2
  def up
3
3
  add_column :trees, :age, :integer
4
4
  end
@@ -1,4 +1,4 @@
1
- class AddTypeToUser < ActiveRecord::Migration
1
+ class AddTypeToUser < ActiveRecord::Migration[4.2]
2
2
  def up
3
3
  add_column :users, :title, :integer
4
4
  end
@@ -1,44 +1,42 @@
1
- # encoding: UTF-8
2
1
  # This file is auto-generated from the current state of the database. Instead
3
2
  # of editing this file, please use the migrations feature of Active Record to
4
3
  # incrementally modify your database, and then regenerate this schema definition.
5
4
  #
6
- # Note that this schema.rb definition is the authoritative source for your
7
- # database schema. If you need to create the application database on another
8
- # system, you should be using db:schema:load, not running all the migrations
9
- # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10
- # you'll amass, the slower it'll run and the greater likelihood for issues).
5
+ # This file is the source Rails uses to define your schema when running `rails
6
+ # db:schema:load`. When creating a new database, `rails db:schema:load` tends to
7
+ # be faster and is potentially less error prone than running all of your
8
+ # migrations from scratch. Old migrations may fail to apply correctly if those
9
+ # migrations use external dependencies or application code.
11
10
  #
12
11
  # It's strongly recommended that you check this file into your version control system.
13
12
 
14
- ActiveRecord::Schema.define(version: 20190716135241) do
13
+ ActiveRecord::Schema.define(version: 2019_07_16_135241) do
15
14
 
16
15
  create_table "isle", force: :cascade do |t|
17
- t.string "name"
18
- t.binary "map"
16
+ t.string "name"
17
+ t.binary "map"
19
18
  t.datetime "created_at"
20
19
  t.datetime "updated_at"
21
20
  end
22
21
 
23
22
  create_table "trees", force: :cascade do |t|
24
- t.string "name"
25
- t.integer "owner_id"
26
- t.integer "cutter_id"
27
- t.integer "island_id"
23
+ t.string "name"
24
+ t.integer "owner_id"
25
+ t.integer "cutter_id"
26
+ t.integer "island_id"
28
27
  t.datetime "created_at"
29
28
  t.datetime "updated_at"
30
- t.integer "age"
29
+ t.integer "age"
30
+ t.index ["cutter_id"], name: "index_trees_on_cutter_id"
31
+ t.index ["island_id"], name: "index_trees_on_island_id"
32
+ t.index ["owner_id"], name: "index_trees_on_owner_id"
31
33
  end
32
34
 
33
- add_index "trees", ["cutter_id"], name: "index_trees_on_cutter_id"
34
- add_index "trees", ["island_id"], name: "index_trees_on_island_id"
35
- add_index "trees", ["owner_id"], name: "index_trees_on_owner_id"
36
-
37
35
  create_table "users", force: :cascade do |t|
38
- t.string "name"
36
+ t.string "name"
39
37
  t.datetime "created_at"
40
38
  t.datetime "updated_at"
41
- t.integer "title"
39
+ t.integer "title"
42
40
  end
43
41
 
44
42
  end