summaryrefslogtreecommitdiff
path: root/app/models
diff options
context:
space:
mode:
Diffstat (limited to 'app/models')
-rw-r--r--app/models/application_record.rb3
-rw-r--r--app/models/asset.rb17
-rw-r--r--app/models/concerns/file_attachment.rb124
-rw-r--r--app/models/event.rb2
-rw-r--r--app/models/menu_item.rb2
-rw-r--r--app/models/node.rb2
-rw-r--r--app/models/occurrence.rb2
-rw-r--r--app/models/page.rb2
-rw-r--r--app/models/permission.rb2
-rw-r--r--app/models/related_asset.rb2
-rw-r--r--app/models/user.rb2
11 files changed, 139 insertions, 21 deletions
diff --git a/app/models/application_record.rb b/app/models/application_record.rb
new file mode 100644
index 0000000..10a4cba
--- /dev/null
+++ b/app/models/application_record.rb
@@ -0,0 +1,3 @@
1class ApplicationRecord < ActiveRecord::Base
2 self.abstract_class = true
3end
diff --git a/app/models/asset.rb b/app/models/asset.rb
index f6526f2..aca0ee8 100644
--- a/app/models/asset.rb
+++ b/app/models/asset.rb
@@ -1,20 +1,11 @@
1class Asset < ActiveRecord::Base 1class Asset < ApplicationRecord
2
3 include FileAttachment
2 4
3 has_many :related_assets, :dependent => :destroy 5 has_many :related_assets, :dependent => :destroy
4 has_many :pages, :through => :related_assets 6 has_many :pages, :through => :related_assets
5 7
6 has_attached_file( 8 scope :images, -> { where(:upload_content_type => ["image/gif", "image/jpeg", "image/png", "image/webp"]) }
7 :upload,
8 :path => ":rails_root/public/system/:attachment/:id/:style/:filename",
9 :url => "/system/:attachment/:id/:style/:filename",
10 :styles => {
11 :medium => "300x300",
12 :thumb => "100x100",
13 :headline => "460x250#"
14 }
15 )
16
17 scope :images, -> { where(:upload_content_type => ["image/gif", "image/jpeg", "image/png"]) }
18 scope :documents, -> { where(:upload_content_type => ["application/pdf", "text/plain", "text/rtf"]) } 9 scope :documents, -> { where(:upload_content_type => ["application/pdf", "text/plain", "text/rtf"]) }
19 scope :audio, -> { where(:upload_content_type => ["audio/mpeg", "audio/x-m4a", "audio/wav", "audio/x-wav"]) } 10 scope :audio, -> { where(:upload_content_type => ["audio/mpeg", "audio/x-m4a", "audio/wav", "audio/x-wav"]) }
20 11
diff --git a/app/models/concerns/file_attachment.rb b/app/models/concerns/file_attachment.rb
new file mode 100644
index 0000000..b3ff0f1
--- /dev/null
+++ b/app/models/concerns/file_attachment.rb
@@ -0,0 +1,124 @@
1# FileAttachment — minimal drop-in replacement for Paperclip's has_attached_file.
2#
3# Provides the same interface used throughout this codebase:
4# asset.upload.url -> "/system/uploads/:id/original/:filename"
5# asset.upload.url(:thumb) -> "/system/uploads/:id/thumb/:filename"
6# asset.upload.content_type -> string
7# asset.upload.size -> integer (bytes)
8#
9# Files are stored at:
10# Rails.root/public/system/uploads/:id/:style/:filename
11#
12# Image variants are generated via ImageMagick (convert) on upload.
13# Non-image files get only an original, no variants.
14#
15# To replace an asset: assign a new file to asset.upload= and save.
16# The filename is fixed on first upload and preserved on replacement,
17# keeping all public URLs stable.
18#
19# Future: if more sophisticated asset management is needed (versioning,
20# S3, on-demand resizing), replace this module and keep the interface.
21
22module FileAttachment
23 extend ActiveSupport::Concern
24
25 STYLES = {
26 medium: { geometry: "300x300>", format: nil },
27 thumb: { geometry: "100x100>", format: nil },
28 headline: { geometry: "460x250!", format: nil }
29 }.freeze
30
31 IMAGE_CONTENT_TYPES = %w[image/jpeg image/gif image/png image/webp].freeze
32
33 included do
34 attr_reader :upload
35
36 after_initialize :build_upload_proxy
37 after_save :process_upload
38 before_destroy :delete_upload_files
39 end
40
41 def upload=(uploaded_file)
42 return if uploaded_file.blank?
43 @pending_upload = uploaded_file
44 # Populate the database columns immediately so validations can use them
45 self.upload_file_name = sanitize_filename(uploaded_file.original_filename)
46 self.upload_content_type = uploaded_file.content_type.to_s.split(';').first.strip
47 self.upload_file_size = uploaded_file.size
48 self.upload_updated_at = Time.current
49 build_upload_proxy
50 end
51
52 private
53
54 def build_upload_proxy
55 @upload = UploadProxy.new(self)
56 end
57
58 def process_upload
59 return unless @pending_upload
60 uploaded_file = @pending_upload
61 @pending_upload = nil
62
63 original_path = file_path(:original)
64 FileUtils.mkdir_p(File.dirname(original_path))
65 FileUtils.cp(uploaded_file.tempfile.path, original_path)
66
67 if IMAGE_CONTENT_TYPES.include?(upload_content_type)
68 generate_variants(original_path)
69 end
70 end
71
72 def generate_variants(original_path)
73 STYLES.each do |style, options|
74 dest_path = file_path(style)
75 FileUtils.mkdir_p(File.dirname(dest_path))
76 system("convert", original_path, "-resize", options[:geometry], dest_path)
77 end
78 end
79
80 def delete_upload_files
81 dir = Rails.root.join("public", "system", "uploads", id.to_s)
82 FileUtils.rm_rf(dir) if Dir.exist?(dir)
83 end
84
85 def file_path(style)
86 Rails.root.join(
87 "public", "system", "uploads",
88 id.to_s, style.to_s, upload_file_name
89 ).to_s
90 end
91
92 def sanitize_filename(filename)
93 File.basename(filename).gsub(/[^\w\.\-]/, '_')
94 end
95
96 # Proxy object returned by asset.upload, providing the Paperclip-compatible
97 # interface used in views: .url, .url(:style), .content_type, .size
98 class UploadProxy
99 def initialize(record)
100 @record = record
101 end
102
103 def url(style = :original)
104 return "" if @record.upload_file_name.blank?
105 "/system/uploads/#{@record.id}/#{style}/#{@record.upload_file_name}"
106 end
107
108 def content_type
109 @record.upload_content_type.to_s
110 end
111
112 def size
113 @record.upload_file_size.to_i
114 end
115
116 def present?
117 @record.upload_file_name.present?
118 end
119
120 def blank?
121 !present?
122 end
123 end
124end
diff --git a/app/models/event.rb b/app/models/event.rb
index 23deed6..94a22e3 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -1,4 +1,4 @@
1class Event < ActiveRecord::Base 1class Event < ApplicationRecord
2 2
3 # Associations 3 # Associations
4 4
diff --git a/app/models/menu_item.rb b/app/models/menu_item.rb
index eb82347..7769b7f 100644
--- a/app/models/menu_item.rb
+++ b/app/models/menu_item.rb
@@ -1,4 +1,4 @@
1class MenuItem < ActiveRecord::Base 1class MenuItem < ApplicationRecord
2 2
3 default_scope -> { where(:type => "MenuItem") } 3 default_scope -> { where(:type => "MenuItem") }
4 4
diff --git a/app/models/node.rb b/app/models/node.rb
index d760f0a..f7a70d0 100644
--- a/app/models/node.rb
+++ b/app/models/node.rb
@@ -1,4 +1,4 @@
1class Node < ActiveRecord::Base 1class Node < ApplicationRecord
2 # Mixins and Plugins 2 # Mixins and Plugins
3 acts_as_nested_set 3 acts_as_nested_set
4 4
diff --git a/app/models/occurrence.rb b/app/models/occurrence.rb
index 8457ffd..3baf447 100644
--- a/app/models/occurrence.rb
+++ b/app/models/occurrence.rb
@@ -1,7 +1,7 @@
1# TODO Make a gem out of the c wrapper 1# TODO Make a gem out of the c wrapper
2require 'chaos_calendar' 2require 'chaos_calendar'
3 3
4class Occurrence < ActiveRecord::Base 4class Occurrence < ApplicationRecord
5 5
6 # Associations 6 # Associations
7 7
diff --git a/app/models/page.rb b/app/models/page.rb
index 93debf8..d1e7439 100644
--- a/app/models/page.rb
+++ b/app/models/page.rb
@@ -1,6 +1,6 @@
1require 'xml' 1require 'xml'
2 2
3class Page < ActiveRecord::Base 3class Page < ApplicationRecord
4 4
5 PUBLIC_TEMPLATE_PATH = File.join(%w(custom page_templates public)) 5 PUBLIC_TEMPLATE_PATH = File.join(%w(custom page_templates public))
6 FULL_PUBLIC_TEMPLATE_PATH = Rails.root.join('app', 'views', PUBLIC_TEMPLATE_PATH) 6 FULL_PUBLIC_TEMPLATE_PATH = Rails.root.join('app', 'views', PUBLIC_TEMPLATE_PATH)
diff --git a/app/models/permission.rb b/app/models/permission.rb
index f304538..1383a4b 100644
--- a/app/models/permission.rb
+++ b/app/models/permission.rb
@@ -1,4 +1,4 @@
1class Permission < ActiveRecord::Base 1class Permission < ApplicationRecord
2 # Validations 2 # Validations
3 validates_presence_of :user_id, :node_id, :granted 3 validates_presence_of :user_id, :node_id, :granted
4 validates_inclusion_of :granted, :in => [true, false] 4 validates_inclusion_of :granted, :in => [true, false]
diff --git a/app/models/related_asset.rb b/app/models/related_asset.rb
index 2b61c51..8f16460 100644
--- a/app/models/related_asset.rb
+++ b/app/models/related_asset.rb
@@ -1,4 +1,4 @@
1class RelatedAsset < ActiveRecord::Base 1class RelatedAsset < ApplicationRecord
2 belongs_to :page 2 belongs_to :page
3 belongs_to :asset 3 belongs_to :asset
4 4
diff --git a/app/models/user.rb b/app/models/user.rb
index a2540b5..92ac33a 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1,6 +1,6 @@
1require 'digest/sha1' 1require 'digest/sha1'
2 2
3class User < ActiveRecord::Base 3class User < ApplicationRecord
4 # Mixins and Plugins 4 # Mixins and Plugins
5 include Authentication 5 include Authentication
6 include Authentication::ByPassword 6 include Authentication::ByPassword