wco_models 3.1.0.31

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 (52) hide show
  1. checksums.yaml +7 -0
  2. data/README.txt +2 -0
  3. data/Rakefile +15 -0
  4. data/app/assets/config/wco_models_manifest.js +4 -0
  5. data/app/assets/stylesheets/wco_models/application.css +22 -0
  6. data/app/assets/stylesheets/wco_models/utils.scss +53 -0
  7. data/app/controllers/ish_models/application_controller.rb +4 -0
  8. data/app/controllers/wco/products_controller.rb +7 -0
  9. data/app/helpers/ish_models/application_helper.rb +4 -0
  10. data/app/jobs/wco_hosting/certbot_job.rb +24 -0
  11. data/app/mailers/ish_models/application_mailer.rb +6 -0
  12. data/app/models/wco/gallery.rb +56 -0
  13. data/app/models/wco/lead.rb +7 -0
  14. data/app/models/wco/leadset.rb +45 -0
  15. data/app/models/wco/newsitem.rb +9 -0
  16. data/app/models/wco/office_action.rb +32 -0
  17. data/app/models/wco/photo.rb +85 -0
  18. data/app/models/wco/premium_item.rb +13 -0
  19. data/app/models/wco/premium_tier.rb +13 -0
  20. data/app/models/wco/price.rb +25 -0
  21. data/app/models/wco/product.rb +18 -0
  22. data/app/models/wco/profile.rb +22 -0
  23. data/app/models/wco/publisher.rb +49 -0
  24. data/app/models/wco/site.rb +57 -0
  25. data/app/models/wco/subscription.rb +22 -0
  26. data/app/models/wco/tag.rb +16 -0
  27. data/app/models/wco/utils.rb +45 -0
  28. data/app/models/wco_email/campaign.rb +55 -0
  29. data/app/models/wco_email/context.rb +114 -0
  30. data/app/models/wco_email/conversation.rb +48 -0
  31. data/app/models/wco_email/email_filter.rb +45 -0
  32. data/app/models/wco_email/message_template.rb +6 -0
  33. data/app/models/wco_email/scheduled_email_action.rb +64 -0
  34. data/app/models/wco_email/tag.rb-trash +15 -0
  35. data/app/models/wco_hosting/appliance.rb +44 -0
  36. data/app/models/wco_hosting/appliance_tmpl.rb +57 -0
  37. data/app/models/wco_hosting/domain.rb +24 -0
  38. data/app/models/wco_hosting/serverhost.rb +205 -0
  39. data/app/views/layouts/ish_models/application.html.erb +15 -0
  40. data/app/views/wco/application/_alerts_notices.haml +22 -0
  41. data/app/views/wco/application/_meta.haml +13 -0
  42. data/app/views/wco_hosting/docker-compose/dc-any.erb +16 -0
  43. data/app/views/wco_hosting/docker-compose/dc-helloworld.erb +16 -0
  44. data/app/views/wco_hosting/docker-compose/dc-wordpress.erb +16 -0
  45. data/app/views/wco_hosting/scripts/create_subdomain.json.erb-trash +16 -0
  46. data/app/views/wco_hosting/scripts/create_volume.erb +21 -0
  47. data/app/views/wco_hosting/scripts/nginx_site.conf.erb +10 -0
  48. data/config/routes.rb +3 -0
  49. data/lib/tasks/ish_models_tasks.rake +4 -0
  50. data/lib/wco/engine.rb +4 -0
  51. data/lib/wco_models.rb +12 -0
  52. metadata +250 -0
