├── .gitignore ├── .rspec ├── .sass-cache ├── 2922b0c424c1ac88972107dc55e666246fd6eb82 │ └── projects.css.scssc ├── 738e198288ca1faac1cad71c06c387d28dbdd411 │ └── projects.css.scssc └── de57125a2fd2452156af3d067c7168ea2adff164 │ └── projects.css.scssc ├── Capfile ├── Gemfile ├── Gemfile.lock ├── README ├── Rakefile ├── app ├── assets │ ├── images │ │ ├── icons │ │ │ ├── github_32.png │ │ │ └── twitter_32.png │ │ └── twitter_32.png │ ├── javascripts │ │ ├── admin │ │ │ ├── permissions.js.coffee │ │ │ ├── states.js.coffee │ │ │ └── users.js.coffee │ │ ├── application.js │ │ ├── assets.js.coffee │ │ ├── comments.js.coffee │ │ ├── projects.js.coffee │ │ ├── tags.js.coffee │ │ ├── tickets.js.coffee │ │ ├── users.js.coffee │ │ └── users │ │ │ └── omniauth_callbacks.js.coffee │ └── stylesheets │ │ ├── admin │ │ ├── permissions.css.scss │ │ ├── states.css.scss │ │ └── users.css.scss │ │ ├── application.css │ │ ├── assets.css.scss │ │ ├── comments.css.scss │ │ ├── projects.css.scss │ │ ├── tags.css.scss │ │ ├── tickets.css.scss │ │ ├── users.css.scss │ │ └── users │ │ └── omniauth_callbacks.css.scss ├── controllers │ ├── admin │ │ ├── base_controller.rb │ │ ├── permissions_controller.rb │ │ ├── states_controller.rb │ │ └── users_controller.rb │ ├── api │ │ ├── v1 │ │ │ ├── base_controller.rb │ │ │ ├── projects_controller.rb │ │ │ └── tickets_controller.rb │ │ ├── v2 │ │ │ ├── base_controller.rb │ │ │ ├── projects_controller.rb │ │ │ └── tickets_controller.rb │ │ └── v3 │ │ │ └── json │ │ │ └── tickets.rb │ ├── application_controller.rb │ ├── comments_controller.rb │ ├── files_controller.rb │ ├── projects_controller.rb │ ├── registrations_controller.rb │ ├── tags_controller.rb │ ├── tickets_controller.rb │ ├── users │ │ └── omniauth_callbacks_controller.rb │ └── users_controller.rb ├── helpers │ ├── admin │ │ ├── permissions_helper.rb │ │ ├── states_helper.rb │ │ └── users_helper.rb │ ├── application_helper.rb │ ├── comments_helper.rb │ ├── files_helper.rb │ ├── oauth_helper.rb │ ├── projects_helper.rb │ ├── tags_helper.rb │ ├── tickets_helper.rb │ ├── users │ │ └── omniauth_callbacks_helper.rb │ └── users_helper.rb ├── jobs │ └── comment_notifier_job.rb ├── mailers │ ├── .gitkeep │ ├── notifier.rb │ └── receiver.rb ├── models │ ├── .gitkeep │ ├── ability.rb │ ├── asset.rb │ ├── comment.rb │ ├── permission.rb │ ├── project.rb │ ├── state.rb │ ├── tag.rb │ ├── ticket.rb │ ├── user.rb │ └── user │ │ └── omniauth_callbacks.rb ├── observers │ └── comment_observer.rb ├── sweepers │ └── tickets_sweeper.rb └── views │ ├── admin │ ├── base │ │ └── index.html.erb │ ├── permissions │ │ └── index.html.erb │ ├── states │ │ ├── _form.html.erb │ │ ├── index.html.erb │ │ └── new.html.erb │ └── users │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── comments │ ├── _comment.html.erb │ └── _form.html.erb │ ├── devise │ ├── confirmations │ │ └── new.html.erb │ ├── mailer │ │ ├── confirmation_instructions.html.erb │ │ ├── reset_password_instructions.html.erb │ │ └── unlock_instructions.html.erb │ ├── passwords │ │ ├── edit.html.erb │ │ └── new.html.erb │ ├── sessions │ │ └── new.html.erb │ ├── shared │ │ └── _links.erb │ └── unlocks │ │ └── new.html.erb │ ├── files │ └── _form.html.erb │ ├── layouts │ └── application.html.erb │ ├── notifier │ ├── comment_updated.html.erb │ └── comment_updated.text.erb │ ├── projects │ ├── _form.html.erb │ ├── edit.html.erb │ ├── index.html.erb │ ├── new.html.erb │ └── show.html.erb │ ├── registrations │ ├── edit.html.erb │ └── new.html.erb │ ├── states │ └── _state.html.erb │ ├── tags │ ├── _form.html.erb │ ├── _tag.html.erb │ └── remove.js.erb │ ├── tickets │ ├── _form.html.erb │ ├── edit.html.erb │ ├── new.html.erb │ └── show.html.erb │ └── users │ └── confirmation.html.erb ├── bin ├── autospec ├── cdiff ├── cucumber ├── decolor ├── edit_json.rb ├── erubis ├── htmldiff ├── launchy ├── ldiff ├── nokogiri ├── oauth ├── prettify_json.rb ├── rackup ├── rails ├── rake ├── rake2thor ├── rdiscount ├── rspec ├── run_celerity_server.rb ├── sass ├── sass-convert ├── thor ├── tilt └── tt ├── config.ru ├── config ├── application.rb ├── boot.rb ├── cucumber.yml ├── database.yml ├── deploy.rb ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── backtrace_silencers.rb │ ├── devise.rb │ ├── forem.rb │ ├── inflections.rb │ ├── mail.rb │ ├── mime_types.rb │ ├── secret_token.rb │ └── session_store.rb ├── locales │ ├── devise.en.yml │ └── en.yml └── routes.rb ├── db ├── migrate │ ├── 20110429131324_create_projects.rb │ ├── 20110430105843_create_tickets.rb │ ├── 20110501000412_devise_create_users.rb │ ├── 20110501013524_add_confirmable_fields_to_users.rb │ ├── 20110501050244_add_user_id_to_tickets.rb │ ├── 20110501071526_add_admin_to_users.rb │ ├── 20110502115151_create_permissions.rb │ ├── 20110507060655_add_attachment_asset_to_ticket.rb │ ├── 20110507074451_create_assets.rb │ ├── 20110508082318_create_comments.rb │ ├── 20110508102640_create_states.rb │ ├── 20110508220912_add_previous_state_id_to_comments.rb │ ├── 20110522222106_add_default_to_states.rb │ ├── 20110524094739_create_tags.rb │ ├── 20110528003127_create_ticket_watchers_table.rb │ ├── 20110528233255_add_authentication_token_to_users.rb │ ├── 20110529081807_add_request_count_to_users.rb │ ├── 20110606015904_add_twitter_fields_to_users.rb │ ├── 20110608062135_add_github_fields_to_users.rb │ ├── 20110609123052_create_forem_topics.rb │ ├── 20110609123053_create_forem_posts.rb │ └── 20110609123054_add_posts_count_to_forem_topics.rb ├── schema.rb └── seeds.rb ├── doc └── README_FOR_APP ├── factories ├── comment_factory.rb ├── project_factory.rb ├── ticket_factory.rb └── user_factory.rb ├── features ├── assigning_permissions.feature ├── creating_comments.feature ├── creating_projects.feature ├── creating_states.feature ├── creating_tickets.feature ├── creating_users.feature ├── deleting_projects.feature ├── deleting_tags.feature ├── deleting_tickets.feature ├── deleting_users.feature ├── editing_projects.feature ├── editing_tickets.feature ├── editing_users.feature ├── github_auth.feature ├── gmail.feature ├── hidden_links.feature ├── managing_states.feature ├── paginating_tickets.feature ├── searching.feature ├── seed.feature ├── signing_in.feature ├── signing_up.feature ├── step_definitions │ ├── app_email_steps.rb │ ├── application_steps.rb │ ├── email_steps.rb │ ├── link_steps.rb │ ├── oauth_steps.rb │ ├── pagination_steps.rb │ ├── permission_steps.rb │ ├── project_steps.rb │ ├── state_steps.rb │ ├── ticket_steps.rb │ ├── user_steps.rb │ └── web_steps.rb ├── support │ ├── after_hook.rb │ ├── email.rb │ ├── env.rb │ ├── factories.rb │ ├── paths.rb │ └── selectors.rb ├── ticket_notifications.feature ├── twitter_auth.feature ├── viewing_projects.feature ├── viewing_tickets.feature └── watching_tickets.feature ├── lib ├── heartbeat.ru ├── heartbeat │ ├── application.rb │ ├── config.ru │ └── test_application.rb ├── link_jumbler.rb └── tasks │ ├── .gitkeep │ └── cucumber.rake ├── public ├── 404.html ├── 422.html ├── 500.html ├── favicon.ico ├── images │ └── rails.png └── robots.txt ├── script ├── cucumber └── rails ├── spec ├── api │ ├── v1 │ │ ├── authentication_spec.rb │ │ ├── project_errors_spec.rb │ │ ├── projects_spec.rb │ │ ├── rate_limit_spec.rb │ │ └── tickets_spec.rb │ ├── v2 │ │ ├── authentication_spec.rb │ │ ├── project_errors_spec.rb │ │ ├── projects_spec.rb │ │ ├── rate_limit_spec.rb │ │ └── tickets_spec.rb │ └── v3 │ │ └── json │ │ └── tickets_spec.rb ├── controllers │ ├── admin │ │ ├── permissions_controller_spec.rb │ │ ├── states_controller_spec.rb │ │ └── users_controller_spec.rb │ ├── comments_controller_spec.rb │ ├── files_controller_spec.rb │ ├── projects_controller_spec.rb │ ├── tags_controller_spec.rb │ ├── tickets_controller_spec.rb │ ├── users │ │ └── omniauth_callbacks_controller_spec.rb │ └── users_controller_spec.rb ├── fixtures │ ├── gradient.txt │ ├── speed.txt │ └── spin.txt ├── helpers │ ├── admin │ │ ├── permissions_helper_spec.rb │ │ ├── states_helper_spec.rb │ │ └── users_helper_spec.rb │ ├── comments_helper_spec.rb │ ├── files_helper_spec.rb │ ├── projects_helper_spec.rb │ ├── tags_helper_spec.rb │ ├── tickets_helper_spec.rb │ ├── users │ │ └── omniauth_callbacks_helper_spec.rb │ └── users_helper_spec.rb ├── mailers │ ├── notifier_spec.rb │ └── receiver_spec.rb ├── models │ ├── asset_spec.rb │ ├── comment_spec.rb │ ├── permission_spec.rb │ ├── project_spec.rb │ ├── state_spec.rb │ ├── tag_spec.rb │ ├── ticket_spec.rb │ └── user_spec.rb ├── spec_helper.rb ├── support │ ├── api │ │ └── helper.rb │ ├── devise.rb │ ├── factories.rb │ └── seed_helpers.rb └── views │ ├── admin │ └── users │ │ └── index.html.erb_spec.rb │ └── users │ └── confirmation.html.erb_spec.rb └── vendor ├── assets └── stylesheets │ └── .gitkeep └── plugins └── .gitkeep /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle 2 | db/*.sqlite3 3 | log/*.log 4 | tmp/ 5 | public/system 6 | files 7 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --colour 2 | -------------------------------------------------------------------------------- /.sass-cache/2922b0c424c1ac88972107dc55e666246fd6eb82/projects.css.scssc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails3book/ticketee/67362e12751ea734354a94e3d78b835ac7761ac7/.sass-cache/2922b0c424c1ac88972107dc55e666246fd6eb82/projects.css.scssc -------------------------------------------------------------------------------- /.sass-cache/738e198288ca1faac1cad71c06c387d28dbdd411/projects.css.scssc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails3book/ticketee/67362e12751ea734354a94e3d78b835ac7761ac7/.sass-cache/738e198288ca1faac1cad71c06c387d28dbdd411/projects.css.scssc -------------------------------------------------------------------------------- /.sass-cache/de57125a2fd2452156af3d067c7168ea2adff164/projects.css.scssc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails3book/ticketee/67362e12751ea734354a94e3d78b835ac7761ac7/.sass-cache/de57125a2fd2452156af3d067c7168ea2adff164/projects.css.scssc -------------------------------------------------------------------------------- /Capfile: -------------------------------------------------------------------------------- 1 | load 'deploy' if respond_to?(:namespace) # cap2 differentiator 2 | Dir['vendor/gems/*/recipes/*.rb','vendor/plugins/*/recipes/*.rb'].each { |plugin| load(plugin) } 3 | 4 | load 'config/deploy' # remove this line to skip loading any of the default tasks -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gem 'rails', '3.1.0' 4 | 5 | gem 'sqlite3' 6 | 7 | gem 'sass-rails', '~> 3.1.0' 8 | gem 'coffee-rails' 9 | 10 | gem 'dynamic_form' 11 | 12 | gem 'devise', '1.4.9' 13 | gem 'cancan' 14 | 15 | gem 'paperclip' 16 | 17 | gem 'searcher' 18 | gem 'kaminari' 19 | 20 | gem 'jquery-rails' 21 | gem "oa-oauth", '0.2.6', :require => "omniauth/oauth" 22 | gem "delayed_job" 23 | 24 | gem "ticketee-forem", :require => "forem", :git => "git://github.com/rails3book/forem" 25 | 26 | gem 'sinatra' 27 | 28 | group :test, :development do 29 | gem 'gmail' 30 | gem 'rspec-rails', '~> 2.6' 31 | end 32 | 33 | group :test do 34 | gem 'rack-test' 35 | gem 'cucumber-rails', '1.0.6' 36 | gem 'capybara' 37 | gem 'database_cleaner' 38 | gem 'factory_girl' 39 | gem 'email_spec' 40 | gem 'launchy' 41 | end 42 | 43 | group :production do 44 | gem 'pg' 45 | end 46 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | # Add your own tasks in files placed in lib/tasks ending in .rake, 3 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 4 | 5 | require File.expand_path('../config/application', __FILE__) 6 | 7 | EdgeTicketee::Application.send :include, ::Rake::DSL if defined?(::Rake::DSL) 8 | 9 | EdgeTicketee::Application.load_tasks 10 | -------------------------------------------------------------------------------- /app/assets/images/icons/github_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails3book/ticketee/67362e12751ea734354a94e3d78b835ac7761ac7/app/assets/images/icons/github_32.png -------------------------------------------------------------------------------- /app/assets/images/icons/twitter_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails3book/ticketee/67362e12751ea734354a94e3d78b835ac7761ac7/app/assets/images/icons/twitter_32.png -------------------------------------------------------------------------------- /app/assets/images/twitter_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails3book/ticketee/67362e12751ea734354a94e3d78b835ac7761ac7/app/assets/images/twitter_32.png -------------------------------------------------------------------------------- /app/assets/javascripts/admin/permissions.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/admin/states.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/admin/users.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // FIXME: Tell people that this is a manifest file, real code should go into discrete files 2 | // FIXME: Tell people how Sprockets and CoffeeScript works 3 | // 4 | //= require jquery 5 | //= require jquery_ujs 6 | //= require_tree . 7 | -------------------------------------------------------------------------------- /app/assets/javascripts/assets.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/comments.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/projects.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/tags.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/tickets.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ 4 | 5 | $(-> 6 | $('a#add_another_file').click(-> 7 | url = "/files/new?number=" + $('#files input').length 8 | $.get(url, 9 | (data)-> 10 | $('#files').append(data) 11 | ) 12 | ) 13 | ) -------------------------------------------------------------------------------- /app/assets/javascripts/users.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/users/omniauth_callbacks.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/admin/permissions.css.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Place all the styles related to the matching controller here. 3 | They will automatically be included in application.css. 4 | You can use Sass (SCSS) here: http://sass-lang.com/ 5 | */ 6 | -------------------------------------------------------------------------------- /app/assets/stylesheets/admin/states.css.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Place all the styles related to the matching controller here. 3 | They will automatically be included in application.css. 4 | You can use Sass (SCSS) here: http://sass-lang.com/ 5 | */ 6 | -------------------------------------------------------------------------------- /app/assets/stylesheets/admin/users.css.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Place all the styles related to the matching controller here. 3 | They will automatically be included in application.css. 4 | You can use Sass (SCSS) here: http://sass-lang.com/ 5 | */ 6 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | * { 2 | padding: 0; 3 | margin: 0; 4 | } 5 | 6 | html { 7 | background: white; 8 | } 9 | 10 | body { 11 | font: 13pt "Myriad Pro", Helvetica; 12 | margin: 5px 10% 0 10%; 13 | height: 100%; 14 | padding: 30px; 15 | -moz-border-radius: 3px; 16 | -webkit-border-radius: 3px; 17 | } 18 | 19 | h1 { 20 | margin-bottom: 20px; 21 | } 22 | 23 | nav { 24 | margin: 10px 0 10px 0; 25 | } 26 | 27 | a { 28 | color: #74CC00; 29 | } 30 | 31 | textarea { 32 | padding: 3px; 33 | margin-top: 5px; 34 | margin-bottom: 5px; 35 | } 36 | 37 | input[type=file] { 38 | margin-top: 10px; 39 | margin-bottom: 10px; 40 | } 41 | 42 | .flash { 43 | padding: 10px; 44 | -moz-border-radius: 10px; 45 | -webkit-border-radius: 10px; 46 | font-weight: bold; 47 | } 48 | 49 | #notice { 50 | background: #85FF00; 51 | color: black; 52 | } 53 | 54 | #alert { 55 | background: #FF3444; 56 | } 57 | 58 | ul#tickets { 59 | padding: 15px; 60 | list-style-type: none; 61 | } 62 | 63 | ul#tickets li { 64 | margin-bottom: 10px; 65 | } 66 | 67 | #permissions { 68 | border: 1px solid #789B2F; 69 | } 70 | #permissions td, #permissions th { 71 | padding: 10px; 72 | margin: 0; 73 | text-align: center; 74 | } 75 | 76 | #permissions thead th { 77 | background: #DFFF77; 78 | border-bottom: 1px solid #789B2F; 79 | padding: 10px; 80 | } 81 | 82 | #permissions tr.odd { 83 | background: #F0FF91; 84 | } 85 | 86 | #permissions tr.even { 87 | background: #C2EC4B; 88 | } 89 | 90 | ul#tickets .state { 91 | padding-right: 5px; 92 | /* margin-right: 4em;*/ 93 | } 94 | 95 | ul#tickets .state { 96 | float: left; 97 | } 98 | 99 | #ticket, .comment { 100 | margin: 15px 0 15px 0; 101 | padding: 15px; 102 | background: #eaeaea; 103 | border: 1px solid #e0e0e0; 104 | -moz-border-radius: 5px; 105 | -webkit-border-radius: 5px; 106 | } 107 | 108 | #ticket a { 109 | color: #67A300; 110 | } 111 | 112 | #ticket menu { 113 | font-size: 75%; 114 | } 115 | 116 | menu, #ticket .state, .states { 117 | float: right; 118 | } 119 | 120 | #ticket section { 121 | clear: both; 122 | } 123 | 124 | .state { 125 | display: inline; 126 | width: 4em; 127 | text-align: center; 128 | font-weight: bold; 129 | -moz-border-radius: 5px; 130 | -webkit-border-radius: 5px; 131 | margin-right: 10px; 132 | font-size: 80%; 133 | padding: 5px; 134 | } 135 | 136 | #ticket #tags { 137 | margin-top: 20px; 138 | } 139 | 140 | #ticket .tag { 141 | background: #232323; 142 | -moz-border-radius: 8px; 143 | -webkit-border-radius: 8px; 144 | color: white; 145 | padding: 8px; 146 | } 147 | 148 | #ticket .tag a { 149 | color: white; 150 | } 151 | 152 | #ticket .tag .delete { 153 | font-size: 125%; 154 | text-decoration: none; 155 | } 156 | 157 | .new_comment { 158 | padding: 15px; 159 | background: #eaeaea; 160 | color: black; 161 | -moz-border-radius: 5px; 162 | -webkit-border-radius: 5px; 163 | } 164 | 165 | .comment, .new_comment { 166 | width: 75%; 167 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/assets.css.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Place all the styles related to the matching controller here. 3 | They will automatically be included in application.css. 4 | You can use Sass (SCSS) here: http://sass-lang.com/ 5 | */ 6 | -------------------------------------------------------------------------------- /app/assets/stylesheets/comments.css.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Place all the styles related to the matching controller here. 3 | They will automatically be included in application.css. 4 | You can use Sass (SCSS) here: http://sass-lang.com/ 5 | */ 6 | -------------------------------------------------------------------------------- /app/assets/stylesheets/projects.css.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Place all the styles related to the matching controller here. 3 | They will automatically be included in application.css. 4 | You can use Sass (SCSS) here: http://sass-lang.com/ 5 | */ 6 | -------------------------------------------------------------------------------- /app/assets/stylesheets/tags.css.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Place all the styles related to the matching controller here. 3 | They will automatically be included in application.css. 4 | You can use Sass (SCSS) here: http://sass-lang.com/ 5 | */ 6 | -------------------------------------------------------------------------------- /app/assets/stylesheets/tickets.css.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Place all the styles related to the matching controller here. 3 | They will automatically be included in application.css. 4 | You can use Sass (SCSS) here: http://sass-lang.com/ 5 | */ 6 | 7 | $('a#add_another_file').click($ -> alert("whoops!")) -------------------------------------------------------------------------------- /app/assets/stylesheets/users.css.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Place all the styles related to the matching controller here. 3 | They will automatically be included in application.css. 4 | You can use Sass (SCSS) here: http://sass-lang.com/ 5 | */ 6 | -------------------------------------------------------------------------------- /app/assets/stylesheets/users/omniauth_callbacks.css.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Place all the styles related to the matching controller here. 3 | They will automatically be included in application.css. 4 | You can use Sass (SCSS) here: http://sass-lang.com/ 5 | */ 6 | -------------------------------------------------------------------------------- /app/controllers/admin/base_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::BaseController < ApplicationController 2 | before_filter :authorize_admin! 3 | 4 | def index 5 | 6 | end 7 | end -------------------------------------------------------------------------------- /app/controllers/admin/permissions_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::PermissionsController < Admin::BaseController 2 | before_filter :find_user 3 | 4 | def index 5 | @ability = Ability.new(@user) 6 | @projects = Project.all 7 | end 8 | 9 | def update 10 | @user.permissions.clear 11 | params[:permissions].each do |id, permissions| 12 | project = Project.find(id) 13 | permissions.each do |permission, checked| 14 | Permission.create!(:user => @user, 15 | :thing => project, 16 | :action => permission) 17 | end 18 | end 19 | flash[:notice] = "Permissions updated." 20 | redirect_to admin_user_permissions_path 21 | end 22 | 23 | private 24 | 25 | def find_user 26 | @user = User.find(params[:user_id]) 27 | end 28 | end -------------------------------------------------------------------------------- /app/controllers/admin/states_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::StatesController < Admin::BaseController 2 | 3 | def index 4 | @states = State.all 5 | end 6 | 7 | def new 8 | @state = State.new 9 | end 10 | 11 | def create 12 | @state = State.new(params[:state]) 13 | if @state.save 14 | flash[:notice] = "State has been created." 15 | redirect_to admin_states_path 16 | else 17 | flash[:alert] = "State has not been created." 18 | render :action => "new" 19 | end 20 | end 21 | 22 | def make_default 23 | @state = State.find(params[:id]) 24 | @state.default! 25 | 26 | flash[:notice] = "#{@state.name} is now the default state." 27 | redirect_to admin_states_path 28 | end 29 | end -------------------------------------------------------------------------------- /app/controllers/admin/users_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::UsersController < Admin::BaseController 2 | before_filter :find_user, :only => [:show, :edit, :update, :destroy] 3 | def index 4 | @users = User.all(:order => "email") 5 | end 6 | 7 | def new 8 | @user = User.new 9 | end 10 | 11 | def create 12 | @user = User.new(params[:user]) 13 | set_admin 14 | if @user.save 15 | flash[:notice] = "User has been created." 16 | redirect_to admin_users_path 17 | else 18 | flash[:alert] = "User has not been created." 19 | render :action => "new" 20 | end 21 | end 22 | 23 | def show 24 | 25 | end 26 | 27 | def edit 28 | 29 | end 30 | 31 | def update 32 | if params[:user][:password].blank? 33 | params[:user].delete(:password) 34 | end 35 | set_admin 36 | if @user.update_attributes(params[:user]) 37 | flash[:notice] = "User has been updated." 38 | redirect_to admin_users_path 39 | else 40 | flash[:alert] = "User has not been updated." 41 | render :action => "edit" 42 | end 43 | end 44 | 45 | def destroy 46 | if @user == current_user 47 | flash[:alert] = "You cannot delete yourself!" 48 | else 49 | @user.destroy 50 | flash[:notice] = "User has been deleted." 51 | end 52 | 53 | redirect_to admin_users_path 54 | end 55 | 56 | private 57 | def set_admin 58 | @user.admin = params[:user][:admin] == "1" 59 | end 60 | 61 | def find_user 62 | @user = User.find(params[:id]) 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /app/controllers/api/v1/base_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::V1::BaseController < ActionController::Base 2 | respond_to :json, :xml 3 | 4 | before_filter :authenticate_user 5 | before_filter :check_rate_limit 6 | 7 | private 8 | def authenticate_user 9 | @current_user = User.find_by_authentication_token(params[:token]) 10 | unless @current_user 11 | respond_with({:error => "Token is invalid." }) 12 | end 13 | end 14 | 15 | def authorize_admin! 16 | if !@current_user.admin? 17 | if !@current_user.admin? 18 | error = { :error => "You must be an admin to do that." } 19 | # warden.custom_failure! 20 | render params[:format].to_sym => error, :status => 401 21 | end 22 | end 23 | end 24 | 25 | def current_user 26 | @current_user 27 | end 28 | 29 | def check_rate_limit 30 | if @current_user.request_count > 100 31 | error = { :error => "Rate limit exceeded." } 32 | respond_with(error, :status => 403) 33 | else 34 | @current_user.increment!(:request_count) 35 | end 36 | end 37 | 38 | end -------------------------------------------------------------------------------- /app/controllers/api/v1/projects_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::V1::ProjectsController < Api::V1::BaseController 2 | before_filter :authorize_admin!, :except => [:index, :show] 3 | before_filter :find_project, :only => [:show, :update, :destroy] 4 | 5 | def index 6 | respond_with(Project.for(current_user)) 7 | end 8 | 9 | def show 10 | respond_with(@project, :methods => "last_ticket") 11 | end 12 | 13 | def create 14 | project = Project.create(params[:project]) 15 | if project.valid? 16 | respond_with(project, :location => api_v1_project_path(project)) 17 | else 18 | respond_with(project) 19 | end 20 | end 21 | 22 | def update 23 | @project.update_attributes(params[:project]) 24 | respond_with(@project) 25 | end 26 | 27 | def destroy 28 | @project.destroy 29 | respond_with(@project) 30 | end 31 | 32 | private 33 | 34 | def find_project 35 | @project = Project.for(current_user).find(params[:id]) 36 | rescue ActiveRecord::RecordNotFound 37 | error = { :error => "The project you were looking for could not be found."} 38 | respond_with(error, :status => 404) 39 | end 40 | end -------------------------------------------------------------------------------- /app/controllers/api/v1/tickets_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::V1::TicketsController < Api::V1::BaseController 2 | before_filter :find_project 3 | 4 | def index 5 | respond_with(@project.tickets) 6 | end 7 | 8 | private 9 | def find_project 10 | @project = Project.for(current_user).find(params[:project_id]) 11 | rescue ActiveRecord::RecordNotFound 12 | error = { :error => "The project you were looking for" + 13 | " could not be found."} 14 | respond_with(error, :status => 404) 15 | end 16 | end -------------------------------------------------------------------------------- /app/controllers/api/v2/base_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::V2::BaseController < ActionController::Base 2 | respond_to :json, :xml 3 | 4 | before_filter :authenticate_user 5 | before_filter :check_rate_limit 6 | 7 | private 8 | def authenticate_user 9 | @current_user = User.find_by_authentication_token(params[:token]) 10 | unless @current_user 11 | respond_with({:error => "Token is invalid." }) 12 | end 13 | end 14 | 15 | def authorize_admin! 16 | if !@current_user.admin? 17 | if !@current_user.admin? 18 | error = { :error => "You must be an admin to do that." } 19 | # warden.custom_failure! 20 | render params[:format].to_sym => error, :status => 401 21 | end 22 | end 23 | end 24 | 25 | def current_user 26 | @current_user 27 | end 28 | 29 | def check_rate_limit 30 | if @current_user.request_count > 100 31 | error = { :error => "Rate limit exceeded." } 32 | respond_with(error, :status => 403) 33 | else 34 | @current_user.increment!(:request_count) 35 | end 36 | end 37 | 38 | end -------------------------------------------------------------------------------- /app/controllers/api/v2/projects_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::V2::ProjectsController < Api::V2::BaseController 2 | before_filter :authorize_admin!, :except => [:index, :show] 3 | before_filter :find_project, :only => [:show, :update, :destroy] 4 | 5 | def index 6 | projects = Project.readable_by(current_user) 7 | respond_with(projects, :except => :name, :methods => :title) 8 | end 9 | 10 | def show 11 | respond_with(@project, :methods => "last_ticket") 12 | end 13 | 14 | def create 15 | project = Project.create(params[:project]) 16 | if project.valid? 17 | respond_with(project, :location => api_v1_project_path(project)) 18 | else 19 | respond_with(project) 20 | end 21 | end 22 | 23 | def update 24 | @project.update_attributes(params[:project]) 25 | respond_with(@project) 26 | end 27 | 28 | def destroy 29 | @project.destroy 30 | respond_with(@project) 31 | end 32 | 33 | private 34 | 35 | def find_project 36 | @project = Project.for(current_user).find(params[:id]) 37 | rescue ActiveRecord::RecordNotFound 38 | error = { :error => "The project you were looking for could not be found."} 39 | respond_with(error, :status => 404) 40 | end 41 | end -------------------------------------------------------------------------------- /app/controllers/api/v2/tickets_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::V2::TicketsController < Api::V2::BaseController 2 | before_filter :find_project 3 | 4 | def index 5 | respond_with(@project.tickets.page(params[:page])) 6 | end 7 | 8 | private 9 | def find_project 10 | @project = Project.for(current_user).find(params[:project_id]) 11 | rescue ActiveRecord::RecordNotFound 12 | error = { :error => "The project you were looking for" + 13 | " could not be found."} 14 | respond_with(error, :status => 404) 15 | end 16 | end -------------------------------------------------------------------------------- /app/controllers/api/v3/json/tickets.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra' 2 | 3 | module Api 4 | module V3 5 | module JSON 6 | class Tickets < Sinatra::Base 7 | before do 8 | headers "Content-Type" => "text/json" 9 | find_user 10 | find_project 11 | end 12 | 13 | get '/' do 14 | @project.tickets.to_json 15 | end 16 | 17 | private 18 | 19 | def params 20 | hash = env["action_dispatch.request.path_parameters"].merge!(super) 21 | HashWithIndifferentAccess.new(hash) 22 | end 23 | 24 | private 25 | 26 | def find_user 27 | @user = User.find_by_authentication_token(params[:token]) 28 | halt 401, "Token is invalid." unless @user 29 | end 30 | 31 | def find_project 32 | @project = Project.for(@user).find(params[:project_id]) 33 | rescue ActiveRecord::RecordNotFound 34 | halt 404, "The project you were looking for could not be found." 35 | end 36 | end 37 | end 38 | end 39 | end -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery 3 | before_filter :find_states 4 | 5 | private 6 | 7 | def authorize_admin! 8 | authenticate_user! 9 | unless current_user.admin? 10 | flash[:alert] = "You must be an admin to do that." 11 | redirect_to root_path 12 | end 13 | end 14 | 15 | def find_states 16 | @states = State.all 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/controllers/comments_controller.rb: -------------------------------------------------------------------------------- 1 | class CommentsController < ApplicationController 2 | before_filter :authenticate_user! 3 | before_filter :find_ticket 4 | 5 | def create 6 | if cannot?(:"change states", @ticket.project) 7 | params[:comment].delete(:state_id) 8 | end 9 | @comment = @ticket.comments.build(params[:comment].merge(:user => current_user)) 10 | if @comment.save 11 | if can?(:tag, @ticket.project) || current_user.admin? 12 | @ticket.tag!(params[:tags]) 13 | end 14 | flash[:notice] = "Comment has been created." 15 | redirect_to [@ticket.project, @ticket] 16 | else 17 | @states = State.all 18 | flash[:alert] = "Comment has not been created." 19 | render :template => "tickets/show" 20 | end 21 | end 22 | 23 | private 24 | 25 | def find_ticket 26 | @ticket = Ticket.find(params[:ticket_id]) 27 | end 28 | end -------------------------------------------------------------------------------- /app/controllers/files_controller.rb: -------------------------------------------------------------------------------- 1 | class FilesController < ApplicationController 2 | before_filter :authenticate_user! 3 | def show 4 | asset = Asset.find(params[:id]) 5 | if can?(:view, asset.ticket.project) 6 | send_file asset.asset.path, :filename => asset.asset_file_name, 7 | :content_type => asset.asset_content_type 8 | else 9 | flash[:alert] = "The asset you were looking for could not be found." 10 | redirect_to root_path 11 | end 12 | end 13 | 14 | def new 15 | @ticket = Ticket.new 16 | asset = @ticket.assets.build 17 | render :partial => "files/form", 18 | :locals => { :number => params[:number].to_i, 19 | :asset => asset } 20 | end 21 | 22 | end -------------------------------------------------------------------------------- /app/controllers/projects_controller.rb: -------------------------------------------------------------------------------- 1 | class ProjectsController < ApplicationController 2 | before_filter :check_for_sign_up 3 | before_filter :authorize_admin!, :except => [:index, :show] 4 | before_filter :authenticate_user!, :only => [:index, :show] 5 | before_filter :find_project, :only => [:show, 6 | :edit, 7 | :update, 8 | :destroy] 9 | 10 | # caches_action :show, :cache_path => (proc do 11 | # project_path(params[:id]) + "/#{current_user.id}/#{params[:page] || 1}" 12 | # end) 13 | 14 | def index 15 | @projects = Project.for(current_user).all 16 | end 17 | 18 | def new 19 | @project = Project.new 20 | end 21 | 22 | def create 23 | @project = Project.new(params[:project]) 24 | if @project.save 25 | flash[:notice] = "Project has been created." 26 | redirect_to @project 27 | else 28 | flash[:alert] = "Project has not been created." 29 | render :action => "new" 30 | end 31 | end 32 | 33 | def show 34 | @tickets = @project.tickets.includes(:tags).page(params[:page]).per(50) 35 | end 36 | 37 | def edit 38 | end 39 | 40 | def update 41 | if @project.update_attributes(params[:project]) 42 | flash[:notice] = "Project has been updated." 43 | redirect_to @project 44 | else 45 | flash[:alert] = "Project has not been updated." 46 | render :action => "edit" 47 | end 48 | end 49 | 50 | def destroy 51 | @project.destroy 52 | flash[:notice] = "Project has been deleted." 53 | redirect_to projects_path 54 | end 55 | 56 | private 57 | def find_project 58 | @project = Project.for(current_user).find(params[:id]) 59 | rescue ActiveRecord::RecordNotFound 60 | flash[:alert] = "The project you were looking" + 61 | " for could not be found." 62 | redirect_to projects_path 63 | end 64 | 65 | def check_for_sign_up 66 | if request.referer == user_registration_url 67 | redirect_to confirm_user_path 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /app/controllers/registrations_controller.rb: -------------------------------------------------------------------------------- 1 | class RegistrationsController < Devise::RegistrationsController 2 | self.scoped_views = false 3 | 4 | def new 5 | super 6 | end 7 | 8 | protected 9 | 10 | def after_inactive_sign_up_path_for(resource) 11 | confirm_user_path 12 | end 13 | end -------------------------------------------------------------------------------- /app/controllers/tags_controller.rb: -------------------------------------------------------------------------------- 1 | class TagsController < ApplicationController 2 | def remove 3 | @ticket = Ticket.find(params[:ticket_id]) 4 | if can?(:tag, @ticket.project) || current_user.admin? 5 | @tag = Tag.find(params[:id]) 6 | @ticket.tags -= [@tag] 7 | @ticket.save 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/controllers/tickets_controller.rb: -------------------------------------------------------------------------------- 1 | class TicketsController < ApplicationController 2 | before_filter :authenticate_user! 3 | before_filter :find_project 4 | before_filter :find_ticket, :only => [:show, 5 | :edit, 6 | :update, 7 | :destroy, 8 | :watch] 9 | before_filter :authorize_create!, :only => [:new, :create] 10 | before_filter :authorize_update!, :only => [:edit, :update] 11 | before_filter :authorize_delete!, :only => [:destroy] 12 | 13 | def new 14 | @ticket = @project.tickets.build 15 | @ticket.assets.build 16 | end 17 | 18 | def create 19 | @ticket = @project.tickets.build(params[:ticket].merge!(:user => current_user)) 20 | if @ticket.save 21 | if can?(:tag, @project) || current_user.admin? 22 | @ticket.tag!(params[:tags]) 23 | end 24 | flash[:notice] = "Ticket has been created." 25 | redirect_to [@project, @ticket] 26 | else 27 | flash[:alert] = "Ticket has not been created." 28 | render :action => "new" 29 | end 30 | end 31 | 32 | def show 33 | @comment = @ticket.comments.build 34 | @states = State.all 35 | fresh_when :last_modified => @ticket.updated_at, 36 | :etag => @ticket.to_s + current_user.id.to_s 37 | end 38 | 39 | def edit 40 | 41 | end 42 | 43 | def update 44 | if @ticket.update_attributes(params[:ticket]) 45 | flash[:notice] = "Ticket has been updated." 46 | redirect_to [@project, @ticket] 47 | else 48 | flash[:alert] = "Ticket has not been updated." 49 | render :action => "edit" 50 | end 51 | end 52 | 53 | def destroy 54 | @ticket.destroy 55 | flash[:notice] = "Ticket has been deleted." 56 | redirect_to @project 57 | end 58 | 59 | def search 60 | @tickets = @project.tickets.search(params[:search]) 61 | @tickets = @tickets.page(params[:page]).per(50) 62 | render "projects/show" 63 | end 64 | 65 | def watch 66 | if @ticket.watchers.exists?(current_user) 67 | @ticket.watchers -= [current_user] 68 | flash[:notice] = "You are no longer watching this ticket." 69 | else 70 | @ticket.watchers << current_user 71 | flash[:notice] = "You are now watching this ticket." 72 | end 73 | 74 | redirect_to project_ticket_path(@ticket.project, @ticket) 75 | end 76 | 77 | 78 | private 79 | def find_project 80 | @project = Project.for(current_user).find(params[:project_id]) 81 | rescue ActiveRecord::RecordNotFound 82 | flash[:alert] = "The project you were looking for could not be found." 83 | redirect_to root_path 84 | end 85 | 86 | def find_ticket 87 | @ticket = @project.tickets.find(params[:id]) 88 | end 89 | 90 | def authorize_create! 91 | if !current_user.admin? && cannot?("create tickets".to_sym, @project) 92 | flash[:alert] = "You cannot create tickets on this project." 93 | redirect_to @project 94 | end 95 | end 96 | 97 | def authorize_update! 98 | if !current_user.admin? && cannot?(:"edit tickets", @project) 99 | flash[:alert] = "You cannot edit tickets on this project." 100 | redirect_to @project 101 | end 102 | end 103 | 104 | def authorize_delete! 105 | if !current_user.admin? && cannot?(:"delete tickets", @project) 106 | flash[:alert] = "You cannot delete tickets from this project." 107 | redirect_to @project 108 | end 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /app/controllers/users/omniauth_callbacks_controller.rb: -------------------------------------------------------------------------------- 1 | class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController 2 | def self.provides_callback_for(*providers) 3 | providers.each do |provider| 4 | class_eval %Q{ 5 | def #{provider} 6 | @user = User.find_or_create_for_#{provider}(env["omniauth.auth"]) 7 | flash[:notice] = "Signed in with #{provider.to_s.titleize} successfully." 8 | sign_in_and_redirect @user, :event => :authentication 9 | end 10 | } 11 | end 12 | end 13 | 14 | provides_callback_for :twitter, :github 15 | end -------------------------------------------------------------------------------- /app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | def confirmation 3 | end 4 | 5 | end 6 | -------------------------------------------------------------------------------- /app/helpers/admin/permissions_helper.rb: -------------------------------------------------------------------------------- 1 | module Admin::PermissionsHelper 2 | def permissions 3 | { 4 | "view" => "View", 5 | "create tickets" => "Create Tickets", 6 | "edit tickets" => "Edit Tickets", 7 | "delete tickets" => "Delete Tickets", 8 | "change states" => "Change States" 9 | } 10 | end 11 | end -------------------------------------------------------------------------------- /app/helpers/admin/states_helper.rb: -------------------------------------------------------------------------------- 1 | module Admin::StatesHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/admin/users_helper.rb: -------------------------------------------------------------------------------- 1 | module Admin::UsersHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | def title(*parts) 3 | @title = (parts << "Ticketee").join(" - ") unless parts.empty? 4 | @title || "Ticketee" 5 | end 6 | 7 | def admins_only(&block) 8 | block.call if current_user.try(:admin?) 9 | nil 10 | end 11 | 12 | def authorized?(permission, thing, &block) 13 | block.call if can?(permission.to_sym, thing) || 14 | current_user.try(:admin?) 15 | nil 16 | end 17 | end -------------------------------------------------------------------------------- /app/helpers/comments_helper.rb: -------------------------------------------------------------------------------- 1 | module CommentsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/files_helper.rb: -------------------------------------------------------------------------------- 1 | module FilesHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/oauth_helper.rb: -------------------------------------------------------------------------------- 1 | module OauthHelper 2 | def auth_providers(*names) 3 | names.each do |name| 4 | concat(link_to(image_tag("icons/#{name}_32.png"), 5 | user_omniauth_authorize_path(name), 6 | :id => "sign_in_with_#{name}")) 7 | end 8 | nil 9 | end 10 | end -------------------------------------------------------------------------------- /app/helpers/projects_helper.rb: -------------------------------------------------------------------------------- 1 | module ProjectsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/tags_helper.rb: -------------------------------------------------------------------------------- 1 | module TagsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/tickets_helper.rb: -------------------------------------------------------------------------------- 1 | module TicketsHelper 2 | def state_for(comment) 3 | content_tag(:div, :class => "states") do 4 | if comment.state 5 | if comment.previous_state && comment.state != comment.previous_state 6 | "#{render comment.previous_state} → #{render comment.state}".html_safe 7 | else 8 | render(comment.state) 9 | end 10 | end 11 | end 12 | end 13 | 14 | def toggle_watching_button 15 | text = if @ticket.watchers.include?(current_user) 16 | "Stop watching this ticket" 17 | else 18 | "Watch this ticket" 19 | end 20 | button_to(text, watch_project_ticket_path(@ticket.project, @ticket)) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/helpers/users/omniauth_callbacks_helper.rb: -------------------------------------------------------------------------------- 1 | module Users::OmniauthCallbacksHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/users_helper.rb: -------------------------------------------------------------------------------- 1 | module UsersHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/jobs/comment_notifier_job.rb: -------------------------------------------------------------------------------- 1 | class CommentNotifierJob < Struct.new(:comment_id) 2 | def perform 3 | comment = Comment.find(comment_id) 4 | watchers = comment.ticket.watchers - [comment.user] 5 | watchers.each do |user| 6 | Notifier.comment_updated(comment, user).deliver 7 | end 8 | end 9 | end -------------------------------------------------------------------------------- /app/mailers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails3book/ticketee/67362e12751ea734354a94e3d78b835ac7761ac7/app/mailers/.gitkeep -------------------------------------------------------------------------------- /app/mailers/notifier.rb: -------------------------------------------------------------------------------- 1 | class Notifier < ActionMailer::Base 2 | default from: "ticketee@gmail.com" 3 | 4 | def comment_updated(comment, user) 5 | @comment = comment 6 | @user = user 7 | mail(:to => user.email, 8 | :from => "Ticketee ", 9 | :subject => "[ticketee] #{comment.ticket.project.name} - #{comment.ticket.title}") 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/mailers/receiver.rb: -------------------------------------------------------------------------------- 1 | class Receiver < ActionMailer::Base 2 | default from: "from@example.com" 3 | 4 | def self.parse(email) 5 | reply_separator = /(.*?)\s?== ADD YOUR REPLY ABOVE THIS LINE ==/m 6 | comment_text = reply_separator.match(email.body.to_s) 7 | if comment_text 8 | to, project_id, ticket_id = email.to.first.split("@")[0].split("+") 9 | project = Project.find(project_id) 10 | ticket = project.tickets.find(ticket_id) 11 | user = User.find_by_email(email.from[0]) 12 | ticket.comments.create(:text => comment_text[1].strip, 13 | :user => user) 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails3book/ticketee/67362e12751ea734354a94e3d78b835ac7761ac7/app/models/.gitkeep -------------------------------------------------------------------------------- /app/models/ability.rb: -------------------------------------------------------------------------------- 1 | class Ability 2 | include CanCan::Ability 3 | 4 | def initialize(user) 5 | user.permissions.each do |permission| 6 | can permission.action.to_sym, permission.thing_type.constantize do |thing| 7 | thing.nil? || 8 | permission.thing_id.nil? || 9 | permission.thing_id == thing.id 10 | end 11 | end 12 | end 13 | end -------------------------------------------------------------------------------- /app/models/asset.rb: -------------------------------------------------------------------------------- 1 | class Asset < ActiveRecord::Base 2 | belongs_to :ticket 3 | 4 | has_attached_file :asset, :path => (Rails.root + "files/:id").to_s 5 | end 6 | -------------------------------------------------------------------------------- /app/models/comment.rb: -------------------------------------------------------------------------------- 1 | class Comment < ActiveRecord::Base 2 | before_create :set_previous_state 3 | after_create :set_ticket_state 4 | after_create :creator_watches_ticket 5 | 6 | belongs_to :user 7 | belongs_to :state 8 | belongs_to :ticket 9 | belongs_to :previous_state, :class_name => "State" 10 | 11 | delegate :project, :to => :ticket 12 | 13 | validates :text, :presence => true 14 | 15 | private 16 | 17 | def set_previous_state 18 | self.previous_state = ticket.state 19 | end 20 | 21 | def set_ticket_state 22 | self.ticket.state = self.state 23 | self.ticket.save! 24 | end 25 | 26 | def creator_watches_ticket 27 | ticket.watchers << user 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/models/permission.rb: -------------------------------------------------------------------------------- 1 | class Permission < ActiveRecord::Base 2 | belongs_to :user 3 | belongs_to :thing, :polymorphic => true 4 | end 5 | -------------------------------------------------------------------------------- /app/models/project.rb: -------------------------------------------------------------------------------- 1 | class Project < ActiveRecord::Base 2 | has_many :tickets 3 | has_many :permissions, :as => :thing 4 | 5 | validates :name, :presence => true 6 | 7 | scope :readable_by, lambda { |user| 8 | joins(:permissions).where(:permissions => { :action => "view", 9 | :user_id => user.id }) 10 | } 11 | 12 | def self.for(user) 13 | user.admin? ? Project : Project.readable_by(user) 14 | end 15 | 16 | def last_ticket 17 | tickets.last 18 | end 19 | 20 | def title 21 | name 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/models/state.rb: -------------------------------------------------------------------------------- 1 | class State < ActiveRecord::Base 2 | def to_s 3 | name 4 | end 5 | 6 | def default! 7 | current_default_state = State.find_by_default(true) 8 | 9 | self.default = true 10 | self.save! 11 | 12 | if current_default_state 13 | current_default_state.default = false 14 | current_default_state.save! 15 | end 16 | 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/models/tag.rb: -------------------------------------------------------------------------------- 1 | class Tag < ActiveRecord::Base 2 | end 3 | -------------------------------------------------------------------------------- /app/models/ticket.rb: -------------------------------------------------------------------------------- 1 | class Ticket < ActiveRecord::Base 2 | paginates_per 50 3 | searcher do 4 | label :tag, :from => :tags, :field => :name 5 | label :state, :from => :state, :field => "name" 6 | end 7 | 8 | belongs_to :project 9 | belongs_to :user 10 | belongs_to :state 11 | 12 | has_many :assets 13 | accepts_nested_attributes_for :assets 14 | 15 | has_many :comments 16 | 17 | has_and_belongs_to_many :tags 18 | has_and_belongs_to_many :watchers, :join_table => "ticket_watchers", 19 | :class_name => "User" 20 | 21 | after_create :creator_watches_me 22 | 23 | validates :title, :presence => true 24 | validates :description, :presence => true, :length => { :minimum => 10 } 25 | 26 | def tag!(tags) 27 | tags = tags.split(" ").map do |tag| 28 | Tag.find_or_create_by_name(tag) 29 | end 30 | 31 | self.tags << tags 32 | end 33 | 34 | private 35 | 36 | def creator_watches_me 37 | self.watchers << user 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ActiveRecord::Base 2 | extend OmniauthCallbacks 3 | # Include default devise modules. Others available are: 4 | # :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable 5 | devise :database_authenticatable, :registerable, 6 | :recoverable, :rememberable, :trackable, 7 | :validatable, :confirmable, :token_authenticatable, :omniauthable 8 | 9 | # Setup accessible (or protected) attributes for your model 10 | attr_accessible :email, :password, :password_confirmation, :remember_me 11 | 12 | has_many :permissions 13 | 14 | def self.reset_request_count! 15 | update_all("request_count = 0", "request_count > 0") 16 | end 17 | 18 | def to_s 19 | "#{display_name} (#{admin? ? "Admin" : "User"})" 20 | end 21 | 22 | def display_name 23 | if twitter_id 24 | "#{twitter_display_name} (@#{twitter_screen_name})" 25 | elsif github_id 26 | "#{github_display_name} (#{github_user_name})" 27 | else 28 | email 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /app/models/user/omniauth_callbacks.rb: -------------------------------------------------------------------------------- 1 | class User < ActiveRecord::Base 2 | module OmniauthCallbacks 3 | def find_or_create_for_twitter(response) 4 | data = response['extra']['user_hash'] 5 | if user = User.find_by_twitter_id(data["id"]) 6 | user 7 | else # Create a user with a stub password. 8 | user = User.new(:email => "twitter+#{data["id"]}@example.com", 9 | :password => Devise.friendly_token[0,20]) 10 | user.twitter_id = data["id"] 11 | user.twitter_screen_name = data["screen_name"] 12 | user.twitter_display_name = data["display_name"] 13 | user.confirm! 14 | user 15 | end 16 | end 17 | 18 | def find_or_create_for_github(response) 19 | data = response['extra']['user_hash'] 20 | if user = User.find_by_github_id(data["id"]) 21 | user 22 | else # Create a user with a stub password. 23 | user = User.new(:email => data["email"], 24 | :password => Devise.friendly_token[0,20]) 25 | user.github_id = data["id"] 26 | user.github_user_name = data["login"] 27 | user.github_display_name = data["name"] 28 | user.confirm! 29 | user 30 | end 31 | end 32 | end 33 | end -------------------------------------------------------------------------------- /app/observers/comment_observer.rb: -------------------------------------------------------------------------------- 1 | class CommentObserver < ActiveRecord::Observer 2 | def after_create(comment) 3 | (comment.ticket.watchers - [comment.user]).each do |user| 4 | Notifier.comment_updated(comment, user).deliver 5 | end 6 | end 7 | end -------------------------------------------------------------------------------- /app/sweepers/tickets_sweeper.rb: -------------------------------------------------------------------------------- 1 | class TicketsSweeper < ActionController::Caching::Sweeper 2 | observe Ticket 3 | def after_create(ticket) 4 | expire_fragments_for_project(ticket.project) 5 | end 6 | 7 | def after_update(ticket) 8 | expire_fragments_for_project(ticket.project) 9 | end 10 | 11 | def after_destroy(ticket) 12 | expire_fragments_for_project(ticket.project) 13 | end 14 | 15 | private 16 | 17 | def expire_fragments_for_project(project) 18 | expire_fragment(/projects\/#{project.id}\/.*?/) 19 | end 20 | end -------------------------------------------------------------------------------- /app/views/admin/base/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= link_to "Users", admin_users_path %> 2 | <%= link_to "States", admin_states_path %> 3 |

