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.
- checksums.yaml +4 -4
- data/app/controllers/forest_liana/actions_controller.rb +8 -1
- data/app/controllers/forest_liana/application_controller.rb +1 -7
- data/app/controllers/forest_liana/authentication_controller.rb +122 -0
- data/app/controllers/forest_liana/base_controller.rb +4 -0
- data/app/controllers/forest_liana/resources_controller.rb +14 -17
- data/app/controllers/forest_liana/router.rb +2 -2
- data/app/controllers/forest_liana/sessions_controller.rb +1 -1
- data/app/controllers/forest_liana/smart_actions_controller.rb +10 -5
- data/app/controllers/forest_liana/stats_controller.rb +5 -5
- data/app/helpers/forest_liana/adapter_helper.rb +1 -1
- data/app/serializers/forest_liana/schema_serializer.rb +2 -2
- data/app/services/forest_liana/apimap_sorter.rb +1 -1
- data/app/services/forest_liana/authentication.rb +59 -0
- data/app/services/forest_liana/authorization_getter.rb +12 -20
- data/app/services/forest_liana/forest_api_requester.rb +14 -5
- data/app/services/forest_liana/ip_whitelist_checker.rb +1 -1
- data/app/services/forest_liana/login_handler.rb +3 -11
- data/app/services/forest_liana/oidc_client_manager.rb +34 -0
- data/app/services/forest_liana/oidc_configuration_retriever.rb +12 -0
- data/app/services/forest_liana/oidc_dynamic_client_registrator.rb +67 -0
- data/app/services/forest_liana/permissions_checker.rb +117 -56
- data/app/services/forest_liana/permissions_formatter.rb +52 -0
- data/app/services/forest_liana/permissions_getter.rb +52 -17
- data/app/services/forest_liana/query_stat_getter.rb +5 -5
- data/app/services/forest_liana/scope_validator.rb +8 -7
- data/app/services/forest_liana/token.rb +27 -0
- data/app/services/forest_liana/utils/beta_schema_utils.rb +13 -0
- data/config/initializers/error-messages.rb +20 -0
- data/config/routes.rb +5 -0
- data/lib/forest_liana.rb +1 -0
- data/lib/forest_liana/bootstrapper.rb +1 -1
- data/lib/forest_liana/collection.rb +2 -2
- data/lib/forest_liana/engine.rb +9 -0
- data/lib/forest_liana/json_printer.rb +1 -1
- data/lib/forest_liana/version.rb +1 -1
- data/spec/dummy/app/assets/config/manifest.js +1 -0
- data/spec/dummy/config/application.rb +1 -1
- data/spec/dummy/config/initializers/forest_liana.rb +1 -0
- data/spec/dummy/db/migrate/20190226172951_create_user.rb +1 -1
- data/spec/dummy/db/migrate/20190226173051_create_isle.rb +1 -1
- data/spec/dummy/db/migrate/20190226174951_create_tree.rb +1 -1
- data/spec/dummy/db/migrate/20190716130830_add_age_to_tree.rb +1 -1
- data/spec/dummy/db/migrate/20190716135241_add_type_to_user.rb +1 -1
- data/spec/dummy/db/schema.rb +18 -20
- data/spec/requests/actions_controller_spec.rb +46 -11
- data/spec/requests/authentications_spec.rb +105 -0
- data/spec/requests/resources_spec.rb +4 -4
- data/spec/requests/sessions_spec.rb +53 -0
- data/spec/services/forest_liana/permissions_checker_acl_disabled_spec.rb +711 -0
- data/spec/services/forest_liana/permissions_checker_acl_enabled_spec.rb +831 -0
- data/spec/services/forest_liana/permissions_formatter_spec.rb +222 -0
- data/spec/services/forest_liana/permissions_getter_spec.rb +83 -0
- data/spec/spec_helper.rb +3 -0
- data/test/dummy/app/assets/config/manifest.js +1 -0
- data/test/dummy/config/application.rb +1 -1
- data/test/dummy/db/migrate/20150608130516_create_date_field.rb +1 -1
- data/test/dummy/db/migrate/20150608131430_create_integer_field.rb +1 -1
- data/test/dummy/db/migrate/20150608131603_create_decimal_field.rb +1 -1
- data/test/dummy/db/migrate/20150608131610_create_float_field.rb +1 -1
- data/test/dummy/db/migrate/20150608132159_create_boolean_field.rb +1 -1
- data/test/dummy/db/migrate/20150608132621_create_string_field.rb +1 -1
- data/test/dummy/db/migrate/20150608133038_create_belongs_to_field.rb +1 -1
- data/test/dummy/db/migrate/20150608133044_create_has_one_field.rb +1 -1
- data/test/dummy/db/migrate/20150608150016_create_has_many_field.rb +1 -1
- data/test/dummy/db/migrate/20150609114636_create_belongs_to_class_name_field.rb +1 -1
- data/test/dummy/db/migrate/20150612112520_create_has_and_belongs_to_many_field.rb +1 -1
- data/test/dummy/db/migrate/20150616150629_create_polymorphic_field.rb +1 -1
- data/test/dummy/db/migrate/20150623115554_create_has_many_class_name_field.rb +1 -1
- data/test/dummy/db/migrate/20150814081918_create_has_many_through_field.rb +1 -1
- data/test/dummy/db/migrate/20160627172810_create_owner.rb +1 -1
- data/test/dummy/db/migrate/20160627172951_create_tree.rb +1 -1
- data/test/dummy/db/migrate/20160628173505_add_timestamps.rb +1 -1
- data/test/dummy/db/migrate/20170614141921_create_serialize_field.rb +1 -1
- data/test/dummy/db/migrate/20181111162121_create_references_table.rb +1 -1
- 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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
-
|
9
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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'].
|
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
|
data/config/routes.rb
CHANGED
@@ -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'
|
data/lib/forest_liana.rb
CHANGED
@@ -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}'."
|
data/lib/forest_liana/engine.rb
CHANGED
@@ -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
|
data/lib/forest_liana/version.rb
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
{}
|
@@ -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
|
|
data/spec/dummy/db/schema.rb
CHANGED
@@ -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
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
# from scratch.
|
10
|
-
#
|
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:
|
13
|
+
ActiveRecord::Schema.define(version: 2019_07_16_135241) do
|
15
14
|
|
16
15
|
create_table "isle", force: :cascade do |t|
|
17
|
-
t.string
|
18
|
-
t.binary
|
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
|
25
|
-
t.integer
|
26
|
-
t.integer
|
27
|
-
t.integer
|
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
|
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
|
36
|
+
t.string "name"
|
39
37
|
t.datetime "created_at"
|
40
38
|
t.datetime "updated_at"
|
41
|
-
t.integer
|
39
|
+
t.integer "title"
|
42
40
|
end
|
43
41
|
|
44
42
|
end
|