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.
- checksums.yaml +4 -4
- data/Rakefile +3 -13
- data/app/assets/config/wco_models_manifest.js +2 -0
- data/app/assets/javascripts/wco/application.js +43 -5
- data/app/assets/javascripts/wco/collapse-expand.js +1 -0
- data/app/assets/javascripts/wco/file_upload.js +32 -0
- data/app/assets/javascripts/wco/shared.js +22 -0
- data/app/assets/stylesheets/wco/application.css +23 -8
- data/app/assets/stylesheets/wco/utils.scss +57 -1
- data/app/controllers/wco/application_controller.rb +9 -0
- data/app/controllers/wco/galleries_controller.rb +143 -0
- data/app/controllers/wco/headlines_controller.rb +71 -0
- data/app/controllers/wco/leads_controller.rb +59 -0
- data/app/controllers/wco/leadsets_controller.rb +90 -0
- data/app/controllers/wco/logs_controller.rb +64 -0
- data/app/controllers/wco/office_action_templates_controller.rb +80 -0
- data/app/controllers/wco/office_actions_controller.rb +71 -0
- data/app/controllers/wco/photos_controller.rb +78 -0
- data/app/controllers/wco/profiles_controller.rb +26 -0
- data/app/controllers/wco/publishers_controller.rb +60 -0
- data/app/controllers/wco/reports_controller.rb +75 -0
- data/app/controllers/wco/sites_controller.rb +52 -0
- data/app/controllers/wco/tags_controller.rb +57 -0
- data/app/helpers/wco/application_helper.rb +42 -0
- data/app/models/wco/asset.rb +26 -0
- data/app/models/wco/gallery.rb +3 -3
- data/app/models/wco/headline.rb +19 -0
- data/app/models/wco/invoice.rb +127 -0
- data/app/models/wco/lead.rb +42 -0
- data/app/models/wco/leadset.rb +8 -2
- data/app/models/wco/log.rb +18 -0
- data/app/models/wco/newsitem.rb +1 -0
- data/app/models/wco/office_action.rb +17 -18
- data/app/models/wco/office_action_template.rb +41 -0
- data/app/models/wco/office_action_template_tie.rb +20 -0
- data/app/models/wco/photo.rb +10 -8
- data/app/models/wco/price.rb +2 -0
- data/app/models/wco/product.rb +2 -0
- data/app/models/wco/profile.rb +1 -0
- data/app/models/wco/publisher.rb +68 -3
- data/app/models/wco/report.rb +48 -0
- data/app/models/wco/site.rb +12 -1
- data/app/models/wco/subscription.rb +1 -0
- data/app/models/wco/tag.rb +17 -4
- data/app/models/wco_email/campaign.rb +7 -11
- data/app/models/wco_email/context.rb +15 -14
- data/app/models/wco_email/conversation.rb +13 -27
- data/app/models/wco_email/{scheduled_email_action.rb → email_action.rb} +24 -20
- data/app/models/wco_email/email_action_template.rb +35 -0
- data/app/models/wco_email/email_filter.rb +9 -12
- data/app/models/wco_email/email_template.rb +124 -0
- data/app/models/wco_email/message.rb +228 -0
- data/app/models/wco_email/message_stub.rb +107 -0
- data/app/models/wco_email/obfuscated_redirect.rb +13 -0
- data/app/models/wco_email/unsubscribe.rb +19 -0
- data/app/models/wco_hosting/appliance.rb +1 -0
- data/app/models/wco_hosting/appliance_tmpl.rb +1 -0
- data/app/models/wco_hosting/domain.rb +1 -0
- data/app/models/wco_hosting/serverhost.rb +1 -0
- data/app/views/layouts/wco/application.haml +30 -17
- data/app/views/wco/_main_footer.haml +43 -0
- data/app/views/wco/_main_header.haml +37 -0
- data/app/views/wco/_main_header.haml-bk +109 -0
- data/app/views/wco/_paginate.haml +8 -0
- data/app/views/wco/_search.haml +8 -0
- data/app/views/wco/application/_debug.haml +0 -0
- data/app/views/wco/galleries/_form.haml +24 -0
- data/app/views/wco/galleries/_header.haml +8 -0
- data/app/views/wco/galleries/_index.haml +40 -0
- data/app/views/wco/galleries/_menu.haml-trash +10 -0
- data/app/views/wco/galleries/_menu_secondary.haml +12 -0
- data/app/views/wco/galleries/_thumbs.haml +9 -0
- data/app/views/wco/galleries/_title.haml +16 -0
- data/app/views/wco/galleries/edit.haml +6 -0
- data/app/views/wco/galleries/new.haml +6 -0
- data/app/views/wco/galleries/show.haml +43 -0
- data/app/views/wco/headlines/_form.haml +17 -0
- data/app/views/wco/headlines/_header.haml +6 -0
- data/app/views/wco/headlines/_index.haml +20 -0
- data/app/views/wco/headlines/edit.haml +6 -0
- data/app/views/wco/headlines/new.haml +2 -0
- data/app/views/wco/invoices/_form.haml-trash +29 -0
- data/app/views/wco/invoices/_header.haml +6 -0
- data/app/views/wco/invoices/_index_list.haml +13 -0
- data/app/views/wco/invoices/_index_table.haml +20 -0
- data/app/views/wco/invoices/index.haml +3 -0
- data/app/views/wco/invoices/new_pdf.haml +18 -0
- data/app/views/wco/invoices/new_stripe.haml +50 -0
- data/app/views/wco/invoices/show.haml +23 -0
- data/app/views/wco/kaminari/_first_page.html.erb +11 -0
- data/app/views/wco/kaminari/_gap.html.erb +8 -0
- data/app/views/wco/kaminari/_last_page.html.erb +11 -0
- data/app/views/wco/kaminari/_next_page.html.erb +11 -0
- data/app/views/wco/kaminari/_page.html.erb +13 -0
- data/app/views/wco/kaminari/_paginator.html.erb +25 -0
- data/app/views/wco/kaminari/_prev_page.html.erb +11 -0
- data/app/views/wco/leads/_form.haml +45 -0
- data/app/views/wco/leads/_form_import.haml +11 -0
- data/app/views/wco/leads/_header.haml +7 -0
- data/app/views/wco/leads/_index.haml +75 -0
- data/app/views/wco/leads/_index_rows.haml +11 -0
- data/app/views/wco/leads/edit.haml +22 -0
- data/app/views/wco/leads/index.haml +7 -0
- data/app/views/wco/leads/new.haml +9 -0
- data/app/views/wco/leads/show.haml +85 -0
- data/app/views/wco/leadsets/_form.haml +28 -0
- data/app/views/wco/leadsets/_header.haml +6 -0
- data/app/views/wco/leadsets/edit.haml +4 -0
- data/app/views/wco/leadsets/index.haml +45 -0
- data/app/views/wco/leadsets/index_dataTables.haml +33 -0
- data/app/views/wco/leadsets/new.haml +6 -0
- data/app/views/wco/leadsets/show.haml +52 -0
- data/app/views/wco/logs/_form.haml +17 -0
- data/app/views/wco/logs/_header.haml +6 -0
- data/app/views/wco/logs/_index.haml +23 -0
- data/app/views/wco/logs/edit.haml +6 -0
- data/app/views/wco/logs/new.haml +2 -0
- data/app/views/wco/office_action_templates/_form.haml +46 -0
- data/app/views/wco/office_action_templates/_header.haml +8 -0
- data/app/views/wco/office_action_templates/_ties.haml +18 -0
- data/app/views/wco/office_action_templates/edit.haml +6 -0
- data/app/views/wco/office_action_templates/index.haml +17 -0
- data/app/views/wco/office_action_templates/new.haml +5 -0
- data/app/views/wco/office_action_templates/show.haml +22 -0
- data/app/views/wco/office_actions/_form.haml +22 -0
- data/app/views/wco/office_actions/_header.haml +6 -0
- data/app/views/wco/office_actions/_index.haml +15 -0
- data/app/views/wco/office_actions/edit.haml +5 -0
- data/app/views/wco/office_actions/index.haml +10 -0
- data/app/views/wco/office_actions/new.haml +4 -0
- data/app/views/wco/office_actions/show.haml +5 -0
- data/app/views/wco/photos/_form.haml +0 -0
- data/app/views/wco/photos/_index_thumbs.haml +7 -0
- data/app/views/wco/photos/_meta.haml +7 -0
- data/app/views/wco/photos/_meta_manager.haml +7 -0
- data/app/views/wco/photos/_multinew.haml +6 -0
- data/app/views/wco/photos/index.haml +3 -0
- data/app/views/wco/photos/new.haml +0 -0
- data/app/views/wco/photos/show.haml +6 -0
- data/app/views/wco/photos/without_gallery.haml +11 -0
- data/app/views/wco/prices/_form.haml +1 -1
- data/app/views/wco/products/_header.haml +6 -0
- data/app/views/wco/publishers/_form.haml +28 -0
- data/app/views/wco/publishers/_header.haml +6 -0
- data/app/views/wco/publishers/edit.haml +4 -0
- data/app/views/wco/publishers/index.haml +37 -0
- data/app/views/wco/publishers/new.haml +4 -0
- data/app/views/wco/reports/_form.haml +28 -0
- data/app/views/wco/reports/_header.haml +14 -0
- data/app/views/wco/reports/edit.haml +4 -0
- data/app/views/wco/reports/index.haml +17 -0
- data/app/views/wco/reports/index_table.haml +17 -0
- data/app/views/wco/reports/new.haml +4 -0
- data/app/views/wco/reports/show.haml +8 -0
- data/app/views/wco/sites/_form.haml +21 -0
- data/app/views/wco/sites/_header.haml +6 -0
- data/app/views/wco/sites/edit.haml +4 -0
- data/app/views/wco/sites/index.haml +24 -0
- data/app/views/wco/sites/new.haml +4 -0
- data/app/views/wco/tags/_form.haml +10 -0
- data/app/views/wco/tags/_header.haml +6 -0
- data/app/views/wco/tags/_index.haml +10 -0
- data/app/views/wco/tags/_index_inline.haml +8 -0
- data/app/views/wco/tags/edit.haml +0 -0
- data/app/views/wco/tags/index.haml +9 -0
- data/app/views/wco/tags/new.haml +0 -0
- data/app/views/wco/tags/show.haml +15 -0
- data/config/routes.rb +42 -0
- data/lib/tasks/db_tasks.rake +13 -0
- data/lib/tasks/office_tasks.rake +36 -0
- data/lib/tasks/scrape_tasks.rake-trash +22 -0
- data/lib/wco/ai_writer.rb +55 -0
- data/lib/wco/engine.rb +0 -1
- data/lib/wco/office_worker.rb +4 -0
- data/lib/wco/scrape_wsj.rb +29 -0
- data/lib/wco/scrape_wsj_capy.rb +44 -0
- data/lib/wco_models.rb +15 -2
- metadata +241 -46
- data/app/helpers/ish_models/application_helper.rb +0 -4
- data/app/models/wco_email/message_template.rb +0 -6
- data/lib/tasks/ish_models_tasks.rake +0 -4
- /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
|
-
|
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: '::
|
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: '
|
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
|
-
|
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
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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 =
|
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
|
-
|
6
|
-
|
5
|
+
include Mongoid::Paranoia
|
7
6
|
store_in collection: 'office_email_conversations'
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
field :
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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::
|
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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
field :
|
24
|
-
scope :active, ->{ where(
|
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 :
|
27
|
-
validates :
|
28
|
-
def
|
29
|
-
def
|
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: '::
|
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({
|
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.
|
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
|
-
|
31
|
-
|
32
|
-
|
33
|
-
field :
|
34
|
-
scope :active, ->{ where(
|
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,
|
37
|
-
belongs_to :
|
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>-=----- >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
|
+
|