wco_models 3.1.0.37 → 3.1.0.38

Sign up to get free protection for your applications and to get access to all the features.
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 +15 -2
  178. metadata +241 -46
  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
+