Welcome to Ticketee's Admin Lounge. Please enjoy your stay.

-------------------------------------------------------------------------------- /app/views/admin/permissions/index.html.erb: -------------------------------------------------------------------------------- 1 |

Permissions for <%= @user %>

2 | <%= form_tag update_user_permissions_path(@user), :method => :put do %> 3 | 4 | 5 | 6 | <% permissions.each do |name, text| %> 7 | 8 | <% end %> 9 | 10 | 11 | <% @projects.each do |project| %> 12 | 13 | 14 | <% permissions.each do |name, text| %> 15 | 18 | <% end %> 19 | 20 | <% end %> 21 | 22 |
Project<%= text %>
<%= project.name %> 16 | <%= check_box_tag "permissions[#{project.id}][#{name}]", 17 | @ability.can?(name.to_sym, project) %>
23 | <%= submit_tag "Update" %> 24 | <% end %> -------------------------------------------------------------------------------- /app/views/admin/states/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for [:admin, @state] do |f| %> 2 |

3 | <%= f.label :name %> 4 | <%= f.text_field :name %> 5 |

6 | 7 | <%= f.submit %> 8 | <% end %> -------------------------------------------------------------------------------- /app/views/admin/states/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= link_to "New State", new_admin_state_path %> 2 | 3 | -------------------------------------------------------------------------------- /app/views/admin/states/new.html.erb: -------------------------------------------------------------------------------- 1 |

