infractores 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +7 -0
  2. data/.env.sample +8 -0
  3. data/.gitignore +22 -0
  4. data/.travis.yml +14 -0
  5. data/CODE_OF_CONDUCT.md +49 -0
  6. data/Gemfile +67 -0
  7. data/Gemfile.lock +417 -0
  8. data/Guardfile +8 -0
  9. data/LICENSE +21 -0
  10. data/Procfile +2 -0
  11. data/README.md +48 -0
  12. data/Rakefile +6 -0
  13. data/app/assets/images/.keep +0 -0
  14. data/app/assets/images/gallery-buttons.png +0 -0
  15. data/app/assets/images/logo-infractores.svg +10 -0
  16. data/app/assets/javascripts/application.js +18 -0
  17. data/app/assets/javascripts/bootstrap.js +4 -0
  18. data/app/assets/javascripts/index.js +82 -0
  19. data/app/assets/javascripts/twitter.js +18 -0
  20. data/app/assets/stylesheets/1-utilities/_variables.scss +12 -0
  21. data/app/assets/stylesheets/2-quarks/_typography.scss +32 -0
  22. data/app/assets/stylesheets/3-atoms/_buttons.scss +3 -0
  23. data/app/assets/stylesheets/4-molecules/_footer.scss +32 -0
  24. data/app/assets/stylesheets/4-molecules/_infraction-gallery.scss +101 -0
  25. data/app/assets/stylesheets/4-molecules/_navbar.scss +71 -0
  26. data/app/assets/stylesheets/5-pages/index.scss +165 -0
  27. data/app/assets/stylesheets/5-pages/show.scss +29 -0
  28. data/app/assets/stylesheets/application.css +18 -0
  29. data/app/assets/stylesheets/bootstrap_and_overrides.css +7 -0
  30. data/app/controllers/application_controller.rb +5 -0
  31. data/app/controllers/concerns/.keep +0 -0
  32. data/app/controllers/infractions_controller.rb +23 -0
  33. data/app/controllers/users_controller.rb +7 -0
  34. data/app/helpers/application_helper.rb +19 -0
  35. data/app/helpers/infractions_helper.rb +6 -0
  36. data/app/mailers/.keep +0 -0
  37. data/app/models/.keep +0 -0
  38. data/app/models/concerns/.keep +0 -0
  39. data/app/models/evidence.rb +23 -0
  40. data/app/models/infraction.rb +43 -0
  41. data/app/models/tweet.rb +54 -0
  42. data/app/models/user.rb +34 -0
  43. data/app/services/twitter_service.rb +51 -0
  44. data/app/uploaders/evidence_uploader.rb +62 -0
  45. data/app/views/infractions/_map.html.erb +17 -0
  46. data/app/views/infractions/_reported_by.html.erb +4 -0
  47. data/app/views/infractions/_summary.html.erb +23 -0
  48. data/app/views/infractions/index.html.erb +18 -0
  49. data/app/views/infractions/show.html.erb +24 -0
  50. data/app/views/layouts/application.html.erb +90 -0
  51. data/app/views/shared/_google_analytics.html.erb +12 -0
  52. data/app/views/users/index.html.erb +21 -0
  53. data/app/workers/tweet_inspector.rb +18 -0
  54. data/bin/bundle +3 -0
  55. data/bin/rails +8 -0
  56. data/bin/rake +8 -0
  57. data/bin/setup +29 -0
  58. data/bin/spring +15 -0
  59. data/config.ru +4 -0
  60. data/config/application.rb +27 -0
  61. data/config/boot.rb +3 -0
  62. data/config/database.yml.sample +85 -0
  63. data/config/environment.rb +5 -0
  64. data/config/environments/development.rb +50 -0
  65. data/config/environments/production.rb +79 -0
  66. data/config/environments/test.rb +42 -0
  67. data/config/initializers/assets.rb +15 -0
  68. data/config/initializers/backtrace_silencers.rb +7 -0
  69. data/config/initializers/carrierwave.rb +17 -0
  70. data/config/initializers/cookies_serializer.rb +3 -0
  71. data/config/initializers/filter_parameter_logging.rb +4 -0
  72. data/config/initializers/inflections.rb +16 -0
  73. data/config/initializers/mime_types.rb +4 -0
  74. data/config/initializers/session_store.rb +3 -0
  75. data/config/initializers/twitter.rb +1 -0
  76. data/config/initializers/wrap_parameters.rb +14 -0
  77. data/config/locales/en.bootstrap.yml +23 -0
  78. data/config/locales/en.yml +23 -0
  79. data/config/locales/es.yml +263 -0
  80. data/config/routes.rb +6 -0
  81. data/config/secrets.yml +22 -0
  82. data/db/migrate/20150824210535_add_tweets_table.rb +8 -0
  83. data/db/migrate/20150829133406_create_infractions.rb +11 -0
  84. data/db/migrate/20150829134520_create_users.rb +12 -0
  85. data/db/migrate/20150829141909_add_user_id_to_tweets.rb +5 -0
  86. data/db/migrate/20150829154430_create_locations.rb +10 -0
  87. data/db/migrate/20150830224603_create_evidences_table.rb +8 -0
  88. data/db/migrate/20150911020838_add_valid_column_to_infractions.rb +6 -0
  89. data/db/migrate/20150911025204_add_index_to_users_username.rb +5 -0
  90. data/db/migrate/20151102220702_add_lat_lon_to_infractions.rb +6 -0
  91. data/db/migrate/20151102221811_remove_locations.rb +5 -0
  92. data/db/migrate/20151117194703_add_reported_at_to_infractions.rb +5 -0
  93. data/db/migrate/20160229023500_change_lat_lon_precision.rb +6 -0
  94. data/db/seeds.rb +7 -0
  95. data/infractores.gemspec +25 -0
  96. data/lib/assets/.keep +0 -0
  97. data/lib/infractores.rb +1 -0
  98. data/lib/infractores/version.rb +3 -0
  99. data/lib/tasks/.keep +0 -0
  100. data/lib/tasks/twitter.rake +16 -0
  101. data/lib/tasks/users.rake +9 -0
  102. data/log/.keep +0 -0
  103. data/public/404.html +67 -0
  104. data/public/422.html +67 -0
  105. data/public/500.html +66 -0
  106. data/public/favicon.ico +0 -0
  107. data/public/robots.txt +5 -0
  108. data/vendor/assets/images/blank.gif +0 -0
  109. data/vendor/assets/images/fancybox_loading.gif +0 -0
  110. data/vendor/assets/images/fancybox_loading@2x.gif +0 -0
  111. data/vendor/assets/images/fancybox_overlay.png +0 -0
  112. data/vendor/assets/images/fancybox_sprite.png +0 -0
  113. data/vendor/assets/images/fancybox_sprite@2x.png +0 -0
  114. data/vendor/assets/images/glyphicons-halflings-white.png +0 -0
  115. data/vendor/assets/images/glyphicons-halflings.png +0 -0
  116. data/vendor/assets/javascripts/.keep +0 -0
  117. data/vendor/assets/javascripts/jquery.fancybox-buttons.js +122 -0
  118. data/vendor/assets/javascripts/jquery.fancybox.pack.js +46 -0
  119. data/vendor/assets/javascripts/livereload.js +1183 -0
  120. data/vendor/assets/stylesheets/.keep +0 -0
  121. data/vendor/assets/stylesheets/jquery.fancybox-buttons.css +97 -0
  122. data/vendor/assets/stylesheets/jquery.fancybox.css +274 -0
  123. metadata +215 -0
