wco_models 3.1.0.37 → 3.1.0.39

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 (182) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +3 -13
  3. data/app/assets/config/wco_models_manifest.js +2 -0
  4. data/app/assets/javascripts/wco/application.js +43 -5
  5. data/app/assets/javascripts/wco/collapse-expand.js +1 -0
  6. data/app/assets/javascripts/wco/file_upload.js +32 -0
  7. data/app/assets/javascripts/wco/shared.js +22 -0
  8. data/app/assets/stylesheets/wco/application.css +23 -8
  9. data/app/assets/stylesheets/wco/utils.scss +57 -1
  10. data/app/controllers/wco/application_controller.rb +9 -0
  11. data/app/controllers/wco/galleries_controller.rb +143 -0
  12. data/app/controllers/wco/headlines_controller.rb +71 -0
  13. data/app/controllers/wco/leads_controller.rb +59 -0
  14. data/app/controllers/wco/leadsets_controller.rb +90 -0
  15. data/app/controllers/wco/logs_controller.rb +64 -0
  16. data/app/controllers/wco/office_action_templates_controller.rb +80 -0
  17. data/app/controllers/wco/office_actions_controller.rb +71 -0
  18. data/app/controllers/wco/photos_controller.rb +78 -0
  19. data/app/controllers/wco/profiles_controller.rb +26 -0
  20. data/app/controllers/wco/publishers_controller.rb +60 -0
  21. data/app/controllers/wco/reports_controller.rb +75 -0
  22. data/app/controllers/wco/sites_controller.rb +52 -0
  23. data/app/controllers/wco/tags_controller.rb +57 -0
  24. data/app/helpers/wco/application_helper.rb +42 -0
  25. data/app/models/wco/asset.rb +26 -0
  26. data/app/models/wco/gallery.rb +3 -3
  27. data/app/models/wco/headline.rb +19 -0
  28. data/app/models/wco/invoice.rb +127 -0
  29. data/app/models/wco/lead.rb +42 -0
  30. data/app/models/wco/leadset.rb +8 -2
  31. data/app/models/wco/log.rb +18 -0
  32. data/app/models/wco/newsitem.rb +1 -0
  33. data/app/models/wco/office_action.rb +17 -18
  34. data/app/models/wco/office_action_template.rb +41 -0
  35. data/app/models/wco/office_action_template_tie.rb +20 -0
  36. data/app/models/wco/photo.rb +10 -8
  37. data/app/models/wco/price.rb +2 -0
  38. data/app/models/wco/product.rb +2 -0
  39. data/app/models/wco/profile.rb +1 -0
  40. data/app/models/wco/publisher.rb +68 -3
  41. data/app/models/wco/report.rb +48 -0
  42. data/app/models/wco/site.rb +12 -1
  43. data/app/models/wco/subscription.rb +1 -0
  44. data/app/models/wco/tag.rb +17 -4
  45. data/app/models/wco_email/campaign.rb +7 -11
  46. data/app/models/wco_email/context.rb +15 -14
  47. data/app/models/wco_email/conversation.rb +13 -27
  48. data/app/models/wco_email/{scheduled_email_action.rb → email_action.rb} +24 -20
  49. data/app/models/wco_email/email_action_template.rb +35 -0
  50. data/app/models/wco_email/email_filter.rb +9 -12
  51. data/app/models/wco_email/email_template.rb +124 -0
  52. data/app/models/wco_email/message.rb +228 -0
  53. data/app/models/wco_email/message_stub.rb +107 -0
  54. data/app/models/wco_email/obfuscated_redirect.rb +13 -0
  55. data/app/models/wco_email/unsubscribe.rb +19 -0
  56. data/app/models/wco_hosting/appliance.rb +1 -0
  57. data/app/models/wco_hosting/appliance_tmpl.rb +1 -0
  58. data/app/models/wco_hosting/domain.rb +1 -0
  59. data/app/models/wco_hosting/serverhost.rb +1 -0
  60. data/app/views/layouts/wco/application.haml +30 -17
  61. data/app/views/wco/_main_footer.haml +43 -0
  62. data/app/views/wco/_main_header.haml +37 -0
  63. data/app/views/wco/_main_header.haml-bk +109 -0
  64. data/app/views/wco/_paginate.haml +8 -0
  65. data/app/views/wco/_search.haml +8 -0
  66. data/app/views/wco/application/_debug.haml +0 -0
  67. data/app/views/wco/galleries/_form.haml +24 -0
  68. data/app/views/wco/galleries/_header.haml +8 -0
  69. data/app/views/wco/galleries/_index.haml +40 -0
  70. data/app/views/wco/galleries/_menu.haml-trash +10 -0
  71. data/app/views/wco/galleries/_menu_secondary.haml +12 -0
  72. data/app/views/wco/galleries/_thumbs.haml +9 -0
  73. data/app/views/wco/galleries/_title.haml +16 -0
  74. data/app/views/wco/galleries/edit.haml +6 -0
  75. data/app/views/wco/galleries/new.haml +6 -0
  76. data/app/views/wco/galleries/show.haml +43 -0
  77. data/app/views/wco/headlines/_form.haml +17 -0
  78. data/app/views/wco/headlines/_header.haml +6 -0
  79. data/app/views/wco/headlines/_index.haml +20 -0
  80. data/app/views/wco/headlines/edit.haml +6 -0
  81. data/app/views/wco/headlines/new.haml +2 -0
  82. data/app/views/wco/invoices/_form.haml-trash +29 -0
  83. data/app/views/wco/invoices/_header.haml +6 -0
  84. data/app/views/wco/invoices/_index_list.haml +13 -0
  85. data/app/views/wco/invoices/_index_table.haml +20 -0
  86. data/app/views/wco/invoices/index.haml +3 -0
  87. data/app/views/wco/invoices/new_pdf.haml +18 -0
  88. data/app/views/wco/invoices/new_stripe.haml +50 -0
  89. data/app/views/wco/invoices/show.haml +23 -0
  90. data/app/views/wco/kaminari/_first_page.html.erb +11 -0
  91. data/app/views/wco/kaminari/_gap.html.erb +8 -0
  92. data/app/views/wco/kaminari/_last_page.html.erb +11 -0
  93. data/app/views/wco/kaminari/_next_page.html.erb +11 -0
  94. data/app/views/wco/kaminari/_page.html.erb +13 -0
  95. data/app/views/wco/kaminari/_paginator.html.erb +25 -0
  96. data/app/views/wco/kaminari/_prev_page.html.erb +11 -0
  97. data/app/views/wco/leads/_form.haml +45 -0
  98. data/app/views/wco/leads/_form_import.haml +11 -0
  99. data/app/views/wco/leads/_header.haml +7 -0
  100. data/app/views/wco/leads/_index.haml +75 -0
  101. data/app/views/wco/leads/_index_rows.haml +11 -0
  102. data/app/views/wco/leads/edit.haml +22 -0
  103. data/app/views/wco/leads/index.haml +7 -0
  104. data/app/views/wco/leads/new.haml +9 -0
  105. data/app/views/wco/leads/show.haml +85 -0
  106. data/app/views/wco/leadsets/_form.haml +28 -0
  107. data/app/views/wco/leadsets/_header.haml +6 -0
  108. data/app/views/wco/leadsets/edit.haml +4 -0
  109. data/app/views/wco/leadsets/index.haml +45 -0
  110. data/app/views/wco/leadsets/index_dataTables.haml +33 -0
  111. data/app/views/wco/leadsets/new.haml +6 -0
  112. data/app/views/wco/leadsets/show.haml +52 -0
  113. data/app/views/wco/logs/_form.haml +17 -0
  114. data/app/views/wco/logs/_header.haml +6 -0
  115. data/app/views/wco/logs/_index.haml +23 -0
  116. data/app/views/wco/logs/edit.haml +6 -0
  117. data/app/views/wco/logs/new.haml +2 -0
  118. data/app/views/wco/office_action_templates/_form.haml +46 -0
  119. data/app/views/wco/office_action_templates/_header.haml +8 -0
  120. data/app/views/wco/office_action_templates/_ties.haml +18 -0
  121. data/app/views/wco/office_action_templates/edit.haml +6 -0
  122. data/app/views/wco/office_action_templates/index.haml +17 -0
  123. data/app/views/wco/office_action_templates/new.haml +5 -0
  124. data/app/views/wco/office_action_templates/show.haml +22 -0
  125. data/app/views/wco/office_actions/_form.haml +22 -0
  126. data/app/views/wco/office_actions/_header.haml +6 -0
  127. data/app/views/wco/office_actions/_index.haml +15 -0
  128. data/app/views/wco/office_actions/edit.haml +5 -0
  129. data/app/views/wco/office_actions/index.haml +10 -0
  130. data/app/views/wco/office_actions/new.haml +4 -0
  131. data/app/views/wco/office_actions/show.haml +5 -0
  132. data/app/views/wco/photos/_form.haml +0 -0
  133. data/app/views/wco/photos/_index_thumbs.haml +7 -0
  134. data/app/views/wco/photos/_meta.haml +7 -0
  135. data/app/views/wco/photos/_meta_manager.haml +7 -0
  136. data/app/views/wco/photos/_multinew.haml +6 -0
  137. data/app/views/wco/photos/index.haml +3 -0
  138. data/app/views/wco/photos/new.haml +0 -0
  139. data/app/views/wco/photos/show.haml +6 -0
  140. data/app/views/wco/photos/without_gallery.haml +11 -0
  141. data/app/views/wco/prices/_form.haml +1 -1
  142. data/app/views/wco/products/_header.haml +6 -0
  143. data/app/views/wco/publishers/_form.haml +28 -0
  144. data/app/views/wco/publishers/_header.haml +6 -0
  145. data/app/views/wco/publishers/edit.haml +4 -0
  146. data/app/views/wco/publishers/index.haml +37 -0
  147. data/app/views/wco/publishers/new.haml +4 -0
  148. data/app/views/wco/reports/_form.haml +28 -0
  149. data/app/views/wco/reports/_header.haml +14 -0
  150. data/app/views/wco/reports/edit.haml +4 -0
  151. data/app/views/wco/reports/index.haml +17 -0
  152. data/app/views/wco/reports/index_table.haml +17 -0
  153. data/app/views/wco/reports/new.haml +4 -0
  154. data/app/views/wco/reports/show.haml +8 -0
  155. data/app/views/wco/sites/_form.haml +21 -0
  156. data/app/views/wco/sites/_header.haml +6 -0
  157. data/app/views/wco/sites/edit.haml +4 -0
  158. data/app/views/wco/sites/index.haml +24 -0
  159. data/app/views/wco/sites/new.haml +4 -0
  160. data/app/views/wco/tags/_form.haml +10 -0
  161. data/app/views/wco/tags/_header.haml +6 -0
  162. data/app/views/wco/tags/_index.haml +10 -0
  163. data/app/views/wco/tags/_index_inline.haml +8 -0
  164. data/app/views/wco/tags/edit.haml +0 -0
  165. data/app/views/wco/tags/index.haml +9 -0
  166. data/app/views/wco/tags/new.haml +0 -0
  167. data/app/views/wco/tags/show.haml +15 -0
  168. data/config/routes.rb +42 -0
  169. data/lib/tasks/db_tasks.rake +13 -0
  170. data/lib/tasks/office_tasks.rake +36 -0
  171. data/lib/tasks/scrape_tasks.rake-trash +22 -0
  172. data/lib/wco/ai_writer.rb +55 -0
  173. data/lib/wco/engine.rb +0 -1
  174. data/lib/wco/office_worker.rb +4 -0
  175. data/lib/wco/scrape_wsj.rb +29 -0
  176. data/lib/wco/scrape_wsj_capy.rb +44 -0
  177. data/lib/wco_models.rb +19 -2
  178. metadata +254 -45
  179. data/app/helpers/ish_models/application_helper.rb +0 -4
  180. data/app/models/wco_email/message_template.rb +0 -6
  181. data/lib/tasks/ish_models_tasks.rake +0 -4
  182. /data/app/views/wco/{application/_alerts_notices.haml → _alerts_notices.haml} +0 -0
