Rails app blueprint
Jumpstart Pro
Add Avo 3 to your Jumpstart Pro app blazing fast!
Run the following command to quickly apply this app blueprint
If you think this blueprint needs changes please submit a PR here.
Full blueprint
# Blueprint info:
# Link: https://avohq.io/blueprints/jumpstart-pro
# Repository: https://github.com/avo-hq/jumpstart-pro-app-template
# List of all Avo files with path and contents
files = {"app/avo/cards/active_subscriptions.rb"=>"class Avo::Cards::ActiveSubscriptions < Avo::Cards::MetricCard\n self.id = \"active_subscriptions\"\n self.label = \"Active subscriptions\"\n self.description = \"Total number of active subscriptions\"\n\n def query\n result ::Pay::Subscription.active.count\n end\nend\n", "app/avo/cards/total_revenue.rb"=>"class Avo::Cards::TotalRevenue < Avo::Cards::MetricCard\n self.id = \"total_revenue\"\n self.label = \"Total revenue\"\n self.prefix = \"$\"\n\n def query\n case arguments[:period]\n when :last_12_months\n result last_12_months\n when :last_month\n result last_month\n when :this_month\n result this_month\n else\n result total_revenue\n end\n end\n\n def total_revenue\n revenue_in_cents = ::Pay::Charge.sum(:amount)\n refunds_in_cents = ::Pay::Charge.sum(:amount_refunded)\n (revenue_in_cents - refunds_in_cents) / 100.0\n end\n\n def last_12_months\n revenue_for_range 12.months.ago..Time.current\n end\n\n def last_month\n month = Time.current.prev_month\n revenue_for_range month.beginning_of_month..month.end_of_month\n end\n\n def this_month\n month = Time.current\n revenue_for_range month.beginning_of_month..month.end_of_month\n end\n\n def revenue_for_range(range)\n revenue_in_cents = ::Pay::Charge.where(created_at: range).sum(:amount)\n refunds_in_cents = ::Pay::Charge.where(created_at: range).sum(:amount_refunded)\n (revenue_in_cents - refunds_in_cents) / 100.0\n end\nend\n", "app/avo/cards/users_count.rb"=>"class Avo::Cards::UsersCount < Avo::Cards::MetricCard\n self.id = \"users_count\"\n self.label = \"Users count\"\n self.description = \"Total number of users registered\"\n\n def query\n result ::User.all.count\n end\nend\n", "app/avo/dashboards/overview.rb"=>"class Avo::Dashboards::Overview < Avo::Dashboards::BaseDashboard\n self.id = \"overview\"\n self.name = \"Overview\"\n self.grid_cols = 4\n\n def cards\n divider label: \"Revenue\"\n card Avo::Cards::TotalRevenue,\n arguments: {\n period: :this_month\n },\n label: \"This month\"\n card Avo::Cards::TotalRevenue,\n arguments: {\n period: :last_month\n },\n label: \"Last month\"\n card Avo::Cards::TotalRevenue,\n arguments: {\n period: :last_12_months\n },\n label: \"Last 12 months\"\n card Avo::Cards::TotalRevenue\n\n divider\n\n card Avo::Cards::UsersCount\n card Avo::Cards::ActiveSubscriptions\n end\nend\n", "app/avo/resources/account.rb"=>"class Avo::Resources::Account < Avo::BaseResource\n self.title = :name\n self.includes = [:owner, :users]\n self.search = {\n query: -> { query.ransack(id_eq: params[:q], name_cont: params[:q], m: \"or\").result(distinct: false) },\n item: -> {\n {\n title: record.name,\n image_url: record.avatar\n }\n }\n }\n\n def fields\n main_panel do\n field :id, as: :id\n field :avatar,\n as: :file,\n is_image: true,\n link_to_resource: true,\n rounded: true,\n hide_on: :forms\n field :owner, as: :belongs_to\n field :name, as: :text\n field :personal, as: :boolean\n field :users_count, as: :text do\n record.users.length\n end\n\n sidebar do\n field :extra_billing_info, as: :textarea\n field :created_at, as: :date_time\n field :updated_at, as: :date_time\n end\n end\n\n tabs do\n field :subscriptions, as: :has_many\n field :pay_customers, as: :has_many\n field :charges, as: :has_many\n field :account_users, as: :has_many\n field :users, as: :has_many\n end\n end\nend\n", "app/avo/resources/account_user.rb"=>"class Avo::Resources::AccountUser < Avo::BaseResource\n self.title = :id\n self.includes = []\n\n def fields\n main_panel do\n field :id, as: :id\n field :account, as: :belongs_to\n field :user, as: :belongs_to\n field :roles,\n as: :boolean_group,\n options: {\n admin: \"Administrator\",\n member: \"Member\",\n }\n field :admin?, as: :boolean do\n record.roles[\"admin\"] == true\n end\n\n sidebar do\n field :created_at, as: :date_time\n field :updated_at, as: :date_time\n end\n end\n end\nend\n", "app/avo/resources/announcement.rb"=>"class Avo::Resources::Announcement < Avo::BaseResource\n self.title = :title\n self.includes = []\n self.search = {\n query: -> { query.ransack(id_eq: params[:q], title_cont: params[:q], m: \"or\").result(distinct: false) },\n item: -> {\n {\n title: record.title,\n }\n }\n }\n\n def fields\n field :id, as: :id\n field :title, as: :text\n field :description, as: :trix, always_show: true\n field :kind, as: :badge, options: {success: \"new\", info: \"improvement\", danger: \"fix\", warning: \"update\"}, hide_on: :forms\n field :kind, as: :select, options: Announcement::TYPES.map { |t| [t, t] }.to_h, show_on: :forms\n field :published_at, as: :date_time\n end\nend\n", "app/avo/resources/charge.rb"=>"class Avo::Resources::Charge < Avo::BaseResource\n self.title = :processor_id\n self.includes = []\n self.model_class = Pay::Charge\n self.search = {\n query: -> { query.ransack(id_eq: params[:q], processor_id_cont: params[:q], m: \"or\").result(distinct: false)},\n item: -> {\n {\n title: record.processor_id\n }\n }\n }\n\n def fields\n field :id, as: :id\n field :customer, as: :belongs_to\n field :amount, as: :text\n field :amount_refunded, as: :text\n field :created_at, as: :date_time\n\n with_options only_on: :show do\n field :processor_id, as: :text, format_using: -> do\n link_to value, view_context.controller.charge_processor_url(model), target: :_blank\n rescue\n value\n end\n field :subscription, as: :belongs_to\n field :updated_at, as: :date_time\n end\n end\nend\n", "app/avo/resources/connected_account.rb"=>"class Avo::Resources::ConnectedAccount < Avo::BaseResource\n self.title = :id\n self.includes = []\n self.model_class = ConnectedAccount\n\n def fields\n field :id, as: :id\n field :user, as: :belongs_to\n field :image_url, as: :external_image\n field :expired?, as: :boolean, hide_on: :forms\n end\nend\n", "app/avo/resources/customer.rb"=>"class Avo::Resources::Customer < Avo::BaseResource\n self.title = :name\n self.includes = [:owner]\n self.search = {\n query: -> { query.ransack(id_eq: params[:q], processor_id_cont: params[:q], m: \"or\").result(distinct: false) },\n item: -> {\n {\n title: record.name,\n }\n }\n }\n self.model_class = Pay::Customer\n\n def fields\n main_panel do\n field :id, as: :id\n field :owner, as: :belongs_to, polymorphic_as: :owner, types: [::Account]\n\n field :processor, as: :badge, options: {success: \"stripe\"}\n field :processor_id, as: :text, format_using: -> do\n link_to(value, view_context.controller.customer_processor_url(model), target: :_blank)\n rescue\n value\n end\n field :default, as: :boolean\n field :data, as: :key_value\n\n sidebar do\n field :created_at, as: :date_time\n field :updated_at, as: :date_time\n field :deleted_at, as: :date_time\n end\n end\n\n tabs do\n field :subscriptions, as: :has_many\n field :charges, as: :has_many\n end\n end\nend\n", "app/avo/resources/payment_method.rb"=>"class Avo::Resources::PaymentMethod < Avo::BaseResource\n self.includes = []\n self.model_class = Pay::PaymentMethod\n self.search = {\n query: -> { query.ransack(id_eq: params[:q], processor_id_cont: params[:q], m: \"or\").result(distinct: false) },\n item: -> {\n {\n title: record.id,\n }\n }\n }\n\n def fields\n main_panel do\n field :id, as: :id\n field :customer, as: :belongs_to\n field :processor_id, as: :text\n field :type, as: :text\n field :default, as: :boolean\n field :data, as: :key_value\n\n sidebar do\n field :extra_billing_info, as: :key_value\n field :created_at, as: :date_time\n field :updated_at, as: :date_time\n end\n end\n end\nend\n", "app/avo/resources/plan.rb"=>"class Avo::Resources::Plan < Avo::BaseResource\n self.title = :name\n self.includes = []\n self.search = {\n query: -> { query.ransack(id_eq: params[:q], name_cont: params[:q], description_cont: params[:q], m: \"or\").result(distinct: false) },\n item: -> {\n {\n title: record.name,\n }\n }\n }\n\n def fields\n main_panel do\n field :id, as: :id\n field :name, as: :text\n field :description, as: :text, hide_on: :index\n field :hidden, as: :boolean\n field :amount, as: :number, help: \"Price in cents\", format_using: -> do\n if view == :edit || view == :new\n value\n else\n (BigDecimal(value.to_s) / 100).round(2)\n end\n end\n field :currency, as: :select, options: Pay::Currency.all.map { |iso, v| [\"\#{iso.upcase} - \#{v[\"name\"]}\", iso] }.to_h\n field :interval, as: :select, options: [:month, :year]\n field :interval_count, as: :number\n field :trial_period_days, as: :number\n\n field :features, as: :tags\n\n sidebar do\n field :created_at, as: :date_time\n field :updated_at, as: :date_time\n field :processor_details, as: :heading, label: \"Processor details\"\n field :stripe_id, as: :text, format_using: -> do\n if view == :edit || view == :new\n value\n else\n link_to value, \"https://dashboard.stripe.com/plans/\#{value}\", target: :_blank\n end\n rescue\n value\n end\n field :braintree_id, as: :text, format_using: -> do\n if view == :edit || view == :new\n value\n else\n link_to value, \"https://sandbox.braintreegateway.com/merchants/\#{Pay::Braintree.merchant_id}/plans/\#{id}\", target: :_blank\n end\n rescue\n value\n end\n field :paddle_id, as: :text\n field :fake_processor_id, as: :text\n end\n end\n end\nend\n", "app/avo/resources/subscription.rb"=>"class Avo::Resources::Subscription < Avo::BaseResource\n self.title = :processor_id\n self.includes = [:owner, :customer]\n self.search = {\n query: -> { query.ransack(id_eq: params[:q], name_cont: params[:q], processor_id_cont: params[:q], m: \"or\").result(distinct: false) },\n item: -> {\n {\n title: record.processor_id,\n }\n }\n }\n self.model_class = Pay::Subscription\n\n def fields\n main_panel do\n field :id, as: :id\n with_options only_on: :index do\n field :customer, as: :belongs_to\n field :name, as: :text\n field :active?, as: :boolean, name: \"Active\"\n field :cancelled?, as: :boolean, name: \"Cancelled\"\n end\n field :owner, as: :has_one\n field :processor_id, as: :text, format_using: ->(id) do\n link_to id, view_context.controller.subscription_processor_url(model), target: :_blank\n rescue\n id\n end\n field :active?, as: :boolean\n field :processor, as: :badge, options: {success: \"Stripe\"} do |model, *args|\n name = record.payment_processor.class.to_s.split(\"::\").second\n if name.downcase.include? \"fake\"\n \"Fake\"\n else\n name\n end\n rescue\n \"Unknown\"\n end\n\n sidebar do\n field :status, as: :status, readonly: true, failed_when: [:canceled], loading_when: [:trialing]\n field :trial_ends_at_ago, as: :text\n field :created_at, as: :date_time, readonly: true\n field :ends_at, as: :date_time, readonly: true\n field :processor_id, as: :text, readonly: true, as_html: true do\n case record.customer.processor\n when \"stripe\"\n link_to(record.processor_id, \"https://dashboard.stripe.com/subscriptions/\#{record.processor_id}\", target: :_blank)\n else\n record.processor_id\n end\n end\n field :processor_plan, as: :text, readonly: true\n field :quantity, as: :number, readonly: true\n end\n end\n end\nend\n", "app/avo/resources/user.rb"=>"class Avo::Resources::User < Avo::BaseResource\n self.title = :name\n self.includes = [:accounts]\n self.search = {\n query: -> { query.ransack(id_eq: params[:q], name_cont: params[:q], email_cont: params[:q], m: \"or\").result(distinct: false) },\n item: -> {\n {\n title: record.name,\n }\n }\n }\n\n def fields\n field :id, as: :id\n field :avatar, as: :file, is_image: true, hide_on: :forms, link_to_resource: true\n field :name, as: :text\n field :email, as: :text\n field :admin, as: :boolean, help: \"Whether the user has admin capabilities.\"\n\n with_options hide_on: :forms do\n field :number_of_accounts, as: :text do\n record.accounts.length\n end\n end\n\n tabs do\n field :accounts, as: :has_many\n field :connected_accounts, as: :has_many\n end\n\n tabs do\n tab \"General\" do\n panel do\n field :time_zone, as: :text\n field :invitations_count, as: :text\n end\n end\n tab \"Account management\" do\n panel do\n field :password, as: :password\n field :password_confirmation, as: :password\n field :reset_password_token, as: :password\n field :reset_password_sent_at, as: :date_time\n field :remember_created_at, as: :date_time\n field :confirmation_token, as: :text\n field :confirmed_at, as: :date_time\n field :confirmation_sent_at, as: :date_time\n field :unconfirmed_email, as: :text\n field :created_at, as: :date_time\n field :updated_at, as: :date_time\n field :invitation_token, as: :text\n field :invitation_created_at, as: :date_time\n field :invitation_sent_at, as: :date_time\n field :invitation_accepted_at, as: :date_time\n field :invitation_limit, as: :number\n field :terms_of_service, as: :boolean\n field :accepted_terms_at, as: :date_time\n field :accepted_privacy_at, as: :date_time\n end\n end\n end\n end\nend\n", "app/controllers/avo/account_users_controller.rb"=>"# This controller has been generated to enable Rails' resource routes.\n# You shouldn't need to modify it in order to use Avo.\nclass Avo::AccountUsersController < Avo::ResourcesController\nend\n", "app/controllers/avo/accounts_controller.rb"=>"class Avo::AccountsController < Avo::ResourcesController\nend\n", "app/controllers/avo/announcements_controller.rb"=>"class Avo::AnnouncementsController < Avo::ResourcesController\nend\n", "app/controllers/avo/charges_controller.rb"=>"# This controller has been generated to enable Rails' resource routes.\n# You shouldn't need to modify it in order to use Avo.\nclass Avo::ChargesController < Avo::ResourcesController\n include Jumpstart::AdministrateHelpers\nend\n", "app/controllers/avo/connected_accounts_controller.rb"=>"# This controller has been generated to enable Rails' resource routes.\n# You shouldn't need to modify it in order to use Avo.\nclass Avo::ConnectedAccountsController < Avo::ResourcesController\nend\n", "app/controllers/avo/customers_controller.rb"=>"class Avo::CustomersController < Avo::ResourcesController\n include Jumpstart::AdministrateHelpers\nend\n", "app/controllers/avo/payment_methods_controller.rb"=>"# This controller has been generated to enable Rails' resource routes.\n# You shouldn't need to modify it in order to use Avo.\nclass Avo::PaymentMethodsController < Avo::ResourcesController\nend\n", "app/controllers/avo/plans_controller.rb"=>"class Avo::PlansController < Avo::ResourcesController\n include Jumpstart::AdministrateHelpers\nend\n", "app/controllers/avo/subscriptions_controller.rb"=>"class Avo::SubscriptionsController < Avo::ResourcesController\n include Jumpstart::AdministrateHelpers\nend\n", "app/controllers/avo/users_controller.rb"=>"class Avo::UsersController < Avo::ResourcesController\nend\n", "config/initializers/avo.rb"=>"# For more information regarding these settings check out our docs https://docs.avohq.io\nAvo.configure do |config|\n ## == Routing ==\n config.root_path = \"/avo\"\n # used only when you have custom `map` configuration in your config.ru\n # config.prefix_path = \"/internal\"\n\n # Where should the user be redirected when visting the `/avo` url\n config.home_path = defined?(Avo::Pro) ? \"/avo/dashboards/overview\" : nil\n\n ## == Licensing ==\n # Add your license key here for Pro or Advanced licenses\n # config.license_key = ENV['AVO_LICENSE_KEY']\n\n ## == Set the context ==\n config.set_context do\n # Return a context object that gets evaluated in Avo::ApplicationController\n {}\n end\n\n ## == Authentication ==\n # config.current_user_method = {}\n # config.authenticate_with do\n # end\n\n ## == Authorization ==\n # config.authorization_methods = {\n # index: 'index?',\n # show: 'show?',\n # edit: 'edit?',\n # new: 'new?',\n # update: 'update?',\n # create: 'create?',\n # destroy: 'destroy?',\n # search: 'search?',\n # }\n # config.raise_error_on_missing_policy = false\n # config.authorization_client = :pundit\n\n ## == Localization ==\n # config.locale = 'en-US'\n\n ## == Resource options ==\n # config.resource_controls_placement = :right\n # config.model_resource_mapping = {}\n # config.default_view_type = :table\n # config.per_page = 24\n # config.per_page_steps = [12, 24, 48, 72]\n # config.via_per_page = 8\n config.id_links_to_resource = true\n # config.cache_resources_on_index_view = true\n ## permanent enable or disable cache_resource_filters, default value is false\n # config.cache_resource_filters = false\n ## provide a lambda to enable or disable cache_resource_filters per user/resource.\n # config.cache_resource_filters = ->(current_user:, resource:) { current_user.cache_resource_filters?}\n\n ## == Customization ==\n # config.app_name = 'Avocadelicious'\n # config.timezone = 'UTC'\n # config.currency = 'USD'\n # config.hide_layout_when_printing = false\n # config.full_width_container = false\n # config.full_width_index_view = false\n # config.search_debounce = 300\n # config.view_component_path = \"app/components\"\n # config.display_license_request_timeout_error = true\n # config.disabled_features = []\n # config.tabs_style = :tabs # can be :tabs or :pills\n # config.buttons_on_form_footers = true\n # config.field_wrapper_layout = true\n\n ## == Branding ==\n # config.branding = {\n # colors: {\n # background: \"248 246 242\",\n # 100 => \"#CEE7F8\",\n # 400 => \"#399EE5\",\n # 500 => \"#0886DE\",\n # 600 => \"#066BB2\",\n # },\n # chart_colors: [\"#0B8AE2\", \"#34C683\", \"#2AB1EE\", \"#34C6A8\"],\n # logo: \"/avo-assets/logo.png\",\n # logomark: \"/avo-assets/logomark.png\",\n # placeholder: \"/avo-assets/placeholder.svg\",\n # favicon: \"/avo-assets/favicon.ico\"\n # }\n\n ## == Breadcrumbs ==\n # config.display_breadcrumbs = true\n # config.set_initial_breadcrumbs do\n # add_breadcrumb \"Home\", '/avo'\n # end\n\n ## == Menus ==\n config.main_menu = -> {\n section \"Dashboards\", icon: \"dashboards\" do\n all_dashboards\n end\n\n section \"Users\", icon: \"heroicons/outline/user-group\" do\n resource :announcement\n resource :user\n resource :account\n resource :account_user\n resource :plan\n end\n\n section \"Pay\", icon: \"heroicons/outline/currency-dollar\" do\n resource :customer\n resource :charge\n resource :payment_method\n resource :subscription\n end\n\n if Rails.env.development?\n link \"Jumpstart Config\", path: Avo::Current.view_context.main_app.jumpstart_path\n end\n }\n config.profile_menu = -> {\n link \"Profile\", path: \"/avo/profile\", icon: \"user-circle\"\n }\nend\n"}
if ARGV.include? "--community-edition"
edition = "community"
elsif ARGV.include? "--pro-edition"
edition = "pro"
elsif ARGV.include? "--advanced-edition"
edition = "advanced"
end
unless edition
# === Fetch the Avo edition ===
question = <<~QUESTION
Which version of Avo would you like to install?
1. Avo Community (default)
2. Avo Pro
3. Avo Advanced
More information about version features here:
https://avohq.io/pricing
QUESTION
puts question
answer = ask("Which version of Avo would you like to install?", default: "1", limited_to: ["1", "2", "3"])
edition = case answer
when "1"
"community"
when "2"
"pro"
when "3"
"advanced"
end
end
# === Add gem to Gemfile ===
case edition
when "community"
gem "avo", ">= 3.1.0"
when "pro"
gem "avo", ">= 3.1.0"
gem "avo-pro", source: "https://packager.dev/avo-hq"
when "advanced"
gem "avo", ">= 3.1.0"
gem "avo-advanced", source: "https://packager.dev/avo-hq"
end
# === Run bundle install ===
Bundler.with_unbundled_env { run "bundle install" }
# === Add route ===
route_contents = <<-ROUTES
# Avo admin panel
if defined?(Avo::Engine)
authenticated :user, lambda { |u| u.admin? } do
mount Avo::Engine, at: Avo.configuration.root_path
end
end
ROUTES
route route_contents
# === Copy template files ===
files.each do |path, contents|
file path, contents
end