@@ -0,0 +1,45 @@
1
+
2
+ module Wco::Utils
3
+
4
+ def export
5
+ out = {}
6
+ %w| created_at updated_at |.map do |f|
7
+ out[f] = send(f)
8
+ end
9
+ export_fields.map do |field|
10
+ if field[-3..-1] == '_id'
11
+ out[field] = send(field).to_s
12
+ else
13
+ out[field] = send(field)
14
+ end
15
+ end
16
+ out[:_id] = id.to_s
17
+ out.with_indifferent_access
18
+ end
19
+
20
+ private
21
+
22
+ def set_slug
23
+ return if !slug.blank?
24
+ if name
25
+ new_slug = name.downcase.gsub(/[^a-z0-9\s]/i, '').gsub(' ', '-')
26
+ else
27
+ new_slug = '1'
28
+ end
29
+ if self.class.where( slug: new_slug ).first
30
+ loop do
31
+ if new_slug[new_slug.length-1].to_i != 0
32
+ # inrement last digit
33
+ last_digit = new_slug[new_slug.length-1].to_i
34
+ new_slug = "#{new_slug[0...new_slug.length-1]}#{last_digit+1}"
35
+ else
36
+ # add '-1' to the end
37
+ new_slug = "#{new_slug}-1"
38
+ end
39
+ break if !self.class.where( slug: new_slug ).first
40
+ end
41
+ end
42
+ self.slug = new_slug
43
+ end
44
+
45
+ end
@@ -0,0 +1,55 @@
1
+
2
+ ##
3
+ ## Sends a campaign.
4
+ ## _vp_ 2023-02-02
5
+ ##
6
+ class WcoEmail::Campaign
7
+ include Mongoid::Document
8
+ include Mongoid::Timestamps
9
+ store_in collection: 'ish_email_campaigns'
10
+
11
+ field :slug
12
+ validates_uniqueness_of :slug, allow_nil: true
13
+
14
+ PAGE_PARAM_NAME = 'email_contexts_page'
15
+
16
+ field :from_email
17
+ validates_presence_of :from_email
18
+
19
+ belongs_to :email_template
20
+ def tmpl; email_template; end
21
+
22
+ field :subject
23
+ field :body
24
+
25
+ field :sent_at, type: DateTime
26
+ field :send_at, type: DateTime
27
+
28
+ has_many :unsubscribes, class_name: '::Ish::EmailUnsubscribe', inverse_of: :campaign
29
+
30
+ def campaign_leads
31
+ return ::EmailCampaignLead.where( email_campaign_id: self.id.to_s ).includes( :lead )
32
+ end
33
+
34
+ def leads
35
+ ::Lead.joins( :email_campaign_leads ).where( 'email_campaign_leads.email_campaign_id' => self.id.to_s )
36
+ end
37
+
38
+ ##
39
+ ## For tracking
40
+ ##
41
+ attr_reader :tid
42
+
43
+ def do_send
44
+ leads.each do |lead|
45
+ ctx = Ctx.create!({
46
+ email_template: tmpl,
47
+ from_email: tmpl.from_email,
48
+ lead_id: lead.id,
49
+ send_at: Time.now,
50
+ subject: tmpl.subject,
51
+ })
52
+ end
53
+ end
54
+
55
+ end
@@ -0,0 +1,114 @@
1
+ ##
2
+ ## Send a single email
3
+ ##
4
+
5
+ class WcoEmail::Context
6
+ include Mongoid::Document
7
+ include Mongoid::Timestamps
8
+ store_in collection: 'ish_email_contexts'
9
+
10
+ field :slug
11
+ validates_uniqueness_of :slug, allow_nil: true
12
+
13
+ field :preview_str, type: :string
14
+ def preview_str
15
+ if self[:preview_str].presence
16
+ return self[:preview_str]
17
+ else
18
+ return tmpl.preview_str
19
+ end
20
+ end
21
+
22
+ field :body
23
+ def body
24
+ if self[:body].presence
25
+ return self[:body]
26
+ else
27
+ return tmpl.body
28
+ end
29
+ end
30
+
31
+ PAGE_PARAM_NAME = 'email_contexts_page'
32
+
33
+ field :from_email
34
+ def from_email
35
+ if self[:from_email].presence
36
+ return self[:from_email]
37
+ else
38
+ return tmpl.from_email
39
+ end
40
+ end
41
+
42
+ field :subject
43
+ def subject
44
+ self[:subject].presence || tmpl.subject
45
+ end
46
+
47
+ belongs_to :email_template
48
+ def tmpl; email_template; end
49
+
50
+ belongs_to :scheduled_email_action, class_name: '::Office::ScheduledEmailAction', optional: true
51
+ def sch; scheduled_email_action; end
52
+
53
+ belongs_to :email_campaign, class_name: 'Ish::EmailCampaign', optional: true
54
+
55
+ field :rendered_str
56
+
57
+ field :sent_at, type: DateTime
58
+ field :send_at, type: DateTime
59
+ field :unsubscribed_at, type: DateTime
60
+
61
+
62
+ def notsent
63
+ Ish::EmailContext.where( sent_at: nil, unsubscribed_at: nil )
64
+ end
65
+ def self.notsent; new.notsent; end
66
+
67
+
68
+ def scheduled
69
+ # 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 })
71
+ end
72
+ def self.scheduled; new.scheduled; end
73
+
74
+
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: []
83
+
84
+ ##
85
+ ## For tracking / utm
86
+ ##
87
+ attr_reader :tid
88
+
89
+ def get_binding
90
+ @lead = lead()
91
+ @utm_tracking_str = {
92
+ 'cid' => lead.id,
93
+ 'utm_campaign' => tmpl.slug,
94
+ 'utm_medium' => 'email',
95
+ 'utm_source' => tmpl.slug,
96
+ }.map { |k, v| "#{k}=#{v}" }.join("&")
97
+ eval( tmpl.config_exe )
98
+ binding()
99
+ end
100
+
101
+ def self.summary
102
+ pipeline = [
103
+ { '$group' => {
104
+ '_id' => { '$dateToString' => { 'format' => "%Y-%m-%d", 'date' => "$sent_at", 'timezone' => 'America/Chicago' } }, 'total' => { '$sum' => 1 }
105
+ } },
106
+ { '$sort' => { '_id': -1 } },
107
+ ]
108
+ outs = Ish::EmailContext.collection.aggregate( pipeline )
109
+ outs.to_a
110
+ end
111
+
112
+ end
113
+ Ctx = WcoEmail::Context
114
+
@@ -0,0 +1,48 @@
1
+
2
+ class WcoEmail::Conversation
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+ # include Mongoid::Paranoia
6
+
7
+ store_in collection: 'office_email_conversations'
8
+
9
+ STATE_UNREAD = 'state_unread'
10
+ STATE_READ = 'state_read'
11
+ STATES = [ STATE_UNREAD, STATE_READ ]
12
+ field :state
13
+
14
+ field :subject
15
+ index({ subject: -1 })
16
+
17
+ field :latest_at
18
+ index({ latest_at: -1 })
19
+
20
+ field :from_emails, type: :array, default: []
21
+ index({ from_emails: -1 })
22
+
23
+ field :preview, default: ''
24
+
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
46
+
47
+ end
48
+ Conv = WcoEmail::Conversation
@@ -0,0 +1,45 @@
1
+
2
+ ##
3
+ ## 2023-03-04 _vp_ When I receive one.
4
+ ##
5
+ class WcoEmail::EmailFilter
6
+ include Mongoid::Document
7
+ include Mongoid::Timestamps
8
+
9
+ field :from_regex
10
+ field :from_exact
11
+ field :subject_regex
12
+ field :subject_exact
13
+ field :body_regex
14
+ field :body_exact
15
+
16
+
17
+ KIND_AUTORESPOND_TMPL = 'autorespond-template'
18
+ KIND_AUTORESPOND_EACT = 'autorespond-email-action'
19
+ KIND_REMOVE_TAG = 'remove-tag'
20
+ KIND_ADD_TAG = 'add-tag'
21
+ KIND_DESTROY_SCHS = 'destroy-schs'
22
+
23
+ KIND_AUTORESPOND = 'autorespond' # @deprecated, DO NOT USE!
24
+ KIND_DELETE = 'delete' # @deprecated, use add-tag
25
+ KIND_SKIP_INBOX = 'skip-inbox' # @deprecated, use remove-tag
26
+
27
+ KINDS = [ nil, KIND_AUTORESPOND_TMPL, KIND_AUTORESPOND_EACT, KIND_ADD_TAG, KIND_REMOVE_TAG, KIND_DESTROY_SCHS]
28
+ field :kind
29
+
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 ) }
35
+
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
43
+
44
+ end
45
+
@@ -0,0 +1,6 @@
1
+
2
+ class WcoEmail::MessageTemplate
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+
6
+ end
@@ -0,0 +1,64 @@
1
+
2
+ require 'mongoid-paranoia'
3
+
4
+ ##
5
+ ## 2023-03-04 _vp_ An instance of an EmailAction.
6
+ ##
7
+ class WcoEmail::ScheduledEmailAction
8
+ include Mongoid::Document
9
+ include Mongoid::Timestamps
10
+ include Mongoid::Paranoia
11
+ store_in collection: 'office_scheduled_email_actions'
12
+
13
+ field :lead_id, type: :integer
14
+ def lead
15
+ Lead.find( lead_id )
16
+ end
17
+
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 ) }
25
+
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
30
+
31
+ has_many :email_contexts, class_name: '::Ish::EmailContext'
32
+ def ctxs; email_contexts; end
33
+
34
+ field :perform_at, type: :time
35
+
36
+ def send_and_roll
37
+ sch = self
38
+ sch.update({ state: Sch::STATE_INACTIVE })
39
+
40
+ # send now
41
+ ctx = Ctx.create!({
42
+ email_template_id: sch.act.tmpl.id,
43
+ from_email: sch.act.tmpl.from_email,
44
+ lead_id: sch.lead.id,
45
+ scheduled_email_action_id: sch.act.id,
46
+ send_at: Time.now,
47
+ subject: sch.act.tmpl.subject,
48
+ })
49
+
50
+ # schedule next actions & update the action
51
+ sch.act.ties.each do |tie|
52
+ next_sch = Sch.find_or_initialize_by({
53
+ lead_id: sch.lead_id,
54
+ email_action_id: tie.next_email_action.id,
55
+ })
56
+ next_sch.perform_at = eval(tie.next_at_exe)
57
+ next_sch.state = Sch::STATE_ACTIVE
58
+ next_sch.save!
59
+ end
60
+ end
61
+
62
+ end
63
+ Sch = WcoEmail::ScheduledEmailAction
64
+
@@ -0,0 +1,15 @@
1
+
2
+ class WcoEmail::Tag
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+
6
+ field :slug
7
+ # index
8
+ # validate presence
9
+ # validate uniqueness ?
10
+
11
+ # parent-child
12
+
13
+ has_and_belongs_to_many :email_conversations, class_name: 'Wco::EmailConversation'
14
+
15
+ end
@@ -0,0 +1,44 @@
1
+
2
+ class WcoHosting::Appliance
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+ store_in collection: 'wco_appliances'
6
+
7
+ belongs_to :leadset, class_name: 'Wco::Leadset', inverse_of: :appliances
8
+
9
+ field :service_name
10
+ before_validation :set_service_name, on: :create, unless: ->{ service_name }
11
+ def set_service_name
12
+ self[:service_name] = host.gsub(".", "_")
13
+ end
14
+
15
+ field :environment
16
+
17
+ field :subdomain
18
+ field :domain
19
+ def host
20
+ "#{subdomain}.#{domain}"
21
+ end
22
+
23
+ field :n_retries, type: :integer, default: 3
24
+
25
+ belongs_to :appliance_tmpl, class_name: 'WcoHosting::ApplianceTmpl'
26
+ def tmpl
27
+ appliance_tmpl
28
+ end
29
+ def kind
30
+ tmpl.kind
31
+ end
32
+
33
+ belongs_to :serverhost, class_name: 'WcoHosting::Serverhost'
34
+
35
+ field :port
36
+
37
+ STATE_PENDING = 'state-pending'
38
+ STATE_LIVE = 'state-live'
39
+ STATE_TERM = 'state-term'
40
+ field :state, default: STATE_PENDING
41
+
42
+ end
43
+
44
+
@@ -0,0 +1,57 @@
1
+
2
+ class WcoHosting::ApplianceTmpl
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+ store_in collection: 'wco_appliance_tmpls'
6
+
7
+ field :kind
8
+ validates :kind, uniqueness: { scope: :version }, presence: true
9
+
10
+ field :version, type: :string, default: '0.0.0'
11
+ validates :version, uniqueness: { scope: :kind }, presence: true
12
+ index({ kind: -1, version: -1 }, { name: :kind_version })
13
+
14
+ field :descr, type: :string
15
+
16
+ field :image
17
+ validates :image, presence: true
18
+
19
+ field :volume_zip
20
+ validates :volume_zip, presence: true
21
+
22
+ ## Only underscores! These become variable names.
23
+ # KIND_CRM = 'kind_crm'
24
+ # KIND_DRUPAL = 'kind_drupal'
25
+ # KIND_HELLOWORLD = 'kind_helloworld'
26
+ # KIND_IROWOR = 'kind_irowor'
27
+ # KIND_JENKINS = 'kind_jenkins'
28
+ # KIND_MATOMO = 'kind_matomo'
29
+ # KIND_MOODLE = 'kind_moodle'
30
+ # KIND_PRESTASHOP = 'kind_prestashop'
31
+ # KIND_SMT = 'kind_smt'
32
+ # KIND_WORDPRESS = 'kind_wordpress'
33
+
34
+ ## 2023-12-08 :: These names are impossible to change already.
35
+ KIND_CRM = 'crm'
36
+ KIND_DRUPAL = 'drupal'
37
+ KIND_HELLOWORLD = 'helloworld'
38
+ KIND_IROWOR = 'irowor'
39
+ KIND_JENKINS = 'jenkins'
40
+ KIND_MATOMO = 'matomo'
41
+ KIND_MOODLE = 'moodle'
42
+ KIND_PRESTASHOP = 'prestashop'
43
+ KIND_SMT = 'smt'
44
+ KIND_WORDPRESS = 'wordpress'
45
+
46
+ KINDS = [ KIND_CRM, KIND_DRUPAL, KIND_HELLOWORLD, KIND_IROWOR,
47
+ KIND_JENKINS, KIND_MATOMO, KIND_MOODLE, KIND_PRESTASHOP, KIND_SMT,
48
+ KIND_WORDPRESS ]
49
+
50
+ has_many :appliances, class_name: 'WcoHosting::Appliance'
51
+
52
+ def self.latest_of kind
53
+ where({ kind: kind }).order_by({ version: :desc }).first
54
+ end
55
+
56
+ end
57
+ AppTmpl = WcoHosting::ApplianceTmpl
@@ -0,0 +1,24 @@
1
+
2
+ class WcoHosting::Domain
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+ store_in collection: 'wco_dns_domains'
6
+
7
+ field :name
8
+ validates :name, presence: true, uniqueness: true
9
+
10
+ ## orbital.city : Z0145070C3DD1OJWHTXJ
11
+ ## oquaney-splicing.com : Z060228025Y0JHUA35GN5
12
+ # field :route53_zone
13
+ # validates :route53_zone, presence: true
14
+
15
+ STATE_ACTIVE = 'active'
16
+ STATE_INACTIVE = 'inactive'
17
+ STATES = [ 'active', 'inactive' ]
18
+ field :state, default: STATE_ACTIVE
19
+
20
+ def self.list
21
+ [[nil,nil]] + all.where({ state: STATE_ACTIVE }).map { |i| [i.name, i.name ] }
22
+ end
23
+
24
+ end