@@ -5,6 +5,7 @@
5
5
  class WcoEmail::Context
6
6
  include Mongoid::Document
7
7
  include Mongoid::Timestamps
8
+ include Mongoid::Paranoia
8
9
  store_in collection: 'ish_email_contexts'
9
10
 
10
11
  field :slug
@@ -44,13 +45,15 @@ class WcoEmail::Context
44
45
  self[:subject].presence || tmpl.subject
45
46
  end
46
47
 
47
- belongs_to :email_template
48
+ has_and_belongs_to_many :leads, class_name: 'Wco::Lead'
49
+
50
+ belongs_to :email_template, class_name: 'WcoEmail::EmailTemplate'
48
51
  def tmpl; email_template; end
49
52
 
50
- belongs_to :scheduled_email_action, class_name: '::Office::ScheduledEmailAction', optional: true
53
+ belongs_to :scheduled_email_action, class_name: 'WcoEmail::ScheduledEmailAction', optional: true
51
54
  def sch; scheduled_email_action; end
52
55
 
53
- belongs_to :email_campaign, class_name: 'Ish::EmailCampaign', optional: true
56
+ belongs_to :email_campaign, class_name: 'WcoEmail::EmailCampaign', optional: true
54
57
 
55
58
  field :rendered_str
56
59
 
