softwear 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +37 -0
  4. data/app/assets/javascripts/softwear/application.js +13 -0
  5. data/app/assets/javascripts/softwear/error_utils.js.coffee +82 -0
  6. data/app/assets/javascripts/softwear/modals.js.coffee +171 -0
  7. data/app/assets/stylesheets/softwear/application.css +33 -0
  8. data/app/controllers/softwear/application_controller.rb +4 -0
  9. data/app/controllers/softwear/error_reports_controller.rb +37 -0
  10. data/app/helpers/softwear/application_helper.rb +4 -0
  11. data/app/helpers/softwear/emails_helper.rb +9 -0
  12. data/app/mailers/error_report_mailer.rb +53 -0
  13. data/app/views/error_report_mailer/send_report.html.erb +30 -0
  14. data/app/views/layouts/softwear/application.html.erb +14 -0
  15. data/app/views/softwear/errors/_error.html.erb +35 -0
  16. data/app/views/softwear/errors/internal_server_error.html.erb +6 -0
  17. data/app/views/softwear/errors/internal_server_error.js.erb +10 -0
  18. data/bin/rails +12 -0
  19. data/config/routes.rb +3 -0
  20. data/lib/softwear/auth/belongs_to_user.rb +43 -0
  21. data/lib/softwear/auth/controller.rb +43 -0
  22. data/lib/softwear/auth/helper.rb +35 -0
  23. data/lib/softwear/auth/model.rb +16 -0
  24. data/lib/softwear/auth/spec.rb +62 -0
  25. data/lib/softwear/auth/standard_model.rb +498 -0
  26. data/lib/softwear/auth/stubbed_model.rb +130 -0
  27. data/lib/softwear/auth/token_authentication.rb +72 -0
  28. data/lib/softwear/engine.rb +5 -0
  29. data/lib/softwear/error_catcher.rb +65 -0
  30. data/lib/softwear/library/api_controller.rb +169 -0
  31. data/lib/softwear/library/capistrano.rb +94 -0
  32. data/lib/softwear/library/controller_authentication.rb +145 -0
  33. data/lib/softwear/library/enqueue.rb +87 -0
  34. data/lib/softwear/library/spec.rb +127 -0
  35. data/lib/softwear/library/tcp_server.rb +107 -0
  36. data/lib/softwear/version.rb +3 -0
  37. data/lib/softwear.rb +107 -0
  38. metadata +172 -0