New State

2 | <%= render "form" %> -------------------------------------------------------------------------------- /app/views/admin/users/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for [:admin, @user] do |f| %> 2 | <%= f.error_messages %> 3 |

4 | <%= f.label :email %> 5 | <%= f.text_field :email %> 6 |

7 | 8 |

9 | <%= f.label :password %> 10 | <%= f.password_field :password %> 11 |

12 | 13 |

14 | <%= f.check_box :admin %> 15 | <%= f.label :admin, "Is an admin?" %> 16 |

17 | <%= f.submit %> 18 | <% end %> -------------------------------------------------------------------------------- /app/views/admin/users/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= render "form" %> -------------------------------------------------------------------------------- /app/views/admin/users/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= link_to "New User", new_admin_user_path %> 2 | -------------------------------------------------------------------------------- /app/views/admin/users/new.html.erb: -------------------------------------------------------------------------------- 1 |

New User

2 | <%= render "form" %> -------------------------------------------------------------------------------- /app/views/admin/users/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= @user %>

2 | <%= link_to "Edit User", edit_admin_user_path(@user) %> 3 | <%= link_to "Delete User", admin_user_path(@user), :method => :delete, 4 | :confirm => "Are you sure you want to delete this user?" %> 5 | <%= link_to "Permissions", admin_user_permissions_path(@user) %> -------------------------------------------------------------------------------- /app/views/comments/_comment.html.erb: -------------------------------------------------------------------------------- 1 | <%= div_for(comment) do %> 2 |

<%= comment.user %>

3 | State: <%= state_for(comment) %> 4 | <%= simple_format(comment.text) %> 5 | <% end %> -------------------------------------------------------------------------------- /app/views/comments/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for [@ticket, @comment] do |f| %> 2 | New comment 3 | <%= f.error_messages %> 4 |

5 | <%= f.label :text %>
6 | <%= f.text_area :text %> 7 |

8 | 9 | <% authorized?(:"change states", @project) do %> 10 |

11 | <%= f.label :state_id %> 12 | <%= f.select :state_id, @states.map { |s| [s.name, s.id] }, 13 | :selected => @ticket.state_id %> 14 |

15 | <% end %> 16 | 17 | <%= render "tags/form" %> 18 | 19 | <%= f.submit %> 20 | <% end %> -------------------------------------------------------------------------------- /app/views/devise/confirmations/new.html.erb: -------------------------------------------------------------------------------- 1 |

Resend confirmation instructions

2 | 3 | <%= form_for(resource, :as => resource_name, :url => confirmation_path(resource_name), :html => { :method => :post }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |

<%= f.label :email %>
7 | <%= f.email_field :email %>

8 | 9 |

<%= f.submit "Resend confirmation instructions" %>

10 | <% end %> 11 | 12 | <%= render :partial => "devise/shared/links" %> -------------------------------------------------------------------------------- /app/views/devise/mailer/confirmation_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Welcome <%= @resource.email %>!

2 | 3 |

You can confirm your account through the link below:

4 | 5 |

<%= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token => @resource.confirmation_token) %>

6 | -------------------------------------------------------------------------------- /app/views/devise/mailer/reset_password_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

Someone has requested a link to change your password, and you can do this through the link below.

4 | 5 |

<%= link_to 'Change my password', edit_password_url(@resource, :reset_password_token => @resource.reset_password_token) %>

6 | 7 |

If you didn't request this, please ignore this email.

8 |

Your password won't change until you access the link above and create a new one.

9 | -------------------------------------------------------------------------------- /app/views/devise/mailer/unlock_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

Your account has been locked due to an excessive amount of unsuccessful sign in attempts.

4 | 5 |

Click the link below to unlock your account:

6 | 7 |

<%= link_to 'Unlock my account', unlock_url(@resource, :unlock_token => @resource.unlock_token) %>

8 | -------------------------------------------------------------------------------- /app/views/devise/passwords/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Change your password

2 | 3 | <%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :put }) do |f| %> 4 | <%= devise_error_messages! %> 5 | <%= f.hidden_field :reset_password_token %> 6 | 7 |

<%= f.label :password, "New password" %>
8 | <%= f.password_field :password %>

9 | 10 |

<%= f.label :password_confirmation, "Confirm new password" %>
11 | <%= f.password_field :password_confirmation %>

12 | 13 |

<%= f.submit "Change my password" %>

14 | <% end %> 15 | 16 | <%= render :partial => "devise/shared/links" %> -------------------------------------------------------------------------------- /app/views/devise/passwords/new.html.erb: -------------------------------------------------------------------------------- 1 |

Forgot your password?

2 | 3 | <%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :post }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |

<%= f.label :email %>
7 | <%= f.email_field :email %>

8 | 9 |

<%= f.submit "Send me reset password instructions" %>

10 | <% end %> 11 | 12 | <%= render :partial => "devise/shared/links" %> -------------------------------------------------------------------------------- /app/views/devise/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 |

Sign in

2 | 3 | <%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %> 4 |

<%= f.label :email %>
5 | <%= f.email_field :email %>

6 | 7 |

<%= f.label :password %>
8 | <%= f.password_field :password %>

9 | 10 | <% if devise_mapping.rememberable? -%> 11 |

<%= f.check_box :remember_me %> <%= f.label :remember_me %>

12 | <% end -%> 13 | 14 |

<%= f.submit "Sign in" %>

15 | <% end %> 16 | 17 | <%= render :partial => "devise/shared/links" %> -------------------------------------------------------------------------------- /app/views/devise/shared/_links.erb: -------------------------------------------------------------------------------- 1 | <%- if controller_name != 'sessions' %> 2 | <%= link_to "Sign in", new_session_path(resource_name) %>
3 | <% end -%> 4 | 5 | <%- if devise_mapping.registerable? && controller_name != 'registrations' %> 6 | <%= link_to "Sign up", new_registration_path(resource_name) %>
7 | <% end -%> 8 | 9 | <%- if devise_mapping.recoverable? && controller_name != 'passwords' %> 10 | <%= link_to "Forgot your password?", new_password_path(resource_name) %>
11 | <% end -%> 12 | 13 | <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> 14 | <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
15 | <% end -%> 16 | 17 | <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> 18 | <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
19 | <% end -%> 20 | 21 | <%- if devise_mapping.omniauthable? %> 22 | <%- resource_class.omniauth_providers.each do |provider| %> 23 | <%= link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider) %>
24 | <% end -%> 25 | <% end -%> -------------------------------------------------------------------------------- /app/views/devise/unlocks/new.html.erb: -------------------------------------------------------------------------------- 1 |

Resend unlock instructions

