require 'sinatra/base' module Metry class Psycho < Sinatra::Base def initialize(app, options={}) super(app) @options = options path = options[:path] || "/psycho" auth = options[:authorize] || proc{true} on_deny = options[:on_deny] || proc{[403, {"Content-Type" => "text/plain"}, "You must log in."]} self.class.class_eval do set :views, File.dirname(__FILE__) + '/psycho' before do if unescape(@request.path_info) =~ /^#{path}/ @env["metry.event"]["ignore"] = "true" if !auth.call(env) reply = on_deny.call(env) status reply.first response.header.merge!(reply[1]) halt reply.last end end end get "#{path}/?" do @visitors = Visitor.all @goals = Goal.all erb :dashboard end get "#{path}/visitor/:id" do @visitor = Visitor.find(params["id"]) erb :visitor end post "#{path}/goals" do Goal.create!(params[:name], params[:path]) redirect url('/') end get "#{path}/goals/new" do erb :new_goal end helpers do define_method(:url) do |url| "#{path}#{url}" end end end end class Goal def self.create!(name, path) Metry.current.add_goal('name' => name, 'path' => path) end def self.all Metry.current.goals.collect{|e| new(e)} end attr_reader :name, :path def initialize(hash) @name = hash['name'] @path = hash['path'] end def visits Metry.current.all_events({'path' => @path}).size end end class Visitor def self.all Metry.current.visitors.collect{|e| new(e)}.select{|e| e.has_events?} end def self.find(id) new(Metry.current.visitor(id)) end def initialize(visitor) @visitor = visitor end def id @visitor['_id'].to_s end def events Metry.current.events_for(id).collect{|e| Event.new(e)} end def has_events? !events.empty? end end class Event def initialize(event) @event = event end def path @event["path"] end end end end