@@ -0,0 +1,130 @@
1
+ module Softwear
2
+ module Auth
3
+ # =======================================================================
4
+ # In development, unless ENV['AUTH_SERVER'] is set, Softwear::Auth::Model is
5
+ # set to this StubbedModel. The StubbedModel does not contact the auth_server,
6
+ # and instead retrieves user info from config/users.yml, which assumes the
7
+ # following format:
8
+ =begin
9
+ ---
10
+ signed_in: nigel@annarbortees.com
11
+
12
+ users:
13
+ admin@softwearcrm.com:
14
+ first_name: Admin
15
+ last_name: User
16
+ roles:
17
+ - sales_manager
18
+
19
+ nigel@annarbortees.com:
20
+ first_name: Nigel
21
+ last_name: Baillie
22
+ roles:
23
+ - developer
24
+ - admin
25
+
26
+ ricky@annarbortees.com:
27
+ first_name: Ricky
28
+ last_name: Winowiecki
29
+ roles:
30
+ - developer
31
+ - administrator
32
+ =end
33
+ # Changes in this .yml file will be reflected live, without restarting your
34
+ # server.
35
+ # =======================================================================
36
+ class StubbedModel < Softwear::Auth::StandardModel
37
+ class << self
38
+ def raw_query(m, *args)
39
+ return 'yes' if m =~ /^token/
40
+ raise %[Cannot perform auth server queries on stubbed auth model. (tried to send "#{m.split(/\s+/).first} ..." query)]
41
+ end
42
+
43
+ def users_yml_file
44
+ Rails.root.join('config', 'users.yml').to_s
45
+ end
46
+
47
+ def users_yml
48
+ if @users_yml
49
+ yml_mtime = File.mtime(users_yml_file)
50
+
51
+ if @users_yml_modified.nil? || yml_mtime > @users_yml_modified
52
+ @users_yml_modified = yml_mtime
53
+ @users_yml = nil
54
+ end
55
+ else
56
+ @users_yml_modified = File.mtime(users_yml_file)
57
+ end
58
+
59
+ if @users_yml.nil?
60
+ @users_yml = YAML.load(IO.read(users_yml_file)).with_indifferent_access
61
+ @users_yml[:users].to_a.each_with_index do |entry, i|
62
+ entry[1][:id] ||= i + 1
63
+ end
64
+ end
65
+ @users_yml
66
+ end
67
+
68
+ #
69
+ # Transforms
70
+ # ['email@email.com', { 'attr1' => 'val1', 'attr2' => 'val2' }]
71
+ # From the yml format
72
+ #
73
+ # Into
74
+ # { 'email' => 'email@email.com', 'attr1' => 'val1', 'attr2' => 'val2' }
75
+ #
76
+ def yml_entry(entry, id_if_default = nil)
77
+ attributes = {}.with_indifferent_access
78
+
79
+ if entry.nil?
80
+ entry = ['usernotfound@example.com', { first_name: 'Unknown', last_name: 'User', id: id_if_default || -1 }]
81
+ end
82
+
83
+ attributes[:email] = entry[0]
84
+ attributes.merge!(entry[1])
85
+ if attributes[:profile_picture]
86
+ attributes[:profile_picture_url] ||= "file://#{attributes[:profile_picture]}"
87
+ end
88
+
89
+ new(attributes).tap { |u| u.instance_variable_set(:@persisted, true) }
90
+ end
91
+
92
+ def find(target_id)
93
+ return nil if target_id.nil?
94
+ yml_entry users_yml[:users].to_a[target_id.to_i - 1], target_id.to_i
95
+ end
96
+
97
+ def all
98
+ users_yml[:users].to_a.map(&method(:yml_entry))
99
+ end
100
+
101
+ def of_role(*roles)
102
+ roles = Array(roles).map(&:to_s)
103
+ all.select { |u| !u.roles.nil? && roles.any? { |r| u.roles.include?(r) } }
104
+ end
105
+
106
+ def auth(_token, _appname = nil)
107
+ signed_in = users_yml[:signed_in]
108
+
109
+ yml_entry [signed_in, users_yml[:users][signed_in]] unless signed_in.blank?
110
+ end
111
+ end
112
+
113
+ def yml_entry(*args)
114
+ self.class.yml_entry(*args)
115
+ end
116
+ def users_yml(*args)
117
+ self.class.users_yml(*args)
118
+ end
119
+
120
+ def reload
121
+ update_attributes yml_entry users_yml[:users].to_a[id - 1]
122
+ self
123
+ end
124
+
125
+ def valid_password?
126
+ true
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,72 @@
1
+ module Softwear
2
+ module Auth
3
+ module TokenAuthentication
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ cattr_accessor :user_class
8
+ cattr_accessor :token_auth_options
9
+ end
10
+
11
+ def token_authenticate_user!
12
+ user_class = self.class.user_class || base_class.user_class || User
13
+ options = (self.class.token_auth_options || base_class.token_auth_options || {}).with_indifferent_access
14
+ params_options = (options[:params] || {}).with_indifferent_access
15
+ headers_options = (options[:headers] || {}).with_indifferent_access
16
+
17
+ email_param = params_options[:email] || 'user_email'
18
+ token_param = params_options[:authentication_token] || 'user_token'
19
+ email_header = headers_options[:email] || 'X-User-Email'
20
+ token_header = headers_options[:authentication_token] || 'X-User-Token'
21
+
22
+ email = params[email_param] || request.headers[email_header]
23
+ token = params[token_param] || request.headers[token_header]
24
+
25
+ return render_unauthorized if email.blank? || token.blank?
26
+
27
+ case user_class.query "token #{Figaro.env.hub_app_name} #{email} #{token}"
28
+ when 'no' then render_unauthorized
29
+ when 'invaild' then render_unauthorized
30
+ when 'sorry' then render_internal_server_error
31
+ when 'yes' then true
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def http_headers
38
+ Hash[
39
+ request.headers.each
40
+ .select { |h| h[0] =~ /^HTTP/ }
41
+ .map { |h| [h[0].gsub(/^HTTP_/, ''), h[1]] }
42
+ ]
43
+ end
44
+
45
+ def render_unauthorized
46
+ Rails.logger.error "#{self.class.name} Token authentication unauthorized request.\n"\
47
+ "Params: #{JSON.pretty_generate(params)}\n"\
48
+ "Headers: #{JSON.pretty_generate(http_headers)}"
49
+
50
+ respond_to do |format|
51
+ format.json do
52
+ render status: :unauthorized,
53
+ json: { error: "Invalid or missing credentials" }
54
+ end
55
+ end
56
+ end
57
+
58
+ def render_internal_server_error
59
+ Rails.logger.error "#{self.class.name} Token authentication request resulted in error.\n"\
60
+ "Params: #{JSON.pretty_generate(params)}\n"\
61
+ "Headers: #{JSON.pretty_generate(http_headers)}"
62
+
63
+ respond_to do |format|
64
+ format.json do
65
+ render status: :internal_server_error,
66
+ json: { error: "Authentication server broke" }
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,5 @@
1
+ module Softwear
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Softwear
4
+ end
5
+ end
@@ -0,0 +1,65 @@
1
+ module Softwear
2
+ module ErrorCatcher
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ rescue_from Exception, StandardError, with: :error_report_form unless Rails.env.test?
7
+ end
8
+
9
+ protected
10
+
11
+ def iterm_annotation(content)
12
+ puts "=======================================================\033]1337;AddAnnotation=#{content}\a"
13
+ end
14
+
15
+ def error_report_form(error)
16
+ time_of_error = Time.now.strftime("%x %I:%M %p")
17
+
18
+ iterm_annotation("Begin #{error.class.name} (#{time_of_error})")
19
+ Rails.logger.error "**** #{error.class.name}: #{error.message} ****\n\n"\
20
+ "\t#{error.backtrace.join("\n\t")}"
21
+ iterm_annotation("End #{error.class.name} (#{time_of_error})")
22
+
23
+ @error = error
24
+ @additional_info = gather_additional_info
25
+
26
+ begin
27
+ respond_to do |format|
28
+ format.html { render 'softwear/errors/internal_server_error', layout: layout_for_error, status: 500 }
29
+ format.js { render 'softwear/errors/internal_server_error', layout: layout_for_error, status: 500 }
30
+ format.json { render json: '{}', status: 500 }
31
+ end
32
+ rescue AbstractController::DoubleRenderError => e
33
+ Rails.logger.error "DOUBLE RENDER ERROR IN CONTROLLER ERROR CATCHER!!! #{e.message}"
34
+ end
35
+ end
36
+
37
+ def filter_params(params)
38
+ new_hash = {}
39
+ params.each do |key, value|
40
+ new_value = value
41
+
42
+ case key.to_s
43
+ when /cc_number/ then new_value = "<FILTERED>"
44
+ when /cc_cvc/ then new_value = "<FILTERED>"
45
+ when /password/ then new_value = "<FILTERED>"
46
+ end
47
+
48
+ new_hash[key] = new_value
49
+ end
50
+ new_hash
51
+ end
52
+
53
+ def gather_additional_info
54
+ JSON.pretty_generate(filter_params(params)) + "|||" +
55
+ instance_variables
56
+ .reject { |v| /^@_/ =~ v.to_s || %i(@view_renderer @output_buffer @view_flow @error).include?(v) }
57
+ .map { |v| "#{v}: #{instance_variable_get(v).inspect}" }
58
+ .join("|||")
59
+ end
60
+
61
+ def layout_for_error
62
+ current_user ? 'application' : 'no_overlay'
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,169 @@
1
+ require 'inherited_resources'
2
+
3
+ module Softwear
4
+ module Library
5
+ class ApiController < ActionController::Base
6
+ include ::InheritedResources::Actions
7
+ include ::InheritedResources::BaseHelpers
8
+ extend ::InheritedResources::ClassMethods
9
+ extend ::InheritedResources::UrlHelpers
10
+
11
+ skip_before_filter :authenticate_user!
12
+ skip_before_filter :verify_authenticity_token
13
+
14
+ before_filter :allow_origin
15
+
16
+ respond_to :json
17
+ self.responder = InheritedResources::Responder
18
+
19
+ self.class_attribute :resource_class, :instance_writer => false unless self.respond_to? :resource_class
20
+ self.class_attribute :parents_symbols, :resources_configuration, :instance_writer => false
21
+
22
+ def self.base_class
23
+ Softwear::Library::ApiController
24
+ end
25
+
26
+ def self.token_authenticate(user_class, options = {})
27
+ include Softwear::Auth::TokenAuthentication
28
+ self.user_class = user_class
29
+ self.token_auth_options = options
30
+ prepend_before_filter :token_authenticate_user!
31
+ end
32
+
33
+ def index(&block)
34
+ yield if block_given?
35
+
36
+ key_values = (permitted_attributes + [:id]).uniq.map do |a|
37
+ [a, params[a]] if params[a]
38
+ end
39
+ .compact
40
+
41
+ self.records ||= resource_class
42
+ self.records = records.where(Hash[key_values])
43
+
44
+ respond_to do |format|
45
+ format.json(&render_json(records))
46
+ end
47
+ end
48
+
49
+ def show
50
+ super do |format|
51
+ format.json(&render_json)
52
+ end
53
+ end
54
+
55
+ def update
56
+ self.record = resource_class.find(params[:id])
57
+
58
+ permitted_attributes.each do |a|
59
+ begin
60
+ record.send("#{a}=", params[a]) if params[a]
61
+ rescue ActiveRecord::AssociationTypeMismatch
62
+ # If you try to send "job" as an attribute to order, we're assuming it's
63
+ # not intentional. Send "job_attributes" or update the job model separately
64
+ # to actually update the job.
65
+ end
66
+ end
67
+
68
+ if record.save
69
+ respond_to { |format| format.json(&render_json) }
70
+ else
71
+ respond_to { |format| format.json(&render_errors) }
72
+ end
73
+ end
74
+
75
+ def create
76
+ create! do |success, failure|
77
+ success.json do
78
+ headers['Location'] = resource_url(record.id)
79
+ render_json(status: 201).call
80
+ end
81
+ failure.json(&render_errors)
82
+ end
83
+ end
84
+
85
+ def options
86
+ head(:ok) if request.request_method == 'OPTIONS'
87
+ end
88
+
89
+ protected
90
+
91
+ def base_class
92
+ self.class.base_class
93
+ end
94
+
95
+ def render_json(options = {})
96
+ proc do
97
+ if options.is_a?(Hash)
98
+ records = nil
99
+ else
100
+ records = options
101
+ options = {}
102
+ end
103
+ rendering = {
104
+ json: (records || record),
105
+ methods: (['id'] + permitted_attributes).uniq,
106
+ include: includes
107
+ }
108
+ .merge(options)
109
+
110
+ render rendering
111
+ end
112
+ end
113
+
114
+ def render_errors(options = {})
115
+ proc do
116
+ Rails.logger.error "API #{record.class.name} ERROR: #{record.errors.full_messages}"
117
+
118
+ rendering = {
119
+ json: { errors: record.errors },
120
+ status: :unprocessable_entity
121
+ }
122
+ .merge(options)
123
+
124
+ render rendering
125
+ end
126
+ end
127
+
128
+ def self.model_name
129
+ name.gsub('Api::', '').gsub('Controller', '').singularize
130
+ end
131
+
132
+ # Override this to specify the :include option of rendering json.
133
+ def includes
134
+ {}
135
+ end
136
+
137
+ def records
138
+ instance_variable_get("@#{resource_class.model_name.collection}")
139
+ end
140
+
141
+ def records=(r)
142
+ instance_variable_set("@#{resource_class.model_name.collection}", r)
143
+ end
144
+
145
+ def record
146
+ instance_variable_get("@#{resource_class.model_name.element}")
147
+ end
148
+
149
+ def record=(r)
150
+ instance_variable_set("@#{resource_class.model_name.element}", r)
151
+ end
152
+
153
+ def allow_origin
154
+ headers['Access-Control-Allow-Origin'] = '*'
155
+ headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, PATCH, DELETE'
156
+ headers['Access-Control-Allow-Headers'] = 'X-Requested-With'
157
+ headers['Access-Control-Allow-Credentials'] = 'true'
158
+ end
159
+
160
+ def permitted_attributes
161
+ resource_class.column_names + ['state_event']
162
+ end
163
+
164
+ def permitted_params
165
+ { resource_class.model_name.element => permitted_attributes }
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,94 @@
1
+ module Softwear
2
+ module Library
3
+ def self.capistrano(context)
4
+ context.instance_eval do
5
+ Rake::Task["deploy:compile_assets"].clear
6
+
7
+ ruby = fetch(:rvm_ruby_string) || fetch(:rvm_ruby_version)
8
+
9
+ namespace :deploy do
10
+
11
+ desc 'Assure softwear-lib is up to date before deploying'
12
+ task :update_softwear_lib do
13
+ on roles(:app), in: :sequence do
14
+ execute "~/.rvm/bin/rvm #{ruby} do gem install --no-ri --no-rdoc softwear-lib"
15
+ end
16
+ end
17
+
18
+ desc 'Signal the running rails app to restart'
19
+ task :restart do
20
+ on roles([:web, :app]), in: :sequence, wait: 5 do
21
+ execute :mkdir, '-p', "#{release_path}/tmp"
22
+ execute :touch, release_path.join('tmp/restart.txt')
23
+ end
24
+ end
25
+
26
+ after :publishing, :restart_sidekiq_manager do
27
+ on roles(:sidekiq) do
28
+ execute 'sudo restart sidekiq-manager'
29
+ end
30
+ end
31
+
32
+ # This is more problematic than helpful
33
+ =begin
34
+ if !$LOAD_PATH.grep(/sunspot_solr/).empty? || fetch(:no_reindex)
35
+ before :publishing, :reindex_solr do
36
+ on roles(:db) do
37
+ with rails_env: fetch(:rails_env) || fetch(:stage) do
38
+ within(release_path) do
39
+ execute :rake, 'sunspot:solr:reindex'
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ =end
46
+
47
+ # This is no longer necessary
48
+ # before :updating, :update_softwear_lib
49
+ after :publishing, :restart
50
+
51
+ desc 'Compile assets'
52
+ task :compile_assets => [:set_rails_env] do
53
+ invoke 'deploy:assets:precompile_local'
54
+ invoke 'deploy:assets:backup_manifest'
55
+ end
56
+
57
+ namespace :assets do
58
+
59
+ desc "Precompile assets locally and then rsync to web servers"
60
+ task :precompile_local do
61
+ # compile assets locally
62
+ run_locally do
63
+ execute "RAILS_ENV=#{fetch(:stage)} bundle exec rake assets:precompile"
64
+ end
65
+
66
+ # rsync to each server
67
+ assets_dir = "public/#{fetch(:assets_prefix) || 'assets'}"
68
+ local_dir = "./#{assets_dir}/"
69
+ on roles( fetch(:assets_roles, [:web]) ) do
70
+ # this needs to be done outside run_locally in order for host to exist
71
+ remote_dir = "#{host.user}@#{host.hostname}:#{release_path}/#{assets_dir}"
72
+
73
+ execute "mkdir -p #{release_path}/#{assets_dir}"
74
+ run_locally { execute "rsync -av --delete #{local_dir} #{remote_dir}" }
75
+
76
+ if fetch(:asset_sync)
77
+ with rails_env: fetch(:rails_env) || fetch(:stage) do
78
+ within(release_path) do
79
+ with_rvm(fetch(:task_ruby_version)) { execute :rake, 'assets:sync' }
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ # clean up
86
+ run_locally { execute "rm -rf #{local_dir}" }
87
+ end
88
+ end
89
+
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,145 @@
1
+ module Softwear
2
+ module Library
3
+ module ControllerAuthentication
4
+ extend ActiveSupport::Concern
5
+
6
+ class NotSignedInError < StandardError
7
+ end
8
+
9
+ included do
10
+ rescue_from NotSignedInError, with: :user_not_signed_in
11
+ rescue_from Softwear::Auth::Model::AuthServerDown, with: :auth_server_down
12
+
13
+ helper_method :current_user
14
+ helper_method :user_signed_in?
15
+
16
+ helper_method :destroy_user_session_path
17
+ helper_method :users_path
18
+ helper_method :user_path
19
+ helper_method :edit_user_path
20
+ end
21
+
22
+ def user_class
23
+ if Softwear::Auth::Model.descendants.size > 1
24
+ raise "More than one descendent of Softwear::Auth::Model is not supported."
25
+ elsif Softwear::Auth::Model.descendants.size == 0
26
+ # Assume there is a "User" model
27
+ begin
28
+ User.inspect
29
+ return User if User.ancestors.include?(Softwear::Auth::Model)
30
+ rescue NameError => _
31
+ end
32
+ raise "Please define a user model that extends Softwear::Auth::Model."
33
+ end
34
+ Softwear::Auth::Model.descendants.first
35
+ end
36
+
37
+ # ====================
38
+ # Action called when a NotSignedInError is raised.
39
+ # ====================
40
+ def user_not_signed_in
41
+ if Softwear::Auth::Model::STUBBED
42
+ self.user_token = "dummy-token"
43
+ authenticate_user!
44
+ else
45
+ redirect_to softwear_hub_url + "/users/sign_in?#{{return_to: request.original_url}.to_param}"
46
+ end
47
+ end
48
+
49
+ # ====================
50
+ # Action called when a NotSignedInError is raised.
51
+ # ====================
52
+ def auth_server_down(error)
53
+ respond_to do |format|
54
+ format.html do
55
+ render inline: \
56
+ "<div class='panel panel-danger'>"\
57
+ "<div class='panel-heading'>"\
58
+ "<h3 class='panel-title'>#{error.message}</h3>"\
59
+ "</div>"\
60
+ "<div class='panel-body'>"\
61
+ "Not all site functions will work until the problem is resolved. "\
62
+ "<a href='javascript' onclick='history.go(-1);return false;' class='btn btn-default'>Go back.</a>"\
63
+ "</div>"\
64
+ "</div>"
65
+ end
66
+
67
+ format.js do
68
+ render inline: "alert(\"#{error.message.gsub('"', '\"')}\");"
69
+ end
70
+ end
71
+ end
72
+
73
+ # ====================
74
+ # Drop this into a before_filter to require a user be signed in on every request -
75
+ # just like in Devise.
76
+ # ====================
77
+ def authenticate_user!
78
+ if user_token.blank?
79
+ raise NotSignedInError, "No token"
80
+ end
81
+
82
+ if user = user_class.auth(user_token)
83
+ @current_user = user
84
+ else
85
+ self.user_token = nil
86
+ raise NotSignedInError, "Invalid token"
87
+ end
88
+ end
89
+
90
+ def current_user
91
+ return @current_user if @current_user
92
+
93
+ if @current_user.nil? && !user_token.blank?
94
+ @current_user = user_class.auth(user_token)
95
+ else
96
+ nil
97
+ end
98
+ end
99
+
100
+ def user_signed_in?
101
+ !!current_user
102
+ end
103
+
104
+ # -- url uelpers --
105
+
106
+ def softwear_hub_url
107
+ if Rails.env.production?
108
+ Figaro.env.softwear_hub_url || (raise "Please set softwear_hub_url in application.yml")
109
+ elsif Rails.env.test?
110
+ 'http://hub.example.com'
111
+ else
112
+ Figaro.env.softwear_hub_url || 'http://localhost:2995'
113
+ end
114
+ end
115
+
116
+ def destroy_user_session_path
117
+ softwear_hub_url + "/users/sign_out"
118
+ end
119
+
120
+ def user_path(user)
121
+ user_id = user.is_a?(user_class) ? user.id : user
122
+ softwear_hub_url + "/users/#{user_id}"
123
+ end
124
+
125
+ def edit_user_path(user)
126
+ user_id = user.is_a?(user_class) ? user.id : user
127
+ softwear_hub_url + "/users/#{user_id}/edit"
128
+ end
129
+
130
+ def users_path
131
+ softwear_hub_url + "/users"
132
+ end
133
+
134
+ private
135
+
136
+ def user_token
137
+ session[:user_token]
138
+ end
139
+
140
+ def user_token=(new_token)
141
+ session[:user_token] = new_token
142
+ end
143
+ end
144
+ end
145
+ end