2 | 3 | <%= form_for(resource, :as => resource_name, :url => unlock_path(resource_name), :html => { :method => :post }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |

<%= f.label :email %>
7 | <%= f.email_field :email %>

8 | 9 |

<%= f.submit "Resend unlock instructions" %>

10 | <% end %> 11 | 12 | <%= render :partial => "devise/shared/links" %> -------------------------------------------------------------------------------- /app/views/files/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= fields_for @ticket do |f| %> 2 | <%= f.fields_for :assets, :child_index => number do |asset| %> 3 |

4 | <%= asset.label :asset, "File ##{number += 1}" %> 5 | <%= asset.file_field :asset %> 6 |

7 | <% end %> 8 | <% end %> -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | <%= stylesheet_link_tag "application" %> 6 | <%= stylesheet_link_tag "projects" %> 7 | <%= javascript_include_tag "application" %> 8 | <%= csrf_meta_tags %> 9 | 10 | 18 | 19 | 20 | <% flash.each do |key, value| %> 21 |
22 | <%= value %> 23 |
24 | <% end %> 25 |

<%= link_to "Ticketee", main_app.root_path %>

26 | 41 | <%= yield %> 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/views/notifier/comment_updated.html.erb: -------------------------------------------------------------------------------- 1 | == ADD YOUR REPLY ABOVE THIS LINE == 2 | 3 | Ticketee 4 | 5 |

6 | Hello. 7 |

8 | 9 |

10 | <%= @comment.user %> has just updated the <%= @comment.ticket.title %> ticket for <%= @comment.ticket.project.name %>. 11 |

12 | 13 |

14 | They wrote: 15 |

16 | 17 |
<%= @comment.text %>
18 | 19 |

20 | You can <%= link_to "view this ticket online here", 21 | project_ticket_url(@comment.ticket.project, @comment.ticket) %> 22 |

-------------------------------------------------------------------------------- /app/views/notifier/comment_updated.text.erb: -------------------------------------------------------------------------------- 1 | == ADD YOUR REPLY ABOVE THIS LINE == 2 | 3 | Hello! 4 | 5 | <%= @comment.user %> has just updated the <%= @comment.ticket.title %> ticket for <%= @comment.ticket.project.name %>. They wrote: 6 | 7 | <%= @comment.text %> 8 | 9 | You can view this ticket online by going to: 10 | <%= project_ticket_url(@comment.ticket.project, @comment.ticket) %> -------------------------------------------------------------------------------- /app/views/projects/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for(@project) do |f| %> 2 | <%= f.error_messages %> 3 |

4 | <%= f.label :name %> 5 | <%= f.text_field :name %> 6 |

7 | <%= f.submit %> 8 | <% end %> -------------------------------------------------------------------------------- /app/views/projects/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Edit project

2 | <%= render "form" %> -------------------------------------------------------------------------------- /app/views/projects/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= admins_only do %> 2 | <%= link_to "New Project", new_project_path %> 3 | <% end %> 4 | 5 |

Projects

6 | -------------------------------------------------------------------------------- /app/views/projects/new.html.erb: -------------------------------------------------------------------------------- 1 |

New project

2 | <%= render "form" %> -------------------------------------------------------------------------------- /app/views/projects/show.html.erb: -------------------------------------------------------------------------------- 1 | <% title(@project.name, "Projects") %> 2 |

<%= @project.name %>

3 | 4 | <%= admins_only do %> 5 | <%= link_to "Edit Project", edit_project_path(@project) %> 6 | <%= link_to "Delete Project", @project, :method => :delete, 7 | :confirm => "Are you sure you want to delete this project?" %> 8 | <% end %> 9 | 10 | <%= authorized?(:"create tickets", @project) do %> 11 | <%= link_to "New Ticket", new_project_ticket_path(@project) %> 12 | <% end %> 13 | 14 | <%= form_tag search_project_tickets_path(@project), 15 | :method => :get do %> 16 | <%= label_tag "search" %> 17 | <%= text_field_tag "search", params[:search] %> 18 | <%= submit_tag "Search" %> 19 | <% end %> 20 | 21 | <% cache "projects/#{@project.id}/#{params[:page] || 1}" do %> 22 | <%= paginate @tickets %> 23 | 31 | <% end %> -------------------------------------------------------------------------------- /app/views/registrations/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Edit <%= resource_name.to_s.humanize %>

2 | 3 | <%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |

<%= f.label :email %>
7 | <%= f.email_field :email %>

8 | 9 |

<%= f.label :password %> (leave blank if you don't want to change it)
10 | <%= f.password_field :password %>

11 | 12 |

<%= f.label :password_confirmation %>
13 | <%= f.password_field :password_confirmation %>

14 | 15 |

<%= f.label :current_password %> (we need your current password to confirm your changes)
16 | <%= f.password_field :current_password %>

17 | 18 |

<%= f.submit "Update" %>

19 | <% end %> 20 | 21 |

Cancel my account

22 | 23 |

Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), :confirm => "Are you sure?", :method => :delete %>.

24 | 25 | <%= link_to "Back", :back %> 26 | -------------------------------------------------------------------------------- /app/views/registrations/new.html.erb: -------------------------------------------------------------------------------- 1 |

Sign up

2 | 3 | <%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |

<%= f.label :email %>
7 | <%= f.email_field :email %>

8 | 9 |

<%= f.label :password %>
10 | <%= f.password_field :password %>

11 | 12 |

<%= f.label :password_confirmation %>
13 | <%= f.password_field :password_confirmation %>

14 | 15 |

<%= f.submit "Sign up" %>

16 | <% end %> 17 | 18 | <%= render :partial => "devise/shared/links" %> 19 | -------------------------------------------------------------------------------- /app/views/states/_state.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= state %> 3 |
-------------------------------------------------------------------------------- /app/views/tags/_form.html.erb: -------------------------------------------------------------------------------- 1 |

2 | <%= label_tag :tags %> 3 | <%= text_field_tag :tags %> 4 |

-------------------------------------------------------------------------------- /app/views/tags/_tag.html.erb: -------------------------------------------------------------------------------- 1 | 2 | <% if can?(:tag, @ticket.project) || current_user.admin? %> 3 | <%= link_to "x", remove_ticket_tag_path(@ticket, tag), 4 | :remote => true, 5 | :method => :delete, 6 | :id => "delete-#{tag.name.parameterize}" %> 7 | <% end %> 8 | <%= link_to tag.name, 9 | search_project_tickets_path(@ticket.project, 10 | :search => "tag:#{tag.name}") %> 11 | -------------------------------------------------------------------------------- /app/views/tags/remove.js.erb: -------------------------------------------------------------------------------- 1 | $('#tag-<%= @tag.name.parameterize %>').remove(); -------------------------------------------------------------------------------- /app/views/tickets/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for [@project, @ticket] do |f| %> 2 | <%= f.error_messages %> 3 |

4 | <%= f.label :title %>
5 | <%= f.text_field :title %> 6 |

7 |

8 | <%= f.label :description %>
9 | <%= f.text_area :description %> 10 |

11 | 12 | <%= render "tags/form" %> 13 | 14 | <% number = 0 %> 15 |
16 | <%= f.fields_for :assets, :child_index => number do |asset| %> 17 |

18 | <%= asset.label :asset, "File ##{number += 1}" %> 19 | <%= asset.file_field :asset %> 20 |

21 | <% end %> 22 |
23 | <%= link_to "Add another file", 'javascript:', :id => "add_another_file" %> 24 | <%= f.submit %> 25 | <% end %> -------------------------------------------------------------------------------- /app/views/tickets/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Editing a ticket in <%= @project.name %>

2 | <%= render "form" %> -------------------------------------------------------------------------------- /app/views/tickets/new.html.erb: -------------------------------------------------------------------------------- 1 |

New Ticket

2 | <%= render "form" %> -------------------------------------------------------------------------------- /app/views/tickets/show.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

<%= @ticket.title %>

3 | 4 | <%= authorized?("edit tickets", @project) do %> 5 | <%= link_to "Edit Ticket", [:edit, @project, @ticket] %> 6 | <% end %> 7 | 8 | <%= authorized?("delete tickets", @project) do %> 9 | <%= link_to "Delete Ticket", 10 | project_ticket_path(@project, @ticket), 11 | :method => :delete, 12 | :confirm => "Are you sure you want to delete this ticket?" %> 13 | <% end %> 14 | 15 |
Created by <%= @ticket.user.display_name %> 16 | <%= render @ticket.state if @ticket.state %> 17 | <%= simple_format(@ticket.description) %> 18 |
<%= render @ticket.tags %>
19 | <% if @ticket.assets.exists? %> 20 |

Attached Files

21 |
22 | <% @ticket.assets.each do |asset| %> 23 |

24 | <%= link_to File.basename(asset.asset_file_name), asset.asset.url %> 25 |

26 |

27 | <%= number_to_human_size(asset.asset.size) %> 28 |

29 | <% end %> 30 |
31 | <% end %> 32 |
33 | 34 |
35 | <%= toggle_watching_button %> 36 |

Watchers

37 | 42 |
43 | 44 |

Comments

45 |
46 | <% if @ticket.comments.exists? %> 47 | <%= render @ticket.comments.select(&:persisted?) %> 48 | <% else %> 49 | There are no comments for this ticket. 50 | <% end %> 51 |
52 | 53 | <%= render "comments/form" %> -------------------------------------------------------------------------------- /app/views/users/confirmation.html.erb: -------------------------------------------------------------------------------- 1 |

You have signed up successfully.

2 | 3 | Please confirm your account before signing in. -------------------------------------------------------------------------------- /bin/autospec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'autospec' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('rspec-core', 'autospec') 17 | -------------------------------------------------------------------------------- /bin/cdiff: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'cdiff' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('term-ansicolor', 'cdiff') 17 | -------------------------------------------------------------------------------- /bin/cucumber: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'cucumber' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('cucumber', 'cucumber') 17 | -------------------------------------------------------------------------------- /bin/decolor: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'decolor' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('term-ansicolor', 'decolor') 17 | -------------------------------------------------------------------------------- /bin/edit_json.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'edit_json.rb' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('json', 'edit_json.rb') 17 | -------------------------------------------------------------------------------- /bin/erubis: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'erubis' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('erubis', 'erubis') 17 | -------------------------------------------------------------------------------- /bin/htmldiff: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'htmldiff' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('diff-lcs', 'htmldiff') 17 | -------------------------------------------------------------------------------- /bin/launchy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'launchy' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('launchy', 'launchy') 17 | -------------------------------------------------------------------------------- /bin/ldiff: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'ldiff' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('diff-lcs', 'ldiff') 17 | -------------------------------------------------------------------------------- /bin/nokogiri: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'nokogiri' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('nokogiri', 'nokogiri') 17 | -------------------------------------------------------------------------------- /bin/oauth: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'oauth' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('oauth', 'oauth') 17 | -------------------------------------------------------------------------------- /bin/prettify_json.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'prettify_json.rb' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('json', 'prettify_json.rb') 17 | -------------------------------------------------------------------------------- /bin/rackup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'rackup' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('rack', 'rackup') 17 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'rails' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('rails', 'rails') 17 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'rake' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('rake', 'rake') 17 | -------------------------------------------------------------------------------- /bin/rake2thor: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'rake2thor' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('thor', 'rake2thor') 17 | -------------------------------------------------------------------------------- /bin/rdiscount: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'rdiscount' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('rdiscount', 'rdiscount') 17 | -------------------------------------------------------------------------------- /bin/rspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'rspec' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('rspec-core', 'rspec') 17 | -------------------------------------------------------------------------------- /bin/run_celerity_server.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'run_celerity_server.rb' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('culerity', 'run_celerity_server.rb') 17 | -------------------------------------------------------------------------------- /bin/sass: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'sass' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('sass', 'sass') 17 | -------------------------------------------------------------------------------- /bin/sass-convert: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'sass-convert' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('sass', 'sass-convert') 17 | -------------------------------------------------------------------------------- /bin/thor: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'thor' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('thor', 'thor') 17 | -------------------------------------------------------------------------------- /bin/tilt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'tilt' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('tilt', 'tilt') 17 | -------------------------------------------------------------------------------- /bin/tt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'tt' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('treetop', 'tt') 17 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run EdgeTicketee::Application 5 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | # If you have a Gemfile, require the gems listed there, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(:default, Rails.env) if defined?(Bundler) 8 | 9 | module EdgeTicketee 10 | class Application < Rails::Application 11 | # Settings in config/environments/* take precedence over those specified here. 12 | # Application configuration should go into files in config/initializers 13 | # -- all .rb files in that directory are automatically loaded. 14 | 15 | # Custom directories with classes and modules you want to be autoloadable. 16 | # config.autoload_paths += %W(#{config.root}/extras) 17 | 18 | # Only load the plugins named here, in the order given (default is alphabetical). 19 | # :all can be used as a placeholder for all plugins not explicitly named. 20 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 21 | 22 | # Activate observers that should always be running. 23 | config.active_record.observers = :comment_observer 24 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer 25 | 26 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 27 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 28 | # config.time_zone = 'Central Time (US & Canada)' 29 | 30 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 31 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 32 | # config.i18n.default_locale = :de 33 | 34 | # JavaScript files you want as :defaults (application.js is always included). 35 | # config.action_view.javascript_expansions[:defaults] = %w(prototype effects dragdrop controls rails) 36 | 37 | # config.generators.test_framework = false 38 | 39 | # Configure the default encoding used in templates for Ruby 1.9. 40 | config.encoding = "utf-8" 41 | 42 | # Configure sensitive parameters which will be filtered from the log file. 43 | config.filter_parameters += [:password] 44 | 45 | # Enable IdentityMap for Active Record, to disable set to false or remove the line below. 46 | config.active_record.identity_map = true 47 | 48 | # Enable the asset pipeline 49 | config.assets.enabled = true 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | # Set up gems listed in the Gemfile. 4 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 5 | 6 | require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) 7 | -------------------------------------------------------------------------------- /config/cucumber.yml: -------------------------------------------------------------------------------- 1 | <% 2 | rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : "" 3 | rerun_opts = rerun.to_s.strip.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}" 4 | std_opts = "-s --format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} --strict --tags ~@wip" 5 | %> 6 | default: <%= std_opts %> features 7 | wip: --tags @wip:3 --wip features 8 | rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags ~@wip 9 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | development: 4 | adapter: sqlite3 5 | database: db/development.sqlite3 6 | pool: 5 7 | timeout: 5000 8 | 9 | # Warning: The database defined as "test" will be erased and 10 | # re-generated from your development database when you run "rake". 11 | # Do not set this db to the same as development or production. 12 | test: &test 13 | adapter: sqlite3 14 | database: db/test.sqlite3 15 | pool: 5 16 | timeout: 5000 17 | 18 | production: 19 | adapter: sqlite3 20 | database: db/development.sqlite3 21 | pool: 5 22 | timeout: 5000 23 | 24 | cucumber: 25 | <<: *test -------------------------------------------------------------------------------- /config/deploy.rb: -------------------------------------------------------------------------------- 1 | $:.unshift(File.expand_path('./lib', ENV['rvm_path'])) 2 | require 'rvm/capistrano' 3 | require 'bundler/capistrano' 4 | 5 | set :application, "ticketee" 6 | set :repository, "git://github.com/rails3book/ticketee.git" 7 | 8 | set :branch, "production" 9 | 10 | set :scm, :git 11 | 12 | role :web, "96.126.113.128" 13 | role :app, "96.126.113.128" 14 | role :db, "96.126.113.128", :primary => true 15 | 16 | set :user, "ticketeeapp.com" 17 | set :deploy_to, "/home/ticketeeapp.com/apps/#{application}" 18 | 19 | set :use_sudo, false 20 | 21 | set :keep_releases, 5 22 | # if you're still using the script/reaper helper you will need 23 | # these http://github.com/rails/irs_process_scripts 24 | 25 | namespace :deploy do 26 | task :start do ; end 27 | task :stop do ; end 28 | task :restart, :roles => :app, :except => { :no_release => true } do 29 | run "#{try_sudo} touch #{File.join(current_path,'tmp','restart.txt')}" 30 | end 31 | end 32 | 33 | task :symlink_database_yml do 34 | run "rm #{release_path}/config/database.yml" 35 | run "ln -sfn #{shared_path}/config/database.yml 36 | #{release_path}/config/database.yml" 37 | end 38 | after "bundle:install", "symlink_database_yml" -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the rails application 5 | EdgeTicketee::Application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | EdgeTicketee::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Log error messages when you accidentally call methods on nil. 10 | config.whiny_nils = true 11 | 12 | # Show full error reports and disable caching 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = true 15 | 16 | # Don't care if the mailer can't send 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger 20 | config.active_support.deprecation = :log 21 | 22 | # Only use best-standards-support built into browsers 23 | config.action_dispatch.best_standards_support = :builtin 24 | 25 | config.action_mailer.default_url_options = { :host => 'localhost:3000' } 26 | end 27 | 28 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | EdgeTicketee::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # Code is not reloaded between requests 5 | config.cache_classes = true 6 | 7 | # Full error reports are disabled and caching is turned on 8 | config.consider_all_requests_local = false 9 | config.action_controller.perform_caching = true 10 | 11 | # Disable Rails's static asset server (Apache or nginx will already do this) 12 | config.serve_static_assets = false 13 | 14 | # Specifies the header that your server uses for sending files 15 | # (comment out if your front-end server doesn't support this) 16 | config.action_dispatch.x_sendfile_header = "X-Sendfile" # Use 'X-Accel-Redirect' for nginx 17 | 18 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 19 | # config.force_ssl = true 20 | 21 | # See everything in the log (default is :info) 22 | # config.log_level = :debug 23 | 24 | # Use a different logger for distributed setups 25 | # config.logger = SyslogLogger.new 26 | 27 | # Use a different cache store in production 28 | # config.cache_store = :mem_cache_store 29 | 30 | # Enable serving of images, stylesheets, and javascripts from an asset server 31 | # config.action_controller.asset_host = "http://assets.example.com" 32 | 33 | # Disable delivery errors, bad email addresses will be ignored 34 | # config.action_mailer.raise_delivery_errors = false 35 | 36 | # Enable threaded mode 37 | # config.threadsafe! 38 | 39 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 40 | # the I18n.default_locale when a translation can not be found) 41 | config.i18n.fallbacks = true 42 | 43 | # Send deprecation notices to registered listeners 44 | config.active_support.deprecation = :notify 45 | end 46 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | EdgeTicketee::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Log error messages when you accidentally call methods on nil. 11 | config.whiny_nils = true 12 | 13 | # Show full error reports and disable caching 14 | config.consider_all_requests_local = true 15 | config.action_controller.perform_caching = false 16 | 17 | # Raise exceptions instead of rendering exception templates 18 | config.action_dispatch.show_exceptions = false 19 | 20 | # Disable request forgery protection in test environment 21 | config.action_controller.allow_forgery_protection = false 22 | 23 | # Tell Action Mailer not to deliver emails to the real world. 24 | # The :test delivery method accumulates sent emails in the 25 | # ActionMailer::Base.deliveries array. 26 | config.action_mailer.delivery_method = :test 27 | 28 | # Use SQL instead of Active Record's schema dumper when creating the test database. 29 | # This is necessary if your schema can't be completely dumped by the schema dumper, 30 | # like if you have constraints or database-specific column types 31 | # config.active_record.schema_format = :sql 32 | 33 | # Print deprecation notices to the stderr 34 | config.active_support.deprecation = :stderr 35 | 36 | config.action_mailer.default_url_options = { :host => 'localhost:3000' } 37 | end 38 | 39 | OmniAuth.config.test_mode = true -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/forem.rb: -------------------------------------------------------------------------------- 1 | Forem::Engine.user_class = User -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | -------------------------------------------------------------------------------- /config/initializers/mail.rb: -------------------------------------------------------------------------------- 1 | ActionMailer::Base.smtp_settings = { 2 | :user_name => "ticketee@gmail.com", 3 | :password => "2uo9icuiwX", 4 | :address => "smtp.gmail.com", 5 | :port => 587, 6 | :tls => true 7 | } -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | EdgeTicketee::Application.config.secret_token = 'ab5afb8566f0d9ea643da2b7a74cad4d508707e161deb95b599509bce621dc229332ee465eb81f79a7ac2b43b3fd0cfbcfa587a20ebb86d2a49437c233f99b7b' 8 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | EdgeTicketee::Application.config.session_store :cookie_store, key: '_edge-ticketee_session' 4 | 5 | # Use the database for sessions instead of the cookie-based default, 6 | # which shouldn't be used to store highly confidential information 7 | # (create the session table with "rails generate session_migration") 8 | # EdgeTicketee::Application.config.session_store :active_record_store 9 | -------------------------------------------------------------------------------- /config/locales/devise.en.yml: -------------------------------------------------------------------------------- 1 | # Additional translations at http://github.com/plataformatec/devise/wiki/I18n 2 | 3 | en: 4 | errors: 5 | messages: 6 | not_found: "not found" 7 | already_confirmed: "was already confirmed, please try signing in" 8 | not_locked: "was not locked" 9 | not_saved: 10 | one: "1 error prohibited this %{resource} from being saved:" 11 | other: "%{count} errors prohibited this %{resource} from being saved:" 12 | 13 | devise: 14 | failure: 15 | unauthenticated: 'You need to sign in or sign up before continuing.' 16 | unconfirmed: 'You have to confirm your account before continuing.' 17 | locked: 'Your account is locked.' 18 | invalid: 'Invalid email or password.' 19 | invalid_token: 'Invalid authentication token.' 20 | timeout: 'Your session expired, please sign in again to continue.' 21 | inactive: 'Your account was not activated yet.' 22 | sessions: 23 | signed_in: 'Signed in successfully.' 24 | signed_out: 'Signed out successfully.' 25 | passwords: 26 | send_instructions: 'You will receive an email with instructions about how to reset your password in a few minutes.' 27 | updated: 'Your password was changed successfully. You are now signed in.' 28 | confirmations: 29 | send_instructions: 'You will receive an email with instructions about how to confirm your account in a few minutes.' 30 | confirmed: 'Your account was successfully confirmed. You are now signed in.' 31 | registrations: 32 | signed_up: 'Welcome! You have signed up successfully.' 33 | inactive_signed_up: 'You have signed up successfully. However, we could not sign you in because your account is %{reason}.' 34 | updated: 'You updated your account successfully.' 35 | destroyed: 'Bye! Your account was successfully cancelled. We hope to see you again soon.' 36 | unlocks: 37 | send_instructions: 'You will receive an email with instructions about how to unlock your account in a few minutes.' 38 | unlocked: 'Your account was successfully unlocked. You are now signed in.' 39 | omniauth_callbacks: 40 | success: 'Successfully authorized from %{kind} account.' 41 | failure: 'Could not authorize you from %{kind} because "%{reason}".' 42 | mailer: 43 | confirmation_instructions: 44 | subject: 'Confirmation instructions' 45 | reset_password_instructions: 46 | subject: 'Reset password instructions' 47 | unlock_instructions: 48 | subject: 'Unlock Instructions' 49 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | hello: "Hello world" 6 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | require 'heartbeat/application' 2 | 3 | EdgeTicketee::Application.routes.draw do 4 | mount Heartbeat::Application, :at => "/heartbeat" 5 | mount Forem::Engine, :at => "/forem" 6 | 7 | namespace :api do 8 | namespace :v1 do 9 | resources :projects do 10 | resources :tickets 11 | end 12 | end 13 | 14 | namespace :v2 do 15 | resources :projects do 16 | resources :tickets 17 | end 18 | end 19 | 20 | namespace :v3 do 21 | namespace :json do 22 | mount Api::V3::JSON::Tickets, 23 | :at => "/projects/:project_id/tickets" 24 | end 25 | end 26 | end 27 | 28 | root :to => "projects#index" 29 | 30 | resources :projects do 31 | resources :tickets do 32 | collection do 33 | get :search 34 | end 35 | 36 | member do 37 | post :watch 38 | end 39 | end 40 | end 41 | 42 | resources :tickets do 43 | resources :comments 44 | resources :tags do 45 | member do 46 | delete :remove 47 | end 48 | end 49 | end 50 | 51 | resources :files 52 | 53 | namespace :admin do 54 | root :to => "base#index" 55 | resources :users do 56 | resources :permissions 57 | end 58 | resources :states do 59 | member do 60 | get :make_default 61 | end 62 | end 63 | end 64 | 65 | devise_for :users, :controllers => { 66 | :registrations => "registrations", 67 | :omniauth_callbacks => "users/omniauth_callbacks" 68 | } 69 | 70 | get '/awaiting_confirmation', 71 | :to => "users#confirmation", 72 | :as => 'confirm_user' 73 | 74 | put '/admin/users/:user_id/permissions', 75 | :to => 'admin/permissions#update', 76 | :as => :update_user_permissions 77 | end 78 | -------------------------------------------------------------------------------- /db/migrate/20110429131324_create_projects.rb: -------------------------------------------------------------------------------- 1 | class CreateProjects < ActiveRecord::Migration 2 | def change 3 | create_table :projects do |t| 4 | t.string :name 5 | 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20110430105843_create_tickets.rb: -------------------------------------------------------------------------------- 1 | class CreateTickets < ActiveRecord::Migration 2 | def change 3 | create_table :tickets do |t| 4 | t.string :title 5 | t.text :description 6 | t.references :project 7 | 8 | t.timestamps 9 | end 10 | add_index :tickets, :project_id 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20110501000412_devise_create_users.rb: -------------------------------------------------------------------------------- 1 | class DeviseCreateUsers < ActiveRecord::Migration 2 | def self.up 3 | create_table(:users) do |t| 4 | t.database_authenticatable :null => false 5 | t.recoverable 6 | t.rememberable 7 | t.trackable 8 | 9 | # t.encryptable 10 | # t.confirmable 11 | # t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both 12 | # t.token_authenticatable 13 | 14 | 15 | t.timestamps 16 | end 17 | 18 | add_index :users, :email, :unique => true 19 | add_index :users, :reset_password_token, :unique => true 20 | # add_index :users, :confirmation_token, :unique => true 21 | # add_index :users, :unlock_token, :unique => true 22 | # add_index :users, :authentication_token, :unique => true 23 | end 24 | 25 | def self.down 26 | drop_table :users 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /db/migrate/20110501013524_add_confirmable_fields_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddConfirmableFieldsToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :confirmation_token, :string 4 | add_column :users, :confirmed_at, :datetime 5 | add_column :users, :confirmation_sent_at, :datetime 6 | end 7 | end -------------------------------------------------------------------------------- /db/migrate/20110501050244_add_user_id_to_tickets.rb: -------------------------------------------------------------------------------- 1 | class AddUserIdToTickets < ActiveRecord::Migration 2 | def change 3 | add_column :tickets, :user_id, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20110501071526_add_admin_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddAdminToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :admin, :boolean, :default => false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20110502115151_create_permissions.rb: -------------------------------------------------------------------------------- 1 | class CreatePermissions < ActiveRecord::Migration 2 | def change 3 | create_table :permissions do |t| 4 | t.integer :user_id 5 | t.integer :thing_id 6 | t.string :thing_type 7 | t.string :action 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20110507060655_add_attachment_asset_to_ticket.rb: -------------------------------------------------------------------------------- 1 | class AddAttachmentAssetToTicket < ActiveRecord::Migration 2 | def self.up 3 | add_column :tickets, :asset_file_name, :string 4 | add_column :tickets, :asset_content_type, :string 5 | add_column :tickets, :asset_file_size, :integer 6 | add_column :tickets, :asset_updated_at, :datetime 7 | end 8 | 9 | def self.down 10 | remove_column :tickets, :asset_file_name 11 | remove_column :tickets, :asset_content_type 12 | remove_column :tickets, :asset_file_size 13 | remove_column :tickets, :asset_updated_at 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /db/migrate/20110507074451_create_assets.rb: -------------------------------------------------------------------------------- 1 | class CreateAssets < ActiveRecord::Migration 2 | def up 3 | create_table :assets do |t| 4 | t.string :asset_file_name 5 | t.integer :asset_file_size 6 | t.string :asset_content_type 7 | t.datetime :asset_updated_at 8 | t.integer :ticket_id 9 | 10 | t.timestamps 11 | end 12 | 13 | [:asset_file_name, 14 | :asset_file_size, 15 | :asset_content_type, 16 | :asset_updated_at].each do |column| 17 | remove_column :tickets, column 18 | end 19 | end 20 | 21 | def down 22 | drop_table :assets 23 | end 24 | end -------------------------------------------------------------------------------- /db/migrate/20110508082318_create_comments.rb: -------------------------------------------------------------------------------- 1 | class CreateComments < ActiveRecord::Migration 2 | def change 3 | create_table :comments do |t| 4 | t.text :text 5 | t.integer :ticket_id 6 | t.integer :user_id 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20110508102640_create_states.rb: -------------------------------------------------------------------------------- 1 | class CreateStates < ActiveRecord::Migration 2 | def up 3 | create_table :states do |t| 4 | t.string :name 5 | t.string :color 6 | t.string :background 7 | end 8 | 9 | add_column :tickets, :state_id, :integer 10 | add_index :tickets, :state_id 11 | 12 | add_column :comments, :state_id, :integer 13 | end 14 | 15 | def down 16 | drop_table :states 17 | remove_column :tickets, :state_id 18 | remove_column :comments, :state_id 19 | end 20 | end -------------------------------------------------------------------------------- /db/migrate/20110508220912_add_previous_state_id_to_comments.rb: -------------------------------------------------------------------------------- 1 | class AddPreviousStateIdToComments < ActiveRecord::Migration 2 | def change 3 | add_column :comments, :previous_state_id, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20110522222106_add_default_to_states.rb: -------------------------------------------------------------------------------- 1 | class AddDefaultToStates < ActiveRecord::Migration 2 | def change 3 | add_column :states, :default, :boolean, :default => false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20110524094739_create_tags.rb: -------------------------------------------------------------------------------- 1 | class CreateTags < ActiveRecord::Migration 2 | def change 3 | create_table :tags do |t| 4 | t.string :name 5 | end 6 | 7 | create_table :tags_tickets, :id => false do |t| 8 | t.integer :tag_id, :ticket_id 9 | end 10 | end 11 | 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20110528003127_create_ticket_watchers_table.rb: -------------------------------------------------------------------------------- 1 | class CreateTicketWatchersTable < ActiveRecord::Migration 2 | def change 3 | create_table :ticket_watchers, :id => false do |t| 4 | t.integer :user_id, :ticket_id 5 | end 6 | end 7 | end -------------------------------------------------------------------------------- /db/migrate/20110528233255_add_authentication_token_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddAuthenticationTokenToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :authentication_token, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20110529081807_add_request_count_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddRequestCountToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :request_count, :integer, :default => 0 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20110606015904_add_twitter_fields_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddTwitterFieldsToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :twitter_id, :string 4 | add_column :users, :twitter_screen_name, :string 5 | add_column :users, :twitter_display_name, :string 6 | end 7 | end -------------------------------------------------------------------------------- /db/migrate/20110608062135_add_github_fields_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddGithubFieldsToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :github_id, :integer 4 | add_column :users, :github_user_name, :string 5 | add_column :users, :github_display_name, :string 6 | end 7 | end -------------------------------------------------------------------------------- /db/migrate/20110609123052_create_forem_topics.rb: -------------------------------------------------------------------------------- 1 | class CreateForemTopics < ActiveRecord::Migration 2 | def change 3 | create_table :forem_topics do |t| 4 | t.text :subject 5 | t.integer :user_id 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20110609123053_create_forem_posts.rb: -------------------------------------------------------------------------------- 1 | class CreateForemPosts < ActiveRecord::Migration 2 | def change 3 | create_table :forem_posts do |t| 4 | t.integer :topic_id 5 | t.text :text 6 | t.integer :user_id 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20110609123054_add_posts_count_to_forem_topics.rb: -------------------------------------------------------------------------------- 1 | class AddPostsCountToForemTopics < ActiveRecord::Migration 2 | def change 3 | add_column :forem_topics, :posts_count, :integer, :default => 0 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | admin_user = User.create(:email => "admin@ticketee.com", 2 | :password => "password") 3 | admin_user.admin = true 4 | admin_user.confirm! 5 | 6 | Project.create(:name => "Ticketee Beta") 7 | 8 | State.create(:name => "New", 9 | :background => "#85FF00", 10 | :color => "white", 11 | :default => true) 12 | 13 | State.create(:name => "Open", 14 | :background => "#00CFFD", 15 | :color => "white") 16 | 17 | State.create(:name => "Closed", 18 | :background => "black", 19 | :color => "white") -------------------------------------------------------------------------------- /doc/README_FOR_APP: -------------------------------------------------------------------------------- 1 | Use this README file to introduce your application and point to useful places in the API for learning more. 2 | Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries. 3 | -------------------------------------------------------------------------------- /factories/comment_factory.rb: -------------------------------------------------------------------------------- 1 | Factory.define :comment do |comment| 2 | comment.text "A plain old boring comment." 3 | comment.ticket { |t| t.association(:ticket) } 4 | comment.user { |u| u.association(:user) } 5 | end -------------------------------------------------------------------------------- /factories/project_factory.rb: -------------------------------------------------------------------------------- 1 | Factory.define :project do |project| 2 | project.name 'Ticketee' 3 | end -------------------------------------------------------------------------------- /factories/ticket_factory.rb: -------------------------------------------------------------------------------- 1 | Factory.define :ticket do |ticket| 2 | ticket.title "A ticket" 3 | ticket.description "A ticket, nothing more" 4 | ticket.user { |u| u.association(:user) } 5 | ticket.project { |p| p.association(:project) } 6 | end -------------------------------------------------------------------------------- /factories/user_factory.rb: -------------------------------------------------------------------------------- 1 | Factory.define :user do |user| 2 | user.sequence(:email) { |n| "user#{n}@ticketee.com" } 3 | user.password "password" 4 | user.password_confirmation "password" 5 | end -------------------------------------------------------------------------------- /features/assigning_permissions.feature: -------------------------------------------------------------------------------- 1 | Feature: Assigning permissions 2 | In order to set up users with the correct permissions 3 | As an admin 4 | I want to check all the boxes 5 | 6 | Background: 7 | Given there are the following users: 8 | | email | password | admin | 9 | | admin@ticketee.com | password | true | 10 | And I am signed in as them 11 | 12 | And there are the following users: 13 | | email | password | 14 | | user@ticketee.com | password | 15 | And there is a project called "TextMate 2" 16 | And "user@ticketee.com" has created a ticket for this project: 17 | | title | description | 18 | | Shiny! | Eye-blindingly so | 19 | 20 | Given there is a state called "Open" 21 | 22 | When I follow "Admin" 23 | And I follow "Users" 24 | And I follow "user@ticketee.com" 25 | And I follow "Permissions" 26 | 27 | Scenario: Viewing a project 28 | When I check "View" for "TextMate 2" 29 | And I press "Update" 30 | And I follow "Sign out" 31 | 32 | Given I am signed in as "user@ticketee.com" 33 | Then I should see "TextMate 2" 34 | 35 | Scenario: Creating tickets for a project 36 | When I check "View" for "TextMate 2" 37 | When I check "Create tickets" for "TextMate 2" 38 | And I press "Update" 39 | And I follow "Sign out" 40 | 41 | Given I am signed in as "user@ticketee.com" 42 | When I follow "TextMate 2" 43 | And I follow "New Ticket" 44 | And I fill in "Title" with "Shiny!" 45 | And I fill in "Description" with "Make it so!" 46 | And I press "Create" 47 | Then I should see "Ticket has been created." 48 | 49 | Scenario: Updating a ticket for a project 50 | When I check "View" for "TextMate 2" 51 | And I check "Edit tickets" for "TextMate 2" 52 | And I press "Update" 53 | And I follow "Sign out" 54 | 55 | Given I am signed in as "user@ticketee.com" 56 | When I follow "TextMate 2" 57 | And I follow "Shiny!" 58 | And I follow "Edit" 59 | And I fill in "Title" with "Really shiny!" 60 | And I press "Update Ticket" 61 | Then I should see "Ticket has been updated" 62 | 63 | Scenario: Deleting a ticket for a project 64 | When I check "View" for "TextMate 2" 65 | And I check "Delete tickets" for "TextMate 2" 66 | And I press "Update" 67 | And I follow "Sign out" 68 | 69 | Given I am signed in as "user@ticketee.com" 70 | When I follow "TextMate 2" 71 | And I follow "Shiny!" 72 | And I follow "Delete" 73 | Then I should see "Ticket has been deleted." 74 | 75 | Scenario: Changing states for a ticket 76 | When I check "View" for "TextMate 2" 77 | And I check "Change States" for "TextMate 2" 78 | And I press "Update" 79 | And I follow "Sign out" 80 | 81 | Given I am signed in as "user@ticketee.com" 82 | When I follow "TextMate 2" 83 | And I follow "Shiny!" 84 | When I fill in "Text" with "Opening this ticket." 85 | And I select "Open" from "State" 86 | And I press "Create Comment" 87 | Then I should see "Comment has been created." 88 | And I should see "Open" within "#ticket .state" -------------------------------------------------------------------------------- /features/creating_comments.feature: -------------------------------------------------------------------------------- 1 | Feature: Creating comments 2 | In order to update a ticket's progress 3 | As a user 4 | I want to leave comments 5 | 6 | Background: 7 | Given there are the following users: 8 | | email | password | 9 | | user@ticketee.com | password | 10 | And I am signed in as them 11 | And there is a project called "Ticketee" 12 | And "user@ticketee.com" can view the "Ticketee" project 13 | And "user@ticketee.com" can tag the "Ticketee" project 14 | And "user@ticketee.com" has created a ticket for this project: 15 | | title | description | 16 | | Change a ticket's state | You should be able to create a comment | 17 | Given I am on the homepage 18 | And I follow "Ticketee" within "#projects" 19 | Given there is a state called "Open" 20 | 21 | Scenario: Creating a comment 22 | When I follow "Change a ticket's state" 23 | And I fill in "Text" with "Added a comment!" 24 | And I press "Create Comment" 25 | Then I should see "Comment has been created." 26 | Then I should see "Added a comment!" within "#comments" 27 | 28 | Scenario: Creating an invalid comment 29 | When I follow "Change a ticket's state" 30 | And I press "Create Comment" 31 | Then I should see "Comment has not been created." 32 | And I should see "Text can't be blank" 33 | 34 | Scenario: Changing a ticket's state 35 | Given "user@ticketee.com" can change states on the "Ticketee" project 36 | When I follow "Change a ticket's state" 37 | When I fill in "Text" with "This is a real issue" 38 | And I select "Open" from "State" 39 | And I press "Create Comment" 40 | Then I should see "Comment has been created." 41 | And I should see "Open" within "#ticket .state" 42 | Then I should see "State: Open" within "#comments" 43 | 44 | Scenario: A user without permission cannot change the state 45 | When I follow "Change a ticket's state" 46 | Then I should not see the "#comment_state_id" element 47 | 48 | Scenario: Adding a tag to a ticket 49 | When I follow "Change a ticket's state" 50 | Then I should not see "bug" within "#ticket #tags" 51 | And I fill in "Text" with "Adding the bug tag" 52 | And I fill in "Tags" with "bug" 53 | And I press "Create Comment" 54 | Then I should see "Comment has been created" 55 | Then I should see "bug" within "#ticket #tags" -------------------------------------------------------------------------------- /features/creating_projects.feature: -------------------------------------------------------------------------------- 1 | Feature: Creating projects 2 | In order to have projects to assign tickets to 3 | As a user 4 | I want to create them easily 5 | 6 | Background: 7 | Given there are the following users: 8 | | email | password | admin | 9 | | admin@ticketee.com | password | true | 10 | And I am signed in as them 11 | Given I am on the homepage 12 | When I follow "New Project" 13 | 14 | Scenario: Creating a project 15 | And I fill in "Name" with "TextMate 2" 16 | And I press "Create Project" 17 | Then I should see "Project has been created." 18 | And I should be on the project page for "TextMate 2" 19 | And I should see "TextMate 2 - Projects - Ticketee" 20 | 21 | Scenario: Creating a project without a name 22 | And I press "Create Project" 23 | Then I should see "Project has not been created." 24 | And I should see "Name can't be blank" -------------------------------------------------------------------------------- /features/creating_states.feature: -------------------------------------------------------------------------------- 1 | Feature: Creating states 2 | In order to be able to specify other states for tickets 3 | As an admin 4 | I want to add them to the application 5 | 6 | Background: 7 | Given there are the following users: 8 | | email | password | admin | 9 | | admin@ticketee.com | password | true | 10 | And I am signed in as them 11 | 12 | Scenario: Creating a state 13 | When I follow "Admin" 14 | And I follow "States" 15 | And I follow "New State" 16 | And I fill in "Name" with "Duplicate" 17 | And I press "Create State" 18 | Then I should see "State has been created." -------------------------------------------------------------------------------- /features/creating_tickets.feature: -------------------------------------------------------------------------------- 1 | Feature: Creating Tickets 2 | In order to create tickets for projects 3 | As a user 4 | I want to be able to select a project and do that 5 | 6 | Background: 7 | Given there is a project called "Internet Explorer" 8 | And there are the following users: 9 | | email | password | 10 | | user@ticketee.com | password | 11 | And "user@ticketee.com" can view the "Internet Explorer" project 12 | And "user@ticketee.com" can tag the "Internet Explorer" project 13 | And "user@ticketee.com" can create tickets in the "Internet Explorer" project 14 | And I am signed in as them 15 | And I am on the homepage 16 | When I follow "Internet Explorer" 17 | And I follow "New Ticket" 18 | 19 | Scenario: Creating a ticket 20 | When I fill in "Title" with "Non-standards compliance" 21 | And I fill in "Description" with "My pages are ugly!" 22 | And I press "Create Ticket" 23 | Then I should see "Ticket has been created." 24 | Then I should see "Created by user@ticketee.com" 25 | 26 | Scenario: Creating a ticket without valid attributes fails 27 | When I press "Create Ticket" 28 | Then I should see "Ticket has not been created." 29 | And I should see "Title can't be blank" 30 | And I should see "Description can't be blank" 31 | 32 | Scenario: Description must be longer than 10 characters 33 | When I fill in "Title" with "Non-standards compliance" 34 | And I fill in "Description" with "it sucks" 35 | And I press "Create Ticket" 36 | Then I should see "Ticket has not been created." 37 | And I should see "Description is too short" 38 | 39 | @javascript 40 | Scenario: Creating a ticket with an attachment 41 | When I fill in "Title" with "Add documentation for blink tag" 42 | And I fill in "Description" with "The blink tag has an undocumented speed attribute" 43 | And I attach the file "spec/fixtures/speed.txt" to "File #1" 44 | And I follow "Add another file" 45 | And I attach the file "spec/fixtures/spin.txt" to "File #2" 46 | And I press "Create Ticket" 47 | Then I should see "Ticket has been created." 48 | And I should see "speed.txt" within "#ticket .assets" 49 | And I should see "spin.txt" within "#ticket .assets" 50 | When I follow "speed.txt" 51 | 52 | Scenario: Creating a ticket with tags 53 | When I fill in "Title" with "Non-standards compliance" 54 | And I fill in "Description" with "My pages are ugly!" 55 | And I fill in "Tags" with "browser visual" 56 | And I press "Create Ticket" 57 | Then I should see "Ticket has been created." 58 | And I should see "browser" within "#ticket #tags" 59 | And I should see "visual" within "#ticket #tags" 60 | -------------------------------------------------------------------------------- /features/creating_users.feature: -------------------------------------------------------------------------------- 1 | Feature: Creating Users 2 | In order to add new users to the system 3 | As an admin 4 | I want to be able to add them through the backend 5 | 6 | Background: 7 | Given there are the following users: 8 | | email | password | admin | 9 | | admin@ticketee.com | password | true | 10 | And I am signed in as them 11 | Given I am on the homepage 12 | When I follow "Admin" 13 | And I follow "Users" 14 | When I follow "New User" 15 | 16 | Scenario: Creating a new user 17 | And I fill in "Email" with "newbie@ticketee.com" 18 | And I fill in "Password" with "password" 19 | And I press "Create User" 20 | Then I should see "User has been created." 21 | 22 | Scenario: Leaving email blank results in an error 23 | When I fill in "Email" with "" 24 | And I fill in "Password" with "password" 25 | And I press "Create User" 26 | Then I should see "User has not been created." 27 | And I should see "Email can't be blank" 28 | 29 | Scenario: Creating an admin user 30 | When I fill in "Email" with "newadmin@ticketee.com" 31 | And I fill in "Password" with "password" 32 | And I check "Is an admin?" 33 | And I press "Create User" 34 | Then I should see "User has been created" 35 | And I should see "newadmin@ticketee.com (Admin)" -------------------------------------------------------------------------------- /features/deleting_projects.feature: -------------------------------------------------------------------------------- 1 | Feature: Deleting projects 2 | In order to remove needless projects 3 | As a project manager 4 | I want to make them disappear 5 | 6 | Background: 7 | Given there are the following users: 8 | | email | password | admin | 9 | | admin@ticketee.com | password | true | 10 | And I am signed in as them 11 | 12 | Scenario: Deleting a project 13 | Given there is a project called "TextMate 2" 14 | And I am on the homepage 15 | When I follow "TextMate 2" 16 | And I follow "Delete Project" 17 | Then I should see "Project has been deleted." 18 | Then I should not see "TextMate 2" -------------------------------------------------------------------------------- /features/deleting_tags.feature: -------------------------------------------------------------------------------- 1 | Feature: Deleting tags 2 | In order to remove old tags 3 | As a user 4 | I want to click a button and make them go away 5 | 6 | Background: 7 | Given there are the following users: 8 | | email | password | 9 | | user@ticketee.com | password | 10 | And I am signed in as them 11 | And there is a project called "Ticketee" 12 | And "user@ticketee.com" can view the "Ticketee" project 13 | And "user@ticketee.com" can tag the "Ticketee" project 14 | And "user@ticketee.com" has created a ticket for this project: 15 | | title | description | tags | 16 | | A tag | Tagging a ticket! | this-tag-must-die | 17 | Given I am on the homepage 18 | When I follow "Ticketee" within "#projects" 19 | And I follow "A tag" 20 | 21 | @javascript 22 | Scenario: Deleting a tag 23 | When I follow "delete-this-tag-must-die" 24 | Then I should not see "this-tag-must-die" -------------------------------------------------------------------------------- /features/deleting_tickets.feature: -------------------------------------------------------------------------------- 1 | Feature: Deleting tickets 2 | In order to remove tickets 3 | As a user 4 | I want to press a button and make them disappear 5 | 6 | Background: 7 | Given there are the following users: 8 | | email | password | 9 | | user@ticketee.com | password | 10 | And I am signed in as them 11 | Given there is a project called "TextMate 2" 12 | And "user@ticketee.com" can view the "TextMate 2" project 13 | And "user@ticketee.com" can delete tickets in the "TextMate 2" project 14 | And "user@ticketee.com" has created a ticket for this project: 15 | | title | description | 16 | | Make it shiny! | Gradients! Starbursts! Oh my! | 17 | Given I am on the homepage 18 | When I follow "TextMate 2" 19 | And I follow "Make it shiny!" 20 | 21 | Scenario: Deleting a ticket 22 | When I follow "Delete" 23 | Then I should see "Ticket has been deleted." 24 | And I should be on the project page for "TextMate 2" -------------------------------------------------------------------------------- /features/deleting_users.feature: -------------------------------------------------------------------------------- 1 | Feature: Deleting users 2 | In order to remove users 3 | As an admin 4 | I want to click a button and delete them 5 | 6 | Background: 7 | Given there are the following users: 8 | | email | password | admin | 9 | | admin@ticketee.com | password | true | 10 | | user@ticketee.com | password | false | 11 | 12 | And I am signed in as "admin@ticketee.com" 13 | Given I am on the homepage 14 | When I follow "Admin" 15 | And I follow "Users" 16 | 17 | Scenario: Deleting a user 18 | And I follow "user@ticketee.com" 19 | When I follow "Delete User" 20 | Then I should see "User has been deleted" 21 | 22 | Scenario: A user cannot delete themselves 23 | When I follow "admin@ticketee.com" 24 | And I follow "Delete User" 25 | Then I should see "You cannot delete yourself!" -------------------------------------------------------------------------------- /features/editing_projects.feature: -------------------------------------------------------------------------------- 1 | Feature: Editing Projects 2 | In order to update project information 3 | As a user 4 | I want to be able to do that through an interface 5 | 6 | Background: 7 | Given there are the following users: 8 | | email | password | admin | 9 | | admin@ticketee.com | password | true | 10 | And I am signed in as them 11 | Given there is a project called "TextMate 2" 12 | And I am on the homepage 13 | When I follow "TextMate 2" 14 | And I follow "Edit Project" 15 | 16 | Scenario: Updating a project 17 | And I fill in "Name" with "TextMate 2 beta" 18 | And I press "Update Project" 19 | Then I should see "Project has been updated." 20 | Then I should be on the project page for "TextMate 2 beta" 21 | 22 | Scenario: Updating a project with invalid attributes is bad 23 | And I fill in "Name" with "" 24 | And I press "Update Project" 25 | Then I should see "Project has not been updated." -------------------------------------------------------------------------------- /features/editing_tickets.feature: -------------------------------------------------------------------------------- 1 | Feature: Editing tickets 2 | In order to alter ticket information 3 | As a user 4 | I want a form to edit the tickets 5 | 6 | Background: 7 | Given there are the following users: 8 | | email | password | 9 | | user@ticketee.com | password | 10 | And I am signed in as them 11 | Given there is a project called "TextMate 2" 12 | And "user@ticketee.com" can view the "TextMate 2" project 13 | And "user@ticketee.com" can edit tickets in the "TextMate 2" project 14 | And "user@ticketee.com" has created a ticket for this project: 15 | | title | description | 16 | | Make it shiny! | Gradients! Starbursts! Oh my! | 17 | Given I am on the homepage 18 | When I follow "TextMate 2" 19 | And I follow "Make it shiny!" 20 | When I follow "Edit Ticket" 21 | 22 | Scenario: Updating a ticket 23 | When I fill in "Title" with "Make it really shiny!" 24 | And I press "Update Ticket" 25 | Then I should see "Ticket has been updated." 26 | And I should see "Make it really shiny!" within "#ticket h2" 27 | But I should not see "Make it shiny!" 28 | 29 | Scenario: Updating a ticket with invalid information 30 | When I fill in "Title" with "" 31 | And I press "Update Ticket" 32 | Then I should see "Ticket has not been updated." -------------------------------------------------------------------------------- /features/editing_users.feature: -------------------------------------------------------------------------------- 1 | Feature: Editing a user 2 | In order to change a user's details 3 | As an admin 4 | I want to be able to modify them through the backend 5 | 6 | Background: 7 | Given there are the following users: 8 | | email | password | admin | 9 | | admin@ticketee.com | password | true | 10 | And I am signed in as them 11 | 12 | Given there are the following users: 13 | | email | password | 14 | | user@ticketee.com | password | 15 | Given I am on the homepage 16 | When I follow "Admin" 17 | And I follow "Users" 18 | And I follow "user@ticketee.com" 19 | And I follow "Edit User" 20 | 21 | Scenario: Updating a user's details 22 | When I fill in "Email" with "newguy@ticketee.com" 23 | And I press "Update User" 24 | Then I should see "User has been updated." 25 | And I should see "newguy@ticketee.com" 26 | And I should not see "user@ticketee.com" 27 | 28 | Scenario: Toggling a user's admin ability 29 | When I check "Is an admin?" 30 | And I press "Update User" 31 | Then I should see "User has been updated." 32 | And I should see "user@ticketee.com (Admin)" 33 | 34 | Scenario: Updating with an invalid email fails 35 | When I fill in "Email" with "fakefakefake" 36 | And I press "Update User" 37 | Then I should see "User has not been updated." 38 | And I should see "Email is invalid" -------------------------------------------------------------------------------- /features/github_auth.feature: -------------------------------------------------------------------------------- 1 | Feature: GitHub auth 2 | In order to sign in using GitHub 3 | As a GitHub user 4 | I want to click an icon and be signed in 5 | 6 | Background: 7 | Given I have mocked a successful GitHub response 8 | 9 | Scenario: Signing in with GitHub 10 | Given I am on the homepage 11 | And I follow "sign_in_with_github" 12 | Then I should see "Signed in with Github successfully." 13 | Then I should see "Signed in as A GitHubber" -------------------------------------------------------------------------------- /features/gmail.feature: -------------------------------------------------------------------------------- 1 | Feature: Gmail 2 | In order to send real world emails 3 | As the application 4 | I want to ensure my configuration is correct 5 | 6 | Background: 7 | Given there are the following users: 8 | | email | password | 9 | | alice@ticketee.com | password | 10 | | ticketee@gmail.com | 2uo9icuiwX | 11 | 12 | And Action Mailer delivers via SMTP 13 | Given there is a project called "TextMate 2" 14 | And "alice@ticketee.com" can view the "TextMate 2" project 15 | And "ticketee@gmail.com" can view the "TextMate 2" project 16 | And "ticketee@gmail.com" has created a ticket for this project: 17 | | title | description | 18 | | Release date | TBA very shortly. | 19 | 20 | Scenario: Receiving a real-world email 21 | Given I am signed in as "alice@ticketee.com" 22 | Given I am on the homepage 23 | When I follow "TextMate 2" 24 | And I follow "Release date" 25 | And I fill in "Text" with "Posting a comment!" 26 | And I press "Create Comment" 27 | Then I should see "Comment has been created." 28 | 29 | When I log into gmail with: 30 | | username | password | 31 | | ticketee@gmail.com | 2uo9icuiwX | 32 | Then there should be an email from Ticketee in my inbox -------------------------------------------------------------------------------- /features/hidden_links.feature: -------------------------------------------------------------------------------- 1 | Feature: Hidden Links 2 | In order to clean up the user experience 3 | As the system 4 | I want to hide links from users who can't act on them 5 | 6 | Background: 7 | Given there are the following users: 8 | | email | password | admin | 9 | | user@ticketee.com | password | false | 10 | | admin@ticketee.com | password | true | 11 | And there is a project called "TextMate 2" 12 | And "user@ticketee.com" has created a ticket for this project: 13 | | title | description | 14 | | Shiny! | My eyes! My eyes! | 15 | And "user@ticketee.com" can view the "TextMate 2" project 16 | 17 | Scenario: New project link is hidden for non-signed-in users 18 | Given I am on the homepage 19 | Then I should not see the "New Project" link 20 | 21 | Scenario: New project link is hidden for signed-in users 22 | Given I am signed in as "user@ticketee.com" 23 | Then I should not see the "New Project" link 24 | 25 | Scenario: New project link is shown to admins 26 | Given I am signed in as "admin@ticketee.com" 27 | Then I should see the "New Project" link 28 | 29 | Scenario: Edit project link is hidden for signed-in users 30 | Given I am signed in as "user@ticketee.com" 31 | When I follow "TextMate 2" 32 | Then I should not see the "Edit Project" link 33 | 34 | Scenario: Edit project link is shown to admins 35 | Given I am signed in as "admin@ticketee.com" 36 | When I follow "TextMate 2" 37 | Then I should see the "Edit Project" link 38 | 39 | Scenario: Delete project link is hidden for signed-in users 40 | Given I am signed in as "user@ticketee.com" 41 | When I follow "TextMate 2" 42 | Then I should not see the "Delete Project" link 43 | 44 | Scenario: Delete project link is shown to admins 45 | Given I am signed in as "admin@ticketee.com" 46 | When I follow "TextMate 2" 47 | Then I should see the "Delete Project" link 48 | 49 | Scenario: New ticket link is shown to a user with permission 50 | Given "user@ticketee.com" can view the "TextMate 2" project 51 | And "user@ticketee.com" can create tickets on the "TextMate 2" project 52 | And I am signed in as "user@ticketee.com" 53 | When I follow "TextMate 2" 54 | Then I should see "New Ticket" 55 | 56 | Scenario: New ticket link is hidden from a user without permission 57 | Given "user@ticketee.com" can view the "TextMate 2" project 58 | And I am signed in as "user@ticketee.com" 59 | When I follow "TextMate 2" 60 | Then I should not see the "New Ticket" link 61 | 62 | Scenario: New ticket link is shown to admins 63 | Given I am signed in as "admin@ticketee.com" 64 | When I follow "TextMate 2" 65 | Then I should see the "New Ticket" link 66 | 67 | Scenario: Edit ticket link is shown to a user with permission 68 | Given "user@ticketee.com" can view the "TextMate 2" project 69 | And "user@ticketee.com" can edit tickets on the "TextMate 2" project 70 | And I am signed in as "user@ticketee.com" 71 | When I follow "TextMate 2" 72 | And I follow "Shiny!" 73 | Then I should see the "Edit" link 74 | 75 | Scenario: Edit ticket link is hidden from a user without permission 76 | Given "user@ticketee.com" can view the "TextMate 2" project 77 | And I am signed in as "user@ticketee.com" 78 | When I follow "TextMate 2" 79 | And I follow "Shiny!" 80 | Then I should not see the "Edit" link 81 | 82 | Scenario: Edit ticket link is shown to admins 83 | Given I am signed in as "admin@ticketee.com" 84 | When I follow "TextMate 2" 85 | And I follow "Shiny!" 86 | Then I should see the "Edit" link 87 | 88 | Scenario: Delete ticket link is shown to a user with permission 89 | Given "user@ticketee.com" can view the "TextMate 2" project 90 | And "user@ticketee.com" can delete tickets in the "TextMate 2" project 91 | And I am signed in as "user@ticketee.com" 92 | When I follow "TextMate 2" 93 | And I follow "Shiny!" 94 | Then I should see "Delete" 95 | 96 | Scenario: Delete ticket link is hidden from a user without permission 97 | Given "user@ticketee.com" can view the "TextMate 2" project 98 | And I am signed in as "user@ticketee.com" 99 | When I follow "TextMate 2" 100 | And I follow "Shiny!" 101 | Then I should not see the "Delete" link 102 | 103 | Scenario: Delete ticket link is shown to admins 104 | Given I am signed in as "admin@ticketee.com" 105 | When I follow "TextMate 2" 106 | And I follow "Shiny!" 107 | Then I should see the "Delete" link 108 | -------------------------------------------------------------------------------- /features/managing_states.feature: -------------------------------------------------------------------------------- 1 | Feature: Managing states 2 | In order to change information about a state 3 | As an admin 4 | I want to be able to set a state's name and default status 5 | 6 | Background: 7 | Given I have run the seed task 8 | And I am signed in as "admin@ticketee.com" 9 | 10 | Scenario: Marking a state as default 11 | Given I am on the homepage 12 | When I follow "Admin" 13 | And I follow "States" 14 | And I follow "Make default" for the "New" state 15 | Then I should see "New is now the default state." -------------------------------------------------------------------------------- /features/paginating_tickets.feature: -------------------------------------------------------------------------------- 1 | Feature: Paginating tickets 2 | In order to ease the load on the server 3 | As the system 4 | I want paginate ticket results 5 | 6 | Background: 7 | Given there is a project called "Internet Explorer" 8 | Given there are the following users: 9 | | email | password | 10 | | user@ticketee.com | password | 11 | And "user@ticketee.com" can view the "Internet Explorer" project 12 | And I am signed in as them 13 | And there are 100 tickets for this project 14 | 15 | When I am on the homepage 16 | And I follow "Internet Explorer" 17 | 18 | Scenario: Viewing the second page 19 | Then I should see 2 pages of pagination 20 | When I follow "Next" within ".pagination .next" 21 | Then I see page 2 of tickets for this project -------------------------------------------------------------------------------- /features/searching.feature: -------------------------------------------------------------------------------- 1 | Feature: Searching 2 | In order to find specific tickets 3 | As a user 4 | I want to enter a search query and get results 5 | 6 | Background: 7 | Given there are the following users: 8 | | email | password | 9 | | user@ticketee.com | password | 10 | And I am signed in as them 11 | And there is a project called "Ticketee" 12 | And "user@ticketee.com" can view the "Ticketee" project 13 | And "user@ticketee.com" can tag the "Ticketee" project 14 | And "user@ticketee.com" has created a ticket for this project: 15 | | title | description | tags | state | 16 | | Tag! | Hey! You're it! | iteration_1 | Open | 17 | And "user@ticketee.com" has created a ticket for this project: 18 | | title | description | tags | state | 19 | | Tagged! | Hey! I'm it now! | iteration_2 | Closed | 20 | Given I am on the homepage 21 | And I follow "Ticketee" within "#projects" 22 | 23 | Scenario: Finding by tag 24 | When I fill in "Search" with "tag:iteration_1" 25 | And I press "Search" 26 | Then I should see "Tag!" 27 | And I should not see "Tagged!" 28 | 29 | Scenario: Finding by state 30 | When I fill in "Search" with "state:Open" 31 | And I press "Search" 32 | Then I should see "Tag!" 33 | And I should not see "Tagged!" 34 | 35 | Scenario: Clicking a tag goes to search results 36 | When I follow "Tag!" 37 | And I follow "iteration_1" 38 | Then I should see "Tag!" 39 | And I should not see "Tagged!" -------------------------------------------------------------------------------- /features/seed.feature: -------------------------------------------------------------------------------- 1 | Feature: Seed Data 2 | In order to fill the database with the basics 3 | As the system 4 | I want to run the seed task 5 | 6 | Scenario: The basics 7 | Given I have run the seed task 8 | And I am signed in as "admin@ticketee.com" 9 | When I follow "Ticketee Beta" 10 | And I follow "New Ticket" 11 | And I fill in "Title" with "Comments with state" 12 | And I fill in "Description" with "Comments always have a state." 13 | And I press "Create Ticket" 14 | Then I should see "New" within "#comment_state_id" 15 | And I should see "Open" within "#comment_state_id" 16 | And I should see "Closed" within "#comment_state_id" -------------------------------------------------------------------------------- /features/signing_in.feature: -------------------------------------------------------------------------------- 1 | Feature: Signing in 2 | In order to use the site 3 | As a user 4 | I want to be able to sign in 5 | 6 | Scenario: Signing in via confirmation 7 | Given there are the following users: 8 | | email | password | unconfirmed | 9 | | user@ticketee.com | password | true | 10 | And "user@ticketee.com" opens the email with subject "Confirmation instructions" 11 | And they click the first link in the email 12 | Then I should see "Your account was successfully confirmed" 13 | Then I should see "Signed in as user@ticketee.com" 14 | 15 | Scenario: Signing in via form 16 | Given there are the following users: 17 | | email | password | 18 | | user@ticketee.com | password | 19 | And I am signed in as them -------------------------------------------------------------------------------- /features/signing_up.feature: -------------------------------------------------------------------------------- 1 | Feature: Signing up 2 | In order to be attributed for my work 3 | As a user 4 | I want to be able to sign up 5 | 6 | Scenario: Signing up 7 | Given I am on the homepage 8 | When I follow "Sign up" 9 | And I fill in "Email" with "user@ticketee.com" 10 | And I fill in "Password" with "password" 11 | And I fill in "Password confirmation" with "password" 12 | And I press "Sign up" 13 | Then I should see "You have signed up successfully." -------------------------------------------------------------------------------- /features/step_definitions/app_email_steps.rb: -------------------------------------------------------------------------------- 1 | Then /^the email should contain (\d+) parts$/ do |num| 2 | current_email.parts.size.should eql(num.to_i) 3 | end 4 | 5 | Then /^there should be a part with content type "([^"]*)"$/ do |content_type| 6 | current_email.parts.detect do |p| 7 | p.content_type.include?(content_type) 8 | end.should_not be_nil 9 | end 10 | 11 | Given /^Action Mailer delivers via SMTP$/ do 12 | ActionMailer::Base.delivery_method = :smtp 13 | end 14 | 15 | When /^I log into gmail with:$/ do |table| 16 | details = table.hashes.first 17 | @gmail = Gmail.connect(details["username"], details["password"]) 18 | end 19 | 20 | Then /^there should be an email from Ticketee in my inbox$/ do 21 | @mails = @gmail.inbox.find(:unread, 22 | :from => "ticketee@gmail.com") do |mail| 23 | if mail.message.subject =~ /^\[ticketee\]/ 24 | mail.delete! 25 | @received_mail = true 26 | end 27 | end 28 | @received_mail.should be_true 29 | end -------------------------------------------------------------------------------- /features/step_definitions/application_steps.rb: -------------------------------------------------------------------------------- 1 | Given /^I have run the seed task$/ do 2 | load Rails.root + "db/seeds.rb" 3 | end 4 | 5 | Then /^I should not see the "([^"]*)" element$/ do |css| 6 | page.should_not(have_css(css), 7 | "Expected to not see the #{css} element, but did.") 8 | end -------------------------------------------------------------------------------- /features/step_definitions/link_steps.rb: -------------------------------------------------------------------------------- 1 | Then /^I should see the "([^\"]*)" link$/ do |text| 2 | page.should(have_css("a", :text => text), 3 | "Expected to see the #{text.inspect} link, but did not.") 4 | end 5 | 6 | Then /^I should not see the "([^\"]*)" link$/ do |text| 7 | page.should_not(have_css("a", :text => text), 8 | "Expected to not see the #{text.inspect} link, but did.") 9 | end -------------------------------------------------------------------------------- /features/step_definitions/oauth_steps.rb: -------------------------------------------------------------------------------- 1 | Given /^we are mocking a successful Twitter response$/ do 2 | OmniAuth.config.mock_auth[:twitter] = { 3 | "extra" => { 4 | "user_hash" => { 5 | "id" => '12345', 6 | "screen_name" => 'twit', 7 | "display_name" => "A Twit" 8 | } 9 | } 10 | } 11 | end 12 | 13 | Given /^I have mocked a successful GitHub response$/ do 14 | OmniAuth.config.mock_auth[:github] = { 15 | "extra" => { 16 | "user_hash" => { 17 | "id" => '12345', 18 | "email" => 'githubber@example.com', 19 | "login" => "githubber", 20 | "name" => "A GitHubber" 21 | } 22 | } 23 | } 24 | end -------------------------------------------------------------------------------- /features/step_definitions/pagination_steps.rb: -------------------------------------------------------------------------------- 1 | Then /^I should see (\d+) pages of pagination$/ do |number| 2 | pages = all(".pagination .page") 3 | pages.count.should eql(number.to_i) 4 | end 5 | 6 | Then /^I see page (\d+) of tickets for this project$/ do |number| 7 | current_page = find(".pagination .current").text.strip 8 | current_page.should eql(number) 9 | end -------------------------------------------------------------------------------- /features/step_definitions/permission_steps.rb: -------------------------------------------------------------------------------- 1 | permission_step = /^"([^"]*)" can ([^"]*?) ([o|i]n)?\s?the "([^"]*)" project/ 2 | Given permission_step do |user, permission, on, project| 3 | create_permission(user, find_project(project), permission) 4 | end 5 | 6 | When /^I check "([^"]*)" for "([^"]*)"$/ do |permission, name| 7 | project = Project.find_by_name!(name) 8 | permission = permission.downcase.gsub(" ", "_") 9 | field_id = "permissions_#{project.id}_#{permission}" 10 | steps(%Q{When I check "#{field_id}"}) 11 | end 12 | 13 | def create_permission(email, object, action) 14 | Permission.create!(:user => User.find_by_email!(email), 15 | :thing => object, 16 | :action => action) 17 | end 18 | 19 | def find_project(name) 20 | Project.find_by_name!(name) 21 | end -------------------------------------------------------------------------------- /features/step_definitions/project_steps.rb: -------------------------------------------------------------------------------- 1 | Given /^there is a project called "([^\"]*)"$/ do |name| 2 | @project = Factory(:project, :name => name) 3 | end -------------------------------------------------------------------------------- /features/step_definitions/state_steps.rb: -------------------------------------------------------------------------------- 1 | Given /^there is a state called "([^"]*)"$/ do |name| 2 | State.create!(:name => name) 3 | end 4 | 5 | When /^I follow "([^"]*)" for the "([^"]*)" state$/ do |link, name| 6 | state = State.find_by_name!(name) 7 | steps(%Q{When I follow "#{link}" within "#state_#{state.id}"}) 8 | end -------------------------------------------------------------------------------- /features/step_definitions/ticket_steps.rb: -------------------------------------------------------------------------------- 1 | Given /^"([^\"]*)" has created a ticket for this project:$/ do |email, table| 2 | table.hashes.each do |attributes| 3 | tags = attributes.delete("tags") 4 | state = attributes.delete("state") 5 | ticket = @project.tickets.create!( 6 | attributes.merge!(:user => 7 | User.find_by_email!(email))) 8 | ticket.state = State.find_or_create_by_name(state) if state 9 | ticket.tag!(tags) if tags 10 | ticket.save 11 | end 12 | end 13 | 14 | Given /^there are (\d+) tickets for this project$/ do |number| 15 | number.to_i.times do |i| 16 | @project.tickets.create!(:title => "Test", 17 | :description => "Placeholder ticket.", 18 | :user => @user) 19 | end 20 | end -------------------------------------------------------------------------------- /features/step_definitions/user_steps.rb: -------------------------------------------------------------------------------- 1 | Given /^there are the following users:$/ do |table| 2 | table.hashes.each do |attributes| 3 | unconfirmed = attributes.delete("unconfirmed") == "true" 4 | @user = User.create!(attributes) 5 | @user.update_attribute("admin", attributes["admin"] == "true") 6 | @user.confirm! unless unconfirmed 7 | end 8 | end 9 | 10 | Given /^I am signed in as them$/ do 11 | steps(%Q{ 12 | Given I am on the homepage 13 | When I follow "Sign in" 14 | And I fill in "Email" with "#{@user.email}" 15 | And I fill in "Password" with "password" 16 | And I press "Sign in" 17 | Then I should see "Signed in successfully." 18 | }) 19 | end 20 | 21 | Given /^I am signed in as "([^\"]*)"$/ do |email| 22 | @user = User.find_by_email!(email) 23 | steps("Given I am signed in as them") 24 | end -------------------------------------------------------------------------------- /features/support/after_hook.rb: -------------------------------------------------------------------------------- 1 | After do 2 | ActionMailer::Base.delivery_method = :test 3 | end -------------------------------------------------------------------------------- /features/support/email.rb: -------------------------------------------------------------------------------- 1 | # Email Spec helpers 2 | require 'email_spec' 3 | require 'email_spec/cucumber' -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | # IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. 2 | # It is recommended to regenerate this file in the future when you upgrade to a 3 | # newer version of cucumber-rails. Consider adding your own code to a new file 4 | # instead of editing this one. Cucumber will automatically load all features/**/*.rb 5 | # files. 6 | 7 | require 'cucumber/rails' 8 | 9 | # Capybara defaults to XPath selectors rather than Webrat's default of CSS3. In 10 | # order to ease the transition to Capybara we set the default here. If you'd 11 | # prefer to use XPath just remove this line and adjust any selectors in your 12 | # steps to use the XPath syntax. 13 | Capybara.default_selector = :css 14 | 15 | # By default, any exception happening in your Rails application will bubble up 16 | # to Cucumber so that your scenario will fail. This is a different from how 17 | # your application behaves in the production environment, where an error page will 18 | # be rendered instead. 19 | # 20 | # Sometimes we want to override this default behaviour and allow Rails to rescue 21 | # exceptions and display an error page (just like when the app is running in production). 22 | # Typical scenarios where you want to do this is when you test your error pages. 23 | # There are two ways to allow Rails to rescue exceptions: 24 | # 25 | # 1) Tag your scenario (or feature) with @allow-rescue 26 | # 27 | # 2) Set the value below to true. Beware that doing this globally is not 28 | # recommended as it will mask a lot of errors for you! 29 | # 30 | ActionController::Base.allow_rescue = false 31 | 32 | # Remove/comment out the lines below if your app doesn't have a database. 33 | # For some databases (like MongoDB and CouchDB) you may need to use :truncation instead. 34 | begin 35 | DatabaseCleaner.strategy = :transaction 36 | rescue NameError 37 | raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it." 38 | end 39 | 40 | -------------------------------------------------------------------------------- /features/support/factories.rb: -------------------------------------------------------------------------------- 1 | Dir[Rails.root + "factories/*.rb"].each do |file| 2 | require file 3 | end -------------------------------------------------------------------------------- /features/support/paths.rb: -------------------------------------------------------------------------------- 1 | module NavigationHelpers 2 | # Maps a name to a path. Used by the 3 | # 4 | # When /^I go to (.+)$/ do |page_name| 5 | # 6 | # step definition in web_steps.rb 7 | # 8 | def path_to(page_name) 9 | case page_name 10 | 11 | when /the home\s?page/ 12 | '/' 13 | 14 | when /the project page for "([^\"]*)"/ 15 | project_path(Project.find_by_name!($1)) 16 | # Add more mappings here. 17 | # Here is an example that pulls values out of the Regexp: 18 | # 19 | # when /^(.*)'s profile page$/i 20 | # user_profile_path(User.find_by_login($1)) 21 | 22 | else 23 | begin 24 | page_name =~ /the (.*) page/ 25 | path_components = $1.split(/\s+/) 26 | self.send(path_components.push('path').join('_').to_sym) 27 | rescue Object => e 28 | raise "Can't find mapping from \"#{page_name}\" to a path.\n" + 29 | "Now, go and add a mapping in #{__FILE__}" 30 | end 31 | end 32 | end 33 | end 34 | 35 | World(NavigationHelpers) 36 | -------------------------------------------------------------------------------- /features/support/selectors.rb: -------------------------------------------------------------------------------- 1 | module HtmlSelectorsHelpers 2 | # Maps a name to a selector. Used primarily by the 3 | # 4 | # When /^(.+) within (.+)$/ do |step, scope| 5 | # 6 | # step definitions in web_steps.rb 7 | # 8 | def selector_for(locator) 9 | case locator 10 | 11 | when /the page/ 12 | "html > body" 13 | 14 | # Add more mappings here. 15 | # Here is an example that pulls values out of the Regexp: 16 | # 17 | # when /the (notice|error|info) flash/ 18 | # ".flash.#{$1}" 19 | 20 | # You can also return an array to use a different selector 21 | # type, like: 22 | # 23 | # when /the header/ 24 | # [:xpath, "//header"] 25 | 26 | # This allows you to provide a quoted selector as the scope 27 | # for "within" steps as was previously the default for the 28 | # web steps: 29 | when /"(.+)"/ 30 | $1 31 | 32 | else 33 | raise "Can't find mapping from \"#{locator}\" to a selector.\n" + 34 | "Now, go and add a mapping in #{__FILE__}" 35 | end 36 | end 37 | end 38 | 39 | World(HtmlSelectorsHelpers) 40 | -------------------------------------------------------------------------------- /features/ticket_notifications.feature: -------------------------------------------------------------------------------- 1 | Feature: Ticket Notifications 2 | Background: 3 | Given there are the following users: 4 | | email | password | 5 | | alice@ticketee.com | password | 6 | | bob@ticketee.com | password | 7 | 8 | Given a clear email queue 9 | 10 | Given there is a project called "TextMate 2" 11 | And "alice@ticketee.com" can view the "TextMate 2" project 12 | And "bob@ticketee.com" can view the "TextMate 2" project 13 | And "alice@ticketee.com" has created a ticket for this project: 14 | | title | description | 15 | | Release date | TBA very shortly. | 16 | 17 | Given I am signed in as "bob@ticketee.com" 18 | Given I am on the homepage 19 | 20 | Scenario: Ticket owner is automatically subscribed to a ticket 21 | When I follow "TextMate 2" 22 | And I follow "Release date" 23 | And I fill in "Text" with "Is it out yet?" 24 | And I press "Create Comment" 25 | 26 | Then "alice@ticketee.com" should receive an email 27 | When "alice@ticketee.com" opens the email 28 | Then they should see "updated the Release date ticket" in the email body 29 | And the email should contain 2 parts 30 | And there should be a part with content type "text/plain" 31 | And there should be a part with content type "text/html" 32 | And they should see "[ticketee] TextMate 2 - Release date" in the email subject 33 | Then they follow "view this ticket online here" in the email 34 | Then I should see "Release date" within "#ticket h2" 35 | 36 | Scenario: Comment authors are automatically subscribed to a ticket 37 | When I follow "TextMate 2" 38 | And I follow "Release date" 39 | And I fill in "Text" with "Is it out yet?" 40 | And I press "Create Comment" 41 | Then I should see "Comment has been created." 42 | When I follow "Sign out" 43 | 44 | Given a clear email queue 45 | 46 | Given I am signed in as "alice@ticketee.com" 47 | When I follow "TextMate 2" 48 | And I follow "Release date" 49 | And I fill in "Text" with "Not yet!" 50 | And I press "Create Comment" 51 | Then I should see "Comment has been created." 52 | Then "bob@ticketee.com" should receive an email 53 | Then "alice@ticketee.com" should have no emails -------------------------------------------------------------------------------- /features/twitter_auth.feature: -------------------------------------------------------------------------------- 1 | Feature: Twitter auth 2 | In order to sign in using Twitter 3 | As a Twitter user 4 | I want to click an icon and be signed in 5 | 6 | Background: 7 | Given we are mocking a successful Twitter response 8 | 9 | Scenario: Signing in with Twitter 10 | Given I am on the homepage 11 | When I follow "sign_in_with_twitter" 12 | Then I should see "Signed in with Twitter successfully." 13 | And I should see "Signed in as A Twit (@twit)" -------------------------------------------------------------------------------- /features/viewing_projects.feature: -------------------------------------------------------------------------------- 1 | Feature: Viewing projects 2 | In order to assign tickets to a project 3 | As a user 4 | I want to be able to see a list of available projects 5 | 6 | Background: 7 | Given there are the following users: 8 | | email | password | 9 | | user@ticketee.com | password | 10 | And I am signed in as them 11 | And there is a project called "TextMate 2" 12 | And "user@ticketee.com" can view the "TextMate 2" project 13 | 14 | And there is a project called "Internet Explorer" 15 | 16 | Scenario: Listing all projects 17 | And I am on the homepage 18 | Then I should not see "Internet Explorer" 19 | When I follow "TextMate 2" 20 | Then I should be on the project page for "TextMate 2" -------------------------------------------------------------------------------- /features/viewing_tickets.feature: -------------------------------------------------------------------------------- 1 | Feature: Viewing tickets 2 | In order to view the tickets for a project 3 | As a user 4 | I want to see them on that project's page 5 | 6 | Background: 7 | Given there are the following users: 8 | | email | password | 9 | | user@ticketee.com | password | 10 | And I am signed in as them 11 | Given there is a project called "TextMate 2" 12 | And "user@ticketee.com" can view the "TextMate 2" project 13 | And "user@ticketee.com" has created a ticket for this project: 14 | | title | description | 15 | | Make it shiny! | Gradients! Starbursts! Oh my! | 16 | And there is a project called "Internet Explorer" 17 | And "user@ticketee.com" can view the "Internet Explorer" project 18 | And "user@ticketee.com" has created a ticket for this project: 19 | | title | description | 20 | | Standards compliance | Isn't a joke. | 21 | And I am on the homepage 22 | 23 | Scenario: Viewing tickets for a given project 24 | When I follow "TextMate 2" 25 | Then I should see "Make it shiny!" 26 | And I should not see "Standards compliance" 27 | When I follow "Make it shiny!" 28 | Then I should see "Make it shiny" within "#ticket h2" 29 | And I should see "Gradients! Starbursts! Oh my!" 30 | 31 | When I follow "Ticketee" 32 | And I follow "Internet Explorer" 33 | Then I should see "Standards compliance" 34 | And I should not see "Make it shiny!" 35 | When I follow "Standards compliance" 36 | Then I should see "Standards compliance" within "#ticket h2" 37 | And I should see "Isn't a joke." -------------------------------------------------------------------------------- /features/watching_tickets.feature: -------------------------------------------------------------------------------- 1 | Feature: Watching tickets 2 | In order to keep up to date with tickets 3 | As a user 4 | I want to choose to subscribe to their updates 5 | 6 | Background: 7 | Given there are the following users: 8 | | email | password | 9 | | user@ticketee.com | password | 10 | Given there is a project called "TextMate 2" 11 | And "user@ticketee.com" can view the "TextMate 2" project 12 | And "user@ticketee.com" has created a ticket for this project: 13 | | title | description | 14 | | Release date | TBA very shortly. | 15 | 16 | Given I am signed in as "user@ticketee.com" 17 | Given I am on the homepage 18 | 19 | Scenario: Ticket watch toggling 20 | When I follow "TextMate 2" 21 | And I follow "Release date" 22 | Then I should see "user@ticketee.com" within "#watchers" 23 | And I press "Stop watching this ticket" 24 | Then I should see "You are no longer watching this ticket" 25 | And I should not see "user@ticketee.com" within "#watchers" 26 | When I press "Watch this ticket" 27 | Then I should see "You are now watching this ticket" 28 | And I should see "user@ticketee.com" within "#watchers" -------------------------------------------------------------------------------- /lib/heartbeat.ru: -------------------------------------------------------------------------------- 1 | 2 | 3 | run Heartbeat::Application -------------------------------------------------------------------------------- /lib/heartbeat/application.rb: -------------------------------------------------------------------------------- 1 | module Heartbeat 2 | class Application 3 | def self.call(env) 4 | default_headers = { "Content-Type" => "text/plain"} 5 | 6 | if env["PATH_INFO"] =~ /200/ 7 | body = "Success!" 8 | status = 200 9 | else 10 | body = "Failure!" 11 | status = 500 12 | end 13 | 14 | [status, default_headers, ["#{env["PATH_INFO"]} == #{body}"]] 15 | end 16 | end 17 | end -------------------------------------------------------------------------------- /lib/heartbeat/config.ru: -------------------------------------------------------------------------------- 1 | heartbeat_root = File.expand_path(File.dirname(__FILE__)) 2 | require heartbeat_root + '/application' 3 | require heartbeat_root + '/test_application' 4 | 5 | app = Rack::Builder.app do 6 | map '/test' do 7 | run Heartbeat::Application 8 | end 9 | 10 | map '/' do 11 | run Heartbeat::TestApplication 12 | end 13 | end 14 | 15 | run app -------------------------------------------------------------------------------- /lib/heartbeat/test_application.rb: -------------------------------------------------------------------------------- 1 | module Heartbeat 2 | class TestApplication 3 | def self.call(env) 4 | default_headers = { "Content-Type" => "text/html"} 5 | body = %Q{ 6 |

Success or FAILURE?!

7 |
8 | 9 |
10 | 11 |
12 | 13 |
14 | } 15 | 16 | [200, default_headers, [body]] 17 | end 18 | end 19 | end -------------------------------------------------------------------------------- /lib/link_jumbler.rb: -------------------------------------------------------------------------------- 1 | require 'nokogiri' 2 | class LinkJumbler 3 | 4 | def initialize(app, letters) 5 | @app = app 6 | @letters = letters 7 | end 8 | 9 | def call(env) 10 | status, headers, response = @app.call(env) 11 | body = Nokogiri::HTML(response.body) 12 | body.css("a").each do |a| 13 | @letters.each do |find, replace| 14 | a.content = a.content.gsub(find.to_s, replace.to_s) 15 | end 16 | end 17 | [status, headers, body.to_s] 18 | end 19 | end -------------------------------------------------------------------------------- /lib/tasks/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails3book/ticketee/67362e12751ea734354a94e3d78b835ac7761ac7/lib/tasks/.gitkeep -------------------------------------------------------------------------------- /lib/tasks/cucumber.rake: -------------------------------------------------------------------------------- 1 | # IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. 2 | # It is recommended to regenerate this file in the future when you upgrade to a 3 | # newer version of cucumber-rails. Consider adding your own code to a new file 4 | # instead of editing this one. Cucumber will automatically load all features/**/*.rb 5 | # files. 6 | 7 | 8 | unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks 9 | 10 | vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first 11 | $LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil? 12 | 13 | begin 14 | require 'cucumber/rake/task' 15 | 16 | namespace :cucumber do 17 | Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t| 18 | t.binary = vendored_cucumber_bin # If nil, the gem's binary is used. 19 | t.fork = true # You may get faster startup if you set this to false 20 | t.profile = 'default' 21 | end 22 | 23 | Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t| 24 | t.binary = vendored_cucumber_bin 25 | t.fork = true # You may get faster startup if you set this to false 26 | t.profile = 'wip' 27 | end 28 | 29 | Cucumber::Rake::Task.new({:rerun => 'db:test:prepare'}, 'Record failing features and run only them if any exist') do |t| 30 | t.binary = vendored_cucumber_bin 31 | t.fork = true # You may get faster startup if you set this to false 32 | t.profile = 'rerun' 33 | end 34 | 35 | desc 'Run all features' 36 | task :all => [:ok, :wip] 37 | end 38 | desc 'Alias for cucumber:ok' 39 | task :cucumber => 'cucumber:ok' 40 | 41 | task :default => :cucumber 42 | 43 | task :features => :cucumber do 44 | STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***" 45 | end 46 | 47 | # In case we don't have ActiveRecord, append a no-op task that we can depend upon. 48 | task 'db:test:prepare' do 49 | end 50 | rescue LoadError 51 | desc 'cucumber rake task not available (cucumber not installed)' 52 | task :cucumber do 53 | abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin' 54 | end 55 | end 56 | 57 | end 58 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The page you were looking for doesn't exist.

