wco_models 3.1.0.270 → 3.1.0.271

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/README.txt +10 -12
  3. data/app/assets/stylesheets/wco/main.scss +24 -0
  4. data/app/assets/stylesheets/wco/newsvideos.scss +45 -0
  5. data/app/controllers/wco/api_controller.rb +2 -2
  6. data/app/controllers/wco/application_controller.rb +45 -0
  7. data/app/controllers/wco/leads_controller.rb +0 -2
  8. data/app/controllers/wco/newspartials_controller.rb +1 -1
  9. data/app/controllers/wco/newsvideos_controller.rb +3 -2
  10. data/app/controllers/wco/publishers_controller.rb +12 -4
  11. data/app/controllers/wco/reports_controller.rb +140 -0
  12. data/app/controllers/wco/tags_controller.rb +18 -5
  13. data/app/controllers/wco/videos_controller.rb +4 -0
  14. data/app/mailers/wco_email/application_mailer.rb +1 -1
  15. data/app/models/ability.rb +3 -0
  16. data/app/models/iro/option.rb +2 -3
  17. data/app/models/iro/position.rb +67 -13
  18. data/app/models/iro/purse.rb +1 -2
  19. data/app/models/iro/stock.rb +20 -0
  20. data/app/models/iro/strategy.rb +57 -40
  21. data/app/models/tda/option.rb +1 -1
  22. data/app/models/wco/lead.rb +6 -5
  23. data/app/models/wco/newspartial.rb +7 -2
  24. data/app/models/wco/newsvideo.rb +2 -0
  25. data/app/models/wco/photo.rb +2 -0
  26. data/app/models/wco/profile.rb +6 -0
  27. data/app/models/wco/publisher.rb +3 -3
  28. data/app/models/wco/report.rb +6 -3
  29. data/app/models/wco/site.rb +2 -2
  30. data/app/models/wco/tag.rb +3 -0
  31. data/app/models/wco/video.rb +28 -3
  32. data/app/models/wco_email/email_filter.rb +1 -1
  33. data/app/models/wco_email/email_filter_action.rb +2 -0
  34. data/app/models/wco_email/email_filter_condition.rb +29 -14
  35. data/app/models/wco_email/message_stub.rb +2 -1
  36. data/app/views/wco/_main_footer.haml +4 -3
  37. data/app/views/wco/_main_header.haml +1 -0
  38. data/app/views/wco/_sidebar.haml +21 -0
  39. data/app/views/wco/newsoverlays/_show_in_newsvideo.haml +7 -5
  40. data/app/views/wco/newspartials/_show_in_newsvideo.haml +35 -28
  41. data/app/views/wco/newspartials/edit.haml +4 -0
  42. data/app/views/wco/newsvideos/_form.haml +10 -3
  43. data/app/views/wco/newsvideos/show.haml +20 -3
  44. data/app/views/wco/profiles/_form.haml +33 -13
  45. data/app/views/wco/publishers/_post_with.haml +7 -0
  46. data/app/views/wco/reports/_form.haml +11 -2
  47. data/app/views/wco/reports/_to_facebook.html +23 -0
  48. data/app/views/wco/reports/show.haml +15 -0
  49. data/app/views/wco/tags/_index_table.haml +4 -1
  50. data/app/views/wco/tags/_index_tree.haml +2 -1
  51. data/app/views/wco/tags/new_for_sidebar.haml +9 -0
  52. data/app/views/wco/tags/show.haml +54 -42
  53. data/app/views/wco/tags/show.haml-bk +63 -0
  54. data/config/initializers/08_integrations.rb +0 -15
  55. data/config/routes.rb +11 -2
  56. data/lib/wco/facebook_poster.rb +17 -0
  57. metadata +23 -3
  58. data/app/views/wco/newspartials/index.haml-trash +0 -6
@@ -5,7 +5,9 @@ class Iro::Position
5
5
  include Mongoid::Paranoia
6
6
  store_in collection: 'iro_positions'
7
7
 
8
- field :next_gain_loss_amount, type: :float
8
+ field :next_gain_loss_amount, type: :float
9
+ field :realized_gain_loss_amount, type: :float, default: 0.0 ## for diagonals only
10
+ def realized_gl; realized_gain_loss_amount; end
9
11
 
10
12
  STATUS_ACTIVE = 'active'
11
13
  STATUS_CLOSED = 'closed'
