device_tracker 0.3.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 (113) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +4 -0
  5. data/Gemfile +4 -0
  6. data/Rakefile +10 -0
  7. data/Readme.md +32 -0
  8. data/bin/console +14 -0
  9. data/bin/setup +7 -0
  10. data/device_tracker.gemspec +63 -0
  11. data/exe/device_tracker +91 -0
  12. data/lib/device_tracker/app.rb +22 -0
  13. data/lib/device_tracker/config-schema.json +111 -0
  14. data/lib/device_tracker/config.ru +9 -0
  15. data/lib/device_tracker/controllers/application_controller.rb +226 -0
  16. data/lib/device_tracker/controllers/devices_controller.rb +315 -0
  17. data/lib/device_tracker/controllers/heartbeat_controller.rb +50 -0
  18. data/lib/device_tracker/controllers/os_controller.rb +38 -0
  19. data/lib/device_tracker/controllers/transactions_controller.rb +18 -0
  20. data/lib/device_tracker/controllers/users_controller.rb +131 -0
  21. data/lib/device_tracker/db/migrate/20150521071815_create_users.rb +14 -0
  22. data/lib/device_tracker/db/migrate/20150521082155_create_devices.rb +19 -0
  23. data/lib/device_tracker/db/migrate/20150521120335_create_operating_systems.rb +9 -0
  24. data/lib/device_tracker/db/migrate/20150527162242_create_transactions.rb +12 -0
  25. data/lib/device_tracker/db/migrate/20151027073050_create_heartbeat.rb +11 -0
  26. data/lib/device_tracker/db/migrate/20151028132946_add_user_verification.rb +8 -0
  27. data/lib/device_tracker/db/migrate/20151028141328_remove_is_active_from_users.rb +6 -0
  28. data/lib/device_tracker/db/migrate/20151029085629_add_password_reset_code_to_users.rb +8 -0
  29. data/lib/device_tracker/db/migrate/20151030130341_add_missing_column_to_devices.rb +8 -0
  30. data/lib/device_tracker/db/migrate/20151102141601_add_serial_number_to_devices.rb +8 -0
  31. data/lib/device_tracker/db/schema.rb +61 -0
  32. data/lib/device_tracker/db/seeds.rb +21 -0
  33. data/lib/device_tracker/dependencies.rb +16 -0
  34. data/lib/device_tracker/device_tracker.rb +18 -0
  35. data/lib/device_tracker/helpers/application_helper.rb +116 -0
  36. data/lib/device_tracker/helpers/user_helper.rb +24 -0
  37. data/lib/device_tracker/models/device.rb +42 -0
  38. data/lib/device_tracker/models/heartbeat.rb +7 -0
  39. data/lib/device_tracker/models/operating_system.rb +7 -0
  40. data/lib/device_tracker/models/transaction.rb +62 -0
  41. data/lib/device_tracker/models/user.rb +28 -0
  42. data/lib/device_tracker/public/css/bootstrap-sortable.css +100 -0
  43. data/lib/device_tracker/public/css/bootstrap.min.css +5 -0
  44. data/lib/device_tracker/public/css/custom.css +88 -0
  45. data/lib/device_tracker/public/favicon/android-chrome-144x144.png +0 -0
  46. data/lib/device_tracker/public/favicon/android-chrome-192x192.png +0 -0
  47. data/lib/device_tracker/public/favicon/android-chrome-36x36.png +0 -0
  48. data/lib/device_tracker/public/favicon/android-chrome-48x48.png +0 -0
  49. data/lib/device_tracker/public/favicon/android-chrome-72x72.png +0 -0
  50. data/lib/device_tracker/public/favicon/android-chrome-96x96.png +0 -0
  51. data/lib/device_tracker/public/favicon/apple-touch-icon-114x114.png +0 -0
  52. data/lib/device_tracker/public/favicon/apple-touch-icon-120x120.png +0 -0
  53. data/lib/device_tracker/public/favicon/apple-touch-icon-144x144.png +0 -0
  54. data/lib/device_tracker/public/favicon/apple-touch-icon-152x152.png +0 -0
  55. data/lib/device_tracker/public/favicon/apple-touch-icon-180x180.png +0 -0
  56. data/lib/device_tracker/public/favicon/apple-touch-icon-57x57.png +0 -0
  57. data/lib/device_tracker/public/favicon/apple-touch-icon-60x60.png +0 -0
  58. data/lib/device_tracker/public/favicon/apple-touch-icon-72x72.png +0 -0
  59. data/lib/device_tracker/public/favicon/apple-touch-icon-76x76.png +0 -0
  60. data/lib/device_tracker/public/favicon/apple-touch-icon-precomposed.png +0 -0
  61. data/lib/device_tracker/public/favicon/apple-touch-icon.png +0 -0
  62. data/lib/device_tracker/public/favicon/browserconfig.xml +12 -0
  63. data/lib/device_tracker/public/favicon/favicon-16x16.png +0 -0
  64. data/lib/device_tracker/public/favicon/favicon-32x32.png +0 -0
  65. data/lib/device_tracker/public/favicon/favicon-96x96.png +0 -0
  66. data/lib/device_tracker/public/favicon/favicon.ico +0 -0
  67. data/lib/device_tracker/public/favicon/manifest.json +41 -0
  68. data/lib/device_tracker/public/favicon/mstile-144x144.png +0 -0
  69. data/lib/device_tracker/public/favicon/mstile-150x150.png +0 -0
  70. data/lib/device_tracker/public/favicon/mstile-310x150.png +0 -0
  71. data/lib/device_tracker/public/favicon/mstile-310x310.png +0 -0
  72. data/lib/device_tracker/public/favicon/mstile-70x70.png +0 -0
  73. data/lib/device_tracker/public/favicon/safari-pinned-tab.svg +21 -0
  74. data/lib/device_tracker/public/favicon.png +0 -0
  75. data/lib/device_tracker/public/fonts/glyphicons-halflings-regular.eot +0 -0
  76. data/lib/device_tracker/public/fonts/glyphicons-halflings-regular.svg +288 -0
  77. data/lib/device_tracker/public/fonts/glyphicons-halflings-regular.ttf +0 -0
  78. data/lib/device_tracker/public/fonts/glyphicons-halflings-regular.woff +0 -0
  79. data/lib/device_tracker/public/fonts/glyphicons-halflings-regular.woff2 +0 -0
  80. data/lib/device_tracker/public/js/bootstrap-sortable.js +211 -0
  81. data/lib/device_tracker/public/js/bootstrap.min.js +7 -0
  82. data/lib/device_tracker/public/js/jquery-2.1.4.min.js +4 -0
  83. data/lib/device_tracker/version.rb +4 -0
  84. data/lib/device_tracker/views/404.erb +25 -0
  85. data/lib/device_tracker/views/_alert.erb +5 -0
  86. data/lib/device_tracker/views/_device_form.erb +52 -0
  87. data/lib/device_tracker/views/_device_list.erb +47 -0
  88. data/lib/device_tracker/views/_footer.erb +3 -0
  89. data/lib/device_tracker/views/_header.erb +97 -0
  90. data/lib/device_tracker/views/_heartbeat_list.erb +25 -0
  91. data/lib/device_tracker/views/_user_form.erb +30 -0
  92. data/lib/device_tracker/views/devices/edit.erb +11 -0
  93. data/lib/device_tracker/views/devices/index.erb +12 -0
  94. data/lib/device_tracker/views/devices/new.erb +10 -0
  95. data/lib/device_tracker/views/devices/show.erb +283 -0
  96. data/lib/device_tracker/views/devices/users.erb +14 -0
  97. data/lib/device_tracker/views/emails/new_password.erb +17 -0
  98. data/lib/device_tracker/views/emails/password_reset.erb +18 -0
  99. data/lib/device_tracker/views/emails/registration.erb +16 -0
  100. data/lib/device_tracker/views/emails/reminder.erb +15 -0
  101. data/lib/device_tracker/views/emails/verification.erb +18 -0
  102. data/lib/device_tracker/views/forgot_password.erb +6 -0
  103. data/lib/device_tracker/views/index.erb +38 -0
  104. data/lib/device_tracker/views/layout.erb +8 -0
  105. data/lib/device_tracker/views/login.erb +14 -0
  106. data/lib/device_tracker/views/operating_system/operating_systems.json.jbuilder +9 -0
  107. data/lib/device_tracker/views/os/manage.erb +38 -0
  108. data/lib/device_tracker/views/transactions/_transactions_list.erb +18 -0
  109. data/lib/device_tracker/views/transactions/index.erb +3 -0
  110. data/lib/device_tracker/views/users/edit.erb +9 -0
  111. data/lib/device_tracker/views/users/manage.erb +31 -0
  112. data/lib/device_tracker/views/users/new.erb +7 -0
  113. metadata +505 -0
