aurora-data-api 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rake_tasks~ +10 -0
- data/.standard.yml +6 -0
- data/CHANGELOG.md +3 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +103 -0
- data/LICENSE.txt +21 -0
- data/README.md +236 -0
- data/Rakefile +18 -0
- data/Steepfile +5 -0
- data/aurora-data-api.gemspec +34 -0
- data/example/.gitignore +58 -0
- data/example/.ruby-version +1 -0
- data/example/Dockerfile +26 -0
- data/example/Gemfile +9 -0
- data/example/Gemfile.lock +48 -0
- data/example/README.md +45 -0
- data/example/Rakefile +48 -0
- data/example/app/depots/comment_depot.rb +6 -0
- data/example/app/depots/entry_depot.rb +6 -0
- data/example/app/depots/user_depot.rb +6 -0
- data/example/app/handlers/main.rb +86 -0
- data/example/app/models/comment.rb +10 -0
- data/example/app/models/entry.rb +12 -0
- data/example/app/models/user.rb +13 -0
- data/example/compose.yml +43 -0
- data/example/db/.gitignore +2 -0
- data/example/db/.keep +0 -0
- data/example/package-lock.json +4740 -0
- data/example/package.json +17 -0
- data/example/serverless.yml +184 -0
- data/exe/aurora-data-api +161 -0
- data/lib/aurora-data-api/data_service.rb +42 -0
- data/lib/aurora-data-api/depot.rb +132 -0
- data/lib/aurora-data-api/environment.rb +41 -0
- data/lib/aurora-data-api/model.rb +152 -0
- data/lib/aurora-data-api/version.rb +5 -0
- data/lib/aurora-data-api.rb +14 -0
- data/sig/aurora-data-api.rbs +20 -0
- data/sig/data_service.rbs +7 -0
- data/sig/depot.rbs +23 -0
- data/sig/environment.rbs +13 -0
- data/sig/model.rbs +39 -0
- data/sig/version.rbs +3 -0
- metadata +120 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
{
|
2
|
+
"name": "aurora-data-api-example",
|
3
|
+
"version": "1.0.0",
|
4
|
+
"description": "",
|
5
|
+
"main": "index.js",
|
6
|
+
"scripts": {
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
8
|
+
},
|
9
|
+
"author": "",
|
10
|
+
"license": "ISC",
|
11
|
+
"devDependencies": {
|
12
|
+
"serverless": "^3.17.0",
|
13
|
+
"serverless-offline": "^8.8.0",
|
14
|
+
"serverless-ruby-layer": "^1.6.0",
|
15
|
+
"serverless-vpc-plugin": "^1.0.4"
|
16
|
+
}
|
17
|
+
}
|
@@ -0,0 +1,184 @@
|
|
1
|
+
service: aurora-data-api-example
|
2
|
+
|
3
|
+
frameworkVersion: '3'
|
4
|
+
|
5
|
+
provider:
|
6
|
+
name: aws
|
7
|
+
runtime: ruby2.7
|
8
|
+
stage: ${opt:stage, self:custom.defaultStage}
|
9
|
+
endpointType: REGIONAL
|
10
|
+
region: ap-northeast-1
|
11
|
+
environment:
|
12
|
+
STAGE: ${sls:stage}
|
13
|
+
TZ: Asia/Tokyo
|
14
|
+
DATA_API_ENDPOINT:
|
15
|
+
Fn::Join:
|
16
|
+
- ""
|
17
|
+
- - "https://"
|
18
|
+
- !GetAtt "RDSCluster.Endpoint.Address"
|
19
|
+
RDS_RESOURCE_ARN: ${self:custom.db_resource_arn}
|
20
|
+
RDS_SECRET_ARN: !Ref DBSecret
|
21
|
+
PGDATABASE: ${self:custom.myEnvironment.PGDATABASE}
|
22
|
+
iam:
|
23
|
+
role:
|
24
|
+
statements:
|
25
|
+
- Effect: "Allow"
|
26
|
+
Action: lambda:InvokeFunction
|
27
|
+
Resource:
|
28
|
+
- !Join
|
29
|
+
- ""
|
30
|
+
- - "arn:aws:lambda:${self:provider.region}:"
|
31
|
+
- !Ref AWS::AccountId
|
32
|
+
- ":function:"
|
33
|
+
- "*"
|
34
|
+
- Effect: "Allow"
|
35
|
+
Action: secretsmanager:GetSecretValue
|
36
|
+
Resource:
|
37
|
+
- !Ref DBSecret
|
38
|
+
- Effect: "Allow"
|
39
|
+
Action:
|
40
|
+
- rds-data:BatchExecuteStatement
|
41
|
+
- rds-data:BeginTransaction
|
42
|
+
- rds-data:CommitTransaction
|
43
|
+
- rds-data:ExecuteStatement
|
44
|
+
- rds-data:RollbackTransaction
|
45
|
+
Resource:
|
46
|
+
- ${self:custom.db_resource_arn}
|
47
|
+
logs:
|
48
|
+
restApi:
|
49
|
+
accessLogging: true
|
50
|
+
format: '{"requestId":"$context.requestId","ip":"$context.identity.sourceIp","requestTime":"$context.requestTime","httpMethod":"$context.httpMethod","routeKey":"$context.routeKey","status":"$context.status","protocol":"$context.protocol","responseLength":"$context.responseLength","errorMessage":"$context.integrationErrorMessage"}'
|
51
|
+
executionLogging: true
|
52
|
+
level: INFO
|
53
|
+
fullExecutionData: true
|
54
|
+
|
55
|
+
plugins:
|
56
|
+
- serverless-offline
|
57
|
+
- serverless-vpc-plugin
|
58
|
+
- serverless-ruby-layer
|
59
|
+
|
60
|
+
custom:
|
61
|
+
defaultStage: offline
|
62
|
+
serverless-offline:
|
63
|
+
httpPort: 4000
|
64
|
+
db_resource_arn: !Join
|
65
|
+
- ""
|
66
|
+
- - "arn:aws:rds:${self:provider.region}:"
|
67
|
+
- !Ref AWS::AccountId
|
68
|
+
- ":cluster:"
|
69
|
+
- !Ref RDSCluster
|
70
|
+
myEnvironment:
|
71
|
+
DBMaxCapacity:
|
72
|
+
prod: 4
|
73
|
+
PGDATABASE: mydatabase
|
74
|
+
ALLOWED_ORIGIN:
|
75
|
+
offline: http://localhost:4000
|
76
|
+
prod: "*" # TODO: You have to specify it
|
77
|
+
vpcConfig:
|
78
|
+
# see https://www.serverless.com/plugins/serverless-vpc-plugin
|
79
|
+
enabled: true
|
80
|
+
cidrBlock: '10.0.0.0/16'
|
81
|
+
createNatGateway: false
|
82
|
+
createNetworkAcl: false
|
83
|
+
createDbSubnet: true
|
84
|
+
createFlowLogs: false
|
85
|
+
createBastionHost: false
|
86
|
+
createNatInstance: false
|
87
|
+
createParameters: true
|
88
|
+
services:
|
89
|
+
- secretsmanager
|
90
|
+
subnetGroups:
|
91
|
+
- rds
|
92
|
+
exportOutputs: true
|
93
|
+
|
94
|
+
resources:
|
95
|
+
Resources:
|
96
|
+
RDSCluster:
|
97
|
+
Type: AWS::RDS::DBCluster
|
98
|
+
Properties:
|
99
|
+
DBClusterIdentifier: !Sub "${self:service}-${self:provider.stage}-aurora-psql"
|
100
|
+
MasterUsername: !Join ['', ['{{resolve:secretsmanager:', !Ref DBSecret, ':SecretString:username}}' ]]
|
101
|
+
MasterUserPassword: !Join ['', ['{{resolve:secretsmanager:', !Ref DBSecret, ':SecretString:password}}' ]]
|
102
|
+
DatabaseName: ${self:custom.myEnvironment.PGDATABASE}
|
103
|
+
Engine: aurora-postgresql
|
104
|
+
EngineMode: serverless
|
105
|
+
EnableHttpEndpoint: true
|
106
|
+
EngineVersion: 10.14
|
107
|
+
ScalingConfiguration:
|
108
|
+
AutoPause: true
|
109
|
+
MaxCapacity: ${self:custom.myEnvironment.DBMaxCapacity.${sls:stage}, 2}
|
110
|
+
MinCapacity: 2
|
111
|
+
SecondsUntilAutoPause: 900 # 15 min for example
|
112
|
+
DBSubnetGroupName:
|
113
|
+
Ref: DBSubnetGroup
|
114
|
+
DBSecret:
|
115
|
+
Type: AWS::SecretsManager::Secret
|
116
|
+
Properties:
|
117
|
+
Name: !Sub "${self:service}-${self:provider.stage}-AuroraUserSecret"
|
118
|
+
Description: RDS database auto-generated user password
|
119
|
+
GenerateSecretString:
|
120
|
+
SecretStringTemplate: !Sub '{"username": "${self:provider.stage}Root"}'
|
121
|
+
GenerateStringKey: "password"
|
122
|
+
PasswordLength: 30
|
123
|
+
ExcludeCharacters: '"@/\'
|
124
|
+
DBSubnetGroup:
|
125
|
+
Type: AWS::RDS::DBSubnetGroup
|
126
|
+
Properties:
|
127
|
+
DBSubnetGroupDescription: CloudFormation managed DB subnet group.
|
128
|
+
SubnetIds:
|
129
|
+
- !Ref "DBSubnet1"
|
130
|
+
- !Ref "DBSubnet2"
|
131
|
+
- !Ref "DBSubnet3"
|
132
|
+
|
133
|
+
functions:
|
134
|
+
hello:
|
135
|
+
handler: app/handlers/main.hello
|
136
|
+
events:
|
137
|
+
- http:
|
138
|
+
path: hello
|
139
|
+
method: get
|
140
|
+
users:
|
141
|
+
handler: app/handlers/main.users
|
142
|
+
events:
|
143
|
+
- http:
|
144
|
+
path: users
|
145
|
+
method: get
|
146
|
+
create_user:
|
147
|
+
handler: app/handlers/main.create_user
|
148
|
+
events:
|
149
|
+
- http:
|
150
|
+
path: create_user
|
151
|
+
method: post
|
152
|
+
update_user:
|
153
|
+
handler: app/handlers/main.update_user
|
154
|
+
events:
|
155
|
+
- http:
|
156
|
+
path: update_user
|
157
|
+
method: put
|
158
|
+
entries:
|
159
|
+
handler: app/handlers/main.entries
|
160
|
+
events:
|
161
|
+
- http:
|
162
|
+
path: entries
|
163
|
+
method: get
|
164
|
+
create_entry:
|
165
|
+
handler: app/handlers/main.create_entry
|
166
|
+
events:
|
167
|
+
- http:
|
168
|
+
path: create_entry
|
169
|
+
method: post
|
170
|
+
delete_entry:
|
171
|
+
handler: app/handlers/main.delete_entry
|
172
|
+
events:
|
173
|
+
- http:
|
174
|
+
path: delete_entry
|
175
|
+
method: post
|
176
|
+
count_entry:
|
177
|
+
handler: app/handlers/main.count_entry
|
178
|
+
events:
|
179
|
+
- http:
|
180
|
+
path: count_entry
|
181
|
+
method: get
|
182
|
+
|
183
|
+
|
184
|
+
|
data/exe/aurora-data-api
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "aurora-data-api/version"
|
4
|
+
require "thor"
|
5
|
+
|
6
|
+
class Date
|
7
|
+
end
|
8
|
+
|
9
|
+
module AuroraDataApi
|
10
|
+
class Error < StandardError; end
|
11
|
+
|
12
|
+
class Schema
|
13
|
+
CREATE_TABLE = []
|
14
|
+
ALTER_TABLE = []
|
15
|
+
end
|
16
|
+
|
17
|
+
class Model
|
18
|
+
SCHEMA = {literal_id: :id}
|
19
|
+
|
20
|
+
def self.literal_id(lit)
|
21
|
+
SCHEMA[:literal_id] = lit
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.table(name)
|
25
|
+
SCHEMA[:table_name] = name
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.schema(&block)
|
29
|
+
converter = Converter.new(SCHEMA, self)
|
30
|
+
converter.head
|
31
|
+
converter.instance_eval(&block)
|
32
|
+
converter.tail
|
33
|
+
Schema::CREATE_TABLE << converter.create_table
|
34
|
+
Schema::ALTER_TABLE << converter.alter_table
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Converter
|
39
|
+
TYPES = {
|
40
|
+
::Date => "date",
|
41
|
+
::Time => "timestamp with time zone",
|
42
|
+
::String => "text",
|
43
|
+
::Integer => "bigint",
|
44
|
+
::Float => "double precision"
|
45
|
+
}
|
46
|
+
|
47
|
+
def initialize(schema, klass)
|
48
|
+
@table_name = schema[:table_name] || "#{klass.name.downcase}s"
|
49
|
+
@literal_id = schema[:literal_id]
|
50
|
+
@create_table = []
|
51
|
+
@alter_table = []
|
52
|
+
end
|
53
|
+
|
54
|
+
attr_reader :create_table, :alter_table
|
55
|
+
|
56
|
+
def head
|
57
|
+
@create_table << %/CREATE TABLE "#{@table_name}" (/
|
58
|
+
@create_table << %( "#{@literal_id}" bigint NOT NULL GENERATED ALWAYS AS IDENTITY,)
|
59
|
+
end
|
60
|
+
|
61
|
+
def tail
|
62
|
+
@create_table << %/ PRIMARY KEY ("#{@literal_id}")/
|
63
|
+
@create_table << %/);\n/
|
64
|
+
end
|
65
|
+
|
66
|
+
def timestamp
|
67
|
+
@create_table << %( "created_at" #{TYPES[Time]} NOT NULL,)
|
68
|
+
@create_table << %( "updated_at" #{TYPES[Time]} NOT NULL,)
|
69
|
+
end
|
70
|
+
|
71
|
+
def col(name, type, **params)
|
72
|
+
line = " "
|
73
|
+
case type
|
74
|
+
when Symbol
|
75
|
+
col_name = "#{name}_#{@literal_id}"
|
76
|
+
line << %("#{col_name}" )
|
77
|
+
line << TYPES[Integer]
|
78
|
+
@alter_table << %/ALTER TABLE ONLY "#{@table_name}" ADD CONSTRAINT "#{@table_name}_#{col_name}_fkey" FOREIGN KEY ("#{col_name}") REFERENCES "#{params[:table]}" ("#{@literal_id}");/
|
79
|
+
else
|
80
|
+
line << %("#{name}" )
|
81
|
+
line << TYPES[type]
|
82
|
+
end
|
83
|
+
params.each do |k, v|
|
84
|
+
case k
|
85
|
+
when :null
|
86
|
+
line << " NOT NULL" unless v
|
87
|
+
when :default
|
88
|
+
line << " DEFAULT "
|
89
|
+
line << case v
|
90
|
+
when String, Symbol
|
91
|
+
"'#{v}'"
|
92
|
+
else
|
93
|
+
v.to_s
|
94
|
+
end
|
95
|
+
when :unique
|
96
|
+
line << " UNIQUE" if v
|
97
|
+
end
|
98
|
+
end
|
99
|
+
line << ","
|
100
|
+
@create_table << line
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class Tool < Thor
|
105
|
+
DEFAULT_MODELS_DIR = "app/models"
|
106
|
+
DEFAULT_OUTPUT_PATH = "db/schema.sql"
|
107
|
+
|
108
|
+
desc "version", "Print version"
|
109
|
+
def version
|
110
|
+
puts "aurora-data-api v#{AuroraDataApi::VERSION}"
|
111
|
+
end
|
112
|
+
|
113
|
+
desc "export", "Overwrite #{DEFAULT_OUTPUT_PATH} by aggregating #{DEFAULT_MODELS_DIR}/*.rb"
|
114
|
+
option :models, aliases: :m, default: DEFAULT_MODELS_DIR
|
115
|
+
option :output, aliases: :o, default: DEFAULT_OUTPUT_PATH
|
116
|
+
def export
|
117
|
+
models_dir = options[:models]
|
118
|
+
output_path = options[:output]
|
119
|
+
Dir.glob("#{models_dir}/*.rb").each do |rb|
|
120
|
+
load rb
|
121
|
+
end
|
122
|
+
if Schema::CREATE_TABLE.empty? && Schema::ALTER_TABLE.empty?
|
123
|
+
puts "Nothing to be exported."
|
124
|
+
exit 1
|
125
|
+
end
|
126
|
+
overwrite = false
|
127
|
+
if File.exist? output_path
|
128
|
+
print "#{output_path} exists. Overwrite? [Y/n]: "
|
129
|
+
answer = $stdin.gets.chomp
|
130
|
+
if %w[Y y yes].include?(answer.chomp)
|
131
|
+
overwrite = true
|
132
|
+
else
|
133
|
+
puts "Abort."
|
134
|
+
exit 1
|
135
|
+
end
|
136
|
+
else
|
137
|
+
FileUtils.touch output_path
|
138
|
+
overwrite = true
|
139
|
+
end
|
140
|
+
if overwrite
|
141
|
+
File.open output_path, "w" do |f|
|
142
|
+
f.write <<~COMMENT
|
143
|
+
/*
|
144
|
+
* This file was automatically genarated by the command:
|
145
|
+
* aurora-data-api export --models #{models_dir} --output #{output_path}
|
146
|
+
*
|
147
|
+
* Genarated at #{Time.now}
|
148
|
+
*
|
149
|
+
* https://github.com/hasumikin/aurora-data-api
|
150
|
+
*/\n
|
151
|
+
COMMENT
|
152
|
+
f.write Schema::CREATE_TABLE.flatten.join("\n")
|
153
|
+
f.write "\n"
|
154
|
+
f.write Schema::ALTER_TABLE.flatten.join("\n")
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
AuroraDataApi::Tool.start
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "aws-sdk-rdsdataservice"
|
4
|
+
require_relative "environment"
|
5
|
+
|
6
|
+
module AuroraDataApi
|
7
|
+
class DataService
|
8
|
+
def initialize
|
9
|
+
client_param = {region: Environment.region}
|
10
|
+
if Environment.offline?
|
11
|
+
offline_config_update
|
12
|
+
client_param[:endpoint] = Environment.offline_endpoint
|
13
|
+
end
|
14
|
+
@client = Aws::RDSDataService::Client.new(client_param)
|
15
|
+
@execute_params = {
|
16
|
+
database: Environment.database_name,
|
17
|
+
secret_arn: Environment.secret_arn,
|
18
|
+
resource_arn: Environment.resource_arn,
|
19
|
+
include_result_metadata: true
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def execute(hash, without_database = false)
|
24
|
+
if without_database
|
25
|
+
@client.execute_statement(
|
26
|
+
hash.merge(@execute_params.reject { |k, _v| k == :database })
|
27
|
+
)
|
28
|
+
else
|
29
|
+
@client.execute_statement(hash.merge(@execute_params))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private def offline_config_update
|
34
|
+
Aws.config.update({
|
35
|
+
credentials: Aws::Credentials.new(
|
36
|
+
"AWS_ACCESS_KEY_dummy",
|
37
|
+
"AWS_SECRET_ACCESS_KEY_dummy"
|
38
|
+
)
|
39
|
+
})
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AuroraDataApi
|
4
|
+
class Depot
|
5
|
+
def self.[](model, &block)
|
6
|
+
# @type var block: Proc
|
7
|
+
depot = new(model, block)
|
8
|
+
AuroraDataApi.const_set("#{model.name}Depot".to_sym, depot)
|
9
|
+
depot
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(model, block)
|
13
|
+
@model = model
|
14
|
+
instance_eval { block.call }
|
15
|
+
@data_service = DataService.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def table_name
|
19
|
+
@table_name ||= Model::SCHEMA[@model.name.to_sym][:table_name] || "#{@model.to_s.downcase}s"
|
20
|
+
end
|
21
|
+
|
22
|
+
def literal_id
|
23
|
+
@literal_id ||= Model::SCHEMA[@model.name.to_sym][:literal_id] || :id
|
24
|
+
end
|
25
|
+
|
26
|
+
def insert(obj)
|
27
|
+
obj.set_timestamp(at: :create)
|
28
|
+
params = obj.build_params
|
29
|
+
res = query(<<~SQL, **params)
|
30
|
+
INSERT INTO "#{obj.table_name}"
|
31
|
+
(#{params.keys.map { |k| "\"#{k}\"" }.join(",")})
|
32
|
+
VALUES
|
33
|
+
(#{params.keys.map { |k| ":#{k}" }.join(",")})
|
34
|
+
RETURNING "#{obj.literal_id}";
|
35
|
+
SQL
|
36
|
+
obj._set_id(res.records[0][0].value)
|
37
|
+
end
|
38
|
+
|
39
|
+
def update(obj)
|
40
|
+
obj.set_timestamp(at: :update)
|
41
|
+
params = obj.build_params
|
42
|
+
query(<<~SQL, **params)
|
43
|
+
UPDATE "#{obj.table_name}" SET
|
44
|
+
#{params.keys.reject { |k| k == obj.literal_id }.map { |k| "\"#{k}\" = :#{k}" }.join(", ")}
|
45
|
+
WHERE "#{obj.literal_id}" = :#{obj.literal_id};
|
46
|
+
SQL
|
47
|
+
true
|
48
|
+
end
|
49
|
+
|
50
|
+
def delete(obj)
|
51
|
+
query(<<~SQL, id: obj.id)
|
52
|
+
DELETE FROM "#{obj.table_name}" WHERE "#{obj.literal_id}" = :id;
|
53
|
+
SQL
|
54
|
+
obj._destroy
|
55
|
+
true
|
56
|
+
end
|
57
|
+
|
58
|
+
def count(str = "", **params)
|
59
|
+
query(
|
60
|
+
"SELECT COUNT(\"#{literal_id}\") FROM \"#{table_name}\" #{str};",
|
61
|
+
**params
|
62
|
+
).records[0][0].long_value
|
63
|
+
end
|
64
|
+
|
65
|
+
def select(str, **params)
|
66
|
+
result = query("select * from \"#{table_name}\" #{str};", **params)
|
67
|
+
related_objects = {}
|
68
|
+
result.records.map do |record|
|
69
|
+
relationships = {}
|
70
|
+
attributes = {}.tap do |attrribute|
|
71
|
+
result.column_metadata.each_with_index do |meta, index|
|
72
|
+
if meta.table_name == table_name.to_s
|
73
|
+
attrribute[meta.name.to_sym] = column_data(meta, record[index])
|
74
|
+
else
|
75
|
+
table_sym = meta.table_name.to_sym
|
76
|
+
name, rel_model = @model.relationship_by(table_sym)
|
77
|
+
if name
|
78
|
+
relationships[name] ||= {}
|
79
|
+
relationships[name][:attr] ||= {}
|
80
|
+
relationships[name][:model] ||= rel_model
|
81
|
+
relationships[name][:attr][meta.name.to_sym] = column_data(meta, record[index])
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
relationships.each do |name, data|
|
87
|
+
related_objects[name] ||= {}
|
88
|
+
id = data[:attr][literal_id]
|
89
|
+
obj = related_objects.dig(name, id)
|
90
|
+
unless obj
|
91
|
+
obj = Kernel.const_get(data[:model]).new(**data[:attr])
|
92
|
+
related_objects[name][id] = obj
|
93
|
+
end
|
94
|
+
attributes[name] = obj
|
95
|
+
end
|
96
|
+
@model.new(attributes)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def column_data(meta, col)
|
101
|
+
return nil if col.is_null
|
102
|
+
case meta.type_name
|
103
|
+
when "text"
|
104
|
+
col.value.gsub("''", "'")
|
105
|
+
when "timestamptz"
|
106
|
+
Time.parse(col.value) + 9 * 60 * 60 # workaround
|
107
|
+
else
|
108
|
+
col.value
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def query(str, **params)
|
113
|
+
@data_service.execute({
|
114
|
+
sql: str,
|
115
|
+
parameters: params.map do |param|
|
116
|
+
hash = {name: param[0].to_s, value: {}}
|
117
|
+
case param[1]
|
118
|
+
when Integer
|
119
|
+
hash[:value][:long_value] = param[1]
|
120
|
+
when Float
|
121
|
+
hash[:value][:double_value] = param[1]
|
122
|
+
when TrueClass, FalseClass
|
123
|
+
hash[:value][:boolean_value] = param[1]
|
124
|
+
else # TODO: confirm format and timezone when Time
|
125
|
+
hash[:value][:string_value] = param[1].to_s.gsub("'", "''")
|
126
|
+
end
|
127
|
+
hash
|
128
|
+
end
|
129
|
+
})
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AuroraDataApi
|
4
|
+
class Environment
|
5
|
+
# Resources about "OFFLINE":
|
6
|
+
# https://www.serverless.com/plugins/serverless-offline
|
7
|
+
# https://github.com/koxudaxi/local-data-api
|
8
|
+
|
9
|
+
def self.offline?
|
10
|
+
ENV["IS_OFFLINE"] == "true"
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.offline_endpoint
|
14
|
+
"http://local-data-api"
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.database_name
|
18
|
+
ENV["PGDATABASE"] || ENV["MYSQL_DATABASE"]
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.region
|
22
|
+
ENV.fetch("AWS_DEFAULT_REGION", "ap-northeast-1")
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.secret_arn
|
26
|
+
if offline?
|
27
|
+
"arn:aws:secretsmanager:us-east-1:123456789012:secret:dummy"
|
28
|
+
else
|
29
|
+
ENV["RDS_SECRET_ARN"]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.resource_arn
|
34
|
+
if offline?
|
35
|
+
"arn:aws:rds:us-east-1:123456789012:cluster:dummy"
|
36
|
+
else
|
37
|
+
ENV["RDS_RESOURCE_ARN"]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|