@@ -29,6 +31,12 @@ class Iro::Position
29
31
 
30
32
  belongs_to :purse, class_name: 'Iro::Purse', inverse_of: :positions
31
33
  index({ purse_id: 1, ticker: 1 })
34
+ index({ deleted_at: 1,
35
+ status: 1,
36
+ expires_on: 1,
37
+ ticker: 1,
38
+ long_or_short: 1,
39
+ inner_strike: 1 })
32
40
 
33
41
  belongs_to :stock, class_name: 'Iro::Stock', inverse_of: :positions
34
42
  field :ticker
@@ -41,8 +49,13 @@ class Iro::Position
41
49
  end
42
50
 
43
51
  belongs_to :strategy, class_name: 'Iro::Strategy', inverse_of: :positions
44
- delegate :long_or_short, to: :strategy
45
- delegate :credit_or_debit, to: :strategy
52
+ field :long_or_short, type: String
53
+ before_save do
54
+ self.long_or_short = strategy.long_or_short
55
+ end
56
+
57
+ # delegate :credit_or_debit, to: :strategy
58
+ field :credit_or_debit, type: String # , default: 'credit'
46
59
 
47
60
  field :put_call, type: :string
48
61
  validates :put_call, presence: true
@@ -67,9 +80,11 @@ class Iro::Position
67
80
 
68
81
  belongs_to :inner, class_name: 'Iro::Option', inverse_of: :pos_of_inner
69
82
  validates_associated :inner
83
+ has_many :inners, class_name: 'Iro::Option', inverse_of: :poss_of_inner ## for history and diagonals
70
84
 
71
85
  belongs_to :outer, class_name: 'Iro::Option', inverse_of: :pos_of_outer, optional: true
72
86
  validates_associated :outer
87
+ has_many :outers, class_name: 'Iro::Option', inverse_of: :poss_of_outer ## for history and diagonals
73
88
 
74
89
  accepts_nested_attributes_for :inner, :outer
75
90
 
@@ -79,8 +94,12 @@ class Iro::Position
79
94
  field :inner_strike, type: :float
80
95
  validates :inner_strike, presence: true ## 2026-02-24 only to make finding easier.
81
96
 
82
- field :expires_on
97
+ field :expires_on, type: :string
83
98
  validates :expires_on, presence: true
99
+ before_save :trim_expires_on
100
+ def trim_expires_on
101
+ self.expires_on = expires_on.to_s[0, 10] if expires_on.present?
102
+ end
84
103
 
85
104
  field :quantity, type: :integer
86
105
  validates :quantity, presence: true
@@ -93,11 +112,18 @@ class Iro::Position
93
112
  field :schwab_order_id, type: :integer
94
113
  field :schwab_status
95
114
 
115
+ def diag_weeks
116
+ pos = self
117
+ ((pos.outer.expires_on - pos.inner.expires_on)/7).to_i
118
+ end
119
+
96
120
  def begin_delta
97
- strategy.send("begin_delta_#{strategy.kind}", self)
121
+ # strategy.send("begin_delta_#{strategy.kind}", self)
122
+ strategy.begin_delta self
98
123
  end
99
124
  def end_delta
100
- strategy.send("end_delta_#{strategy.kind}", self)
125
+ # strategy.send("end_delta_#{strategy.kind}", self)
126
+ strategy.end_delta self
101
127
  end
102
128
 
103
129
  def breakeven
@@ -116,11 +142,23 @@ class Iro::Position
116
142
  p = self
117
143
  p.inner.strike + p.max_gain
118
144
  end
145
+ def breakeven_short_debit_put_spread
146
+ p = self
147
+ p.inner.strike - p.inner.begin_price + p.outer.begin_price
148
+ end
119
149
  ## 2026-02-23
120
150
  def breakeven_long_credit_put_spread
121
151
  p = self
122
152
  p.inner.strike - p.max_gain
123
153
  end
154
+ def breakeven_diag_long_call_spread
155
+ p = self
156
+ realized_gl + p.outer.strike + p.outer.begin_price - p.inner.begin_price
157
+ end
158
+ def breakeven_diag_short_put_spread
159
+ p = self
160
+ p.inner.strike + p.max_gain + p.realized_gl
161
+ end
124
162
 
125
163
 
126
164
  def current_underlying_strike
@@ -182,6 +220,15 @@ class Iro::Position
182
220
  def net_amount_short_credit_call_spread