@@ -0,0 +1,165 @@
1
+ @import "1-utilities/variables";
2
+
3
+ body.overflow{
4
+ overflow: hidden;
5
+ }
6
+ div.infractions {
7
+ padding-top: 50px;
8
+ .main-map{
9
+ position: relative;
10
+ z-index: 0;
11
+ .map{
12
+ background-color: $light_yellow;
13
+ position: fixed !important;
14
+ height: 100%;
15
+ width:100%;
16
+ top: 0;
17
+ }
18
+ }
19
+ h2 {
20
+ font-size: 3em;
21
+ text-align: center;
22
+ margin-top: 1em;
23
+ margin-bottom: 1em;
24
+ }
25
+ .list-container{
26
+ width: 50%;
27
+ position: relative;
28
+ z-index: 1;
29
+ background: rgba(247, 104, 92, 0.85);
30
+ max-width: 550px;
31
+ ul {
32
+ padding: 0px 0 50px 0;
33
+ width: 85%;
34
+ margin: auto;
35
+ li.infraction {
36
+ padding-top: 25%;
37
+ margin-top: -10%;
38
+ &.odd, &.even{
39
+ border: 0;
40
+ background: none;
41
+ }
42
+ .content{
43
+ list-style-type: none;
44
+ overflow: hidden;
45
+ background-color: $white;
46
+ border:0;
47
+ -webkit-border-radius: 4px;
48
+ -moz-border-radius: 4px;
49
+ border-radius: 4px;
50
+ min-height: 400px;
51
+ padding: 0;
52
+ margin: auto;
53
+ .show-infraction{
54
+ margin: 4%;
55
+ padding: 1% 3%;
56
+ border: 1px solid #F7685C;
57
+ display: inline-block;
58
+ font-size: 14px;
59
+ -webkit-border-radius: 4px;
60
+ -moz-border-radius: 4px;
61
+ border-radius: 4px;
62
+ &:hover, &:focus{
63
+ text-decoration: none;
64
+ background: $red;
65
+ color: $white;
66
+ }
67
+ }
68
+ p{
69
+ border-radius: 4px 4px 0 0;
70
+ text-transform: uppercase;
71
+ margin-bottom: 0;
72
+ color: $blue;
73
+ background-color: $light_red;
74
+ overflow: hidden;
75
+ span{
76
+ padding: 0.8em 1em;
77
+ float: left;
78
+ &.date{
79
+ float: right;
80
+ border-left: 2px solid $white;
81
+ background-color: $yellow;
82
+ }
83
+ }
84
+ a{
85
+ text-transform: lowercase;
86
+ color: $red;
87
+ font-weight: 500;
88
+ }
89
+ }
90
+ div.details {
91
+ a {
92
+ padding-right: 2px;
93
+ display: inline-block;
94
+ float: none;
95
+ }
96
+ }
97
+ .description {
98
+ width: 100%;
99
+ float: none;
100
+ min-height: 200px;
101
+ .tweet{margin:2%;}
102
+ a { padding-right: 3px; }
103
+ }
104
+ .infraction-map{
105
+ display: none;
106
+ border-top: 2px solid $red;
107
+ margin: 2%;
108
+ h4{
109
+ a{
110
+ display: block;
111
+ &:after{
112
+ font-family: 'FontAwesome';
113
+ content: "\f078";
114
+ float: right;
115
+ }
116
+ }
117
+ }
118
+ .map {
119
+ display: none;
120
+ width: 100%;
121
+ height: 300px;
122
+ background-color: $light_yellow;
123
+ }
124
+ }
125
+ }
126
+ }
127
+ }
128
+
129
+ }
130
+ }
131
+ @media screen and (max-width: 768px){
132
+ div.infractions{
133
+ .list-container{
134
+ width: 100%;
135
+ max-width: none;
136
+ ul{
137
+ max-width: 500px;
138
+ li.infraction{
139
+ .content{
140
+ .infraction-map{
141
+ display: block;
142
+ }
143
+ .show-infraction{
144
+ display: none;
145
+ }
146
+ }
147
+ }
148
+ }
149
+ }
150
+ }
151
+ }
152
+ @media screen and (max-width: 500px){
153
+ div.infractions{
154
+ .list-container{
155
+ ul{
156
+ li.infraction{
157
+ p{
158
+ span.date{display: none;}
159
+ }
160
+ .description{min-height: initial;}
161
+ }
162
+ }
163
+ }
164
+ }
165
+ }
@@ -0,0 +1,29 @@
1
+ .container.infractions-show {
2
+ img {
3
+ padding: 1em;
4
+ margin: 0.2em;
5
+ }
6
+
7
+ .detail {
8
+ padding: 1em;
9
+ border: 1px solid #CCC;
10
+ margin-bottom: 1em;
11
+ }
12
+
13
+ .row.bordered {
14
+ border: 1px solid #CCC;
15
+ }
16
+
17
+ .map {
18
+ margin-right: 1em;
19
+ margin-top: 1em;
20
+ }
21
+
22
+ .tweet {
23
+ min-height: 210px;
24
+ }
25
+
26
+ .source {
27
+ margin-left: 0;
28
+ }
29
+ }
@@ -0,0 +1,18 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any styles
10
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11
+ * file per style scope.
12
+ *
13
+ *= require 'bootstrap_and_overrides'
14
+ *= require jquery.fancybox
15
+ *= require jquery.fancybox-buttons
16
+ *= require_tree .
17
+ *= require_self
18
+ */
@@ -0,0 +1,7 @@
1
+ /*
2
+ =require twitter-bootstrap-static/bootstrap
3
+
4
+ Use Font Awesome icons (default)
5
+ To use Glyphicons sprites instead of Font Awesome, replace with "require twitter-bootstrap-static/sprites"
6
+ =require twitter-bootstrap-static/fontawesome
7
+ */
@@ -0,0 +1,5 @@
1
+ class ApplicationController < ActionController::Base
2
+ # Prevent CSRF attacks by raising an exception.
3
+ # For APIs, you may want to use :null_session instead.
4
+ protect_from_forgery with: :exception
5
+ end
File without changes
@@ -0,0 +1,23 @@
1
+ class InfractionsController < ApplicationController
2
+ before_filter :load_scope, only: [:index]
3
+
4
+ # GET /infractions
5
+ def index
6
+ @infractions = @scope.legitimate.order("infractions.reported_at DESC")
7
+ @infraction_markers = @infractions.pluck(:lat, :lon, :id)
8
+ end
9
+
10
+ # GET /infractions/:id
11
+ def show
12
+ @infraction = Infraction.includes(:user).find_by(tweet_id: params[:id])
13
+ end
14
+
15
+ private
16
+
17
+ def load_scope
18
+ @scope = Infraction.includes(:evidences, :tweet, :user)
19
+ if params[:username]
20
+ @scope = @scope.where("users.username" => params[:username])
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,7 @@
1
+ class UsersController < ApplicationController
2
+
3
+ # GET /users
4
+ def index
5
+ @users = User.where("infractions_count > 0").order("infractions_count DESC")
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ module ApplicationHelper
2
+
3
+ def calculate_class(*arr)
4
+ arr.any? {|x| current_page?("/#{x}") } ? 'active' : ''
5
+ end
6
+
7
+ def decorated_username(infraction)
8
+ username = infraction.username
9
+ "<a href='https://twitter.com/#{username}' target='_blank'>@#{username}</a>"
10
+ end
11
+
12
+ def screen_name
13
+ "@#{TwitterService.instance.screen_name}"
14
+ end
15
+
16
+ def screen_name_hashtag
17
+ "##{TwitterService.instance.screen_name}"
18
+ end
19
+ end
@@ -0,0 +1,6 @@
1
+ module InfractionsHelper
2
+
3
+ def decorated_time(infraction)
4
+ infraction.tweet.created_at
5
+ end
6
+ end
File without changes
File without changes
File without changes
@@ -0,0 +1,23 @@
1
+ class Evidence < ActiveRecord::Base
2
+ belongs_to :infraction
3
+
4
+ mount_uploader :media, EvidenceUploader
5
+
6
+ # Builds an [Array] of [Evidence] records from a [Tweet] using the media
7
+ # entities
8
+ #
9
+ # @param tweet [Tweet]
10
+ # @return [Array]
11
+ def self.build_from(tweet)
12
+ result = []
13
+
14
+ if extended_entities = tweet.source.extended_entities
15
+ extended_entities["media"].each do |media|
16
+ result << self.new(infraction: tweet.infraction,
17
+ remote_media_url: media["media_url_https"])
18
+ end
19
+ end
20
+
21
+ result
22
+ end
23
+ end
@@ -0,0 +1,43 @@
1
+ class Infraction < ActiveRecord::Base
2
+ belongs_to :tweet
3
+
4
+ has_many :evidences
5
+ has_one :user, through: :tweet
6
+
7
+ validates_presence_of :lon
8
+ validates_presence_of :lat
9
+ validates_presence_of :evidences
10
+
11
+ delegate :source_url, to: :tweet
12
+ delegate :username, :update_infractions_counter!, to: :user
13
+
14
+ after_save :update_infractions_counter!
15
+
16
+ scope :legitimate, -> { where(legitimate: true) }
17
+
18
+ # Builds an {Infraction} using the {Tweet}
19
+ #
20
+ # @param tweet [Tweet]
21
+ # @return [Infraction]
22
+ def self.build_from(tweet)
23
+ if tweet.source.geo
24
+ lat = tweet.source.geo["coordinates"][0]
25
+ lon = tweet.source.geo["coordinates"][1]
26
+ else
27
+ lat = lon = nil
28
+ end
29
+
30
+ result = self.new(
31
+ tweet: tweet,
32
+ description: tweet.source.text,
33
+ lat: lat,
34
+ lon: lon,
35
+ reported_at: tweet.source["created_at"]
36
+ )
37
+
38
+ result.evidences = Evidence.build_from(tweet)
39
+
40
+ result
41
+ end
42
+
43
+ end
@@ -0,0 +1,54 @@
1
+ class Tweet < ActiveRecord::Base
2
+ self.primary_key = :id
3
+
4
+ validates :id, :json, :user_id, presence: true
5
+ validates :id, uniqueness: true
6
+
7
+ belongs_to :user
8
+ has_one :infraction
9
+
10
+ validates_presence_of :user
11
+
12
+ after_save :enqueue_infraction_job
13
+
14
+ # It uses a {Twitter::Tweet} to find and update an existing {Tweet} or create
15
+ # a new one
16
+ #
17
+ # @param tuit [Twitter::Tweet]
18
+ # @return [Tweet]
19
+ def self.find_or_create!(tuit)
20
+ opts = {
21
+ id: tuit.id }
22
+
23
+ if result = self.find_by(opts)
24
+ result.update_attributes!(json: tuit.to_json)
25
+ else
26
+ result = self.new(opts.merge(json: tuit.to_json))
27
+ user = User.find_or_create!(result)
28
+ result.user = user
29
+ result.save!
30
+ end
31
+
32
+ result
33
+ end
34
+
35
+ def source
36
+ @source ||= OpenStruct.new(JSON.load(json))
37
+ end
38
+
39
+ def source_url
40
+ "https://twitter.com/#{user.username}/status/#{source.id_str}"
41
+ end
42
+
43
+ def created_at
44
+ @created_at ||= Time.parse(source["created_at"])
45
+ @created_at.in_time_zone
46
+ end
47
+
48
+ private
49
+
50
+ def enqueue_infraction_job
51
+ TweetInspector.perform_async(self.id)
52
+ end
53
+
54
+ end
@@ -0,0 +1,34 @@
1
+ class User < ActiveRecord::Base
2
+ has_many :tweets
3
+
4
+ validates :username, :name, :twitter_user_id, presence: true
5
+ has_many :infractions, through: :tweets
6
+
7
+ # It uses a {Tweet} to find and update an existing {User} or create
8
+ # a new one
9
+ #
10
+ # @param tuit [Tweet]
11
+ # @return [User]
12
+ def self.find_or_create!(tuit)
13
+ usr = OpenStruct.new(tuit.source["user"])
14
+
15
+ if result = User.find_by(twitter_user_id: usr.id)
16
+ result.update_attributes!(username: usr.screen_name,
17
+ profile_image_url: usr.profile_image_url_https,
18
+ name: usr.name)
19
+ else
20
+ result = User.new(username: usr.screen_name,
21
+ profile_image_url: usr.profile_image_url_https,
22
+ name: usr.name,
23
+ twitter_user_id: usr.id)
24
+
25
+ result.save!
26
+ end
27
+
28
+ result
29
+ end
30
+
31
+ def update_infractions_counter!
32
+ update_column :infractions_count, infractions.legitimate.count
33
+ end
34
+ end