Skip to content

Commit

Permalink
feat: add public visibility to projects
Browse files Browse the repository at this point in the history
  • Loading branch information
shafy authored Mar 20, 2022
1 parent 0b5c44c commit fbaed00
Show file tree
Hide file tree
Showing 38 changed files with 545 additions and 188 deletions.
10 changes: 0 additions & 10 deletions app/controllers/api/api_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,8 @@
module Api
class ApiController < ApplicationController
skip_before_action :verify_authenticity_token
skip_before_action :authenticate_user!
# before_action :authorize_request
around_action :handle_exceptions

# def authorize_request
# token = UserAuthorizationService.new(request.headers).authenticate_request!
# # set auth0_id
# @auth0_id = token[0]["sub"]
# rescue JWT::VerificationError, JWT::DecodeError
# render json: { error: 'Not Authenticated' }, status: :unauthorized
# end

private

def handle_exceptions
Expand Down
17 changes: 11 additions & 6 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# frozen_string_literal: true

class ApplicationController < ActionController::Base
before_action :authenticate_user!

include InactiveAlertable

private
Expand All @@ -15,14 +13,21 @@ def show_test_alert
end

def set_project
@project = Project.find_by(name: params[:project_slug]&.downcase, user: current_user)
user = User.find_by(hash_id: params[:user_id])
@project = Project.find_by(name: params[:project_slug]&.downcase, user: user)
end

def authorize_project_user
return redirect_to projects_path unless current_user
return redirect_to user_projects_path(params[:user_id]) unless @project

# don't show test data in public projects
if !current_user && @project.public && params[:test] == "true"
return redirect_to user_projects_path(params[:user_id])
end

return redirect_to projects_path unless @project
# don't authorize is project if public
return if @project.public

return redirect_to projects_path unless current_user == @project.user
return redirect_to user_projects_path(params[:user_id]) unless current_user == @project.user
end
end
4 changes: 3 additions & 1 deletion app/controllers/concerns/inactive_alertable.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# frozen_string_literal: true

# These alerts are only used in the Fugu Cloud veresion (not imporatnt if you're self-hosting)
# These alerts are only used in the Fugu Cloud version (not important if you're self-hosting)
module InactiveAlertable
extend ActiveSupport::Concern

private

def show_not_active_flash
return if @project&.public

return unless ENV["FUGU_CLOUD"] == "true"

flash.now[:not_active] = user_canceled_flash if current_user.canceled?
Expand Down
7 changes: 5 additions & 2 deletions app/controllers/events_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class EventsController < ApplicationController
include EventNameable

before_action :set_project, only: %i[index show]
before_action :authenticate_user!, unless: -> { @project.public }
before_action :authorize_project_user, only: %i[index show]
before_action :set_api_key, only: %i[index show]
before_action :set_dates, only: %i[show]
Expand All @@ -29,7 +30,8 @@ def index
%i[prop date agg].each { |i| new_params[i] = cookies.permanent[i] if cookies.permanent[i] }

selected_event = cookies.permanent[:slug] || @event_names.first
redirect_to project_event_path(
redirect_to user_project_event_path(
params[:user_id],
@project.name,
selected_event.parameterize,
params: new_params
Expand All @@ -38,7 +40,8 @@ def index

def show
unless @selected_event
return redirect_to project_events_path(@project.name, params: { test: params[:test] })
return redirect_to user_project_events_path(params[:user_id], @project.name,
params: { test: params[:test] })
end

events_array = Event.with_aggregation(
Expand Down
18 changes: 13 additions & 5 deletions app/controllers/funnels_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class FunnelsController < ApplicationController
include EventNameable

before_action :set_project, only: %i[show index new create]
before_action :authenticate_user!, unless: -> { @project.public }
before_action :authorize_project_user, only: %i[index new]
before_action :show_test_alert, only: %i[show index]
before_action :show_test_funnel_creation_alert, only: %i[new]
Expand All @@ -23,7 +24,8 @@ class FunnelsController < ApplicationController
def index
return render layout: "data_view" unless @funnel_names&.first

redirect_to project_funnel_path(
redirect_to user_project_funnel_path(
params[:user_id],
@project.name,
@funnel_names.first.parameterize,
params: { test: params[:test] }
Expand All @@ -32,7 +34,11 @@ def index

def show
unless @funnel
return redirect_to project_funnels_path(@project.name, params: { test: params[:test] })
return redirect_to user_project_funnels_path(
params[:user_id],
@project.name,
params: { test: params[:test] }
)
end

@funnel_data = @funnel_event_names.map do |e|
Expand All @@ -50,14 +56,16 @@ def new

def create
if @funnel.save
redirect_to project_funnel_path(
redirect_to user_project_funnel_path(
current_user.hash_id,
@project.name,
@funnel.name.parameterize,
params: { test: params[:test] }
)
else
flash[:error] = "We couldn't create your funnel: #{@funnel.errors.full_messages.first}"
redirect_to new_project_funnel_path(
redirect_to new_user_project_funnel_path(
current_user.hash_id,
@project.name,
params: { test: params[:test] }
)
Expand All @@ -67,7 +75,7 @@ def create
private

def funnel_params
params.require(:funnel).permit(:name, funnel_steps_attributes: %w[event_name])
params.require(:funnel).permit(:user_id, :name, funnel_steps_attributes: %w[event_name])
end

def track_event
Expand Down
24 changes: 21 additions & 3 deletions app/controllers/projects_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# frozen_string_literal: true

class ProjectsController < ApplicationController
before_action :authenticate_user!

before_action :set_project, only: %i[settings]
before_action :show_not_active_flash, only: %i[settings]

Expand All @@ -16,10 +18,26 @@ def create
@project = Project.new(name: project_params[:name], user: current_user)
if @project.save
@project.create_api_keys
redirect_to project_events_path(@project.name)
redirect_to user_project_events_path(current_user.hash_id, @project.name)
else
flash.now[:error] = "We couldn't create your project: #{@project.errors.full_messages.first}"
render new_project_path, status: :unprocessable_entity
render "projects/new", status: :unprocessable_entity
end
end

def edit
user = User.find_by(hash_id: params[:user_id])
@project = Project.find_by(name: params[:slug]&.downcase, user: user)
end

def update
@project = Project.find(project_params[:project_id])

if @project.update(name: project_params[:name], public: project_params[:public])
redirect_to user_project_settings_path(current_user.hash_id, @project.name.downcase)
else
flash.now[:error] = "We couldn't update your project: #{@project.errors.full_messages.first}"
render "projects/edit", status: :unprocessable_entity
end
end

Expand All @@ -39,6 +57,6 @@ def settings; end
private

def project_params
params.require(:project).permit(:name)
params.require(:project).permit(:project_id, :user_id, :name, :public)
end
end
2 changes: 1 addition & 1 deletion app/controllers/stripe_controller.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# frozen_string_literal: true

class StripeController < ApplicationController
before_action :authenticate_user!, except: %i[webhooks]
skip_before_action :verify_authenticity_token, only: %i[webhooks]
skip_before_action :authenticate_user!, only: %i[webhooks]
before_action :verify_webhook, only: %i[webhooks]

def checkout_session
Expand Down
43 changes: 27 additions & 16 deletions app/helpers/event_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,51 +46,59 @@ def date_select_options(url_params, aggregation, chart_type)
end

def build_event_url(url_params, aggregation, event_name)
project_event_path(
user_project_event_path(
url_params[:user_id],
url_params[:project_slug],
event_name.parameterize,
url_params.except(:prop, :project_slug, :slug)
url_params.except(:prop, :project_slug, :slug, :user_id)
.merge({ agg: aggregation })
)
end

def build_funnel_url(url_params, funnel_name)
project_funnel_path(
user_project_funnel_path(
url_params[:user_id],
url_params[:project_slug],
funnel_name.parameterize,
url_params.except(:prop, :project_slug, :slug)
url_params.except(:prop, :project_slug, :slug, :user_id)
)
end

def build_agg_url(url_params, agg)
project_event_path(
user_project_event_path(
url_params[:user_id],
url_params[:project_slug],
url_params[:slug],
url_params.except(:project_slug, :slug).merge({ agg: agg })
url_params.except(:project_slug, :slug, :user_id).merge({ agg: agg })
)
end

def build_property_url(url_params, aggregation, prop)
project_event_path(
user_project_event_path(
url_params[:user_id],
url_params[:project_slug],
url_params[:slug],
url_params.except(:project_slug, :slug).merge({ prop: prop }).merge({ agg: aggregation })
url_params.except(:project_slug, :slug,
:user_id).merge({ prop: prop }).merge({ agg: aggregation })
)
end

def build_date_url(url_params, aggregation, date, chart_type)
case chart_type
when "event"
project_event_path(
user_project_event_path(
url_params[:user_id],
url_params[:project_slug],
url_params[:slug],
url_params.except(:project_slug, :slug).merge({ date: date }).merge({ agg: aggregation })
url_params.except(:project_slug, :slug,
:user_id).merge({ date: date }).merge({ agg: aggregation })
)
when "funnel"
project_funnel_path(
user_project_funnel_path(
url_params[:user_id],
url_params[:project_slug],
url_params[:slug],
url_params.except(:project_slug, :slug).merge({ date: date })
url_params.except(:project_slug, :slug, :user_id).merge({ date: date })
)
end
end
Expand All @@ -99,17 +107,20 @@ def build_test_toggle_url(url_params, aggregation, test, chart_type)
case chart_type
when "event"
url_params = url_params.permit(*Event::EVENT_PARAMS)
project_event_path(
user_project_event_path(
url_params[:user_id],
url_params[:project_slug],
url_params[:slug],
url_params.except(:project_slug, :slug).merge({ test: test }).merge({ agg: aggregation })
url_params.except(:project_slug, :slug, :user_id)
.merge({ test: test }).merge({ agg: aggregation })
)
when "funnel"
url_params = url_params.permit(*Funnel::FUNNEL_PARAMS)
project_funnel_path(
user_project_funnel_path(
url_params[:user_id],
url_params[:project_slug],
url_params[:slug],
url_params.except(:project_slug, :slug).merge({ test: test })
url_params.except(:project_slug, :slug, :user_id).merge({ test: test })
)
end
end
Expand Down
2 changes: 1 addition & 1 deletion app/models/event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ class Event < ApplicationRecord
IPV6_REGEX = /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/
# rubocop:enable Layout/LineLength
EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
EVENT_PARAMS = %i[project_slug slug test event prop agg date].freeze
EVENT_PARAMS = %i[user_id project_slug slug test event prop agg date].freeze

def self.distinct_events_names(api_key)
Event.where(api_key: api_key).order(name: :asc).distinct.pluck(:name)
Expand Down
2 changes: 1 addition & 1 deletion app/models/funnel.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class Funnel < ApplicationRecord
before_validation :titleize_name
before_validation :strip_name

FUNNEL_PARAMS = %i[project_slug slug test event prop date].freeze
FUNNEL_PARAMS = %i[user_id project_slug slug test event prop date].freeze

private

Expand Down
1 change: 1 addition & 0 deletions app/models/project.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#
# id :bigint not null, primary key
# name :string not null
# public :boolean default(FALSE)
# created_at :datetime not null
# updated_at :datetime not null
# user_id :bigint not null
Expand Down
18 changes: 18 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,22 @@
# unconfirmed_email :string
# created_at :datetime not null
# updated_at :datetime not null
# hash_id :string not null
# stripe_customer_id :string
#
# Indexes
#
# index_users_on_confirmation_token (confirmation_token) UNIQUE
# index_users_on_email (email) UNIQUE
# index_users_on_hash_id (hash_id) UNIQUE
# index_users_on_reset_password_token (reset_password_token) UNIQUE
#

class User < ApplicationRecord
has_many :projects, dependent: :destroy

validates :hash_id, presence: true, allow_blank: false

enum status: {
inactive: 0,
active: 1,
Expand All @@ -39,4 +43,18 @@ class User < ApplicationRecord
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable

before_validation :add_hash_id, on: :create

def add_hash_id
# generate random hash_id for the user before being created
return if hash_id.present?

hash = SecureRandom.alphanumeric(4).downcase
unless User.exists?(hash_id: hash)
self.hash_id = hash
return
end
add_hash_id
end
end
Loading

0 comments on commit fbaed00

Please sign in to comment.