183
221
  return net_amount_long_credit_put_spread
184
222
  end
223
+ def net_amount_short_debit_put_spread
224
+ inner.end_price - inner.begin_price + outer.begin_price - outer.end_price
225
+ end
226
+ def net_amount_diag_long_call_spread
227
+ inner.begin_price - outer.begin_price + outer.end_price - inner.end_price + realized_gl
228
+ end
229
+ def net_amount_diag_short_put_spread
230
+ net_amount_diag_long_call_spread
231
+ end
185
232
 
186
233
  def max_gain # each
187
234
  strategy.send("max_gain_#{strategy.kind}", self)
@@ -228,7 +275,7 @@ class Iro::Position
228
275
 
229
276
  count = 1
230
277
  @positions.each do |pos|
231
- # puts! pos.to_s, 'pos TMP'
278
+ # puts! pos.id.to_s, '#sync_all.pos'
232
279
 
233
280
  quotes_h = Tda::Option.get_quotes_h({
234
281
  contractType: 'ALL',
@@ -237,12 +284,17 @@ class Iro::Position
237
284
  toDate: expiration_dates.last,
238
285
  })
239
286
 
240
- pos.inner.end_price = quotes_h[pos.expires_on.to_s][pos.put_call][pos.inner.strike][:price]
241
- pos.inner.end_delta = quotes_h[pos.expires_on.to_s][pos.put_call][pos.inner.strike][:delta]
287
+ pos.inner.end_price = quotes_h[pos.expires_on.to_date.to_s][pos.put_call][pos.inner.strike][:price]
288
+ pos.inner.end_delta = quotes_h[pos.expires_on.to_date.to_s][pos.put_call][pos.inner.strike][:delta]
242
289
  pos.inner.save ? print("#{count}^") : print("#{count}X")
243
- if [ Iro::Strategy::KIND_LONG_CREDIT_PUT_SPREAD, Iro::Strategy::KIND_SHORT_CREDIT_CALL_SPREAD ].include?( pos.strategy.kind )
244
- pos.outer.end_price = quotes_h[pos.expires_on.to_s][pos.put_call][pos.outer.strike][:price]
245
- pos.outer.end_delta = quotes_h[pos.expires_on.to_s][pos.put_call][pos.outer.strike][:delta]
290
+
291
+ # if [ Iro::Strategy::KIND_LONG_CREDIT_PUT_SPREAD,
292
+ # Iro::Strategy::KIND_SHORT_CREDIT_CALL_SPREAD,
293
+ # Iro::Strategy::KIND_DIAG_LONG_CALL_SPREAD,
294
+ # Iro::Strategy::KIND_DIAG_SHORT_PUT_SPREAD ].include?( pos.strategy.kind )
295
+ if pos.outer
296
+ pos.outer.end_price = quotes_h[pos.expires_on.to_date.to_s][pos.put_call][pos.outer.strike][:price]
297
+ pos.outer.end_delta = quotes_h[pos.expires_on.to_date.to_s][pos.put_call][pos.outer.strike][:delta]
246
298
  pos.outer.save ? print('^') : print('X')
247
299
  end
248
300
  count = count+1
@@ -433,7 +485,9 @@ class Iro::Position
433
485
 
434
486
  def to_s
435
487
  out = "#{stock} (#{q}) #{expires_on.to_datetime.strftime('%b %d')} #{strategy.long_or_short} ["
436
- if Iro::Strategy::LONG == long_or_short
488
+ if Iro::Strategy::KIND_SHORT_DEBIT_PUT_SPREAD == strategy.kind
489
+ out = out + "$#{outer.strike} << $#{inner.strike}"
490
+ elsif Iro::Strategy::LONG == long_or_short
437
491
  if outer&.strike
438
492
  out = out + "$#{outer.strike} << "
439
493
  end
@@ -24,8 +24,7 @@ class Iro::Purse
24
24
  ## with unit 100, .0001
25
25
  field :summary_unit, type: :float, default: 0.001
26
26
 
27
- ## for rolling only:
28
- field :height, type: :integer, default: 100
27
+ field :height, type: :integer, default: 100 ## px/q, units modal
29
28
 
30
29
  field :mark_every_n_usd, type: :float, default: 1
31
30
  field :n_next_positions, type: :integer, default: 5
@@ -32,6 +32,26 @@ class ::Iro::Stock
32
32
 
