rrx_api 0.1.0 → 8.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rspec +1 -1
- data/Gemfile +3 -4
- data/Gemfile.lock +127 -118
- data/README.md +1 -1
- data/app/controllers/concerns/rrx_api/authenticatable.rb +79 -0
- data/app/controllers/rrx_api/controller.rb +1 -0
- data/app/controllers/rrx_api/health_controller.rb +33 -0
- data/app/models/concerns/arel_query.rb +189 -0
- data/app/models/rrx_api/record.rb +12 -0
- data/config/routes.rb +13 -0
- data/lib/generators/rrx_api/base.rb +95 -0
- data/lib/generators/rrx_api/docker_generator.rb +19 -0
- data/lib/generators/rrx_api/github_generator.rb +83 -0
- data/lib/generators/rrx_api/install_generator.rb +120 -0
- data/lib/generators/rrx_api/templates/docker/Dockerfile.tt +1 -0
- data/lib/generators/rrx_api/templates/github/build/workflows/build.yml.tt +71 -0
- data/lib/generators/rrx_api/templates/github/deploy/workflows/deploy.yml.tt +62 -0
- data/lib/generators/rrx_api/templates/terraform/aws/iam.tf.tt +37 -0
- data/lib/generators/rrx_api/templates/terraform/aws/main.tf.tt +44 -0
- data/lib/generators/rrx_api/templates/terraform/aws/service.tf.tt +67 -0
- data/lib/generators/rrx_api/terraform_generator.rb +76 -0
- data/lib/rrx_api/auth/base.rb +29 -0
- data/lib/rrx_api/auth/firebase.rb +70 -0
- data/lib/rrx_api/engine.rb +111 -0
- data/lib/rrx_api/version.rb +3 -2
- data/lib/rrx_api.rb +1 -1
- metadata +69 -37
- data/.idea/.gitignore +0 -8
- data/.idea/inspectionProfiles/Project_Default.xml +0 -6
- data/.idea/modules.xml +0 -8
- data/.idea/rrx_api.iml +0 -255
- data/.idea/vcs.xml +0 -6
- data/exe/rrx_api_setup +0 -37
- data/exe/sources/config/initializers/cors.rb +0 -21
- data/exe/sources/config/initializers/generators.rb +0 -6
- data/lib/rrx_api/railtie.rb +0 -44
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ArelQuery
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
module Cast
|
|
7
|
+
def self.included(mod)
|
|
8
|
+
mod.class_eval do
|
|
9
|
+
alias_method :create_cast, :cast if method_defined?(:cast)
|
|
10
|
+
|
|
11
|
+
def cast(type)
|
|
12
|
+
::Arel::Nodes::NamedFunction.new "CAST", [self.as(type)]
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class WithHelper
|
|
19
|
+
attr_reader :name
|
|
20
|
+
|
|
21
|
+
def initialize(name, relation)
|
|
22
|
+
raise ArgumentError, 'Name must be a Symbol or String' unless name.is_a?(Symbol) || name.is_a?(String)
|
|
23
|
+
raise ArgumentError, 'Relation must be an Arel node or select manager' unless relation.is_a?(Arel::Nodes::Node) || relation.is_a?(Arel::SelectManager)
|
|
24
|
+
|
|
25
|
+
@name = name
|
|
26
|
+
@relation = relation
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def [](column)
|
|
30
|
+
table[column]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def table
|
|
34
|
+
@table ||= Arel::Table.new(@name)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def to_cte
|
|
38
|
+
Arel::Nodes::Cte.new(@name, @relation)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class ArelHelper
|
|
43
|
+
attr_reader :model
|
|
44
|
+
|
|
45
|
+
delegate :star, :sql, to: Arel
|
|
46
|
+
delegate :project, :where, :from, :join, :outer_join, :group, :order, :alias, to: :@table
|
|
47
|
+
delegate :with, to: :from
|
|
48
|
+
|
|
49
|
+
def initialize(model)
|
|
50
|
+
@model = model
|
|
51
|
+
@table = model.arel_table if model.respond_to?(:arel_table)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def table(name = nil)
|
|
55
|
+
if name
|
|
56
|
+
Arel::Table.new(name)
|
|
57
|
+
elsif @table
|
|
58
|
+
@table
|
|
59
|
+
else
|
|
60
|
+
raise ArgumentError, 'No table available. Provide a table name.'
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
alias t table
|
|
65
|
+
|
|
66
|
+
def literal(value)
|
|
67
|
+
Arel::Nodes::SqlLiteral.new(value.to_s)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
alias l literal
|
|
71
|
+
alias lit literal
|
|
72
|
+
|
|
73
|
+
def string(value)
|
|
74
|
+
literal "'#{value}'"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
alias s string
|
|
78
|
+
alias str string
|
|
79
|
+
|
|
80
|
+
# @return [Arel::SelectManager]
|
|
81
|
+
def select(...)
|
|
82
|
+
Arel::SelectManager.new(...)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def as(what, alias_name)
|
|
86
|
+
raise ArgumentError, 'Alias name must be a Symbol or String' unless alias_name.is_a?(Symbol) || alias_name.is_a?(String)
|
|
87
|
+
|
|
88
|
+
Arel::Nodes::As.new(what, literal(alias_name))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def for_with(name, relation)
|
|
92
|
+
WithHelper.new(name, relation)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def results(query)
|
|
96
|
+
connection.select_all(query)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def rows(query)
|
|
100
|
+
results(query).to_a
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def connection
|
|
104
|
+
ActiveRecord::Base.connection
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def respond_to_missing?(_method_name, _include_private = false)
|
|
108
|
+
true
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
FUNCTION_METHOD_PATTERN = /\A[a-z]+(_[a-z]+)*\z/
|
|
112
|
+
|
|
113
|
+
def method_missing(method_name, *args, &block)
|
|
114
|
+
node_class = "Arel::Nodes::#{method_name.to_s.camelize}".safe_constantize
|
|
115
|
+
|
|
116
|
+
if node_class
|
|
117
|
+
self.class.define_method method_name do |*method_args|
|
|
118
|
+
node_class.new(*method_args)
|
|
119
|
+
end
|
|
120
|
+
elsif @table&.respond_to?(method_name)
|
|
121
|
+
return @table.send(method_name, *args)
|
|
122
|
+
elsif method_name.to_s =~ FUNCTION_METHOD_PATTERN
|
|
123
|
+
self.class.define_method method_name do |*method_args|
|
|
124
|
+
Arel::Nodes::NamedFunction.new(method_name.to_s, method_args)
|
|
125
|
+
end
|
|
126
|
+
else
|
|
127
|
+
return super
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
send(method_name, *args)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def column(name)
|
|
134
|
+
@table[name]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
alias col column
|
|
138
|
+
alias c column
|
|
139
|
+
|
|
140
|
+
def [](it)
|
|
141
|
+
case it
|
|
142
|
+
when Symbol
|
|
143
|
+
@table[it]
|
|
144
|
+
when String
|
|
145
|
+
string(it)
|
|
146
|
+
else
|
|
147
|
+
literal(it)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
class_methods do
|
|
153
|
+
def aq(*args)
|
|
154
|
+
@arel_helper ||= ArelHelper.new(self)
|
|
155
|
+
args.empty? ? @arel_helper : @arel_helper.sql(*args)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
included do
|
|
160
|
+
def aq(...)
|
|
161
|
+
self.class.aq(...)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
ActiveSupport.on_load :active_record do
|
|
167
|
+
class ::Arel::SelectManager
|
|
168
|
+
def table
|
|
169
|
+
@ast.cores[0].source.left
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def join_to(other, from: :id, to: :id, on: nil, type: :inner)
|
|
173
|
+
on ||= table[from].eq(other[to])
|
|
174
|
+
join_node_class = case type
|
|
175
|
+
when :inner then Arel::Nodes::InnerJoin
|
|
176
|
+
when :left, :outer then Arel::Nodes::OuterJoin
|
|
177
|
+
when :right then Arel::Nodes::RightOuterJoin
|
|
178
|
+
when :full then Arel::Nodes::FullOuterJoin
|
|
179
|
+
else raise ArgumentError, "Unknown join type: #{type.inspect}"
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
other = other.table if other.respond_to?(:table)
|
|
183
|
+
join(other, join_node_class).on(on)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
::Arel::Nodes::Node.include(ArelQuery::Cast)
|
|
188
|
+
::Arel::Attributes::Attribute.include(ArelQuery::Cast)
|
|
189
|
+
end
|
|
@@ -4,9 +4,21 @@ module RrxApi
|
|
|
4
4
|
class Record < ActiveRecord::Base
|
|
5
5
|
self.abstract_class = true
|
|
6
6
|
|
|
7
|
+
include ArelQuery
|
|
8
|
+
|
|
9
|
+
before_create :set_new_id
|
|
10
|
+
|
|
7
11
|
# @return [RrxLogging::Logger]
|
|
8
12
|
def logger
|
|
9
13
|
@logger ||= RrxLogging.current || Rails.logger
|
|
10
14
|
end
|
|
15
|
+
|
|
16
|
+
protected
|
|
17
|
+
|
|
18
|
+
def set_new_id
|
|
19
|
+
self.id ||= Random.uuid if has_attribute?(:id)
|
|
20
|
+
end
|
|
21
|
+
|
|
11
22
|
end
|
|
23
|
+
|
|
12
24
|
end
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
RrxApi::Engine.routes.draw do
|
|
2
|
+
if Rails.root.join('swagger').exist?
|
|
3
|
+
require 'rswag/api/engine'
|
|
4
|
+
require 'rswag/ui/engine'
|
|
5
|
+
|
|
6
|
+
mount Rswag::Ui::Engine => '/api-docs'
|
|
7
|
+
mount Rswag::Api::Engine => '/api-docs'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
healthcheck_path = Rails.application.config.healthcheck_route
|
|
11
|
+
healthcheck_path = "/#{healthcheck_path}" unless healthcheck_path.start_with?('/')
|
|
12
|
+
get healthcheck_path => 'rrx_api/health#show', as: :rrx_health_check
|
|
13
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RrxApi
|
|
4
|
+
module Generators
|
|
5
|
+
class Base < ::Rails::Generators::Base
|
|
6
|
+
hide!
|
|
7
|
+
|
|
8
|
+
def self.init!(description = nil)
|
|
9
|
+
# noinspection RubyMismatchedArgumentType
|
|
10
|
+
source_root Pathname(__dir__).join('templates')
|
|
11
|
+
desc description if description
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
protected
|
|
15
|
+
|
|
16
|
+
# class Version
|
|
17
|
+
# def initialize(str)
|
|
18
|
+
# @version = str.to_s
|
|
19
|
+
# @segments = @version.split('.').map(&:to_i)
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# def to_s
|
|
23
|
+
# @version
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# def major
|
|
27
|
+
# @segments[0]
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# def minor
|
|
31
|
+
# @segments[1]
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# def build
|
|
35
|
+
# @segments[2] || 0
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
# def major_minor
|
|
39
|
+
# "#{major}.#{minor}"
|
|
40
|
+
# end
|
|
41
|
+
#
|
|
42
|
+
# def <=>(other)
|
|
43
|
+
# return nil unless other.is_a?(Version)
|
|
44
|
+
#
|
|
45
|
+
# result = major <=> other.major
|
|
46
|
+
# result = minor <=> other.minor if result.zero?
|
|
47
|
+
# result = build <=> other.build if result.zero?
|
|
48
|
+
# result
|
|
49
|
+
# end
|
|
50
|
+
# end
|
|
51
|
+
|
|
52
|
+
def destination_path
|
|
53
|
+
@destination_path = Pathname(destination_root)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def app_name
|
|
57
|
+
@app_name ||= destination_path.basename.to_s
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @return [String] Current Ruby version in format "MAJOR.MINOR"
|
|
61
|
+
def ruby_version
|
|
62
|
+
@ruby_version ||= bundle_max_ruby_version || current_ruby_version
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @return [Bundler::Definition]
|
|
66
|
+
def bundle
|
|
67
|
+
@bundle ||= Bundler::Definition.build(
|
|
68
|
+
destination_path.join('Gemfile'),
|
|
69
|
+
destination_path.join('Gemfile.lock'),
|
|
70
|
+
nil
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def current_ruby_version
|
|
75
|
+
RUBY_VERSION.split('.')[0..1].join('.')
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def bundle_max_ruby_version
|
|
79
|
+
return nil unless bundle.ruby_version
|
|
80
|
+
|
|
81
|
+
version = Gem::Requirement.new(*bundle.ruby_version.versions)
|
|
82
|
+
max_minor = version
|
|
83
|
+
.requirements
|
|
84
|
+
.map { |_, v| v.segments[0..1] } # [major, minor]
|
|
85
|
+
|
|
86
|
+
if max_minor.any?
|
|
87
|
+
max_minor.min_by { |major, minor| [major, minor] }.join('.')
|
|
88
|
+
else
|
|
89
|
+
nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module RrxApi
|
|
6
|
+
module Generators
|
|
7
|
+
class DockerGenerator < Base
|
|
8
|
+
init! 'Generates configuration files for building a docker image.'
|
|
9
|
+
|
|
10
|
+
class_option :image_name, type: :string, desc: 'Name of the Docker image. Defaults to application name.'
|
|
11
|
+
|
|
12
|
+
def docker
|
|
13
|
+
template 'docker/Dockerfile.tt', 'Dockerfile'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
alias image_name app_name
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require_relative 'base'
|
|
3
|
+
|
|
4
|
+
module RrxApi
|
|
5
|
+
module Generators
|
|
6
|
+
class GithubGenerator < Base
|
|
7
|
+
init! 'Generates GitHub Actions workflows for building and deploying the application using Docker.'
|
|
8
|
+
|
|
9
|
+
class_option :deploy,
|
|
10
|
+
type: :boolean,
|
|
11
|
+
default: false,
|
|
12
|
+
desc: 'Include deployment workflow'
|
|
13
|
+
|
|
14
|
+
class_option :terraform_repo,
|
|
15
|
+
type: :string,
|
|
16
|
+
default: 'terraform',
|
|
17
|
+
desc: 'Terraform repository name (if using deployment workflow)'
|
|
18
|
+
|
|
19
|
+
class_option :terraform_module,
|
|
20
|
+
type: :string,
|
|
21
|
+
desc: 'Terraform module name (if using deployment workflow). Default is the application name.'
|
|
22
|
+
|
|
23
|
+
class_option :database,
|
|
24
|
+
type: :string,
|
|
25
|
+
default: 'auto',
|
|
26
|
+
enum: %w[auto postgresql mysql mariadb sqlite none],
|
|
27
|
+
desc: 'Database type'
|
|
28
|
+
|
|
29
|
+
def github
|
|
30
|
+
directory 'github/build', '.github'
|
|
31
|
+
directory 'github/deploy', '.github' if deploy?
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def deploy?
|
|
37
|
+
options[:deploy]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def database
|
|
41
|
+
@database ||= options[:database] == 'auto' ? detect_database : options[:database]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def terraform_repo
|
|
45
|
+
options[:terraform_repo]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def terraform_module
|
|
49
|
+
options[:terraform_module] || app_name
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def docker_packages
|
|
53
|
+
''
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def detect_database
|
|
57
|
+
config_path = destination_path.join('config/database.yml')
|
|
58
|
+
if config_path.exist?
|
|
59
|
+
# @type {Hash}
|
|
60
|
+
config = YAML.safe_load(config_path.read, symbolize_names: true, aliases: true)
|
|
61
|
+
adapter = config.dig(:test, :adapter).to_s.downcase
|
|
62
|
+
case adapter
|
|
63
|
+
when /postgresql/, /psql/
|
|
64
|
+
'postgresql'
|
|
65
|
+
when /mysql/
|
|
66
|
+
if yes?('Detected MySQL adapter in database.yml. Are you using MariaDB? (Yn)')
|
|
67
|
+
'mariadb'
|
|
68
|
+
else
|
|
69
|
+
'mysql'
|
|
70
|
+
end
|
|
71
|
+
when /sqlite/
|
|
72
|
+
'sqlite'
|
|
73
|
+
else
|
|
74
|
+
say_error 'Unsupported database adapter detected in config/database.yml. Please specify the database type explicitly using --database option.'
|
|
75
|
+
exit 1
|
|
76
|
+
end
|
|
77
|
+
else
|
|
78
|
+
'none'
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module RrxApi
|
|
6
|
+
module Generators
|
|
7
|
+
class InstallGenerator < Base
|
|
8
|
+
init! 'Installs the RRX API gem and its dependencies.'
|
|
9
|
+
|
|
10
|
+
class_option :skip_rrx_dev, type: :boolean, default: false, hide: true
|
|
11
|
+
|
|
12
|
+
# Updates the application configuration file with specific dependencies and settings.
|
|
13
|
+
# The method performs the following operations:
|
|
14
|
+
# - Reads the application configuration file located at 'config/application.rb'.
|
|
15
|
+
# - Removes existing comments and unnecessary `require` statements from the file.
|
|
16
|
+
# - Includes necessary `require` directives for the application's gem dependencies.
|
|
17
|
+
# - Injects or updates the Bundler require statement to include the necessary gems.
|
|
18
|
+
# - Cleans up unwanted whitespace in the file content.
|
|
19
|
+
# - Rewrites the configuration file with the updated content.
|
|
20
|
+
# - Appends additional configuration settings for time zone, schema format, and session management.
|
|
21
|
+
#
|
|
22
|
+
# @return [void] Since the primary purpose is file modification, it does not return a value directly.
|
|
23
|
+
def update_application
|
|
24
|
+
# @type [Pathname]
|
|
25
|
+
app_file = Pathname(destination_root).join('config', 'application.rb')
|
|
26
|
+
app_code = app_file.read
|
|
27
|
+
|
|
28
|
+
# Assume full replace if we've never modified before.
|
|
29
|
+
# Otherwise, create_file will prompt to replace it.
|
|
30
|
+
remove_file app_file unless app_code =~ /rrx_api/
|
|
31
|
+
|
|
32
|
+
app_code.gsub!(/^\s*#.*\r?\n/, '')
|
|
33
|
+
app_code.gsub!(/^(?:#\s+)?require ["'].*\r?\n/, '')
|
|
34
|
+
|
|
35
|
+
requires = application_gems.map do |gem|
|
|
36
|
+
"require '#{gem}'"
|
|
37
|
+
end.join("\n")
|
|
38
|
+
|
|
39
|
+
app_code.sub!(/^(Bundler.require.*)$/) do |str|
|
|
40
|
+
<<~REQ
|
|
41
|
+
#{requires}
|
|
42
|
+
|
|
43
|
+
#{str}
|
|
44
|
+
REQ
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Remove existing application config lines
|
|
48
|
+
APPLICATION_CONFIG.each do |line|
|
|
49
|
+
app_code.gsub!(/^\s*#{line}\W*.*\n/, '')
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Remove unnecessary whitespace
|
|
53
|
+
app_code.lstrip!
|
|
54
|
+
app_code.gsub!(/^\s*\r?\n(\s*\r?\n)+/, "\n")
|
|
55
|
+
|
|
56
|
+
# puts app_code
|
|
57
|
+
create_file app_file, app_code
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def update_base_classes
|
|
61
|
+
gsub_file 'app/models/application_record.rb',
|
|
62
|
+
/ApplicationRecord.*/,
|
|
63
|
+
'ApplicationRecord < RrxApi::Record'
|
|
64
|
+
|
|
65
|
+
gsub_file 'app/controllers/application_controller.rb',
|
|
66
|
+
/ApplicationController.*/,
|
|
67
|
+
'ApplicationController < RrxApi::Controller'
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def routes
|
|
71
|
+
inject_into_file 'config/routes.rb',
|
|
72
|
+
after: "Rails.application.routes.draw do\n" do
|
|
73
|
+
<<~RUBY
|
|
74
|
+
mount RrxApi::Engine => '/'
|
|
75
|
+
RUBY
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def rrx_dev
|
|
80
|
+
generate 'rrx_dev:install' unless options[:skip_rrx_dev]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def asdf_versions
|
|
84
|
+
create_file '.tool-versions', <<~VERSIONS
|
|
85
|
+
ruby #{ruby_version}
|
|
86
|
+
VERSIONS
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
# Configs to remove from the application.rb file
|
|
92
|
+
APPLICATION_CONFIG = <<~CONFIG.split("\n").map(&:strip).freeze
|
|
93
|
+
config.time_zone
|
|
94
|
+
config.active_support.to_time_preserves_timezone
|
|
95
|
+
config.active_record.schema_format
|
|
96
|
+
config.session_store
|
|
97
|
+
config.middleware.use ActionDispatch::Cookies
|
|
98
|
+
config.middleware.use ActionDispatch::Session::CookieStore
|
|
99
|
+
CONFIG
|
|
100
|
+
|
|
101
|
+
# @return [Array<String>] The list of application gem names that are dependencies
|
|
102
|
+
def application_gems
|
|
103
|
+
gems = %w[rrx_api]
|
|
104
|
+
gems.concat(%w[rrx_jobs active_job].select { |name| gem?(name) })
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# @param [String] name
|
|
108
|
+
# @return [Boolean] True if gem is a dependency
|
|
109
|
+
def gem?(name)
|
|
110
|
+
bundle_gems.include?(name)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# @return [Set<String>]
|
|
114
|
+
def bundle_gems
|
|
115
|
+
@bundle_gems ||= bundle.dependencies.map(&:name).to_set
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
FROM ddrew555/rrx_docker:<%= ruby_version %>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
name: Build
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
<% if deploy? %>
|
|
6
|
+
inputs:
|
|
7
|
+
dev_deploy:
|
|
8
|
+
type: boolean
|
|
9
|
+
default: true
|
|
10
|
+
description: 'Deploy build to development environment'
|
|
11
|
+
<% end %>
|
|
12
|
+
|
|
13
|
+
push:
|
|
14
|
+
branches: [main]
|
|
15
|
+
|
|
16
|
+
env:
|
|
17
|
+
image_name: '<%= app_name %>'
|
|
18
|
+
major_version: 1
|
|
19
|
+
minor_version: 0
|
|
20
|
+
development: ${{ github.ref_name != 'main' }}
|
|
21
|
+
|
|
22
|
+
permissions: write-all
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
build:
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v4
|
|
29
|
+
with:
|
|
30
|
+
fetch-depth: 0
|
|
31
|
+
|
|
32
|
+
- uses: dan-drew/asdf-actions/tool-versions@v1
|
|
33
|
+
|
|
34
|
+
- name: Test
|
|
35
|
+
uses: rails-rrx/actions/test@main
|
|
36
|
+
with:
|
|
37
|
+
ruby_version: <%= ruby_version %>
|
|
38
|
+
database: <%= database %>
|
|
39
|
+
|
|
40
|
+
- uses: dan-drew/actions/next-version@main
|
|
41
|
+
with:
|
|
42
|
+
suffix: ${{ env.development && '-dev' || '' }}
|
|
43
|
+
|
|
44
|
+
- name: Build
|
|
45
|
+
uses: rails-rrx/actions/docker-build@main
|
|
46
|
+
with:
|
|
47
|
+
repository: ${{ vars.DOCKER_REPOSITORY }}
|
|
48
|
+
username: ${{ secrets.DOCKER_USERNAME }}
|
|
49
|
+
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
50
|
+
image_name: $${{ env.image_name }}
|
|
51
|
+
image_version: ${{ env.next_version }}
|
|
52
|
+
database: <%= database %>
|
|
53
|
+
latest: true
|
|
54
|
+
packages: '<%= docker_packages %>'
|
|
55
|
+
|
|
56
|
+
- uses: dan-drew/actions/push-version@main
|
|
57
|
+
if: ${{ ! env.development }}
|
|
58
|
+
<% if deploy? %>
|
|
59
|
+
- name: Deploy Development
|
|
60
|
+
uses: actions/github-script@v7
|
|
61
|
+
condition: ${{ inputs.dev_deploy }}
|
|
62
|
+
with:
|
|
63
|
+
script: |
|
|
64
|
+
github.rest.actions.createWorkflowDispatch({
|
|
65
|
+
owner: context.repo.owner,
|
|
66
|
+
repo: context.repo.repo,
|
|
67
|
+
workflow_id: 'deploy.yml',
|
|
68
|
+
ref: context.ref,
|
|
69
|
+
inputs: { version: '${{ env.next_version }}' }
|
|
70
|
+
});
|
|
71
|
+
<% end %>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
name: Deploy
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
version:
|
|
7
|
+
required: true
|
|
8
|
+
type: string
|
|
9
|
+
description: 'Version to deploy'
|
|
10
|
+
environment:
|
|
11
|
+
default: development
|
|
12
|
+
type: string
|
|
13
|
+
description: 'Environment to deploy'
|
|
14
|
+
|
|
15
|
+
env:
|
|
16
|
+
image_name: '<%= app_name %>'
|
|
17
|
+
image_tag: "${{ inputs.version }}"
|
|
18
|
+
terraform_repo: '<%= terraform_repo %>'
|
|
19
|
+
terraform_module: '<%= terraform_module %>'
|
|
20
|
+
repository: ${{ vars.DOCKER_REPOSITORY }}
|
|
21
|
+
username: ${{ secrets.DOCKER_USERNAME }}
|
|
22
|
+
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
23
|
+
|
|
24
|
+
permissions: write-all
|
|
25
|
+
|
|
26
|
+
jobs:
|
|
27
|
+
deploy:
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
steps:
|
|
30
|
+
- name: Tag Docker Image
|
|
31
|
+
run: |
|
|
32
|
+
readonly image_ref="${repository}/${image_name}"
|
|
33
|
+
readonly source_image="${image_ref}:${image_tag}"
|
|
34
|
+
readonly dest_image="${image_ref}:${{ inputs.environment }}"
|
|
35
|
+
|
|
36
|
+
echo ::group::Login
|
|
37
|
+
cat <<-PASSWORD | docker login "$repository" -u "$username" --password-stdin
|
|
38
|
+
${password}
|
|
39
|
+
PASSWORD
|
|
40
|
+
echo ::endgroup::
|
|
41
|
+
|
|
42
|
+
echo ::group::Tag
|
|
43
|
+
docker pull -q "${source_image}"
|
|
44
|
+
docker tag "${source_image}" "${dest_image}"
|
|
45
|
+
docker push -q "${dest_image}"
|
|
46
|
+
echo ::endgroup::
|
|
47
|
+
|
|
48
|
+
- name: Trigger Terraform
|
|
49
|
+
uses: actions/github-script@v7
|
|
50
|
+
with:
|
|
51
|
+
github-token: ${{ secrets.TERRAFORM_GITHUB_TOKEN }}
|
|
52
|
+
script: |
|
|
53
|
+
github.rest.repos.createDispatchEvent({
|
|
54
|
+
owner: context.repo.owner,
|
|
55
|
+
repo: 'terraform',
|
|
56
|
+
event_type: 'apply',
|
|
57
|
+
|
|
58
|
+
client_payload: {
|
|
59
|
+
path: '${{ env.terraform_module }}',
|
|
60
|
+
environment: '${{ inputs.environment }}'
|
|
61
|
+
}
|
|
62
|
+
});
|