device_tracker 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }