module Warden
  module GitHub
    module Rails
      module Routes
        # Enforces an authenticated GitHub user for the routes. If not
        # authenticated, it initiates the OAuth flow.
        #
        # Team and organization memberships can be checked by specifying a hash
        # such as `team: 'foobar'` or `org: 'my_company'`.
        def github_authenticate(scope=nil, options={}, &routes_block)
          github_constraint(scope, options, routes_block) do |warden, scope|
            warden.authenticate!(scope: scope)
          end
        end

        # The routes will only be visible to authenticated GitHub users. When
        # not authenticated, it does not initiate the OAuth flow.
        #
        # Team and organization memberships can be checked by specifying a hash
        # such as `team: 'foobar'` or `org: 'my_company'`.
        def github_authenticated(scope=nil, options={}, &routes_block)
          github_constraint(scope, options, routes_block) do |warden, scope|
            warden.authenticated?(scope: scope)
          end
        end

        # The routes will only be visible to all but authenticated GitHub users.
        #
        # This constraint currently does not check for memberships since of its
        # limited usage.
        def github_unauthenticated(scope=nil, options={}, &routes_block)
          github_constraint(scope, options, routes_block) do |warden, scope|
            not warden.authenticated?(scope: scope)
          end
        end

        private

        def github_constraint(scope, options, routes_block, &block)
          options, scope = scope, nil  if scope.is_a? Hash
          scope ||= Rails.default_scope

          constraint = lambda do |request|
            warden = request.env['warden']

            if block.call(warden, scope)
              if (user = warden.user(scope))
                evaled_options = github_eval_options(options, request)
                github_enforce_options(user, evaled_options)
              else
                true
              end
            end
          end

          constraints(constraint, &routes_block)
        end

        def github_enforce_options(user, options)
          if (teams = options[:team])
            Array(teams).any? { |team| user.team_member?(Rails.team_id(team)) }
          elsif (org = options[:org] || options[:organization])
            user.organization_member?(org)
          else
            true
          end
        end

        def github_eval_options(options, request)
          Hash[options.map { |k,v|
            if v.is_a?(Proc)
              [k, v.call(request)]
            else
              [k, v]
            end
          }]
        end
      end
    end
  end
end

ActionDispatch::Routing::Mapper.send(:include, Warden::GitHub::Rails::Routes)