33
33
  field :descr, type: :string
34
34
 
35
+ ## for charting:
36
+ field :min, type: :integer
37
+ field :max, type: :integer
38
+ field :step, type: :integer, default: 1.0 ## for histograms. I liked 50 bars, that's $1-3 bucket size.
39
+
40
+ validate :step_must_fit_range
41
+ def step_must_fit_range
42
+ return if min.blank? || max.blank? || step.blank?
43
+
44
+ range = max. - min.to_i
45
+ step_val = step.to_i
46
+
47
+
48
+ if !((max - min) % step).zero?
49
+ errors.add(:step, "must evenly divide (max-min)/step")
50
+ end
51
+ end
52
+
53
+
54
+
35
55
  has_many :positions, class_name: '::Iro::Position', inverse_of: :stock
36
56
  has_many :strategies, class_name: '::Iro::Strategy', inverse_of: :stock
37
57
  # has_many :purses, class_name: '::Iro::Purse', inverse_of: :stock
@@ -24,6 +24,8 @@ class Iro::Strategy
24
24
  belongs_to :purse, class_name: 'Iro::Purse', inverse_of: :strategies
25
25
 
26
26
  KIND_COVERED_CALL = 'covered_call'
27
+ KIND_DIAG_LONG_CALL_SPREAD = 'diag_long_call_spread'
28
+ KIND_DIAG_SHORT_PUT_SPREAD = 'diag_short_put_spread'
27
29
  KIND_IRON_CONDOR = 'iron_condor'
28
30
  KIND_LONG_CREDIT_PUT_SPREAD = 'long_credit_put_spread'
29
31
  KIND_LONG_DEBIT_CALL_SPREAD = 'long_debit_call_spread'
@@ -36,6 +38,8 @@ class Iro::Strategy
36
38
  KIND_WHEEL = 'wheel' ## @deprecated, use covered_call