@@ -60,26 +63,24 @@ class WcoEmail::Context
60
63
 
61
64
 
62
65
  def notsent
63
- Ish::EmailContext.where( sent_at: nil, unsubscribed_at: nil )
66
+ WcoEmail::EmailContext.where( sent_at: nil, unsubscribed_at: nil )
64
67
  end
65
68
  def self.notsent; new.notsent; end
66
69
 
67
70
 
68
71
  def scheduled
69
72
  # or({ :send_at.lte => Time.now }, { :send_at => nil }) ## This won't work b/c I need draft state!
70
- Ish::EmailContext.where({ :send_at.lte => Time.now })
73
+ WcoEmail::EmailContext.where({ :send_at.lte => Time.now })
71
74
  end
72
75
  def self.scheduled; new.scheduled; end
73
76
 
74
77
 
75
- ## like belongs_to to_lead , but Lead is SQL to just the lead_id
76
- field :lead_id, type: :integer
77
- def lead; Lead.find( lead_id ); end
78
- def to_email; lead[:email]; end ## @TODO: remove, just use the lead. _vp_ 2023-03-27
79
- # ## no `to` field b/c that's the lead
80
- # field :tos, type: :array, default: []
81
- field :cc, type: :string
82
- field :ccs, type: :array, default: []
78
+ belongs_to :lead, class_name: 'Wco::Lead'
79
+
80
+ field :cc, type: :string
81
+ field :ccs, type: :array, default: []
82
+ field :bcc, type: :string
83
+ field :bccs, type: :array, default: []
83
84
 