23 |

You may have mistyped the address or the page may have moved.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The change you wanted was rejected.

23 |

Maybe you tried to change something you didn't have access to.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

We're sorry, but something went wrong.

23 |

We've been notified about this issue and we'll take a look at it shortly.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails3book/ticketee/67362e12751ea734354a94e3d78b835ac7761ac7/public/favicon.ico -------------------------------------------------------------------------------- /public/images/rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails3book/ticketee/67362e12751ea734354a94e3d78b835ac7761ac7/public/images/rails.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-Agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /script/cucumber: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | vendored_cucumber_bin = Dir["#{File.dirname(__FILE__)}/../vendor/{gems,plugins}/cucumber*/bin/cucumber"].first 4 | if vendored_cucumber_bin 5 | load File.expand_path(vendored_cucumber_bin) 6 | else 7 | require 'rubygems' unless ENV['NO_RUBYGEMS'] 8 | require 'cucumber' 9 | load Cucumber::BINARY 10 | end 11 | -------------------------------------------------------------------------------- /script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /spec/api/v1/authentication_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "API errors", :type => :api do 4 | 5 | it "making a request with no token" do 6 | get "/api/v1/projects.json", :token => "" 7 | error = { :error => "Token is invalid." } 8 | last_response.body.should eql(error.to_json) 9 | end 10 | 11 | end -------------------------------------------------------------------------------- /spec/api/v1/project_errors_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Project API errors", :type => :api do 4 | context "standard users" do 5 | let(:user) { create_user! } 6 | 7 | it "cannot create projects" do 8 | post "/api/v1/projects.json", 9 | :token => user.authentication_token, 10 | :project => { 11 | :name => "Ticketee" 12 | } 13 | error = { :error => "You must be an admin to do that." } 14 | last_response.body.should eql(error.to_json) 15 | last_response.status.should eql(401) 16 | Project.find_by_name("Ticketee").should be_nil 17 | end 18 | 19 | it "cannot view projects they do not have access to" do 20 | project = Factory(:project) 21 | 22 | get "/api/v1/projects/#{project.id}.json", 23 | :token => user.authentication_token 24 | error = { :error => "The project you were looking for" + 25 | " could not be found." } 26 | last_response.status.should eql(404) 27 | last_response.body.should eql(error.to_json) 28 | end 29 | end 30 | end -------------------------------------------------------------------------------- /spec/api/v1/projects_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "/api/v1/projects", :type => :api do 4 | let(:user) { create_user! } 5 | let(:token) { user.authentication_token } 6 | 7 | before do 8 | @project = Factory(:project) 9 | user.permissions.create!(:action => "view", :thing => @project) 10 | end 11 | 12 | context "projects viewable by this user" do 13 | before do 14 | Factory(:project, :name => "Access denied.") 15 | end 16 | 17 | let(:url) { "/api/v1/projects" } 18 | it "JSON" do 19 | get "#{url}.json", :token => token 20 | 21 | projects_json = Project.for(user).all.to_json 22 | last_response.body.should eql(projects_json) 23 | last_response.status.should eql(200) 24 | 25 | projects = JSON.parse(last_response.body) 26 | 27 | projects.any? do |p| 28 | p["project"]["name"] == "Ticketee" 29 | end.should be_true 30 | 31 | projects.any? do |p| 32 | p["project"]["name"] == "Access Denied" 33 | end.should be_false 34 | end 35 | 36 | it "XML" do 37 | get "#{url}.xml", :token => token 38 | last_response.body.should eql(Project.readable_by(user).to_xml) 39 | projects = Nokogiri::XML(last_response.body) 40 | projects.css("project name").text.should eql("Ticketee") 41 | end 42 | end 43 | 44 | context "creating a project" do 45 | before do 46 | user.admin = true 47 | user.save 48 | end 49 | 50 | let(:url) { "/api/v1/projects" } 51 | 52 | it "sucessful JSON" do 53 | post "#{url}.json", :token => token, 54 | :project => { 55 | :name => "Inspector" 56 | } 57 | 58 | project = Project.find_by_name("Inspector") 59 | route = "/api/v1/projects/#{project.id}" 60 | 61 | last_response.status.should eql(201) 62 | last_response.headers["Location"].should eql(route) 63 | 64 | last_response.body.should eql(project.to_json) 65 | end 66 | 67 | it "unsuccessful JSON" do 68 | post "#{url}.json", :token => token, 69 | :project => {} 70 | last_response.status.should eql(422) 71 | errors = {"name" => ["can't be blank"]}.to_json 72 | last_response.body.should eql(errors) 73 | end 74 | end 75 | 76 | context "show" do 77 | let(:url) { "/api/v1/projects/#{@project.id}"} 78 | 79 | before do 80 | Factory(:ticket, :project => @project) 81 | end 82 | 83 | it "JSON" do 84 | get "#{url}.json", :token => token 85 | project = @project.to_json(:methods => "last_ticket") 86 | last_response.body.should eql(project) 87 | last_response.status.should eql(200) 88 | 89 | project_response = JSON.parse(last_response.body)["project"] 90 | 91 | ticket_title = project_response["last_ticket"]["ticket"]["title"] 92 | ticket_title.should_not be_blank 93 | end 94 | end 95 | 96 | context "updating a project" do 97 | before do 98 | user.admin = true 99 | user.save 100 | end 101 | 102 | let(:url) { "/api/v1/projects/#{@project.id}" } 103 | it "successful JSON" do 104 | @project.name.should eql("Ticketee") 105 | put "#{url}.json", :token => token, 106 | :project => { 107 | :name => "Not Ticketee" 108 | } 109 | last_response.status.should eql(200) 110 | 111 | @project.reload 112 | @project.name.should eql("Not Ticketee") 113 | last_response.body.should eql("{}") 114 | end 115 | 116 | it "unsuccessful JSON" do 117 | @project.name.should eql("Ticketee") 118 | put "#{url}.json", :token => token, 119 | :project => { 120 | :name => "" 121 | } 122 | last_response.status.should eql(422) 123 | 124 | @project.reload 125 | @project.name.should eql("Ticketee") 126 | errors = { :name => ["can't be blank"]} 127 | last_response.body.should eql(errors.to_json) 128 | end 129 | end 130 | 131 | context "deleting a project" do 132 | before do 133 | user.admin = true 134 | user.save 135 | end 136 | 137 | let(:url) { "/api/v1/projects/#{@project.id}" } 138 | it "JSON" do 139 | delete "#{url}.json", :token => token 140 | last_response.status.should eql(200) 141 | end 142 | end 143 | end -------------------------------------------------------------------------------- /spec/api/v1/rate_limit_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "rate limiting", :type => :api do 4 | let(:user) { create_user! } 5 | 6 | it "counts the user's requests" do 7 | user.request_count.should eql(0) 8 | get '/api/v1/projects.json', :token => user.authentication_token 9 | user.reload 10 | user.request_count.should eql(1) 11 | end 12 | 13 | it "stops a user if they have exceeded the limit" do 14 | user.update_attribute(:request_count, 200) 15 | get '/api/v1/projects.json', :token => user.authentication_token 16 | error = { :error => "Rate limit exceeded." } 17 | last_response.status.should eql(403) 18 | last_response.body.should eql(error.to_json) 19 | end 20 | end -------------------------------------------------------------------------------- /spec/api/v1/tickets_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "/api/v1/tickets", :type => :api do 4 | let(:project) { Factory(:project, :name => "Ticketee") } 5 | 6 | before do 7 | @user = create_user! 8 | @user.update_attribute(:admin, true) 9 | @user.permissions.create!(:action => "view", 10 | :thing => project) 11 | end 12 | 13 | let(:token) { @user.authentication_token } 14 | 15 | context "index" do 16 | before do 17 | 5.times do 18 | Factory(:ticket, :project => project, :user => @user) 19 | end 20 | end 21 | 22 | let(:url) { "/api/v1/projects/#{project.id}/tickets" } 23 | 24 | it "XML" do 25 | get "#{url}.xml", :token => token 26 | last_response.body.should eql(project.tickets.to_xml) 27 | end 28 | 29 | it "JSON" do 30 | get "#{url}.json", :token => token 31 | last_response.body.should eql(project.tickets.to_json) 32 | end 33 | end 34 | end -------------------------------------------------------------------------------- /spec/api/v2/authentication_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "API errors", :type => :api do 4 | 5 | it "making a request with no token" do 6 | get "/api/v1/projects.json", :token => "" 7 | error = { :error => "Token is invalid." } 8 | last_response.body.should eql(error.to_json) 9 | end 10 | 11 | end -------------------------------------------------------------------------------- /spec/api/v2/project_errors_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Project API errors", :type => :api do 4 | context "standard users" do 5 | let(:user) { create_user! } 6 | 7 | it "cannot create projects" do 8 | post "/api/v1/projects.json", 9 | :token => user.authentication_token, 10 | :project => { 11 | :name => "Ticketee" 12 | } 13 | error = { :error => "You must be an admin to do that." } 14 | last_response.body.should eql(error.to_json) 15 | last_response.status.should eql(401) 16 | Project.find_by_name("Ticketee").should be_nil 17 | end 18 | 19 | it "cannot view projects they do not have access to" do 20 | project = Factory(:project) 21 | 22 | get "/api/v1/projects/#{project.id}.json", 23 | :token => user.authentication_token 24 | error = { :error => "The project you were looking for" + 25 | " could not be found." } 26 | last_response.status.should eql(404) 27 | last_response.body.should eql(error.to_json) 28 | end 29 | end 30 | end -------------------------------------------------------------------------------- /spec/api/v2/projects_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "/api/v2/projects", :type => :api do 4 | let(:user) { create_user! } 5 | let(:token) { user.authentication_token } 6 | 7 | before do 8 | @project = Factory(:project) 9 | user.permissions.create!(:action => "view", :thing => @project) 10 | end 11 | 12 | context "projects viewable by this user" do 13 | before do 14 | Factory(:project, :name => "Access denied.") 15 | end 16 | 17 | let(:url) { "/api/v2/projects" } 18 | let(:options) { { :except => :name, :methods => :title } } 19 | 20 | it "JSON" do 21 | get "#{url}.json", :token => token 22 | 23 | body = Project.readable_by(user).to_json(options) 24 | 25 | last_response.body.should eql(body) 26 | last_response.status.should eql(200) 27 | 28 | projects = JSON.parse(last_response.body) 29 | projects.any? do |p| 30 | p["project"]["title"] == "Ticketee" 31 | end.should be_true 32 | 33 | projects.all? do |p| 34 | p["project"]["name"].blank? 35 | end.should be_true 36 | end 37 | 38 | it "XML" do 39 | get "#{url}.xml", :token => token 40 | 41 | body = Project.readable_by(user).to_xml(options) 42 | last_response.body.should eql(body) 43 | projects = Nokogiri::XML(last_response.body) 44 | projects.css("project title").text.should eql("Ticketee") 45 | projects.css("project name").text.should eql("") 46 | end 47 | end 48 | 49 | context "creating a project" do 50 | before do 51 | user.admin = true 52 | user.save 53 | end 54 | 55 | let(:url) { "/api/v1/projects" } 56 | 57 | it "sucessful JSON" do 58 | post "#{url}.json", :token => token, 59 | :project => { 60 | :name => "Inspector" 61 | } 62 | 63 | project = Project.find_by_name("Inspector") 64 | route = "/api/v1/projects/#{project.id}" 65 | 66 | last_response.status.should eql(201) 67 | last_response.headers["Location"].should eql(route) 68 | 69 | last_response.body.should eql(project.to_json) 70 | end 71 | 72 | it "unsuccessful JSON" do 73 | post "#{url}.json", :token => token, 74 | :project => {} 75 | last_response.status.should eql(422) 76 | errors = {"name" => ["can't be blank"]}.to_json 77 | last_response.body.should eql(errors) 78 | end 79 | end 80 | 81 | context "show" do 82 | let(:url) { "/api/v1/projects/#{@project.id}"} 83 | 84 | before do 85 | Factory(:ticket, :project => @project) 86 | end 87 | 88 | it "JSON" do 89 | get "#{url}.json", :token => token 90 | project = @project.to_json(:methods => "last_ticket") 91 | last_response.body.should eql(project) 92 | last_response.status.should eql(200) 93 | 94 | project_response = JSON.parse(last_response.body)["project"] 95 | 96 | ticket_title = project_response["last_ticket"]["ticket"]["title"] 97 | ticket_title.should_not be_blank 98 | end 99 | end 100 | 101 | context "updating a project" do 102 | before do 103 | user.admin = true 104 | user.save 105 | end 106 | 107 | let(:url) { "/api/v1/projects/#{@project.id}" } 108 | it "successful JSON" do 109 | @project.name.should eql("Ticketee") 110 | put "#{url}.json", :token => token, 111 | :project => { 112 | :name => "Not Ticketee" 113 | } 114 | last_response.status.should eql(200) 115 | 116 | @project.reload 117 | @project.name.should eql("Not Ticketee") 118 | last_response.body.should eql("{}") 119 | end 120 | 121 | it "unsuccessful JSON" do 122 | @project.name.should eql("Ticketee") 123 | put "#{url}.json", :token => token, 124 | :project => { 125 | :name => "" 126 | } 127 | last_response.status.should eql(422) 128 | 129 | @project.reload 130 | @project.name.should eql("Ticketee") 131 | errors = { :name => ["can't be blank"]} 132 | last_response.body.should eql(errors.to_json) 133 | end 134 | end 135 | 136 | context "deleting a project" do 137 | before do 138 | user.admin = true 139 | user.save 140 | end 141 | 142 | let(:url) { "/api/v1/projects/#{@project.id}" } 143 | it "JSON" do 144 | delete "#{url}.json", :token => token 145 | last_response.status.should eql(200) 146 | end 147 | end 148 | end -------------------------------------------------------------------------------- /spec/api/v2/rate_limit_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "rate limiting", :type => :api do 4 | let(:user) { create_user! } 5 | 6 | it "counts the user's requests" do 7 | user.request_count.should eql(0) 8 | get '/api/v1/projects.json', :token => user.authentication_token 9 | user.reload 10 | user.request_count.should eql(1) 11 | end 12 | 13 | it "stops a user if they have exceeded the limit" do 14 | user.update_attribute(:request_count, 200) 15 | get '/api/v1/projects.json', :token => user.authentication_token 16 | error = { :error => "Rate limit exceeded." } 17 | last_response.status.should eql(403) 18 | last_response.body.should eql(error.to_json) 19 | end 20 | end -------------------------------------------------------------------------------- /spec/api/v2/tickets_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "/api/v2/tickets", :type => :api do 4 | let(:project) { Factory(:project, :name => "Ticketee") } 5 | 6 | before do 7 | @user = create_user! 8 | @user.update_attribute(:admin, true) 9 | @user.permissions.create!(:action => "view", 10 | :thing => project) 11 | end 12 | 13 | let(:token) { @user.authentication_token } 14 | 15 | context "index" do 16 | before do 17 | 5.times do 18 | Factory(:ticket, :project => project, :user => @user) 19 | end 20 | end 21 | 22 | let(:url) { "/api/v2/projects/#{project.id}/tickets" } 23 | 24 | it "XML" do 25 | get "#{url}.xml", :token => token 26 | last_response.body.should eql(project.tickets.to_xml) 27 | end 28 | 29 | it "JSON" do 30 | get "#{url}.json", :token => token 31 | last_response.body.should eql(project.tickets.to_json) 32 | end 33 | end 34 | 35 | context "pagination" do 36 | before do 37 | 100.times do 38 | Factory(:ticket, :project => project, :user => @user) 39 | end 40 | end 41 | 42 | it "gets the first page" do 43 | get "/api/v2/projects/#{project.id}/tickets.json", 44 | :token => token, 45 | :page => 1 46 | 47 | last_response.body.should eql(project.tickets.page(1).per(50).to_json) 48 | end 49 | 50 | it "gets the second page" do 51 | get "/api/v2/projects/#{project.id}/tickets.json?page=2", 52 | :token => token, 53 | :page => 2 54 | 55 | last_response.body.should eql(project.tickets.page(2).per(50).to_json) 56 | end 57 | end 58 | end -------------------------------------------------------------------------------- /spec/api/v3/json/tickets_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Api::V3::JSON::Tickets, :type => :api do 4 | let(:project) { Factory(:project) } 5 | let(:user) { Factory(:user) } 6 | let(:token) { user.authentication_token } 7 | 8 | before do 9 | Factory(:ticket, :project => project) 10 | user.permissions.create!(:thing => project, :action => "view") 11 | end 12 | 13 | let(:url) { "/api/v3/json/projects/#{project.id}/tickets" } 14 | 15 | context "successful requests" do 16 | 17 | it "can get a list of tickets" do 18 | get url, :token => token 19 | last_response.body.should eql(project.tickets.to_json) 20 | end 21 | end 22 | 23 | context "unsuccessful requests" do 24 | it "doesn't pass through a token" do 25 | get url 26 | last_response.status.should eql(401) 27 | last_response.body.should eql("Token is invalid.") 28 | end 29 | 30 | it "cannot access a project that they don't have permission to" do 31 | user.permissions.delete_all 32 | get url, :token => token 33 | last_response.status.should eql(404) 34 | end 35 | end 36 | end -------------------------------------------------------------------------------- /spec/controllers/admin/permissions_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Admin::PermissionsController do 4 | 5 | end 6 | -------------------------------------------------------------------------------- /spec/controllers/admin/states_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Admin::StatesController do 4 | 5 | end 6 | -------------------------------------------------------------------------------- /spec/controllers/admin/users_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Admin::UsersController do 4 | let(:user) do 5 | user = Factory(:user) 6 | user.confirm! 7 | user 8 | end 9 | 10 | context "standard users" do 11 | before do 12 | sign_in(:user, user) 13 | end 14 | 15 | it "are not able to access the index action" do 16 | get 'index' 17 | response.should redirect_to(root_path) 18 | flash[:alert].should eql("You must be an admin to do that.") 19 | end 20 | end 21 | end -------------------------------------------------------------------------------- /spec/controllers/comments_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe CommentsController do 4 | 5 | let(:user) { create_user! } 6 | let(:project) { Project.create!(:name => "Ticketee") } 7 | 8 | let(:ticket) do 9 | project.tickets.create(:title => "State transitions", 10 | :description => "Can't be hacked.", 11 | :user => user) 12 | end 13 | 14 | let(:state) { State.create!(:name => "New") } 15 | 16 | context "a user without permission to set state" do 17 | before do 18 | sign_in(:user, user) 19 | end 20 | 21 | it "cannot transition a state by passing through state_id" do 22 | post :create, { :tags => "", 23 | :comment => { :text => "Hacked!", 24 | :state_id => state.id }, 25 | :ticket_id => ticket.id } 26 | ticket.reload 27 | ticket.state.should eql(nil) 28 | end 29 | 30 | it "cannot tag a ticket without permission" do 31 | post :create, { :tags => "one two", :comment => { :text => "Tag!" }, 32 | :ticket_id => ticket.id } 33 | ticket.reload 34 | ticket.tags.should be_empty 35 | end 36 | end 37 | 38 | end 39 | -------------------------------------------------------------------------------- /spec/controllers/files_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe FilesController do 4 | let(:project) { Factory(:project) } 5 | let(:ticket) { Factory(:ticket, :project => project) } 6 | let(:good_user) { create_user! } 7 | let(:bad_user) { create_user! } 8 | 9 | let(:path) { Rails.root + "spec/fixtures/speed.txt" } 10 | let(:asset) do 11 | ticket.assets.create(:asset => File.open(path)) 12 | end 13 | 14 | before do 15 | good_user.permissions.create!(:action => "view", :thing => project) 16 | end 17 | 18 | context "users with access" do 19 | 20 | before do 21 | sign_in(:user, good_user) 22 | end 23 | 24 | it "can access assets in a project" do 25 | get 'show', :id => asset.id 26 | response.body.should eql(File.read(path)) 27 | end 28 | end 29 | 30 | context "users without access" do 31 | before do 32 | sign_in(:user, bad_user) 33 | end 34 | 35 | it "cannot access assets in this project" do 36 | get 'show', :id => asset.id 37 | response.should redirect_to(root_path) 38 | flash[:alert].should eql("The asset you were looking for could not be found.") 39 | end 40 | end 41 | 42 | end -------------------------------------------------------------------------------- /spec/controllers/projects_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ProjectsController do 4 | let(:user) do 5 | user = Factory(:user) 6 | user.confirm! 7 | user 8 | end 9 | 10 | let(:project) { Factory(:project) } 11 | 12 | context "standard users" do 13 | 14 | it "cannot access the show action" do 15 | sign_in(:user, user) 16 | get :show, :id => project.id 17 | response.should redirect_to(projects_path) 18 | flash[:alert].should eql("The project you were looking for could not be found.") 19 | end 20 | 21 | { "new" => "get", 22 | "create" => "post", 23 | "edit" => "get", 24 | "update" => "put", 25 | "destroy" => "delete" }.each do |action, method| 26 | it "cannot access the #{action} action" do 27 | sign_in(:user, user) 28 | send(method, action.dup, :id => project.id) 29 | response.should redirect_to(root_path) 30 | flash[:alert].should eql("You must be an admin to do that.") 31 | end 32 | end 33 | end 34 | 35 | it "displays an error for a missing project" do 36 | sign_in(:user, user) 37 | get :show, :id => "not-here" 38 | response.should redirect_to(projects_path) 39 | message = "The project you were looking for could not be found." 40 | flash[:alert].should eql(message) 41 | end 42 | 43 | end 44 | -------------------------------------------------------------------------------- /spec/controllers/tags_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe TagsController do 4 | 5 | end 6 | -------------------------------------------------------------------------------- /spec/controllers/tickets_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe TicketsController do 4 | let(:user) { create_user! } 5 | let(:project) { Factory(:project) } 6 | let(:ticket) { Factory(:ticket, :project => project) } 7 | 8 | context "standard users" do 9 | it "cannot access a ticket for a project" do 10 | sign_in(:user, user) 11 | get :show, :id => ticket.id, :project_id => project.id 12 | response.should redirect_to(root_path) 13 | flash[:alert].should eql("The project you were looking for could not be found.") 14 | end 15 | 16 | context "with permission to view the project" do 17 | 18 | before do 19 | sign_in(:user, user) 20 | Permission.create(:user => user, :thing => project, :action => "view") 21 | end 22 | 23 | def cannot_create_tickets! 24 | response.should redirect_to(project) 25 | flash[:alert].should eql("You cannot create tickets on this project.") 26 | end 27 | 28 | def cannot_update_tickets! 29 | response.should redirect_to(project) 30 | flash[:alert].should eql("You cannot edit tickets on this project.") 31 | end 32 | 33 | it "cannot begin to create a ticket" do 34 | get :new, :project_id => project.id 35 | cannot_create_tickets! 36 | end 37 | 38 | it "cannot create a ticket without permission" do 39 | post :create, :project_id => project.id 40 | cannot_create_tickets! 41 | end 42 | 43 | it "cannot edit a ticket without permission" do 44 | get :edit, { :project_id => project.id, :id => ticket.id } 45 | cannot_update_tickets! 46 | end 47 | 48 | it "cannot update a ticket without permission" do 49 | put :update, { :project_id => project.id, :id => ticket.id } 50 | cannot_update_tickets! 51 | end 52 | 53 | it "cannot delete a ticket without permission" do 54 | delete :destroy, { :project_id => project.id, :id => ticket.id } 55 | response.should redirect_to(project) 56 | flash[:alert].should eql("You cannot delete tickets from this project.") 57 | end 58 | 59 | it "can create tickets, but not tag them" do 60 | Permission.create(:user => user, :thing => project, :action => "create tickets") 61 | post :create, :ticket => { :title => "New ticket!", 62 | :description => "Brand spankin' new" }, 63 | :project_id => project.id, 64 | :tags => "these are tags" 65 | 66 | Ticket.last.tags.should be_empty 67 | end 68 | end 69 | end 70 | end -------------------------------------------------------------------------------- /spec/controllers/users/omniauth_callbacks_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Users::OmniauthCallbacksController do 4 | 5 | end 6 | -------------------------------------------------------------------------------- /spec/controllers/users_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe UsersController do 4 | 5 | describe "GET 'confirmation'" do 6 | it "should be successful" do 7 | get 'confirmation' 8 | response.should be_success 9 | end 10 | end 11 | 12 | end 13 | -------------------------------------------------------------------------------- /spec/fixtures/gradient.txt: -------------------------------------------------------------------------------- 1 | Everything looks better with a gradient! -------------------------------------------------------------------------------- /spec/fixtures/speed.txt: -------------------------------------------------------------------------------- 1 | The blink tag can blink faster if you use the speed="hyper" attribute. -------------------------------------------------------------------------------- /spec/fixtures/spin.txt: -------------------------------------------------------------------------------- 1 | Spinning blink tags have a 200% higher click rate! -------------------------------------------------------------------------------- /spec/helpers/admin/permissions_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | # Specs in this file have access to a helper object that includes 4 | # the Admin::PermissionsHelper. For example: 5 | # 6 | # describe Admin::PermissionsHelper do 7 | # describe "string concat" do 8 | # it "concats two strings with spaces" do 9 | # helper.concat_strings("this","that").should == "this that" 10 | # end 11 | # end 12 | # end 13 | describe Admin::PermissionsHelper do 14 | pending "add some examples to (or delete) #{__FILE__}" 15 | end 16 | -------------------------------------------------------------------------------- /spec/helpers/admin/states_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | # Specs in this file have access to a helper object that includes 4 | # the Admin::StatesHelper. For example: 5 | # 6 | # describe Admin::StatesHelper do 7 | # describe "string concat" do 8 | # it "concats two strings with spaces" do 9 | # helper.concat_strings("this","that").should == "this that" 10 | # end 11 | # end 12 | # end 13 | describe Admin::StatesHelper do 14 | pending "add some examples to (or delete) #{__FILE__}" 15 | end 16 | -------------------------------------------------------------------------------- /spec/helpers/admin/users_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | # Specs in this file have access to a helper object that includes 4 | # the Admin::UsersHelper. For example: 5 | # 6 | # describe Admin::UsersHelper do 7 | # describe "string concat" do 8 | # it "concats two strings with spaces" do 9 | # helper.concat_strings("this","that").should == "this that" 10 | # end 11 | # end 12 | # end 13 | describe Admin::UsersHelper do 14 | pending "add some examples to (or delete) #{__FILE__}" 15 | end 16 | -------------------------------------------------------------------------------- /spec/helpers/comments_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | # Specs in this file have access to a helper object that includes 4 | # the CommentsHelper. For example: 5 | # 6 | # describe CommentsHelper do 7 | # describe "string concat" do 8 | # it "concats two strings with spaces" do 9 | # helper.concat_strings("this","that").should == "this that" 10 | # end 11 | # end 12 | # end 13 | describe CommentsHelper do 14 | pending "add some examples to (or delete) #{__FILE__}" 15 | end 16 | -------------------------------------------------------------------------------- /spec/helpers/files_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | # Specs in this file have access to a helper object that includes 4 | # the AssetsHelper. For example: 5 | # 6 | # describe AssetsHelper do 7 | # describe "string concat" do 8 | # it "concats two strings with spaces" do 9 | # helper.concat_strings("this","that").should == "this that" 10 | # end 11 | # end 12 | # end 13 | describe FilesHelper do 14 | pending "add some examples to (or delete) #{__FILE__}" 15 | end 16 | -------------------------------------------------------------------------------- /spec/helpers/projects_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | # Specs in this file have access to a helper object that includes 4 | # the ProjectsHelper. For example: 5 | # 6 | # describe ProjectsHelper do 7 | # describe "string concat" do 8 | # it "concats two strings with spaces" do 9 | # helper.concat_strings("this","that").should == "this that" 10 | # end 11 | # end 12 | # end 13 | describe ProjectsHelper do 14 | pending "add some examples to (or delete) #{__FILE__}" 15 | end 16 | -------------------------------------------------------------------------------- /spec/helpers/tags_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | # Specs in this file have access to a helper object that includes 4 | # the TagsHelper. For example: 5 | # 6 | # describe TagsHelper do 7 | # describe "string concat" do 8 | # it "concats two strings with spaces" do 9 | # helper.concat_strings("this","that").should == "this that" 10 | # end 11 | # end 12 | # end 13 | describe TagsHelper do 14 | pending "add some examples to (or delete) #{__FILE__}" 15 | end 16 | -------------------------------------------------------------------------------- /spec/helpers/tickets_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | # Specs in this file have access to a helper object that includes 4 | # the TicketsHelper. For example: 5 | # 6 | # describe TicketsHelper do 7 | # describe "string concat" do 8 | # it "concats two strings with spaces" do 9 | # helper.concat_strings("this","that").should == "this that" 10 | # end 11 | # end 12 | # end 13 | describe TicketsHelper do 14 | pending "add some examples to (or delete) #{__FILE__}" 15 | end 16 | -------------------------------------------------------------------------------- /spec/helpers/users/omniauth_callbacks_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | # Specs in this file have access to a helper object that includes 4 | # the Users::OmniauthCallbacksHelper. For example: 5 | # 6 | # describe Users::OmniauthCallbacksHelper do 7 | # describe "string concat" do 8 | # it "concats two strings with spaces" do 9 | # helper.concat_strings("this","that").should == "this that" 10 | # end 11 | # end 12 | # end 13 | describe Users::OmniauthCallbacksHelper do 14 | pending "add some examples to (or delete) #{__FILE__}" 15 | end 16 | -------------------------------------------------------------------------------- /spec/helpers/users_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | # Specs in this file have access to a helper object that includes 4 | # the UsersHelper. For example: 5 | # 6 | # describe UsersHelper do 7 | # describe "string concat" do 8 | # it "concats two strings with spaces" do 9 | # helper.concat_strings("this","that").should == "this that" 10 | # end 11 | # end 12 | # end 13 | describe UsersHelper do 14 | pending "add some examples to (or delete) #{__FILE__}" 15 | end 16 | -------------------------------------------------------------------------------- /spec/mailers/notifier_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Notifier do 4 | it "correctly sets the reply-to" do 5 | comment = Factory(:comment) 6 | mail = ActionMailer::Base.deliveries.last 7 | mail.from.should eql(["ticketee+#{comment.project.id}+" + 8 | "#{comment.ticket.id}@gmail.com"]) 9 | end 10 | end -------------------------------------------------------------------------------- /spec/mailers/receiver_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Receiver do 4 | 5 | it "parses a reply from a comment update into a comment" do 6 | comment = Factory(:comment) 7 | ticket = comment.ticket 8 | 9 | comment_email = ActionMailer::Base.deliveries.last 10 | 11 | user = Factory(:user) 12 | 13 | mail = Mail.new(:from => user.email, 14 | :subject => "Re: #{comment_email.subject}", 15 | :body => %Q{This is a brand new comment 16 | #{comment_email.default_part_body} 17 | }, 18 | :to => comment_email.from) 19 | 20 | 21 | lambda { Receiver.parse(mail) }.should( 22 | change(ticket.comments, :count).by(1) 23 | ) 24 | 25 | ticket.comments.last.text.should eql("This is a brand new comment") 26 | 27 | end 28 | end -------------------------------------------------------------------------------- /spec/models/asset_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Asset do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /spec/models/comment_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Comment do 4 | let(:user) { Factory(:user) } 5 | 6 | before do 7 | @ticket = Factory(:ticket) 8 | @ticket.watchers << user 9 | end 10 | 11 | it "notifies people through a delayed job" do 12 | Delayed::Job.count.should eql(0) 13 | ticket.comments.create!(:text => "This is a comment", 14 | :user => ticket.user) 15 | Delayed::Job.count.should eql(1) 16 | 17 | Delayed::Worker.new.work_off! 18 | Delayed::Job.count.should eql(0) 19 | 20 | email = ActionMailer::Base.deliveries.last 21 | email.to.should eql(user.email) 22 | end 23 | end -------------------------------------------------------------------------------- /spec/models/permission_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Permission do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /spec/models/project_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Project do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /spec/models/state_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe State do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /spec/models/tag_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Tag do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /spec/models/ticket_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Ticket do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /spec/models/user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe User do 4 | it "resets user request count" do 5 | user = Factory(:user) 6 | user.update_attribute(:request_count, 42) 7 | User.reset_request_count! 8 | user.reload 9 | user.request_count.should eql(0) 10 | end 11 | end -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file is copied to spec/ when you run 'rails generate rspec:install' 2 | ENV["RAILS_ENV"] ||= 'test' 3 | require File.expand_path("../../config/environment", __FILE__) 4 | require 'rspec/rails' 5 | 6 | # Requires supporting ruby files with custom matchers and macros, etc, 7 | # in spec/support/ and its subdirectories. 8 | Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} 9 | 10 | RSpec.configure do |config| 11 | # == Mock Framework 12 | # 13 | # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line: 14 | # 15 | # config.mock_with :mocha 16 | # config.mock_with :flexmock 17 | # config.mock_with :rr 18 | config.mock_with :rspec 19 | 20 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 21 | config.fixture_path = "#{::Rails.root}/spec/fixtures" 22 | 23 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 24 | # examples within a transaction, remove the following line or assign false 25 | # instead of true. 26 | config.use_transactional_fixtures = true 27 | end 28 | -------------------------------------------------------------------------------- /spec/support/api/helper.rb: -------------------------------------------------------------------------------- 1 | module ApiHelper 2 | include Rack::Test::Methods 3 | 4 | def app 5 | Rails.application 6 | end 7 | end 8 | 9 | RSpec.configure do |c| 10 | c.include ApiHelper, :type => :api 11 | end -------------------------------------------------------------------------------- /spec/support/devise.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.include Devise::TestHelpers, :type => :controller 3 | end -------------------------------------------------------------------------------- /spec/support/factories.rb: -------------------------------------------------------------------------------- 1 | Dir[Rails.root + "factories/*.rb"].each do |file| 2 | require file 3 | end -------------------------------------------------------------------------------- /spec/support/seed_helpers.rb: -------------------------------------------------------------------------------- 1 | module SeedHelpers 2 | def create_user!(attributes={}) 3 | user = Factory(:user, attributes) 4 | user.confirm! 5 | user 6 | end 7 | end 8 | 9 | RSpec.configure do |config| 10 | config.include SeedHelpers 11 | end -------------------------------------------------------------------------------- /spec/views/admin/users/index.html.erb_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "users/index.html.erb" do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /spec/views/users/confirmation.html.erb_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "users/confirmation.html.erb" do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails3book/ticketee/67362e12751ea734354a94e3d78b835ac7761ac7/vendor/assets/stylesheets/.gitkeep -------------------------------------------------------------------------------- /vendor/plugins/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails3book/ticketee/67362e12751ea734354a94e3d78b835ac7761ac7/vendor/plugins/.gitkeep --------------------------------------------------------------------------------