37
39
  KINDS = [ nil,
38
40
  KIND_COVERED_CALL,
41
+ KIND_DIAG_LONG_CALL_SPREAD,
42
+ KIND_DIAG_SHORT_PUT_SPREAD,
39
43
  KIND_IRON_CONDOR,
40
44
  KIND_LONG_CREDIT_PUT_SPREAD,
41
45
  KIND_LONG_DEBIT_CALL_SPREAD,
@@ -49,6 +53,12 @@ class Iro::Strategy
49
53
 
50
54
  def put_call
51
55
  case kind
56
+ when Iro::Strategy::KIND_COVERED_CALL
57
+ put_call = 'CALL'
58
+ when Iro::Strategy::KIND_DIAG_LONG_CALL_SPREAD
59
+ put_call = 'CALL'
60
+ when Iro::Strategy::KIND_DIAG_SHORT_PUT_SPREAD
61
+ put_call = 'PUT'
52
62
  when Iro::Strategy::KIND_LONG_CREDIT_PUT_SPREAD
53
63
  put_call = 'PUT'
54
64
  when Iro::Strategy::KIND_LONG_DEBIT_CALL_SPREAD
@@ -57,8 +67,7 @@ class Iro::Strategy
57
67
  put_call = 'CALL'
58
68
  when Iro::Strategy::KIND_SHORT_DEBIT_PUT_SPREAD
59
69
  put_call = 'PUT'
60
- when Iro::Strategy::KIND_COVERED_CALL
61
- put_call = 'CALL'
70
+
62
71
  when Iro::Strategy::KIND_SPREAD
63
72
  if credit_or_debit == CREDIT
64
73
  if long_or_short == LONG
@@ -77,11 +86,11 @@ class Iro::Strategy
77
86
  end
78
87
 
79
88
 
80
- field :threshold_usd_above_mark, type: :float
89
+ field :threshold_usd_above_mark, type: :float, default: 1.00
81
90
  validates :threshold_usd_above_mark, presence: true
82
91
 
83
92
  field :threshold_pos_delta, type: :float # offensive: roll b/c markets are going my way
84
- field :threshold_neg_delta, type: :float # defensive: roll b/c markets are going against me
93
+ field :threshold_neg_delta, type: :float, default: 0.8 # defensive: roll b/c markets are going against me
85
94
  field :threshold_netp, type: :float
86
95
  field :threshold_dte, type: :integer, default: 1
87
96
 
@@ -91,7 +100,7 @@ class Iro::Strategy
91
100
  field :next_outer_strike, type: :float
92
101
  field :next_spread_amount, type: :float # e.g. $20 for a $2000 NVDA spread
93
102
 
94
- field :next_usd_above_mark, type: :float
103
+ field :next_usd_above_mark, type: :float, default: 1.00
95
104
  validates :next_usd_above_mark, presence: true
96
105
 
97
106
 
@@ -108,12 +117,12 @@ class Iro::Strategy
108
117
  field :tgt_exposure, type: :float
109
118
 
110
119
 
120
+ def begin_delta p
121
+ _begin_delta_spread p
122
+ end
111
123
  def begin_delta_covered_call p
112
124
  p.inner.begin_delta
113
125
  end
114
- # def begin_delta_wheel p
115
- # p.inner.begin_delta
116
- # end
117
126
  def _begin_delta_spread p
118
127
  p.inner.begin_delta - p.outer.begin_delta
119
128
  end
@@ -123,13 +132,19 @@ class Iro::Strategy
123
132
  def begin_delta_short_credit_call_spread p
124
133
  _begin_delta_spread p
125
134
  end
135
+ def begin_delta_diag_long_call_spread p
136
+ _begin_delta_spread p
137
+ end
138
+ def begin_delta_diag_short_put_spread p
139
+ _begin_delta_spread p
140
+ end
126
141
 
142
+ def end_delta p
143
+ _end_delta_spread p
144
+ end
127
145
  def end_delta_covered_call p
128
146
  p.inner.end_delta
129
147
  end
130
- # def end_delta_wheel p
131
- # p.inner.end_delta
132
- # end
133
148
  def _end_delta_spread p
134
149
  p.inner.end_delta - p.outer.end_delta
135
150
  end
@@ -139,7 +154,21 @@ class Iro::Strategy
139
154
  def end_delta_short_credit_call_spread p
140
155
  _end_delta_spread p
141
156
  end
157
+ def end_delta_diag_long_call_spread p
158
+ _end_delta_spread p
159
+ end
160
+ def end_delta_diag_short_put_spread p
161
+ _end_delta_spread p
162
+ end
163
+
142
164
 
165
+ def max_gain_diag_long_call_spread p ## each
166
+ # p.outer.strike - p.inner.strike + p.outer.begin_price - p.inner.begin_price
167
+ p.inner.strike - p.outer.strike - p.outer.begin_price + p.inner.begin_price
168
+ end
169
+ def max_gain_diag_short_put_spread p ## each
170
+ p.outer.strike - p.inner.strike - p.outer.begin_price + p.inner.begin_price
171
+ end
143
172
 
144
173
  def max_gain_covered_call p ## each
145
174
  p.inner.begin_price # - 0.66
@@ -168,21 +197,31 @@ class Iro::Strategy
168
197
  end
169
198
 
170
199
 
200
+ def max_loss_diag_long_call_spread p
201
+ # p.inner.strike - p.outer.strike + p.inner.begin_price + p.outer.begin_price
202
+ p.outer.begin_price - p.inner.begin_price - p.realized_gl
203
+ end
204
+ def max_loss_diag_short_put_spread p
205
+ p.outer.begin_price - p.inner.begin_price - p.realized_gl ## same as above, max_loss_diag_long_call_spread
206
+ end
171
207
  def max_loss_covered_call p
172
208
  p.inner.begin_price*10 # just suppose 10,000%
173
209
  end
174
210
  def max_loss_long_credit_put_spread p
175
211
  out = p.inner.strike - p.outer.strike
176
212
  end
177
- def max_loss_long_debit_call_spread p
178
- out = p.outer.strike - p.inner.strike
179
- end
180
- def max_loss_short_debit_put_spread p # different
181
- out = p.inner.strike - p.outer.strike
182
- end
213
+ # def max_loss_long_debit_call_spread p
214
+ # out = p.outer.strike - p.inner.strike
215
+ # end
216
+ # def max_loss_short_debit_put_spread p # different
217
+ # out = p.inner.strike - p.outer.strike
218
+ # end
183
219
  def max_loss_short_credit_call_spread p
184
220
  out = p.outer.strike - p.inner.strike
185
221
  end
222
+ def max_loss_short_debit_put_spread p
223
+ p.inner.begin_price - p.outer.begin_price
224
+ end
186
225
  def max_loss_spread p
187
226
  ( p.outer.strike - p.inner.strike ).abs
188
227
  end
@@ -193,26 +232,9 @@ class Iro::Strategy
193
232
 
194
233
 
195
234
  def net_amount_spread p
196
- p.inner.begin_price - p.inner.end_price
235
+ p.inner.begin_price - p.inner.end_price - p.outer.begin_price + p.outer.end_price
197
236
  end
198
- # def net_amount_long_credit_put_spread p
199
- # p.inner.begin_price - p.inner.end_price
200
- # end
201
-
202
237
 
203
- ## 2024-05-09 _TODO
204
- ## 2025-10-11 _TODO
205
- ## 2026-02-23 trash, makes no sense.
206
- =begin
207
- def next_inner_strike_on expires_on
208
- outs = ::Tda::Option.get_quotes({
209
- contractType: put_call,
210
- expirationDate: expires_on,
211
- ticker: stock.ticker,
212
- })
213
- puts! outs, 'next_inner_strike_on -> outs'
214
- end
215
- =end
216
238
 
217
239
 
218
240
  ##
@@ -277,11 +299,6 @@ class Iro::Strategy
277
299
  def calc_rollp_long_credit_put_spread p
278
300
  stock.reload
279
301
 
280
- # puts! p, '#calc_rollp_long_credit_put_spread'
281
- # puts! p.inner, 'p.inner'
282
- # puts! stock, 'stock'
283
- # puts! attributes, 'strategy attributes'
284
-
285
302
  if ( p.expires_on.to_date - Time.now.to_date ).to_i < 1
286
303
  return [ 0.99, '0 DTE, must exit' ]
287
304
  end
@@ -178,7 +178,7 @@ class Tda::Option
178
178
 
179
179
  ## 2026-02-23 use this instead.
180
180
  def self.get_quotes_h params
181
- # puts! params, 'Tda::Option#get_quotes_h ...'
181
+ # puts! params, 'Tda::Option#get_quotes_h params ...'
182
182
 
183
183
  profile = Wco::Profile.find_by email: 'piousbox@gmail.com'
184
184
  opts = {}
@@ -28,11 +28,6 @@ class Wco::Lead
28
28
  field :memory, type: Hash, default: {}
29
29
 
30
30
  belongs_to :leadset, class_name: 'Wco::Leadset'
31
- before_validation :set_leadset, on: :create
32
- def set_leadset
33
- domain = email.split('@')[1]
34
- self.leadset ||= Wco::Leadset.find_or_create_by({ company_url: domain })
35
- end
36
31
  before_validation :normalize_email, on: :create
37
32
  def normalize_email
38
33
  self[:email] = email.downcase
@@ -49,6 +44,12 @@ class Wco::Lead
49
44
  end
50
45
  return a
51
46
  end
47
+ before_validation :set_leadset, on: :create
48
+ def set_leadset
49
+ domain = email.split('@')[1]
50
+ self.leadset ||= Wco::Leadset.find_or_create_by({ company_url: domain })
51
+ end
52
+
52
53
  def self.find_or_create_by_email email
53
54
  email = self.normalize_email email
54
55
  out = where( email: email ).first
@@ -92,12 +92,17 @@ class Wco::Newspartial
92
92
  return out
93
93
  end
94
94
 
95
+ ##
96
+ ## Can I make do without puppet driver? Probably not: I need the audio worklet.
97
+ ##
95
98
  def generate_video
96
99
  cmd = "cd #{ISHLIB3JS_ROOT} ;
97
100
  node ./src/talking_head/example_puppeteer_wired.js \
98
- --api_key=#{SIMPLE_API_KEY} \
99
- --api_secret=#{SIMPLE_API_SECRET} \
101
+ --api_key=#{WCO_SIMPLE_API_KEY} \
102
+ --api_secret=#{WCO_SIMPLE_API_SECRET} \
100
103
  --wco_origin=#{WCO_ORIGIN} \
104
+ --w_px=#{newsvideo.w_px} \
105
+ --h_px=#{newsvideo.h_px} \
101
106
  --newspartial_id=#{self[:id]} ";
102
107
 
103
108
  puts "+++ cmd:"
@@ -26,6 +26,8 @@ class Wco::Newsvideo
26
26
  field :config_json, type: :string
27
27
  field :duration_ms, type: :integer
28
28
 
29
+ field :w_px, type: Integer
30
+ field :h_px, type: Integer
29
31
  field :x, :type => Float
30
32
  field :y, :type => Float
31
33
  field :z, :type => Float
@@ -15,6 +15,8 @@ class Wco::Photo
15
15
  belongs_to :gallery, class_name: 'Wco::Gallery', optional: true
16
16
  belongs_to :lead, class_name: 'Wco::Lead', optional: true
17
17
 
18
+ belongs_to :report, class_name: 'Wco::Report', optional: true
19
+
18
20
  has_many :email_templates, class_name: 'WcoEmail::EmailTemplate'
19
21
  # belongs_to :newsitem, :optional => true
20
22
 
@@ -10,6 +10,7 @@ class Wco::Profile
10
10
  validates :email, presence: true, uniqueness: true
11
11
 
12
12
  field :name
13
+ field :descr
13
14
 
14
15
 
15
16
  field :per_page, type: :integer, default: 25
@@ -31,10 +32,15 @@ class Wco::Profile
31
32
  field :smtp_password
32
33
  field :smtp_port
33
34
 
35
+ field :linkedin_client_id
36
+ field :linkedin_client_secret
37
+ field :linkedin_access_token
38
+
34
39
 
35
40
  has_many :newsvideos, class_name: 'Wco::Newsvideo'
36
41
  has_many :reports, class_name: 'Wco::Report'
37
42
  has_many :stocks, class_name: 'Iro::Stock'
43
+ has_many :sidebar_tags, class_name: 'Wco::Tag', inverse_of: :sidebar_profile
38
44
 
39
45
  belongs_to :leadset, class_name: 'Wco::Leadset', inverse_of: :profile
40
46
  has_many :newsitems, class_name: 'Wco::Newsitem'
@@ -28,13 +28,13 @@ class Wco::Publisher
28
28
  @headers = {}
29
29
  @ctx = OpenStruct.new
30
30
 
31
- puts! context_eval, 'context_eval'
31
+ # puts! context_eval, 'context_eval'
32
32
  eval( context_eval )
33
- puts! @ctx, '@ctx'
33
+ # puts! @ctx, '@ctx'
34
34
 
35
35
  tmpl = ERB.new post_body_tmpl
36
36
  body = JSON.parse tmpl.result(binding)
37
- puts! body, 'body'
37
+ # puts! body, 'body'
38
38
 
39
39
  out = Wco::HTTParty.post( "#{@site.origin}#{post_path}", {
40
40
  body: body.to_json,
@@ -13,7 +13,7 @@ class Wco::Report
13
13
 
14
14
  field :title
15
15
  validates :title, presence: true # , uniqueness: true
16
- index({ title: 1 }, { unique: true })
16
+ index({ title: 1 })
17
17
  def name ; title ; end
18
18
 
19
19
  field :subtitle
@@ -25,6 +25,9 @@ class Wco::Report
25
25
  before_validation :set_slug, on: :create
26
26
 
27
27
  field :body
28
+ def body_json
29
+ body.gsub(/\r/, '').gsub(/\n\n+/, '<br /><br />').to_json
30
+ end
28
31
 
29
32
  field :x, :type => Float
30
33
  field :y, :type => Float
@@ -32,8 +35,8 @@ class Wco::Report
32
35
 
33
36
  belongs_to :author, class_name: 'Wco::Profile'
34
37
 
35
- # has_one :image_thumb
36
- # has_one :image_hero
38
+ has_one :image_thumb, class_name: 'Wco::Photo', inverse_of: :report, dependent: :destroy
39
+ accepts_nested_attributes_for :image_thumb
37
40
 
38
41
  has_and_belongs_to_many :tags
39
42
 
@@ -25,7 +25,7 @@ class Wco::Site
25
25
  validates :slug, presence: true, uniqueness: true
26
26
 
27
27
  field :origin # http://pi.local
28
- validates :origin, presence: true, uniqueness: true
28
+ # validates :origin, presence: true, uniqueness: true
29
29
 
30
30
  field :post_path # /node?_format=hal_json
31
31
  field :username
@@ -39,7 +39,7 @@ class Wco::Site
39
39
  end
40
40
 
41
41
  def self.list
42
- [[nil,nil]] + all.map { |s| [ s.origin, s.id ] }
42
+ [[nil,nil]] + all.map { |s| [ s.slug, s.id ] }
43
43
  end
44
44
 
45
45
  def body
@@ -9,10 +9,13 @@ class Wco::Tag
9
9
  validates :slug, presence: true, uniqueness: true
10
10
  index({ slug: -1 })
11
11
 
12
+ field :weight, type: :string, default: 'jjj'
13
+
12
14
  belongs_to :parent, class_name: '::Wco::Tag', inverse_of: :sons, optional: true
13
15
  has_many :sons, class_name: '::Wco::Tag', inverse_of: :parent
14
16
 
15
17
  belongs_to :site, class_name: '::Wco::Site', optional: true
18
+ belongs_to :sidebar_profile, class_name: 'Wco::Profile', optional: true
16
19
  has_many :email_filters, class_name: '::WcoEmail::EmailFilter', inverse_of: :tag
17
20
  has_many :email_templates, class_name: '::WcoEmail::EmailTemplate', inverse_of: :tag
18
21
  has_many :ajects, class_name: '::WcoEmail::EmailFilterAction', inverse_of: :aject
@@ -1,5 +1,6 @@
1
1
 
2
2
  require 'mongoid_paperclip'
3
+ require 'streamio-ffmpeg'
3
4
 
4
5
  class Wco::Video
5
6
  include Mongoid::Document
@@ -33,9 +34,6 @@ class Wco::Video
33
34
 
34
35
  field :duration_ms, type: :integer
35
36
 
36
- # belongs_to :user_profile, :class_name => 'Ish::UserProfile', :inverse_of => :videos
37
- # has_and_belongs_to_many :shared_profiles, :class_name => 'Ish::UserProfile', :inverse_of => :shared_videos
38
-
39
37
  belongs_to :lead, optional: true
40
38
  belongs_to :newspartial, optional: true
41
39
  belongs_to :newsvideo, optional: true
@@ -77,10 +75,37 @@ class Wco::Video
77
75
  %w| name descr |
78
76
  end
79
77
 
78
+ before_create :set_duration_ms
79
+ def set_duration_ms
80
+ return unless video.queued_for_write[:original]
81
+ path = video.queued_for_write[:original].path
82
+ movie = ::FFMPEG::Movie.new(path)
83
+ self.duration_ms = (movie.duration * 1000).to_i if movie.duration
84
+ end
85
+
86
+ before_create :set_title
87
+ def set_title
88
+ return unless video.present?
89
+ filename = video_file_name # Paperclip metadata
90
+ return unless filename
91
+ self.name = File.basename(filename, ".*") if self.name.blank?
92
+ end
93
+
80
94
  def self.list
81
95
  [['', nil]] + self.unscoped.order_by( :created_at => :desc ).map do |item|
82
96
  [ "#{item.created_at.strftime('%Y%m%d')} #{item.name}", item.id ]
83
97
  end
84
98
  end
85
99
 
100
+ def generate_thumbnail
101
+ return unless video.queued_for_write[:original]
102
+
103
+ input_path = video.queued_for_write[:original].path
104
+ output_path = Rails.root.join('tmp', "thumb_#{SecureRandom.hex}.jpg")
105
+ movie = ::FFMPEG::Movie.new(input_path)
106
+ movie.screenshot(output_path.to_s, seek_time: 1) ## at time 00:00:01 seconds
107
+ self.thumb = File.open(output_path)
108
+ File.delete(output_path) if File.exist?(output_path)
109
+ end
110
+
86
111
  end
@@ -28,7 +28,7 @@ class WcoEmail::EmailFilter
28
28
 
29
29
  has_many :actions, class_name: '::WcoEmail::EmailFilterAction', inverse_of: :email_filter
30
30
  accepts_nested_attributes_for :actions, allow_destroy: true, reject_if: :all_blank
31
- # validate :validate_actions
31
+ validate :validate_actions
32
32
  def validate_actions
33
33
  if actions.length == 0
34
34
  errors.add(:actions, 'must be present')
@@ -22,6 +22,7 @@ class WcoEmail::EmailFilterAction
22
22
  field :value
23
23
 
24
24
  belongs_to :aject, polymorphic: true # , optional: true # eg tag, EAT, OAT
25
+ accepts_nested_attributes_for :aject, allow_destroy: true, reject_if: :all_blank
25
26
  # validates :aject_id, presence: true
26
27
 
27
28
  ## 2026-04-02 not anymore.
@@ -46,3 +47,4 @@ class WcoEmail::EmailFilterAction
46
47
  end
47
48
 
48
49
  end
50
+ EFA = WcoEmail::EmailFilterAction