84
85
  ##
85
86
  ## For tracking / utm
@@ -105,7 +106,7 @@ class WcoEmail::Context
105
106
  } },
106
107
  { '$sort' => { '_id': -1 } },
107
108
  ]
108
- outs = Ish::EmailContext.collection.aggregate( pipeline )
109
+ outs = WcoEmail::EmailContext.collection.aggregate( pipeline )
109
110
  outs.to_a
110
111
  end
111
112
 
@@ -2,14 +2,17 @@
2
2
  class WcoEmail::Conversation
3
3
  include Mongoid::Document
4
4
  include Mongoid::Timestamps
5
- # include Mongoid::Paranoia
6
-
5
+ include Mongoid::Paranoia
7
6
  store_in collection: 'office_email_conversations'
8
7
 
9
- STATE_UNREAD = 'state_unread'
10
- STATE_READ = 'state_read'
11
- STATES = [ STATE_UNREAD, STATE_READ ]
12
- field :state
8
+ STATUS_UNREAD = 'status_unread'
9
+ STATUS_READ = 'status_read'
10
+ STATUSES = [ STATUS_UNREAD, STATUS_READ ]
11
+ field :status
12
+ scope :unread, ->{ where( status: WcoEmail::Conversation::STATUS_UNREAD ) }
13
+ def unread?
14
+ status == STATUS_UNREAD
15
+ end
13
16
 
14
17
  field :subject
15
18
  index({ subject: -1 })
@@ -22,27 +25,10 @@ class WcoEmail::Conversation
22
25
 
23
26
  field :preview, default: ''
24
27
 
25
- # has_many :lead_ties, class_name: 'Office::EmailConversationLead'
26
- # def lead_ids
27
- # email_conversation_leads.map( &:lead_id )
28
- # end
29
- # field :lead_ids, type: :array, default: []
30
- # def leads
31
- # Lead.find( lead_ties.map( &:lead_id ) )
32
- # end
33
-
34
- # has_many :email_messages, class_name: 'Office::EmailMessage'
35
- # has_many :email_conversation_tags, class_name: 'Office::EmailConversationTag'
36
-
37
- has_and_belongs_to_many :tags, class_name: 'Wco::Tag'
38
-
39
- # def self.in_tag tag
40
- # case tag.class
41
- # when String
42
- # tag = Wco::Tag.find_by slug: tag
43
- # end
44
- # where( :tag_ids => tag.id )
45
- # end
28
+ has_many :messages, class_name: '::WcoEmail::Message'
29
+
30
+ has_and_belongs_to_many :tags, class_name: 'Wco::Tag'
31
+ has_and_belongs_to_many :leads, class_name: 'Wco::Lead'
46
32
 
47
33
  end
48
34
  Conv = WcoEmail::Conversation
@@ -3,39 +3,39 @@ require 'mongoid-paranoia'
3
3
 
4
4
  ##
5
5
  ## 2023-03-04 _vp_ An instance of an EmailAction.
6
+ ## 2023-03-04 _vp_ An instance of an EmailActionTemplate.
6
7
  ##
7
- class WcoEmail::ScheduledEmailAction
8
+ class WcoEmail::EmailAction
8
9
  include Mongoid::Document
9
10
  include Mongoid::Timestamps
10
11
  include Mongoid::Paranoia
11
12
  store_in collection: 'office_scheduled_email_actions'
12
13
 
13
- field :lead_id, type: :integer
14
- def lead
15
- Lead.find( lead_id )
16
- end
17
14
 
18
- STATE_ACTIVE = 'active'
19
- STATE_INACTIVE = 'inactive'
20
- STATE_TRASH = 'trash'
21
- STATE_UNSUBSCRIBED = 'unsubscribed'
22
- STATES = [ STATE_ACTIVE, STATE_INACTIVE, STATE_UNSUBSCRIBED, STATE_TRASH ]
23
- field :state, type: :string
24
- scope :active, ->{ where( state: STATE_ACTIVE ) }
15
+ STATUS_ACTIVE = 'active'
16
+ STATUS_INACTIVE = 'inactive'
17
+ STATUS_TRASH = 'trash'
18
+ STATUS_UNSUBSCRIBED = 'unsubscribed'
19
+ STATUSS = [ STATUS_ACTIVE, STATUS_INACTIVE, STATUS_UNSUBSCRIBED, STATUS_TRASH ]
20
+ field :status, type: :string
21
+ scope :active, ->{ where( status: STATUS_ACTIVE ) }
22
+
23
+ belongs_to :lead, class_name: 'Wco::Lead'
25
24
 
26
- belongs_to :email_action, class_name: '::Office::EmailAction'
27
- validates :email_action, uniqueness: { scope: :lead_id }
28
- def act; email_action; end
29
- def act= a; email_action= a; end
25
+ belongs_to :email_action_template, class_name: 'WcoEmail::EmailActionTemplate'
26
+ validates :email_action_template, uniqueness: { scope: :lead }
27
+ def tmpl; email_action_template; end
28
+ def tmpl= k; email_action_template = k; end
30
29
 
