module Souls STRAINS = ["service", "api", "graph", "media", "admin"] module Init class << self def create_souls strain: 1, app_name: "souls" config_needed = (1..2) project = {} project[:strain] = STRAINS[strain.to_i - 1] begin puts "Google Cloud Main PROJECT_ID(This project will controll Cloud DNS): (default: elsoul2)" project[:main_project_id] = STDIN.gets.chomp project[:main_project_id] == "" ? project[:main_project_id] = "elsoul2" : true puts "Google Cloud PROJECT_ID: (default: elsoul2)" project[:project_id] = STDIN.gets.chomp project[:project_id] = "elsoul2" if project[:project_id] == "" puts "VPC Network Name: (default: default)" project[:network] = STDIN.gets.chomp project[:network] == "" ? project[:network] = "default" : true puts "Namespace: (default: souls)" project[:namespace] = STDIN.gets.chomp project[:namespace] == "" ? project[:namespace] = "souls" : true if project[:strain] == "service" puts "Service Name: (default: blog-service)" project[:service_name] = STDIN.gets.chomp project[:service_name] == "" ? project[:service_name] = "blog-service" : true end puts "Instance MachineType: (default: custom-1-6656)" project[:machine_type] = STDIN.gets.chomp project[:machine_type] == "" ? project[:machine_type] = "custom-1-6656" : true puts "Zone: (default: us-central1-a)" project[:zone] = STDIN.gets.chomp project[:zone] == "" ? project[:zone] = "us-central1-a" : true puts "Domain: (e.g API: el-soul.com, Serive: xds:///blog-service:8000)" project[:domain] = STDIN.gets.chomp project[:domain] == "" ? project[:domain] = "el-soul.com" : true puts "Google Application Credentials Path: (default: #{app_name}/config/credentials.json)" project[:google_application_credentials] = STDIN.gets.chomp project[:google_application_credentials] == "" ? project[:google_application_credentials] = "./config/credentials.json" : true puts project puts "Enter to finish set up!" confirm = STDIN.gets.chomp raise StandardError, "Retry" unless confirm == "" download_souls app_name: app_name, repository_name: "souls_#{STRAINS[strain.to_i - 1]}" initial_config_init app_name: app_name, project: project if config_needed.include?(strain) rescue StandardError => error puts error retry end end def initial_config_init app_name: "souls", project: {} puts "Generating souls conf..." `touch "#{app_name}/config/initializers/souls.rb"` file_path = "#{app_name}/config/initializers/souls.rb" File.open(file_path, "w") do |f| f.write <<~EOS Souls.configure do |config| config.main_project_id = "#{project[:main_project_id]}" config.project_id = "#{project[:project_id]}" config.app = "#{app_name}" config.namespace = "#{project[:namespace]}" config.service_name = "#{project[:service_name]}" config.network = "#{project[:network]}" config.machine_type = "#{project[:machine_type]}" config.zone = "#{project[:zone]}" config.domain = "#{project[:domain]}" config.google_application_credentials = "#{project[:google_application_credentials]}" config.strain = "#{project[:strain]}" config.proto_package_name = "souls" end EOS end end def config_init app_name: "souls", project: {} `touch ./config/initializers/souls.rb` file_path = "./config/initializers/souls.rb" puts "Generating souls conf..." sleep(rand(0.1..0.3)) puts "Generated!" puts "Let's Edit SOULs Conf: `#{file_path}`" File.open(file_path, "w") do |f| f.write <<~EOS Souls.configure do |config| config.main_project_id = "#{project[:main_project_id]}" config.project_id = "#{project[:project_id]}" config.app = "#{app_name}" config.namespace = "#{project[:namespace]}" config.service_name = "#{project[:service_name]}" config.network = "#{project[:network]}" config.machine_type = "#{project[:machine_type]}" config.zone = "#{project[:zone]}" config.domain = "#{project[:domain]}" config.google_application_credentials = "#{project[:google_application_credentials]}" config.strain = "#{project[:strain]}" config.proto_package_name = "souls" end EOS end end def get_version repository_name: "souls_service" data = JSON.parse `curl \ -H "Accept: application/vnd.github.v3+json" \ -s https://api.github.com/repos/elsoul/#{repository_name}/releases` data[0]["tag_name"] end def download_souls app_name: "souls", repository_name: "souls_service" version = get_version repository_name: repository_name system "curl -OL https://github.com/elsoul/#{repository_name}/archive/#{version}.tar.gz" system "tar -zxvf ./#{version}.tar.gz" system "mkdir #{app_name}" folder = version.delete "v" system "cp -r #{repository_name}-#{folder}/ #{app_name}/" system "rm -rf #{version}.tar.gz && rm -rf #{repository_name}-#{folder}" txt = <<~TEXT _____ ____ __ ____#{' '} / ___// __ \\/ / / / / _____ \\__ \\/ / / / / / / / / ___/ ___/ / /_/ / /_/ / /___(__ )#{' '} /____/\\____/\\____/_____/____/#{' '} TEXT puts txt puts "==============================" puts "Welcome to SOULs!" puts "SOULs Version: #{Souls::VERSION}" puts "==============================" puts "$ cd #{app_name}" puts "------------------------------" end def proto proto_package_name: "souls", service: "blog" system "grpc_tools_ruby_protoc -I ./protos --ruby_out=./app/services --grpc_out=./app/services ./protos/#{service}.proto" service_pb_path = "./app/services/#{service}_services_pb.rb" new_file = "./app/services/#{proto_package_name}.rb" File.open(new_file, "w") do |new_line| File.open(service_pb_path, "r") do |f| f.each_line.with_index do |line, i| case i when 0 new_line.write "# Generated by the SOULs protocol buffer compiler. DO NOT EDIT!\n" when 3 next else puts "#{i}: #{line}" new_line.write line.to_s end end end end FileUtils.rm service_pb_path puts "SOULs Proto Created!" end def service_deploy service_name = Souls.configuration.service_name health_check_name = "#{service_name}-hc" firewall_rule_name = "#{service_name}-allow-health-checks" zone = Souls.configuration.zone url_map_name = "#{service_name}-url-map" path_matcher_name = "#{service_name}-path-mathcher" port = "5000" proxy_name = "#{service_name}-proxy" forwarding_rule_name = "#{service_name}-forwarding-rule" Souls.create_service_account Souls.create_service_account_key Souls.add_service_account_role Souls.add_service_account_role role: "roles/containerregistry.ServiceAgent" Souls.create_health_check health_check_name: health_check_name Souls.create_firewall_rule firewall_rule_name: firewall_rule_name Souls.create_backend_service service_name: service_name, health_check_name: health_check_name Souls.export_network_group file_path = "./config/neg_name" File.open(file_path) do |f| Souls.add_backend_service service_name: service_name, neg_name: f.gets.to_s, zone: zone end Souls.create_url_map url_map_name: url_map_name, service_name: service_name Souls.create_path_matcher url_map_name: url_map_name, service_name: service_name, path_matcher_name: path_matcher_name, hostname: app, port: port Souls.create_target_grpc_proxy proxy_name: proxy_name, url_map_name: url_map_name Souls.create_forwarding_rule forwarding_rule_name: forwarding_rule_name, proxy_name: proxy_name, port: port end def api_deploy Souls.service_api_enable Souls.create_service_account Souls.create_service_account_key Souls.add_service_account_role Souls.add_service_account_role role: "roles/containerregistry.ServiceAgent" Souls.config_set Souls.create_network Souls.create_cluster Souls.get_credentials Souls.create_namespace Souls.create_ip Souls.create_ssl Souls.apply_deployment Souls.apply_service Souls.apply_ingress puts "Wainting for Ingress to get IP..." sleep 1 puts "This migth take a few mins..." sleep 90 Souls.create_dns_conf Souls.config_set_main Souls.set_dns Souls.config_set end def local_deploy `souls i run_psql` `docker pull` end def service_update `souls i apply_deployment` end def api_update `souls i apply_deployment` end def service_delete service_name = Souls.configuration.service_name firewall_rule_name = "#{service_name}-allow-health-checks" url_map_name = "#{service_name}-url-map" proxy_name = "#{service_name}-proxy" forwarding_rule_name = "#{service_name}-forwarding-rule" Souls.delete_forwarding_rule forwarding_rule_name: forwarding_rule_name Souls.delete_target_grpc_proxy proxy_name: proxy_name Souls.delete_url_map url_map_name: url_map_name Souls.delete_backend_service service_name: service_name Souls.delete_health_check health_check_name: health_check_name Souls.delete_firewall_rule firewall_rule_name: firewall_rule_name end def api_delete `souls i delete_deployment` `souls i delete_secret` `souls i delete_service` `souls i delete_ingress` end def model class_name: "souls" file_path = "./app/models/#{class_name}.rb" File.open(file_path, "w") do |f| f.write <<~EOS class #{class_name.camelize} < ActiveRecord::Base end EOS end [file_path] end def type_check type { bigint: "Integer", string: "String", float: "Float", text: "String", datetime: "GraphQL::Types::ISO8601DateTime", date: "GraphQL::Types::ISO8601DateTime", boolean: "Boolean", integer: "Integer" }[type.to_sym] end def get_test_type type { bigint: 1, float: 4.2, string: '"MyString"', text: '"MyString"', datetime: "Time.now", date: "Time.now", boolean: false, integer: 1 }[type.to_sym] end def table_check line: "", class_name: "" if line.include?("create_table") && (line.split(" ")[1].gsub("\"", "").gsub(",", "") == class_name.pluralize.to_s) return true end false end def create_mutation_head class_name: "souls" file_path = "./app/graphql/mutations/#{class_name}/create_#{class_name}.rb" File.open(file_path, "w") do |new_line| new_line.write <<~EOS module Mutations module #{class_name.camelize} class Create#{class_name.camelize} < BaseMutation field :#{class_name}, Types::#{class_name.camelize}Type, null: false field :error, String, null: true EOS end end def create_mutation_params class_name: "souls" file_path = "./app/graphql/mutations/#{class_name}/create_#{class_name}.rb" path = "./db/schema.rb" @on = false File.open(file_path, "a") do |new_line| File.open(path, "r") do |f| f.each_line.with_index do |line, i| if @on break if line.include?("end") || line.include?("t.index") field = "[String]" if line.include?("array: true") type, name = line.split(",")[0].gsub("\"", "").scan(/((?<=t\.).+(?=\s)) (.+)/)[0] field ||= type_check type case name when "created_at", "updated_at" next else new_line.write " argument :#{name}, #{field}, required: false\n" end end @on = true if table_check(line: line, class_name: class_name) end end end end def create_mutation_end class_name: "souls" file_path = "./app/graphql/mutations/#{class_name}/create_#{class_name}.rb" File.open(file_path, "a") do |new_line| new_line.write <<~EOS def resolve **args #{class_name} = #{class_name.camelize}.new args if #{class_name}.save { #{class_name}: #{class_name} } else { error: #{class_name}.errors.full_messages } end rescue StandardError => error GraphQL::ExecutionError.new error end end end end EOS end file_path end def update_mutation_head class_name: "souls" file_path = "./app/graphql/mutations/#{class_name}/update_#{class_name}.rb" File.open(file_path, "w") do |new_line| new_line.write <<~EOS module Mutations module #{class_name.camelize} class Update#{class_name.camelize} < BaseMutation field :#{class_name}, Types::#{class_name.camelize}Type, null: false EOS end end def update_mutation_params class_name: "souls" file_path = "./app/graphql/mutations/#{class_name}/update_#{class_name}.rb" path = "./db/schema.rb" @on = false File.open(file_path, "a") do |new_line| File.open(path, "r") do |f| f.each_line.with_index do |line, i| if @on break if line.include?("end") || line.include?("t.index") field = "[String]" if line.include?("array: true") type, name = line.split(",")[0].gsub("\"", "").scan(/((?<=t\.).+(?=\s)) (.+)/)[0] field ||= type_check type case name when "created_at", "updated_at" next else new_line.write " argument :#{name}, #{field}, required: false\n" end end @on = true if table_check(line: line, class_name: class_name) end end end end def update_mutation_end class_name: "souls" file_path = "./app/graphql/mutations/#{class_name}/update_#{class_name}.rb" File.open(file_path, "a") do |new_line| new_line.write <<~EOS def resolve **args #{class_name} = #{class_name.camelize}.find args[:id] #{class_name}.update args { #{class_name}: #{class_name.camelize}.find(args[:id]) } rescue StandardError => error GraphQL::ExecutionError.new error end end end end EOS end file_path end def update_mutation class_name: "souls" update_mutation_head class_name: class_name update_mutation_params class_name: class_name update_mutation_end class_name: class_name end def delete_mutation class_name: "souls" file_path = "./app/graphql/mutations/#{class_name}/delete_#{class_name}.rb" File.open(file_path, "w") do |f| f.write <<~EOS module Mutations module #{class_name.camelize} class Delete#{class_name.camelize} < BaseMutation field :#{class_name}, Types::#{class_name.camelize}Type, null: false argument :id, Integer, required: true def resolve id: #{class_name} = #{class_name.camelize}.find id #{class_name}.destroy { #{class_name}: #{class_name} } rescue StandardError => error GraphQL::ExecutionError.new error end end end end EOS end file_path end def create_confirm puts "Directory already exists, Overwrite?? (Y/N)" input = STDIN.gets.chomp return true if input == "Y" raise StandardError.new "Directory Already Exist!" end def mutation class_name: "souls" singularized_class_name = class_name.singularize if Dir.exist? "./app/graphql/mutations/#{singularized_class_name}" # create_confirm FileUtils.rm_r("./app/graphql/mutations/#{singularized_class_name}") end Dir.mkdir "./app/graphql/mutations/#{singularized_class_name}" create_mutation_head class_name: singularized_class_name create_mutation_params class_name: singularized_class_name [ create_mutation_end(class_name: singularized_class_name), update_mutation(class_name: singularized_class_name), delete_mutation(class_name: singularized_class_name) ] end def create_query class_name: "souls" file_path = "./app/graphql/queries/#{class_name.pluralize}.rb" File.open(file_path, "w") do |f| f.write <<~EOS module Queries class #{class_name.camelize.pluralize} < Queries::BaseQuery type [Types::#{class_name.camelize}Type], null: false def resolve ::#{class_name.camelize}.all rescue StandardError => error GraphQL::ExecutionError.new error end end end EOS end file_path end def create_queries class_name: "souls" file_path = "./app/graphql/queries/#{class_name}.rb" File.open(file_path, "w") do |f| f.write <<~EOS module Queries class #{class_name.camelize} < Queries::BaseQuery type Types::#{class_name.camelize}Type, null: false argument :id, Integer, required: true def resolve id: ::#{class_name.camelize}.find(id) rescue StandardError => error GraphQL::ExecutionError.new error end end end EOS file_path end end def query class_name: "souls" singularized_class_name = class_name.singularize [create_query(class_name: singularized_class_name), create_queries(class_name: singularized_class_name)] end def create_type_head class_name: "souls" file_path = "./app/graphql/types/#{class_name}_type.rb" File.open(file_path, "w") do |f| f.write <<~EOS module Types class #{class_name.camelize}Type < GraphQL::Schema::Object implements GraphQL::Types::Relay::Node EOS end end def create_type_params class_name: "souls" file_path = "./app/graphql/types/#{class_name}_type.rb" path = "./db/schema.rb" @on = false File.open(file_path, "a") do |new_line| File.open(path, "r") do |f| f.each_line.with_index do |line, i| if @on new_line.write "\n" && break if line.include?("end") || line.include?("t.index") field = "[String]" if line.include?("array: true") type, name = line.split(",")[0].gsub("\"", "").scan(/((?<=t\.).+(?=\s)) (.+)/)[0] field ||= type_check type new_line.write " field :#{name}, #{field}, null: true\n" end if table_check(line: line, class_name: class_name) @on = true new_line.write " global_id_field :id\n" end end end end end def create_type_end class_name: "souls" file_path = "./app/graphql/types/#{class_name}_type.rb" File.open(file_path, "a") do |f| f.write <<~EOS end end EOS end [file_path] end def type class_name: "souls" singularized_class_name = class_name.singularize create_type_head class_name: singularized_class_name create_type_params class_name: singularized_class_name create_type_end class_name: singularized_class_name end def rspec_factory_head class_name: "souls" file_path = "./spec/factories/#{class_name.pluralize}.rb" File.open(file_path, "w") do |f| f.write <<~EOS FactoryBot.define do factory :#{class_name} do EOS end end def rspec_factory_params class_name: "souls" file_path = "./spec/factories/#{class_name.pluralize}.rb" path = "./db/schema.rb" @on = false File.open(file_path, "a") do |new_line| File.open(path, "r") do |f| f.each_line.with_index do |line, i| if @on new_line.write "\n" && break if line.include?("end") || line.include?("t.index") field = '["tag1", "tag2", "tag3"]' if line.include?("array: true") type, name = line.split(",")[0].gsub("\"", "").scan(/((?<=t\.).+(?=\s)) (.+)/)[0] field ||= get_test_type type new_line.write " #{name} { #{field} }\n" end if table_check(line: line, class_name: class_name) @on = true end end end end end def rspec_factory_end class_name: "souls" file_path = "./spec/factories/#{class_name.pluralize}.rb" File.open(file_path, "a") do |f| f.write <<~EOS end end EOS end [file_path] end def rspec_factory class_name: "souls" singularized_class_name = class_name.singularize rspec_factory_head class_name: singularized_class_name rspec_factory_params class_name: singularized_class_name rspec_factory_end class_name: singularized_class_name end def rspec_model class_name: "souls" file_path = "./spec/models/#{class_name}_spec.rb" File.open(file_path, "w") do |f| f.write <<~EOS RSpec.describe #{class_name.camelize}, type: :model do it "作成する" do expect(FactoryBot.build(:#{class_name.singularize})).to be_valid end end EOS end [file_path] end def rspec_mutation class_name: "souls" # if needed end def rspec_query class_name: "souls" # if needed end def rspec_type class_name: "souls" # if needed end def get_end_point file_path = "./app/graphql/types/mutation_type.rb" File.open(file_path, "r") do |f| f.each_line.with_index do |line, i| return i if line.include?("end") end end end def get_tables path = "./db/schema.rb" tables = [] File.open(path, "r") do |f| f.each_line.with_index do |line, i| tables << line.split("\"")[1] if line.include?("create_table") end end tables end def add_mutation_type class_name: "souls" # let's do this later end def add_query_type class_name: "souls" # let's do this later end def migrate class_name: "souls" # `rake db:migrate` singularized_class_name = class_name.singularize model_paths = model class_name: singularized_class_name type_paths = type class_name: singularized_class_name rspec_factory_paths = rspec_factory class_name: singularized_class_name rspec_model_paths = rspec_model class_name: singularized_class_name query_path = query class_name: singularized_class_name mutation_path = mutation class_name: singularized_class_name [ model: model_paths, type: type_paths, rspec_factory: rspec_factory_paths, rspec_model: rspec_model_paths, query: query_path, mutation: mutation_path, add_query_type: [ "field :#{singularized_class_name}, resolver: Queries::#{singularized_class_name.camelize}", "field :#{singularized_class_name.pluralize}, Types::#{singularized_class_name.camelize}Type.connection_type, null: true" ], add_mutation_type: [ "field :create_#{singularized_class_name}, mutation: Mutations::#{singularized_class_name.camelize}::Create#{singularized_class_name.camelize}", "field :update_#{singularized_class_name}, mutation: Mutations::#{singularized_class_name.camelize}::Update#{singularized_class_name.camelize}", "field :delete_#{singularized_class_name}, mutation: Mutations::#{singularized_class_name.camelize}::Delete#{singularized_class_name.camelize}" ] ] end def migrate_all puts "◆◆◆ Let's Auto Generate CRUD API ◆◆◆\n" paths = get_tables.map do |class_name| migrate class_name: class_name end puts "\n============== Model ======================\n\n" paths.each do |class_name| class_name.each do |path| path[:model].each { |line| puts line } end end puts "\n============== Type =======================\n\n" paths.each do |class_name| class_name.each do |path| path[:type].each { |line| puts line } end end puts "\n============== FactoryBot =================\n\n" paths.each do |class_name| class_name.each do |path| path[:rspec_factory].each { |line| puts line } end end puts "\n============== RspecModel =================\n\n" paths.each do |class_name| class_name.each do |path| path[:rspec_model].each { |line| puts line } end end puts "\n============== Query ======================\n\n" paths.each do |class_name| class_name.each do |path| path[:query].each { |line| puts line } end end puts "\n============== Mutation ===================\n\n" paths.each do |class_name| class_name.each do |path| path[:mutation].each { |line| puts line } end end puts "\nAll files created from ./db/schema.rb" puts "\n\n" puts "\n##########################################################\n" puts "# #\n" puts "# Add These Lines at ./app/graphql/types/query_type.rb #\n" puts "# #\n" puts "##########################################################\n\n\n" paths.each do |class_name| class_name.each do |path| path[:add_query_type].each { |line| puts line } end end puts "\n#############################################################\n" puts "# #\n" puts "# Add These Lines at ./app/graphql/types/mutation_type.rb #\n" puts "# #\n" puts "#############################################################\n\n\n" paths.each do |class_name| class_name.each do |path| path[:add_mutation_type].each { |line| puts line } end end end end end end