require File.dirname(__FILE__) + '/lib/integrity'
require 'sinatra'
require 'diddies'
require 'hacks'
set :root, Integrity.root
set :public, Integrity.root / "public"
set :views, Integrity.root / "views"
enable :sessions
include Integrity
configure :development do
config = Integrity.root / "config" / "config.yml"
Integrity.config = config if File.exists? config
end
configure do
Integrity.new
end
not_found do
status 404
show :not_found, :title => "lost, are we?"
end
error do
@error = request.env['sinatra.error']
status 500
show :error, :title => "something has gone terribly wrong"
end
before do
# The browser only sends http auth data for requests that are explicitly
# required to do so. This way we get the real values of +#logged_in?+ and
# +#current_user+
login_required if session[:user]
end
get "/" do
@projects = Project.all(authorized? ? {} : { :public => true })
show :home, :title => "projects"
end
get "/login" do
login_required
session[:user] = current_user
redirect "/"
end
get "/new" do
login_required
@project = Project.new
show :new, :title => ["projects", "new project"]
end
post "/" do
login_required
@project = Project.new(params[:project_data])
if @project.save
@project.enable_notifiers(params["enabled_notifiers[]"], params["notifiers"])
redirect project_url(@project)
else
show :new, :title => ["projects", "new project"]
end
end
get "/:project" do
login_required unless current_project.public?
show :project, :title => ["projects", current_project.permalink]
end
put "/:project" do
login_required
if current_project.update_attributes(params[:project_data])
current_project.enable_notifiers(params["enabled_notifiers[]"], params["notifiers"])
redirect project_url(current_project)
else
show :new, :title => ["projects", current_project.permalink, "edit"]
end
end
delete "/:project" do
login_required
current_project.destroy
redirect "/"
end
get "/:project/edit" do
login_required
show :new, :title => ["projects", current_project.permalink, "edit"]
end
post "/:project/push" do
login_required
content_type 'text/plain'
begin
payload = JSON.parse(params[:payload] || "")
payload['commits'].reverse.each do |commit|
current_project.build(commit['id']) if payload['ref'] =~ /#{current_project.branch}/
end
'Thanks, build started.'
rescue JSON::ParserError => exception
invalid_payload!(exception.to_s)
end
end
post "/:project/builds" do
login_required
current_project.build
redirect project_url(@project)
end
get '/:project/builds/:build' do
login_required unless current_project.public?
show :build, :title => ["projects", current_project.permalink, current_build.short_commit_identifier]
end
get "/integrity.css" do
header "Content-Type" => "text/css; charset=utf-8"
sass :integrity
end
helpers do
include Rack::Utils
include Sinatra::Authorization
alias_method :h, :escape_html
def authorization_realm
"Integrity"
end
def authorized?
return true unless Integrity.config[:use_basic_auth]
!!request.env['REMOTE_USER']
end
def authorize(user, password)
if Integrity.config[:hash_admin_password]
password = Digest::SHA1.hexdigest(password)
end
!Integrity.config[:use_basic_auth] ||
(Integrity.config[:admin_username] == user &&
Integrity.config[:admin_password] == password)
end
def unauthorized!(realm=authorization_realm)
header 'WWW-Authenticate' => %(Basic realm="#{realm}")
throw :halt, [401, show(:unauthorized, :title => "incorrect credentials")]
end
def invalid_payload!(msg=nil)
throw :halt, [422, msg || 'No payload given']
end
def current_project
@project ||= Project.first(:permalink => params[:project]) or raise Sinatra::NotFound
end
def current_build
@build ||= current_project.builds.first(:commit_identifier => params[:build]) or raise Sinatra::NotFound
end
def show(view, options={})
@title = breadcrumbs(*options[:title])
haml view
end
def pages
@pages ||= [["projects", "/"], ["new project", "/new"]]
end
def breadcrumbs(*crumbs)
crumbs[0..-2].map do |crumb|
if page_data = pages.detect {|c| c.first == crumb }
%Q(#{page_data.first})
elsif @project && @project.permalink == crumb
%Q(#{@project.permalink})
end
end + [crumbs.last]
end
def cycle(*values)
@cycles ||= {}
@cycles[values] ||= -1 # first value returned is 0
next_value = @cycles[values] = (@cycles[values] + 1) % values.size
values[next_value]
end
def project_url(project, *path)
"/" << [project.permalink, *path].join("/")
end
def push_url_for(project)
Addressable::URI.parse(Integrity.config[:base_uri]).join("#{project_url(project)}/push").to_s
end
def build_url(build)
"/#{build.project.permalink}/builds/#{build.commit_identifier}"
end
def filter_attributes_of(model)
valid = model.properties.collect {|p| p.name.to_s }
Hash[*params.dup.select {|k,_| valid.include?(k) }.flatten]
end
def errors_on(object, field)
return "" unless errors = object.errors.on(field)
errors.map {|e| e.gsub(/#{field} /i, "") }.join(", ")
end
def error_class(object, field)
object.errors.on(field).nil? ? "" : "with_errors"
end
def checkbox(name, condition, extras={})
attrs = { :name => name, :type => "checkbox" }.merge(condition ? { :checked => "checked" } : {})
attrs.merge(extras)
end
def bash_color_codes(string)
string.gsub("\e[0m", '').
gsub("\e[31m", '').
gsub("\e[32m", '').
gsub("\e[33m", '').
gsub("\e[34m", '').
gsub("\e[35m", '').
gsub("\e[36m", '').
gsub("\e[37m", '')
end
def pretty_date(date_time)
today = Date.today
if date_time.day == today.day && date_time.month == today.month && date_time.year == today.year
"today"
elsif date_time.day == today.day - 1 && date_time.month == today.month && date_time.year == today.year
"yesterday"
else
date_time.strftime("on %b %d%o")
end
end
def notifier_form(notifier)
haml(notifier.to_haml, :layout => :notifier, :locals => {
:config => current_project.config_for(notifier),
:notifier => "#{notifier.to_s.split(/::/).last}",
:enabled => current_project.notifies?(notifier)
})
end
end