31
- has_many :email_contexts, class_name: '::Ish::EmailContext'
30
+ has_many :email_contexts, class_name: 'WcoEmail::Context'
32
31
  def ctxs; email_contexts; end
33
32
 
34
33
  field :perform_at, type: :time
35
34
 
35
+
36
36
  def send_and_roll
37
37
  sch = self
38
- sch.update({ state: Sch::STATE_INACTIVE })
38
+ sch.update({ status: STATUS_INACTIVE })
39
39
 
40
40
  # send now
41
41
  ctx = Ctx.create!({
@@ -54,11 +54,15 @@ class WcoEmail::ScheduledEmailAction
54
54
  email_action_id: tie.next_email_action.id,
55
55
  })
56
56
  next_sch.perform_at = eval(tie.next_at_exe)
57
- next_sch.state = Sch::STATE_ACTIVE
57
+ next_sch.status = STATUS_ACTIVE
58
58
  next_sch.save!
59
59
  end
60
60
  end
61
61
 
62
+ def self.list
63
+ [[nil,nil]] + all.map { |p| [ "#{p.lead.email} :: #{p.tmpl.slug}", p.id ] }
64
+ end
65
+
66
+
62
67
  end
63
- Sch = WcoEmail::ScheduledEmailAction
64
68
 
@@ -0,0 +1,35 @@
1
+
2
+ ##
3
+ ## 2023-03-04 _vp_ When I receive one.
4
+ ## 2023-03-04 _vp_ When I send one, forever.
5
+ ##
6
+ class WcoEmail::EmailActionTemplate
7
+ include Mongoid::Document
8
+ include Mongoid::Timestamps
9
+ include Mongoid::Paranoia
10
+ store_in collection: 'wco_email_email_actions'
11
+
12
+ field :slug, type: :string
13
+ validates :slug, uniqueness: true, allow_nil: true
14
+ index({ slug: 1 }, { unique: true, name: "slug_idx" })
15
+
16
+ belongs_to :email_template, class_name: 'WcoEmail::EmailTemplate'
17
+ def tmpl; email_template; end
18
+
19
+ has_many :scheduled_email_actions, class_name: 'WcoEmail::ScheduledEmailAction'
20
+ def schs; scheduled_email_actions; end
21
+
22
+ has_many :ties, class_name: 'WcoEmail::EmailActionTie', inverse_of: :email_action
23
+ has_many :prev_ties, class_name: 'WcoEmail::EmailActionTie', inverse_of: :next_email_action
24
+ accepts_nested_attributes_for :ties
25
+
26
+ has_many :email_filters, class_name: 'WcoEmail::EmailFilter', inverse_of: :email_action
27
+
28
+ ## @TODO: change this right now.
29
+ field :deleted_at, default: nil, type: :time
30
+
31
+ def self.list
32
+ [[nil,nil]] + WcoEmail::EmailAction.where({ :deleted_at => nil }).map { |a| [ a.slug, a.id ] }
33
+ end
34
+
35
+ end
@@ -5,6 +5,8 @@
5
5
  class WcoEmail::EmailFilter
6
6
  include Mongoid::Document
7
7
  include Mongoid::Timestamps
8
+ include Mongoid::Paranoia
9
+ store_in collection: 'office_email_filters' # 'wco_email_email_filters'
8
10
 
9
11
  field :from_regex
10
12
  field :from_exact
@@ -27,19 +29,14 @@ class WcoEmail::EmailFilter
27
29
  KINDS = [ nil, KIND_AUTORESPOND_TMPL, KIND_AUTORESPOND_EACT, KIND_ADD_TAG, KIND_REMOVE_TAG, KIND_DESTROY_SCHS]
28
30
  field :kind
29
31
 
30
- STATE_ACTIVE = 'active'
31
- STATE_INACTIVE = 'inactive'
32
- STATES = [ STATE_ACTIVE, STATE_INACTIVE ]
33
- field :state, type: :string, default: STATE_ACTIVE
34
- scope :active, ->{ where( state: STATE_ACTIVE ) }
32
+ STATUS_ACTIVE = 'active'
33
+ STATUS_INACTIVE = 'inactive'
34
+ STATUSS = [ STATUS_ACTIVE, STATUS_INACTIVE ]
35
+ field :status, type: :string, default: STATUS_ACTIVE
36
+ scope :active, ->{ where( status: STATUS_ACTIVE ) }
35
37
 
36
- belongs_to :email_template, class_name: 'Ish::EmailTemplate', optional: true
37
- belongs_to :email_action, class_name: 'Office::EmailAction', optional: true
38
-
39
- field :wp_term_id, type: :integer
40
- def category
41
- self.wp_term_id && WpTag.find( self.wp_term_id )
42
- end
38
+ belongs_to :email_template, class_name: 'WcoEmail::EmailTemplate', optional: true
39
+ belongs_to :email_action_template, class_name: 'WcoEmail::EmailActionTemplate', optional: true
43
40
 