@@ -0,0 +1,61 @@
1
+ # encoding: UTF-8
2
+ ActiveRecord::Schema.define(version: 20_151_109_121_036) do
3
+ create_table 'devices', force: :cascade do |t|
4
+ t.string 'unid'
5
+ t.string 'manufacturer'
6
+ t.string 'device'
7
+ t.text 'description'
8
+ t.datetime 'checked_out_since'
9
+ t.boolean 'available', default: true
10
+ t.string 'imei'
11
+ t.integer 'operating_system_id'
12
+ t.integer 'user_id'
13
+ t.boolean 'sim_card', default: false
14
+ t.boolean 'debug_device', default: false
15
+ t.datetime 'created_at', null: false
16
+ t.datetime 'updated_at', null: false
17
+ t.boolean 'missing', default: false
18
+ t.integer 'checkout_count', default: 0
19
+ t.string 'serial_number'
20
+ end
21
+
22
+ add_index 'devices', ['unid'], name: 'index_devices_on_unid'
23
+
24
+ create_table 'heartbeats', force: :cascade do |t|
25
+ t.decimal 'longitude', precision: 10, scale: 6
26
+ t.decimal 'latitude', precision: 10, scale: 6
27
+ t.integer 'device_id'
28
+ t.datetime 'created_at', null: false
29
+ t.datetime 'updated_at', null: false
30
+ end
31
+
32
+ create_table 'operating_systems', force: :cascade do |t|
33
+ t.string 'name'
34
+ t.integer 'api_level'
35
+ end
36
+
37
+ create_table 'transactions', force: :cascade do |t|
38
+ t.string 'transaction_type'
39
+ t.integer 'user_id'
40
+ t.integer 'device_id'
41
+ t.text 'description'
42
+ t.datetime 'created_at'
43
+ end
44
+
45
+ add_index 'transactions', ['transaction_type'],
46
+ name: 'index_transactions_on_transaction_type'
47
+
48
+ create_table 'users', force: :cascade do |t|
49
+ t.string 'username'
50
+ t.string 'name'
51
+ t.string 'password_digest'
52
+ t.string 'email'
53
+ t.boolean 'is_admin', default: false
54
+ t.datetime 'created_at', null: false
55
+ t.datetime 'updated_at', null: false
56
+ t.boolean 'is_verified', default: false
57
+ t.string 'reset_code'
58
+ end
59
+
60
+ add_index 'users', ['username'], name: 'index_users_on_username'
61
+ end
@@ -0,0 +1,21 @@
1
+ require 'faker'
2
+
3
+ module DeviceTracker
4
+ # :nodoc:
5
+ class Seed
6
+ def self.seed(user)
7
+ admin = User.where(email: user['email'])
8
+
9
+ return if admin.exists?
10
+
11
+ admin = User.find_or_create_by(name: user['name'],
12
+ username: 'admin',
13
+ email: user['email'],
14
+ is_admin: true,
15
+ is_verified: true)
16
+ admin.password = user['password']
17
+ admin.save
18
+ puts "Added #{admin.username} to the database."
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ %w(
5
+ sinatra
6
+ sinatra/activerecord
7
+ sinatra/flash
8
+ sinatra/partial
9
+ tilt/erb
10
+ json
11
+ pony
12
+ ).each { |d| require d }
13
+
14
+ Dir.glob(
15
+ "#{File.dirname(__FILE__)}/{helpers,controllers,models}/*.rb"
16
+ ).each { |file| require file }
@@ -0,0 +1,18 @@
1
+ require_relative './version'
2
+ require_relative './dependencies'
3
+ require_relative './app'
4
+ require 'rack'
5
+
6
+ # :nodoc:
7
+ module DeviceTracker
8
+ def self.start(host, port)
9
+ options = {
10
+ Host: host,
11
+ Port: port
12
+ }
13
+
14
+ Rack::Handler::Thin.run(App.new, options) do |server|
15
+ [:INT, :TERM].each { |sig| trap(sig) { server.stop } }
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,116 @@
1
+ module DeviceTracker
2
+ # :nodoc:
3
+ module ApplicationHelper
4
+ def permission?(user_id)
5
+ if logged_in_user[:is_admin] != true &&
6
+ logged_in_user[:id] != user_id.to_i
7
+ return true
8
+ end
9
+ false
10
+ end
11
+
12
+ def admin_check
13
+ if admin?
14
+ true
15
+ else
16
+ flash_and_go_back 'warning',
17
+ ['You don\'t have permission to access this page.']
18
+ end
19
+ end
20
+
21
+ def verification_email(user)
22
+ @user = user
23
+ @get_started_link = request.base_url + '/devices'
24
+ body = ERB.new(
25
+ File.read(settings.email_path + '/verification.erb')
26
+ ).result(binding)
27
+
28
+ send_email(
29
+ to: @user.email,
30
+ subject: 'Account Verified | Device Tracker',
31
+ body: body
32
+ )
33
+ end
34
+
35
+ def admin_email_registration(request, user)
36
+ @user = user
37
+ @verification_link = "#{request.base_url}/users/#{user.id}/edit"
38
+
39
+ body = ERB.new(
40
+ File.read(settings.email_path + '/registration.erb')
41
+ ).result(binding)
42
+
43
+ send_email(
44
+ to: [User.where(is_admin: true).map(& :email)],
45
+ subject: 'New Registration | Device Tracker',
46
+ body: body
47
+ )
48
+ end
49
+
50
+ def send_email(data)
51
+ Pony.mail(
52
+ to: data[:to],
53
+ from: data[:from] || 'no-reply@device-tracker',
54
+ subject: data[:subject],
55
+ html_body: data[:body]
56
+ )
57
+ rescue Net::OpenTimeout
58
+ puts ">>> Net::OpenTimeout exception for [#{subject}]"
59
+ end
60
+
61
+ def admin?
62
+ user = logged_in_user
63
+ return if user.nil?
64
+ user[:is_admin]
65
+ end
66
+
67
+ def value_for(name, object)
68
+ if flash[name]
69
+ flash[name]
70
+ elsif object
71
+ object.send name
72
+ end
73
+ end
74
+
75
+ def generate_activation_code(size = 6)
76
+ charset = %w(0 1 2 3 4 6 7 8 9 A C D E F G H J K M N P Q R T V W X Y Z)
77
+ (0...size).map { charset.to_a[rand(charset.size)] }.join
78
+ end
79
+
80
+ def protected!
81
+ if logged_in_user.nil?
82
+ create_flash 'info', ['You must be logged in to access this page.']
83
+ redirect '/login'
84
+ end
85
+ true
86
+ end
87
+
88
+ def report_transaction(message, type, device = nil)
89
+ transaction = Transaction.new
90
+ transaction.user_id = logged_in_user[:id] if session[:user]
91
+
92
+ transaction.description = message
93
+ transaction.transaction_type = type
94
+ transaction.device_id = device.id unless device.nil?
95
+
96
+ transaction.save!
97
+ end
98
+
99
+ def create_flash(type, message)
100
+ flash[:message] = { css_class: type, message: message }
101
+ end
102
+
103
+ def flash_and_go_back(type, message)
104
+ create_flash(type, message)
105
+ redirect back
106
+ end
107
+
108
+ def valid_heartbeat?(data)
109
+ return false if data['heartbeat'].nil?
110
+ heartbeat = data['heartbeat']
111
+ return false if heartbeat['device_id'].nil? ||
112
+ heartbeat['longitude'].nil? || heartbeat['latitude'].nil?
113
+ true
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,24 @@
1
+ # :nodoc:
2
+ module DeviceTracker
3
+ # :nodoc:
4
+ module UserHelper
5
+ def logged_in_user
6
+ session[:user] unless session[:user].nil?
7
+ end
8
+
9
+ def allowed_to_edit_page?(current_user, user_id)
10
+ # Admins can view any page
11
+ if current_user[:is_admin] || current_user[:id] == user_id.to_i
12
+ return true
13
+ end
14
+ false
15
+ end
16
+
17
+ def user_params
18
+ %w(password password_confirmation).each do |key|
19
+ params[:user].delete key
20
+ end
21
+ params[:user]
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,42 @@
1
+ require 'date'
2
+
3
+ module DeviceTracker
4
+ # :nodoc:
5
+ class Device < ActiveRecord::Base
6
+ belongs_to :operating_system
7
+ belongs_to :user
8
+
9
+ has_many :transactions
10
+ has_many :heartbeats
11
+
12
+ validates :unid,
13
+ :manufacturer,
14
+ :device,
15
+ :description,
16
+ :operating_system,
17
+ presence: true
18
+
19
+ validates :unid, uniqueness: true
20
+
21
+ def unid=(unid)
22
+ self[:unid] = unid.upcase
23
+ end
24
+
25
+ def full_name
26
+ self[:manufacturer] + ' ' + self[:device]
27
+ end
28
+
29
+ def passed_use_by_date?
30
+ (!self[:available] &&
31
+ (Time.now - self[:checked_out_since]).to_i / 1.day >= 3)
32
+ end
33
+
34
+ def checked_out_since_formatted
35
+ self[:checked_out_since].strftime('%A, %d %B %Y at %I:%M%p')
36
+ end
37
+
38
+ def days_checked_out
39
+ (Time.now.to_date - self[:checked_out_since].to_date).floor
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,7 @@
1
+ module DeviceTracker
2
+ # :nodoc:
3
+ class Heartbeat < ActiveRecord::Base
4
+ validates :longitude, :latitude, :device_id, presence: true
5
+ belongs_to :device
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module DeviceTracker
2
+ # :nodoc:
3
+ class OperatingSystem < ActiveRecord::Base
4
+ validates :name, presence: true, uniqueness: true
5
+ has_many :devices
6
+ end
7
+ end
@@ -0,0 +1,62 @@
1
+ require 'date'
2
+
3
+ module DeviceTracker
4
+ # :nodoc:
5
+ class Transaction < ActiveRecord::Base
6
+ belongs_to :user
7
+ belongs_to :device
8
+
9
+ validates :transaction_type, :description, presence: true
10
+ validates :transaction_type,
11
+ inclusion: { in: %w(CHECKOUT
12
+ RETURN
13
+ REGISTRATION
14
+ LOGIN
15
+ ACTIVATE
16
+ DEACTIVATE
17
+ DELETION
18
+ MISSING
19
+ FOUND),
20
+ message: '%{value} is not a valid transaction.' }
21
+
22
+ def created_at
23
+ self[:created_at].strftime('%a %d %b %Y at %H:%M:%S')
24
+ end
25
+
26
+ def self.missing
27
+ 'MISSING'
28
+ end
29
+
30
+ def self.found
31
+ 'FOUND'
32
+ end
33
+
34
+ def self.activate
35
+ 'ACTIVATE'
36
+ end
37
+
38
+ def self.deactivate
39
+ 'DEACTIVATE'
40
+ end
41
+
42
+ def self.checkout
43
+ 'CHECKOUT'
44
+ end
45
+
46
+ def self.return
47
+ 'RETURN'
48
+ end
49
+
50
+ def self.registration
51
+ 'REGISTRATION'
52
+ end
53
+
54
+ def self.login
55
+ 'LOGIN'
56
+ end
57
+
58
+ def self.deletion
59
+ 'DELETION'
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,28 @@
1
+ require 'bcrypt'
2
+
3
+ module DeviceTracker
4
+ # :nodoc:
5
+ class User < ActiveRecord::Base
6
+ has_secure_password
7
+ has_many :devices
8
+ has_many :transactions
9
+ validates :username, :name, :email, presence: true
10
+ validates :password, presence: { if: :password_required? }
11
+ validates_format_of :email,
12
+ with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i,
13
+ on: :create
14
+ validates :username, uniqueness: true
15
+ validates :email, uniqueness: true
16
+ validates_confirmation_of :password
17
+
18
+ def flashable_attrs
19
+ [:username, :email, :name]
20
+ end
21
+
22
+ protected
23
+
24
+ def password_required?
25
+ self.new_record?
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,100 @@
1
+ table.sortable span.sign {
2
+ display: block;
3
+ position: absolute;
4
+ top: 50%;
5
+ right: 5px;
6
+ font-size: 12px;
7
+ margin-top: -10px;
8
+ color: #bfbfc1;
9
+ }
10
+
11
+ table.sortable th:after {
12
+ display: block;
13
+ position: absolute;
14
+ top: 50%;
15
+ right: 5px;
16
+ font-size: 12px;
17
+ margin-top: -10px;
18
+ color: #bfbfc1;
19
+ }
20
+
21
+ table.sortable th.arrow:after {
22
+ content: '';
23
+ }
24
+
25
+ table.sortable span.arrow, span.reversed, th.arrow.down:after, th.reversedarrow.down:after, th.arrow.up:after, th.reversedarrow.up:after {
26
+ border-style: solid;
27
+ border-width: 5px;
28
+ font-size: 0;
29
+ border-color: #ccc transparent transparent transparent;
30
+ line-height: 0;
31
+ height: 0;
32
+ width: 0;
33
+ margin-top: -2px;
34
+ }
35
+
36
+ table.sortable span.arrow.up, th.arrow.up:after {
37
+ border-color: transparent transparent #ccc transparent;
38
+ margin-top: -7px;
39
+ }
40
+
41
+ table.sortable span.reversed, th.reversedarrow.down:after {
42
+ border-color: transparent transparent #ccc transparent;
43
+ margin-top: -7px;
44
+ }
45
+
46
+ table.sortable span.reversed.up, th.reversedarrow.up:after {
47
+ border-color: #ccc transparent transparent transparent;
48
+ margin-top: -2px;
49
+ }
50
+
51
+ table.sortable span.az:before, th.az.down:after {
52
+ content: "a .. z";
53
+ }
54
+
55
+ table.sortable span.az.up:before, th.az.up:after {
56
+ content: "z .. a";
57
+ }
58
+
59
+ table.sortable th.az.nosort:after, th.AZ.nosort:after, th._19.nosort:after, th.month.nosort:after {
60
+ content: "..";
61
+ }
62
+
63
+ table.sortable span.AZ:before, th.AZ.down:after {
64
+ content: "A .. Z";
65
+ }
66
+
67
+ table.sortable span.AZ.up:before, th.AZ.up:after {
68
+ content: "Z .. A";
69
+ }
70
+
71
+ table.sortable span._19:before, th._19.down:after {
72
+ content: "1 .. 9";
73
+ }
74
+
75
+ table.sortable span._19.up:before, th._19.up:after {
76
+ content: "9 .. 1";
77
+ }
78
+
79
+ table.sortable span.month:before, th.month.down:after {
80
+ content: "jan .. dec";
81
+ }
82
+
83
+ table.sortable span.month.up:before, th.month.up:after {
84
+ content: "dec .. jan";
85
+ }
86
+
87
+ table.sortable thead th:not([data-defaultsort=disabled]) {
88
+ cursor: pointer;
89
+ position: relative;
90
+ top: 0;
91
+ left: 0;
92
+ }
93
+
94
+ table.sortable thead th:hover:not([data-defaultsort=disabled]) {
95
+ background: #efefef;
96
+ }
97
+
98
+ table.sortable thead th div.mozilla {
99
+ position: relative;
100
+ }