* file attachments in db * rubocop * factor out a bunch of file types * thumb and medium image styles" * syntax error in concern * markdown is also plaintext * rubocop
This commit is contained in:
parent
b16617286f
commit
696ff396b0
9 changed files with 185 additions and 91 deletions
38
app/models/attachment.rb
Normal file
38
app/models/attachment.rb
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
class Attachment < ApplicationRecord
|
||||||
|
belongs_to :attachable, polymorphic: true
|
||||||
|
|
||||||
|
has_attached_file :file,
|
||||||
|
styles: lambda { |a|
|
||||||
|
if a.instance.image?
|
||||||
|
{
|
||||||
|
thumb: 'x128#',
|
||||||
|
medium: 'x320>'
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{}
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
validates_attachment_content_type :file, content_type: Attachable.allowed_types
|
||||||
|
|
||||||
|
def image?
|
||||||
|
Attachable.image_types.include?(file.instance.file_content_type)
|
||||||
|
end
|
||||||
|
|
||||||
|
def audio?
|
||||||
|
Attachable.audio_types.include?(file.instance.file_content_type)
|
||||||
|
end
|
||||||
|
|
||||||
|
def text?
|
||||||
|
Attachable.text_types.include?(file.instance.file_content_type)
|
||||||
|
end
|
||||||
|
|
||||||
|
def pdf?
|
||||||
|
Attachable.pdf_types.include?(file.instance.file_content_type)
|
||||||
|
end
|
||||||
|
|
||||||
|
def document?
|
||||||
|
text? || pdf?
|
||||||
|
end
|
||||||
|
end
|
50
app/models/concerns/attachable.rb
Normal file
50
app/models/concerns/attachable.rb
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
module Attachable
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
has_many :attachments, as: :attachable, dependent: :destroy
|
||||||
|
end
|
||||||
|
|
||||||
|
def images
|
||||||
|
attachments.where(file_content_type: image_types)
|
||||||
|
end
|
||||||
|
|
||||||
|
def audios
|
||||||
|
attachments.where(file_content_type: audio_types)
|
||||||
|
end
|
||||||
|
|
||||||
|
def texts
|
||||||
|
attachments.where(file_content_type: text_types)
|
||||||
|
end
|
||||||
|
|
||||||
|
def pdfs
|
||||||
|
attachments.where(file_content_type: pdf_types)
|
||||||
|
end
|
||||||
|
|
||||||
|
def documents
|
||||||
|
attachments.where(file_content_type: text_types + pdf_types)
|
||||||
|
end
|
||||||
|
|
||||||
|
class << self
|
||||||
|
def image_types
|
||||||
|
['image/png', 'image/gif', 'image/jpeg']
|
||||||
|
end
|
||||||
|
|
||||||
|
def audio_types
|
||||||
|
['audio/ogg', 'audio/mp3']
|
||||||
|
end
|
||||||
|
|
||||||
|
def text_types
|
||||||
|
['text/plain']
|
||||||
|
end
|
||||||
|
|
||||||
|
def pdf_types
|
||||||
|
['application/pdf']
|
||||||
|
end
|
||||||
|
|
||||||
|
def allowed_types
|
||||||
|
image_types + audio_types + text_types + pdf_types
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,6 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
class Topic < ApplicationRecord
|
class Topic < ApplicationRecord
|
||||||
include TopicsHelper
|
include TopicsHelper
|
||||||
|
include Attachable
|
||||||
|
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
belongs_to :defer_to_map, class_name: 'Map', foreign_key: 'defer_to_map_id'
|
belongs_to :defer_to_map, class_name: 'Map', foreign_key: 'defer_to_map_id'
|
||||||
|
@ -21,23 +22,6 @@ class Topic < ApplicationRecord
|
||||||
validates :permission, presence: true
|
validates :permission, presence: true
|
||||||
validates :permission, inclusion: { in: Perm::ISSIONS.map(&:to_s) }
|
validates :permission, inclusion: { in: Perm::ISSIONS.map(&:to_s) }
|
||||||
|
|
||||||
# This method associates the attribute ":image" with a file attachment
|
|
||||||
has_attached_file :image
|
|
||||||
|
|
||||||
# , styles: {
|
|
||||||
# thumb: '100x100>',
|
|
||||||
# square: '200x200#',
|
|
||||||
# medium: '300x300>'
|
|
||||||
# }
|
|
||||||
|
|
||||||
# Validate the attached image is image/jpg, image/png, etc
|
|
||||||
validates_attachment_content_type :image, content_type: /\Aimage\/.*\Z/
|
|
||||||
|
|
||||||
# This method associates the attribute ":image" with a file attachment
|
|
||||||
has_attached_file :audio
|
|
||||||
# Validate the attached audio is audio/wav, audio/mp3, etc
|
|
||||||
validates_attachment_content_type :audio, content_type: /\Aaudio\/.*\Z/
|
|
||||||
|
|
||||||
def synapses
|
def synapses
|
||||||
synapses1.or(synapses2)
|
synapses1.or(synapses2)
|
||||||
end
|
end
|
||||||
|
|
|
@ -29,4 +29,7 @@ Rails.application.configure do
|
||||||
# Expands the lines which load the assets
|
# Expands the lines which load the assets
|
||||||
config.assets.debug = false
|
config.assets.debug = false
|
||||||
config.assets.quiet = true
|
config.assets.quiet = true
|
||||||
|
|
||||||
|
# S3 file storage
|
||||||
|
config.paperclip_defaults = {} # store on local machine for dev
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
class Rack::Attack
|
|
||||||
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
|
|
||||||
|
|
||||||
# Throttle all requests by IP (60rpm)
|
|
||||||
#
|
|
||||||
# Key: "rack::attack:#{Time.now.to_i/:period}:req/ip:#{req.ip}"
|
|
||||||
# throttle('req/ip', :limit => 300, :period => 5.minutes) do |req|
|
|
||||||
# req.ip # unless req.path.start_with?('/assets')
|
|
||||||
# end
|
|
||||||
|
|
||||||
# Throttle POST requests to /login by IP address
|
|
||||||
#
|
|
||||||
# Key: "rack::attack:#{Time.now.to_i/:period}:logins/ip:#{req.ip}"
|
|
||||||
throttle('logins/ip', limit: 5, period: 20.seconds) do |req|
|
|
||||||
req.ip if req.path == '/login' && req.post?
|
|
||||||
end
|
|
||||||
|
|
||||||
# Throttle POST requests to /login by email param
|
|
||||||
#
|
|
||||||
# Key: "rack::attack:#{Time.now.to_i/:period}:logins/email:#{req.email}"
|
|
||||||
#
|
|
||||||
# Note: This creates a problem where a malicious user could intentionally
|
|
||||||
# throttle logins for another user and force their login requests to be
|
|
||||||
# denied, but that's not very common and shouldn't happen to you. (Knock
|
|
||||||
# on wood!)
|
|
||||||
throttle('logins/email', limit: 5, period: 20.seconds) do |req|
|
|
||||||
if req.path == '/login' && req.post?
|
|
||||||
# return the email if present, nil otherwise
|
|
||||||
req.params['email'].presence
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
throttle('load_url_title/req/5mins/ip', limit: 300, period: 5.minutes) do |req|
|
|
||||||
req.ip if req.path == 'hacks/load_url_title'
|
|
||||||
end
|
|
||||||
throttle('load_url_title/req/1s/ip', limit: 5, period: 1.second) do |req|
|
|
||||||
# If the return value is truthy, the cache key for the return value
|
|
||||||
# is incremented and compared with the limit. In this case:
|
|
||||||
# "rack::attack:#{Time.now.to_i/1.second}:load_url_title/req/ip:#{req.ip}"
|
|
||||||
#
|
|
||||||
# If falsy, the cache key is neither incremented nor checked.
|
|
||||||
|
|
||||||
req.ip if req.path == 'hacks/load_url_title'
|
|
||||||
end
|
|
||||||
|
|
||||||
self.throttled_response = lambda do |env|
|
|
||||||
now = Time.now
|
|
||||||
match_data = env['rack.attack.match_data']
|
|
||||||
period = match_data[:period]
|
|
||||||
limit = match_data[:limit]
|
|
||||||
|
|
||||||
headers = {
|
|
||||||
'X-RateLimit-Limit' => limit.to_s,
|
|
||||||
'X-RateLimit-Remaining' => '0',
|
|
||||||
'X-RateLimit-Reset' => (now + (period - now.to_i % period)).to_s
|
|
||||||
}
|
|
||||||
|
|
||||||
[429, headers, ['']]
|
|
||||||
end
|
|
||||||
end
|
|
63
config/initializers/rack_attack.rb
Normal file
63
config/initializers/rack_attack.rb
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
module Rack
|
||||||
|
class Attack
|
||||||
|
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
|
||||||
|
|
||||||
|
# Throttle all requests by IP (60rpm)
|
||||||
|
#
|
||||||
|
# Key: "rack::attack:#{Time.now.to_i/:period}:req/ip:#{req.ip}"
|
||||||
|
# throttle('req/ip', :limit => 300, :period => 5.minutes) do |req|
|
||||||
|
# req.ip # unless req.path.start_with?('/assets')
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Throttle POST requests to /login by IP address
|
||||||
|
#
|
||||||
|
# Key: "rack::attack:#{Time.now.to_i/:period}:logins/ip:#{req.ip}"
|
||||||
|
throttle('logins/ip', limit: 5, period: 20.seconds) do |req|
|
||||||
|
req.ip if req.path == '/login' && req.post?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Throttle POST requests to /login by email param
|
||||||
|
#
|
||||||
|
# Key: "rack::attack:#{Time.now.to_i/:period}:logins/email:#{req.email}"
|
||||||
|
#
|
||||||
|
# Note: This creates a problem where a malicious user could intentionally
|
||||||
|
# throttle logins for another user and force their login requests to be
|
||||||
|
# denied, but that's not very common and shouldn't happen to you. (Knock
|
||||||
|
# on wood!)
|
||||||
|
throttle('logins/email', limit: 5, period: 20.seconds) do |req|
|
||||||
|
if req.path == '/login' && req.post?
|
||||||
|
# return the email if present, nil otherwise
|
||||||
|
req.params['email'].presence
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
throttle('load_url_title/req/5mins/ip', limit: 300, period: 5.minutes) do |req|
|
||||||
|
req.ip if req.path == 'hacks/load_url_title'
|
||||||
|
end
|
||||||
|
throttle('load_url_title/req/1s/ip', limit: 5, period: 1.second) do |req|
|
||||||
|
# If the return value is truthy, the cache key for the return value
|
||||||
|
# is incremented and compared with the limit. In this case:
|
||||||
|
# "rack::attack:#{Time.now.to_i/1.second}:load_url_title/req/ip:#{req.ip}"
|
||||||
|
#
|
||||||
|
# If falsy, the cache key is neither incremented nor checked.
|
||||||
|
|
||||||
|
req.ip if req.path == 'hacks/load_url_title'
|
||||||
|
end
|
||||||
|
|
||||||
|
self.throttled_response = lambda do |env|
|
||||||
|
now = Time.zone.now
|
||||||
|
match_data = env['rack.attack.match_data']
|
||||||
|
period = match_data[:period]
|
||||||
|
limit = match_data[:limit]
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'X-RateLimit-Limit' => limit.to_s,
|
||||||
|
'X-RateLimit-Remaining' => '0',
|
||||||
|
'X-RateLimit-Reset' => (now + (period - now.to_i % period)).to_s
|
||||||
|
}
|
||||||
|
|
||||||
|
[429, headers, ['']]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,9 +1,10 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
Warden::Manager.after_set_user do |user, auth, opts|
|
Warden::Manager.after_set_user do |user, auth, opts|
|
||||||
scope = opts[:scope]
|
scope = opts[:scope]
|
||||||
auth.cookies.signed["#{scope}.id"] = user.id
|
auth.cookies.signed["#{scope}.id"] = user.id
|
||||||
auth.cookies.signed["#{scope}.expires_at"] = 30.minutes.from_now
|
auth.cookies.signed["#{scope}.expires_at"] = 30.minutes.from_now
|
||||||
end
|
end
|
||||||
Warden::Manager.before_logout do |user, auth, opts|
|
Warden::Manager.before_logout do |_user, auth, opts|
|
||||||
scope = opts[:scope]
|
scope = opts[:scope]
|
||||||
auth.cookies.signed["#{scope}.id"] = nil
|
auth.cookies.signed["#{scope}.id"] = nil
|
||||||
auth.cookies.signed["#{scope}.expires_at"] = nil
|
auth.cookies.signed["#{scope}.expires_at"] = nil
|
||||||
|
|
12
db/migrate/20170122201451_create_attachments.rb
Normal file
12
db/migrate/20170122201451_create_attachments.rb
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
class CreateAttachments < ActiveRecord::Migration[5.0]
|
||||||
|
def change
|
||||||
|
create_table :attachments do |t|
|
||||||
|
t.references :attachable, polymorphic: true
|
||||||
|
t.attachment :file
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
remove_attachment :topics, :image
|
||||||
|
remove_attachment :topics, :audio
|
||||||
|
end
|
||||||
|
end
|
22
db/schema.rb
22
db/schema.rb
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 20161218183817) do
|
ActiveRecord::Schema.define(version: 20170122201451) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
@ -26,6 +26,18 @@ ActiveRecord::Schema.define(version: 20161218183817) do
|
||||||
t.index ["user_id"], name: "index_access_requests_on_user_id", using: :btree
|
t.index ["user_id"], name: "index_access_requests_on_user_id", using: :btree
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "attachments", force: :cascade do |t|
|
||||||
|
t.string "attachable_type"
|
||||||
|
t.integer "attachable_id"
|
||||||
|
t.string "file_file_name"
|
||||||
|
t.string "file_content_type"
|
||||||
|
t.integer "file_file_size"
|
||||||
|
t.datetime "file_updated_at"
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["attachable_type", "attachable_id"], name: "index_attachments_on_attachable_type_and_attachable_id", using: :btree
|
||||||
|
end
|
||||||
|
|
||||||
create_table "delayed_jobs", force: :cascade do |t|
|
create_table "delayed_jobs", force: :cascade do |t|
|
||||||
t.integer "priority", default: 0, null: false
|
t.integer "priority", default: 0, null: false
|
||||||
t.integer "attempts", default: 0, null: false
|
t.integer "attempts", default: 0, null: false
|
||||||
|
@ -272,14 +284,6 @@ ActiveRecord::Schema.define(version: 20161218183817) do
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.text "permission"
|
t.text "permission"
|
||||||
t.string "image_file_name", limit: 255
|
|
||||||
t.string "image_content_type", limit: 255
|
|
||||||
t.integer "image_file_size"
|
|
||||||
t.datetime "image_updated_at"
|
|
||||||
t.string "audio_file_name", limit: 255
|
|
||||||
t.string "audio_content_type", limit: 255
|
|
||||||
t.integer "audio_file_size"
|
|
||||||
t.datetime "audio_updated_at"
|
|
||||||
t.integer "defer_to_map_id"
|
t.integer "defer_to_map_id"
|
||||||
t.index ["metacode_id"], name: "index_topics_on_metacode_id", using: :btree
|
t.index ["metacode_id"], name: "index_topics_on_metacode_id", using: :btree
|
||||||
t.index ["user_id"], name: "index_topics_on_user_id", using: :btree
|
t.index ["user_id"], name: "index_topics_on_user_id", using: :btree
|
||||||
|
|
Loading…
Add table
Reference in a new issue