44
41
  end
45
42
 
@@ -0,0 +1,124 @@
1
+
2
+ class WcoEmail::EmailTemplate
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+ include Mongoid::Paranoia
6
+ store_in collection: 'ish_email_templates'
7
+
8
+ field :slug
9
+ validates :slug, presence: true, uniqueness: true
10
+ index({ slug: 1 }, { unique: true, name: "slug_idx" })
11
+ def to_s
12
+ "Tmpl:#{slug}"
13
+ end
14
+
15
+ field :preview_str, type: :string
16
+
17
+ field :layout, type: :string, default: 'plain'
18
+ LAYOUTS = %w| plain
19
+ m20221201react m20221222merryxmas
20
+ m202309_feedback
21
+ m202309_ror4
22
+ marketing_node_1
23
+ marketing_react_1
24
+ marketing_react_2
25
+ marketing_react_3
26
+ marketing_ror_1
27
+ marketing_ror_2
28
+ marketing_wordpres_1
29
+ marketing_wordpress_2
30
+ piousbox_roundborders
31
+ plain
32
+ tracking_footer
33
+ wasyaco_roundborders |
34
+
35
+ field :subject
36
+ field :body
37
+ field :can_unsubscribe, type: :boolean, default: true
38
+ field :config_exe, default: "" ## unused! _vp_ 2023-09-24
39
+ field :config_json, type: Object, default: '{}'
40
+ field :layout, default: 'plain'
41
+
42
+ DEFAULT_FROM_EMAIL = 'Victor Pudeyev <victor@wasya.co>'
43
+ FROM_EMAILS = [
44
+ 'Annesque Studio <hello@annesque.studio>',
45
+ 'Annesque Studio <no-reply@annesque.studio>',
46
+
47
+ 'BJJCollective <hello@bjjcollective.com>',
48
+ 'BJJCollective <no-reply@bjjcollective.com>',
49
+
50
+ 'DemmiTV <hello@demmi.tv>',
51
+ 'DemmiTV <no-reply@demmi.tv>',
52
+
53
+ 'Victor Pudeyev <victor@fedfis.com>',
54
+
55
+ 'Infinite Shelter <hello@infiniteshelter.com>',
56
+ 'Infinite Shelter <no-reply@infiniteshelter.com>',
57
+
58
+ 'Oquaney Splicing <hello@oquaney-splicing.com>',
59
+ 'Oquaney Splicing <no-reply@oquaney-splicing.com>',
60
+
61
+ 'Victor Pudeyev <piousbox@gmail.com>',
62
+ 'Victor Pudeyev <victor@piousbox.com>',
63
+ 'Victor Pudeyev <no-reply@piousbox.com>',
64
+ 'Victor Pudeyev <victor@pudeyev.com>',
65
+
66
+ 'Sender SBS <hello@sender.sbs>',
67
+ 'Sender SBS <no-reply@sender.sbs>',
68
+
69
+ 'WasyaCo Consulting <admin@wasya.co>',
70
+ 'Alex WCo <alex@wasya.co>',
71
+ 'Bailey WCo <bailey@wasya.co>',
72
+ 'Cameron WCo <cameron@wasya.co>',
73
+ 'WasyaCo Consulting <hello@wasya.co>',
74
+ 'Jess WCo <jess@wasya.co>',
75
+ 'WasyaCo Consulting <no-reply@wasya.co>',
76
+ 'Victor Pudeyev <victor@wasya.co>',
77
+
78
+ 'WasyaCo Consulting <admin@wasyaco.com>',
79
+ 'Alex WCo <alex@wasyaco.com>',
80
+ 'Bailey WCo <bailey@wasyaco.com>',
81
+ 'Cameron WCo <cameron@wasyaco.com>',
82
+ 'WasyaCo Consulting <hello@wasyaco.com>',
83
+ 'WasyaCo Consulting <no-reply@wasyaco.com>',
84
+ 'Victor Pudeyev <victor@wasyaco.com>',
85
+
86
+ 'Wasya Co Mailer <no-reply@wco.com.de>',
87
+ 'Wasya Co Mailer <wasyacomailer@gmail.com>',
88
+ ];
89
+ field :from_email
90
+ def self.from_email_list
91
+ [ [nil, nil] ] + FROM_EMAILS.map { |i| [i, i] }
92
+ end
93
+
94
+ SIGNATURE = <<~AOL
95
+ <div>
96
+ <div><br></div>
97
+ <div>Regards,</div>
98
+ <div>-=----- &gt;8 --</div>
99
+ <div>Victor Pudeyev<br>Director of Engineering<br><a href="mailto:victor@wasya.co" target="_blank">victor@wasya.co</a> | <a href="https://tidycal.com/wasya-co/30min" target="_blank">Book a chat</a><br></div>
100
+ </div><hr /><br /><br /><br />
101
+ AOL
102
+
103
+ ## 2023-03-04 _vp_ This works!
104
+ def get_binding
105
+ @lead = Lead.where( email: 'stub@wasya.co' ).first
106
+ binding()
107
+ end
108
+
109
+ has_many :email_actions, class_name: 'WcoEmail::EmailAction'
110
+ has_many :email_contexts, class_name: 'WcoEmail::EmailContext'
111
+ has_many :email_filters, class_name: 'WcoEmail::EmailFilter'
112
+ has_many :unsubscribes, class_name: 'WcoEmail::Unsubscribe'
113
+
114
+ SLUG_BLANK = 'blank'
115
+ def self.blank_template
116
+ out = Tmpl.find_or_create_by({ slug: SLUG_BLANK })
117
+ end
118
+ def self.blank; self.blank_template; end
119
+
120
+ def self.list
121
+ all.map { |p| [ p.id, p.slug ] }
122
+ end
123
+
124
+ end
@@ -0,0 +1,228 @@
1
+
2
+ require 'action_view'
3
+
4
+ ##
5
+ ## When I receive one.
6
+ ## 2023-12-28 _vp_ Continue.
7
+ ##
8
+ class WcoEmail::Message
9
+ include Mongoid::Document
10
+ include Mongoid::Timestamps
11
+ include Mongoid::Paranoia
12
+ store_in collection: 'wco_email_message'
13
+
14
+ field :message_id # MESSAGE-ID
15
+ validates :message_id, presence: true, uniqueness: true
16
+ index({ message_id: 1 }, { unique: true, name: "message_id_idx" })
17
+
18
+ field :in_reply_to_id, type: :string
19
+
20
+ field :object_key, type: :string ## aka 'filename', use with bucket name + prefix. I need this!
21
+ validates :object_key, presence: true, uniqueness: true
22
+ index({ object_key: 1 }, { unique: true, name: "object_key_idx" })
23
+
24
+ field :subject
25
+
26
+ field :part_html
27
+
28
+ def part_html_sanitized
29
+ doc = Nokogiri::HTML part_html
30
+ images = doc.search('img')
31
+ images.each do |img|
32
+ img['src'] = 'missing'
33
+ end
34
+ doc.search('script').remove
35
+ doc.search('meta').remove
36
+ # doc.xpath('//@style').remove
37
+ doc.to_s.gsub('http', '').gsub('href="s://', 'href="https://')
38
+ end
39
+
40
+ def part_html_fully_sanitized
41
+ ActionView::Base.full_sanitizer.sanitize( part_html||'' ).squish
42
+ end
43
+
44
+ def preview_str
45
+ part_html_fully_sanitized[0..200]
46
+ end
47
+
48
+ field :part_txt
49
+
50
+ field :from, type: :string
51
+ field :froms, type: Array, default: []
52
+ field :to, type: :string
53
+ field :tos, type: Array, default: []
54
+ field :cc, type: :string
55
+ field :ccs, type: Array, default: []
56
+ def all_ccs; (tos||[]) + (ccs||[]) + (froms||[]); end
57
+ field :bcc, type: :string
58
+ field :bccs, type: Array, default: []
59
+
60
+ field :logs, type: Array, default: []
61
+
62
+ field :date, type: DateTime
63
+ def received_at ; date ; end
64
+
65
+ belongs_to :conversation, class_name: 'WcoEmail::Conversation'
66
+ def conv ; email_conversation ; end
67
+
68
+ belongs_to :stub, class_name: 'WcoEmail::MessageStub'
69
+
70
+ has_many :assets, class_name: 'Wco::Asset'
71
+ has_many :photos, class_name: 'Wco::Photo'
72
+
73
+ def apply_filter filter
74
+ case filter.kind
75
+
76
+ when ::Office::EmailFilter::KIND_DESTROY_SCHS
77
+ conv.add_tag ::WpTag::TRASH
78
+ conv.remove_tag ::WpTag::INBOX
79
+ lead.schs.each do |sch|
80
+ sch.update_attributes({ state: ::Sch::STATE_TRASH })
81
+ end
82
+
83
+ when ::Office::EmailFilter::KIND_ADD_TAG
84
+ conv.add_tag filter.wp_term_id
85
+ if ::WpTag::TRASH == ::WpTag.find( filter.wp_term_id ).slug
86
+ conv.remove_tag ::WpTag::INBOX
87
+ end
88
+
89
+ when ::Office::EmailFilter::KIND_REMOVE_TAG
90
+ conv.remove_tag filter.wp_term_id
91
+
92
+ when ::Office::EmailFilter::KIND_AUTORESPOND_TMPL
93
+ Ish::EmailContext.create({
94
+ email_template: filter.email_template,
95
+ lead_id: lead.id,
96
+ send_at: Time.now + 22.minutes,
97
+ })
98
+
99
+ when ::Office::EmailFilter::KIND_AUTORESPOND_EACT
100
+ ::Sch.create({
101
+ email_action: filter.email_action,
102
+ state: ::Sch::STATE_ACTIVE,
103
+ lead_id: lead.id,
104
+ perform_at: Time.now + 22.minutes,
105
+ })
106
+
107
+ else
108
+ raise "unknown filter kind: #{filter.kind}"
109
+ end
110
+ end
111
+
112
+ ## From: https://stackoverflow.com/questions/24672834/how-do-i-remove-emoji-from-string/24673322
113
+ def self.strip_emoji(text)
114
+ text = '' if text.blank?
115
+ text = text.force_encoding('utf-8').encode
116
+ clean = ""
117
+
118
+ # symbols & pics
119
+ regex = /[\u{1f300}-\u{1f5ff}]/
120
+ clean = text.gsub regex, ""
121
+
122
+ # enclosed chars
123
+ regex = /[\u{2500}-\u{2BEF}]/ # I changed this to exclude chinese char
124
+ clean = clean.gsub regex, ""
125
+
126
+ # emoticons
127
+ regex = /[\u{1f600}-\u{1f64f}]/
128
+ clean = clean.gsub regex, ""
129
+
130
+ #dingbats
131
+ regex = /[\u{2702}-\u{27b0}]/
132
+ clean = clean.gsub regex, ""
133
+ end
134
+ def strip_emoji text
135
+ WcoEmail::Message.strip_emoji text
136
+ end
137
+
138
+ ## For recursive parts of type `related`.
139
+ ## Content dispositions:
140
+ # "inline; creation-date=\"Tue, 11 Apr 2023 19:39:42 GMT\"; filename=image005.png; modification-date=\"Tue, 11 Apr 2023 19:47:53 GMT\"; size=14916",
141
+ #
142
+ ## Content Types:
143
+ # "application/pdf; name=\"Securities Forward Agreement -- HaulHub Inc -- Victor Pudeyev -- 2021-10-26.docx.pdf\""
144
+ # "image/jpeg; name=TX_DL_2.jpg"
145
+ # "image/png; name=image005.png"
146
+ # "multipart/alternative; boundary=_000_BL0PR10MB2913C560ADE059F0AB3A6D11829A9BL0PR10MB2913namp_",
147
+ # "text/html; charset=utf-8"
148
+ # "text/plain; charset=UTF-8"
149
+ # "text/calendar; charset=utf-8; method=REQUEST"
150
+ def churn_subpart part
151
+ if part.content_disposition&.include?('attachment')
152
+ save_attachment( part, filename: "subpart-attachment" )
153
+ else
154
+ if part.content_type.include?("multipart")
155
+ part.parts.each do |subpart|
156
+ churn_subpart( subpart )
157
+ end
158
+ else
159
+ if part.content_type.include?('text/html')
160
+ self.part_html = strip_emoji( part.decoded )
161
+
162
+ elsif part.content_type.include?("text/plain")
163
+ self.part_txt = part.decoded
164
+
165
+ elsif part.content_type.include?("text/calendar")
166
+ save_attachment( part, filename: 'subpart-calendar.ics' )
167
+
168
+ elsif part.content_type.include?("application/pdf")
169
+ save_attachment( part, filename: 'subpart.pdf' )
170
+
171
+ elsif part.content_type.include?("image/jpeg")
172
+ save_attachment( part, filename: 'subpart.jpg' )
173
+
174
+ elsif part.content_type.include?("image/png")
175
+ save_attachment( part, filename: 'subpart.png' )
176
+
177
+ else
178
+ save_attachment( part, filename: 'subpart-unspecified' )
179
+ self.logs.push "444 No action for a part with content_type #{part.content_type}"
180
+
181
+ end
182
+ end
183
+ end
184
+ end
185
+
186
+ def save_attachment att, filename: "no-filename-specified"
187
+ config = JSON.parse(stub.config)
188
+ if defined?( config['process_images'] ) &&
189
+ false == config['process_images']
190
+ return
191
+ end
192
+
193
+ content_type = att.content_type.split(';')[0]
194
+ if content_type.include? 'image'
195
+ photo = Wco::Photo.new({
196
+ content_type: content_type,
197
+ email_message_id: self.id,
198
+ image_data: att.body.encoded,
199
+ original_filename: att.content_type_parameters[:name],
200
+ })
201
+ photo.decode_base64_image
202
+ photo.save
203
+
204
+ else
205
+ filename = CGI.escape( att.filename || filename )
206
+ sio = StringIO.new att.body.decoded
207
+ File.open("/tmp/#{filename}", 'w:UTF-8:ASCII-8BIT') do |f|
208
+ f.puts(sio.read)
209
+ end
210
+ asset = Wco::Asset.new({
211
+ email_message: self,
212
+ filename: filename,
213
+ object: File.open("/tmp/#{filename}"),
214
+ })
215
+ if !asset.save
216
+ self.logs.push "Could not save a wco asset: #{asset.errors.full_messages.join(", ")}"
217
+ self.save
218
+ end
219
+ end
220
+ end
221
+
222
+
223
+ end
224
+ ::Msg = WcoEmail::Message
225
+
226
+
227
+
228
+