cloudrider 0.3.22 → 0.3.23
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/generica/app/assets/javascripts/components/product-display.js.em +2 -2
- data/generica/app/assets/javascripts/components/product-listing.js.em +2 -2
- data/generica/app/assets/javascripts/controllers/admin/products/slice_controller.js.em +7 -1
- data/generica/app/assets/javascripts/controllers/users/offers/index_controller.js.em +10 -6
- data/generica/app/assets/javascripts/controllers/users/products/slice_controller.js.em +7 -1
- data/generica/app/assets/javascripts/models/product.js.em +2 -0
- data/generica/app/assets/javascripts/routes/users/offers/index_route.js.em +11 -1
- data/generica/app/assets/javascripts/templates/admin/products/slice.emblem +16 -0
- data/generica/app/assets/javascripts/templates/users/offers/index.emblem +5 -1
- data/generica/app/assets/javascripts/templates/users/offers/slice.emblem +10 -3
- data/generica/app/assets/javascripts/templates/users/products/slice.emblem +16 -0
- data/generica/app/controllers/apiv1/offers/index_controller.rb +32 -9
- data/generica/app/controllers/apiv1/products/update_controller.rb +27 -0
- data/generica/app/controllers/users/products/update_controller.rb +1 -1
- data/generica/app/mailers/apiv1/notifications_mailer/new_offer_context.rb +1 -1
- data/generica/app/models/admin/user.rb +8 -0
- data/generica/app/models/apiv1/email_request.rb +4 -0
- data/generica/app/models/apiv1/offer_message.rb +11 -0
- data/generica/app/models/apiv1/picture.rb +4 -0
- data/generica/app/models/apiv1/product.rb +26 -1
- data/generica/app/models/apiv1/products_machine.rb +9 -2
- data/generica/app/varissets/javascripts/templates/components/product-display.emblem.erb +6 -0
- data/generica/app/varissets/javascripts/templates/components/product-listing.emblem.erb +3 -0
- data/generica/app/varissets/javascripts/templates/products/product/offers/_anonymous-form.emblem.erb +3 -3
- data/generica/app/varissets/javascripts/templates/products/product/show.emblem.erb +3 -0
- data/generica/config/routes.rb +1 -0
- data/generica/config/schedule.rb.yml +26 -0
- data/generica/db/migrate/20141212162545_add_finished_at_to_apiv1_products.rb +5 -0
- data/generica/lib/generica/email_slave.rb +51 -45
- data/generica/lib/tasks/email_slave.rake +6 -3
- data/generica/spec/generica/email_slave_spec.rb +55 -0
- data/lib/cloudrider/backend_commander.rb +6 -1
- data/lib/cloudrider/serverside/schedule_ruby.rb +14 -0
- data/lib/cloudrider/version.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0d2e8d222b358db32bdff3dd54e567bbe612cd7f
|
4
|
+
data.tar.gz: 9a0ffe6c7954a0b6de2126945616cff8eac71b29
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 797256f15b86160f8388dd0293bb2f0f04a658c5ed17357174132f24ebe0c40456d69886e409b1c66d5bc1eea2fd6c0171665df5419fda2e7b11dfe48e8e809e
|
7
|
+
data.tar.gz: 471366a69c0816ba7032e8681c15ff6357f54b7afd4dbc91a6f8785b22d2b3f0724e450522fd3d578bfe181b73c9ddd82014795993102529b4158339c1524cc2
|
@@ -2,5 +2,5 @@ class Apiv1.ProductDisplayComponent extends Ember.Component
|
|
2
2
|
attributeBindings: ["class"]
|
3
3
|
classNames: ['product-display']
|
4
4
|
|
5
|
-
+computed product.
|
6
|
-
productImageStyle: -> "background-image: url(#{@get 'product.
|
5
|
+
+computed product.thumbnail
|
6
|
+
productImageStyle: -> "background-image: url(#{@get 'product.thumbnail' });"
|
@@ -2,5 +2,5 @@ class Apiv1.ProductListingComponent extends Ember.Component
|
|
2
2
|
attributeBindings: ["class"]
|
3
3
|
classNames: ['product-listing']
|
4
4
|
|
5
|
-
+computed product.
|
6
|
-
productImageStyle: -> "background-image: url(#{@get 'product.
|
5
|
+
+computed product.thumbnail
|
6
|
+
productImageStyle: -> "background-image: url(#{@get 'product.thumbnail' });"
|
@@ -1,4 +1,10 @@
|
|
1
1
|
class Apiv1.AdminProductsSliceController extends Ember.ObjectController
|
2
|
+
successfulSave: (contact) ->
|
3
|
+
Apiv1.Flash.register "success", "successful", 5000
|
4
|
+
failedSave: (reason) ->
|
5
|
+
Apiv1.Flash.register "warning", "unsuccessful: #{reason.status}", 5000
|
2
6
|
actions:
|
7
|
+
requestToggleFinish: ->
|
8
|
+
@model.save().then(_.bind @successfulSave, @).catch(_.bind @failedSave, @)
|
3
9
|
requestDelete: ->
|
4
|
-
@model.destroyRecord()
|
10
|
+
@model.destroyRecord().then(_.bind @successfulSave, @).catch(_.bind @failedSave, @)
|
@@ -1,10 +1,14 @@
|
|
1
1
|
class Apiv1.UsersOffersIndexController extends Ember.ObjectController
|
2
|
-
queryParams: ["page", "per"]
|
2
|
+
queryParams: ["page", "per", "f"]
|
3
3
|
page: 1
|
4
4
|
per: 15
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
f: null
|
6
|
+
choices: [
|
7
|
+
{ id: "2me", display: "offers made to me" },
|
8
|
+
{ id: "4me", display: "offers I've made" }
|
9
|
+
]
|
10
|
+
+computed model
|
11
|
+
offers: -> @model
|
8
12
|
|
9
|
-
+computed
|
10
|
-
metadatum: -> @get("
|
13
|
+
+computed model.content.meta
|
14
|
+
metadatum: -> @get("model.content.meta")
|
@@ -1,4 +1,10 @@
|
|
1
1
|
class Apiv1.UsersProductsSliceController extends Ember.ObjectController
|
2
|
+
successfulSave: (contact) ->
|
3
|
+
Apiv1.Flash.register "success", "successful", 5000
|
4
|
+
failedSave: (reason) ->
|
5
|
+
Apiv1.Flash.register "warning", "unsuccessful: #{reason.status}", 5000
|
2
6
|
actions:
|
7
|
+
requestToggleFinish: ->
|
8
|
+
@model.save().then(_.bind @successfulSave, @).catch(_.bind @failedSave, @)
|
3
9
|
requestDelete: ->
|
4
|
-
@model.destroyRecord()
|
10
|
+
@model.destroyRecord().then(_.bind @successfulSave, @).catch(_.bind @failedSave, @)
|
@@ -9,6 +9,8 @@ class Apiv1.Product extends DS.Model
|
|
9
9
|
showcaseOrder: DS.attr "number"
|
10
10
|
createdAt: DS.attr "date"
|
11
11
|
updatedAt: DS.attr "date"
|
12
|
+
isFinished: DS.attr "boolean"
|
13
|
+
thumbnail: DS.attr "string"
|
12
14
|
attachments: DS.hasMany "attachment", async: true
|
13
15
|
pictures: DS.hasMany "picture", async: true
|
14
16
|
taxons: DS.hasMany "taxon", async: true
|
@@ -1 +1,11 @@
|
|
1
|
-
class Apiv1.UsersOffersIndexRoute extends Ember.Route
|
1
|
+
class Apiv1.UsersOffersIndexRoute extends Ember.Route
|
2
|
+
queryParams:
|
3
|
+
page:
|
4
|
+
refreshModel: true
|
5
|
+
per:
|
6
|
+
refreshModel: true
|
7
|
+
f:
|
8
|
+
refreshModel: true
|
9
|
+
|
10
|
+
model: (params) ->
|
11
|
+
@store.find "offer", params
|
@@ -7,6 +7,22 @@
|
|
7
7
|
if model.hasShowOrder
|
8
8
|
span #
|
9
9
|
span= model.showcaseOrder
|
10
|
+
if model.isFinished
|
11
|
+
span.parantheses
|
12
|
+
tr-span en="finished"
|
13
|
+
.edit
|
14
|
+
if model.isPending
|
15
|
+
i.fa.fa-cog.fa-spin
|
16
|
+
span.prespace
|
17
|
+
tr-span en="loading"
|
18
|
+
else
|
19
|
+
.pointer click="requestToggleFinish"
|
20
|
+
if model.isFinished
|
21
|
+
span.prespace
|
22
|
+
tr-span en="unfinish"
|
23
|
+
else
|
24
|
+
span.prespace
|
25
|
+
tr-span en="mark finish"
|
10
26
|
.edit
|
11
27
|
link-to "admin.product.edit" model.id
|
12
28
|
i.fa.fa-edit
|
@@ -1,3 +1,7 @@
|
|
1
|
+
.row
|
2
|
+
.small-6.medium-4.large-2.columns
|
3
|
+
Ember.Select content=choices optionValuePath="content.id" optionLabelPath="content.display" value=f
|
4
|
+
|
1
5
|
.row.admin-messages-index
|
2
6
|
.small-12.columns
|
3
7
|
if offers.isPending
|
@@ -12,7 +16,7 @@
|
|
12
16
|
.admin-message-slice
|
13
17
|
i.fa.fa-times
|
14
18
|
span.prespace
|
15
|
-
tr-span en="
|
19
|
+
tr-span en="you have no offers"
|
16
20
|
|
17
21
|
.row
|
18
22
|
.small-4.medium-3.large-2.columns
|
@@ -1,5 +1,5 @@
|
|
1
1
|
.admin-message-slice
|
2
|
-
.actions
|
2
|
+
link-to "products.product.show" model.product.id class="actions"
|
3
3
|
.view
|
4
4
|
i.fa.fa-user
|
5
5
|
span.prespace= model.fromCompany
|
@@ -11,8 +11,7 @@
|
|
11
11
|
.media-subject
|
12
12
|
span.bold= model.priceTerms
|
13
13
|
span.spacebar.prespace -
|
14
|
-
|
15
|
-
span= model.product.material
|
14
|
+
span= model.product.material
|
16
15
|
|
17
16
|
.media-text= model.message
|
18
17
|
.media-extras
|
@@ -29,3 +28,11 @@
|
|
29
28
|
dt
|
30
29
|
tr-span en="company location"
|
31
30
|
dd= model.companyAddress
|
31
|
+
if model.message
|
32
|
+
dt
|
33
|
+
tr-span en="message"
|
34
|
+
dd= model.message
|
35
|
+
if model.status
|
36
|
+
dt
|
37
|
+
tr-span en="status"
|
38
|
+
dd= model.status
|
@@ -7,6 +7,22 @@
|
|
7
7
|
if model.hasShowOrder
|
8
8
|
span #
|
9
9
|
span= model.showcaseOrder
|
10
|
+
if model.isFinished
|
11
|
+
span.parantheses
|
12
|
+
tr-span en="finished"
|
13
|
+
.edit
|
14
|
+
if model.isPending
|
15
|
+
i.fa.fa-cog.fa-spin
|
16
|
+
span.prespace
|
17
|
+
tr-span en="loading"
|
18
|
+
else
|
19
|
+
.pointer click="requestToggleFinish"
|
20
|
+
if model.isFinished
|
21
|
+
span.prespace
|
22
|
+
tr-span en="unfinish"
|
23
|
+
else
|
24
|
+
span.prespace
|
25
|
+
tr-span en="mark finish"
|
10
26
|
.edit
|
11
27
|
link-to "users.product.edit" model.id
|
12
28
|
i.fa.fa-edit
|
@@ -1,17 +1,37 @@
|
|
1
1
|
class Apiv1::Offers::IndexController < Apiv1::UsersController
|
2
2
|
def index
|
3
|
-
render json: { offers:
|
3
|
+
render json: { offers: _offers_search_process.call, meta: _offers_meta_process.call }
|
4
4
|
end
|
5
5
|
private
|
6
|
-
def
|
7
|
-
{
|
8
|
-
page: _page,
|
9
|
-
per: _per,
|
10
|
-
count: current_user.offers.count
|
11
|
-
}
|
6
|
+
def _meta_hashify
|
7
|
+
Arrows.lift -> (count) { { page: _page, per: _per, count: count } }
|
12
8
|
end
|
13
|
-
def
|
14
|
-
|
9
|
+
def _offers_meta_process
|
10
|
+
_raw_search_process >> _ask_count >> _meta_hashify
|
11
|
+
end
|
12
|
+
def _ask_count
|
13
|
+
Arrows.lift -> (offers) { offers.count }
|
14
|
+
end
|
15
|
+
def _offers_search_process
|
16
|
+
_raw_search_process >> _apply_pagination >= _ember_hashify
|
17
|
+
end
|
18
|
+
def _ember_hashify
|
19
|
+
Arrows.lift -> (offer) { offer.to_ember_hash }
|
20
|
+
end
|
21
|
+
def _raw_search_process
|
22
|
+
Arrows.lift(current_user.offers) >> _either_from_or_to_me >> (_offers_from_me ^ _offers_to_me)
|
23
|
+
end
|
24
|
+
def _offers_from_me
|
25
|
+
Arrows.lift -> (offers) { offers.made_to current_user }
|
26
|
+
end
|
27
|
+
def _offers_to_me
|
28
|
+
Arrows.lift -> (offers) { offers.made_by current_user }
|
29
|
+
end
|
30
|
+
def _apply_pagination
|
31
|
+
Arrows.lift -> (offers) { offers.page(_page).per(_per) }
|
32
|
+
end
|
33
|
+
def _either_from_or_to_me
|
34
|
+
Arrows.lift -> (offers) { _to_me? ? Arrows.good(offers) : Arrows.evil(offers) }
|
15
35
|
end
|
16
36
|
def _page
|
17
37
|
params[:page] || 1
|
@@ -19,4 +39,7 @@ class Apiv1::Offers::IndexController < Apiv1::UsersController
|
|
19
39
|
def _per
|
20
40
|
params[:per] || 15
|
21
41
|
end
|
42
|
+
def _to_me?
|
43
|
+
params[:f] == "2me"
|
44
|
+
end
|
22
45
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class Apiv1::Products::UpdateController < Users::Products::UpdateController
|
2
|
+
before_filter :_ensure_product_ownership
|
3
|
+
def update
|
4
|
+
render json: { product: _toggle_finish_process.call }
|
5
|
+
end
|
6
|
+
private
|
7
|
+
def _toggle_finish_process
|
8
|
+
Arrows.lift(_product) >> _decide_finish_or_unfinish >> (_finish_listing! ^ _unfinish_listing!) >> _ember_hashify
|
9
|
+
end
|
10
|
+
def _ember_hashify
|
11
|
+
Arrows.lift -> (product) { product.to_ember_hash }
|
12
|
+
end
|
13
|
+
def _unfinish_listing!
|
14
|
+
Arrows.lift -> (product) { product.tap(&:mark_unfinish!) }
|
15
|
+
end
|
16
|
+
def _finish_listing!
|
17
|
+
Arrows.lift -> (product) { product.tap(&:mark_finish!) }
|
18
|
+
end
|
19
|
+
def _decide_finish_or_unfinish
|
20
|
+
Arrows.lift -> (product) { product.unfinished? ? Arrows.good(product) : Arrows.evil(product) }
|
21
|
+
end
|
22
|
+
def _ensure_product_ownership
|
23
|
+
unless current_user.owns? _product
|
24
|
+
render json: { message: "This isn't your listing" }, status: 401
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -10,7 +10,7 @@ class Users::Products::UpdateController < Apiv1::UsersController
|
|
10
10
|
end
|
11
11
|
private
|
12
12
|
def _ensure_product_ownership
|
13
|
-
unless current_user.owns?
|
13
|
+
unless current_user.admin? && current_user.owns?(_product)
|
14
14
|
render json: { message: "This isn't your listing" }, status: 401
|
15
15
|
end
|
16
16
|
end
|
@@ -54,6 +54,14 @@ class Admin::User < ActiveRecord::Base
|
|
54
54
|
through: :products,
|
55
55
|
class_name: 'Apiv1::OfferMessage'
|
56
56
|
|
57
|
+
def default_email
|
58
|
+
primary_contact.try(:email) || email
|
59
|
+
end
|
60
|
+
|
61
|
+
def all_known_emails
|
62
|
+
contacts.map(&:email).push(email).select(&:present?)
|
63
|
+
end
|
64
|
+
|
57
65
|
def to_ember_hash
|
58
66
|
{
|
59
67
|
id: id,
|
@@ -22,10 +22,21 @@ class Apiv1::OfferMessage < ActiveRecord::Base
|
|
22
22
|
belongs_to :product,
|
23
23
|
class_name: 'Apiv1::Product'
|
24
24
|
|
25
|
+
has_one :product_owner,
|
26
|
+
through: :product,
|
27
|
+
source: :user,
|
28
|
+
class_name: 'Admin::User'
|
29
|
+
|
25
30
|
validates :price_terms,
|
26
31
|
:product,
|
27
32
|
presence: true
|
28
33
|
|
34
|
+
scope :made_to,
|
35
|
+
-> (user) { joins(:product_owner).where "#{Admin::User.table_name}.id" => user.id }
|
36
|
+
|
37
|
+
scope :made_by,
|
38
|
+
-> (user) { where "#{self.table_name}.sender_email" => user.all_known_emails }
|
39
|
+
|
29
40
|
def to_ember_hash
|
30
41
|
attributes.merge price_terms: price_terms,
|
31
42
|
notes: notes
|
@@ -15,6 +15,7 @@
|
|
15
15
|
# updated_at :datetime
|
16
16
|
# showcase_order :integer
|
17
17
|
# deleted_at :datetime
|
18
|
+
# finished_at :datetime
|
18
19
|
#
|
19
20
|
|
20
21
|
class Apiv1::Product < ActiveRecord::Base
|
@@ -57,6 +58,12 @@ class Apiv1::Product < ActiveRecord::Base
|
|
57
58
|
|
58
59
|
scope :belonging_to_taxon,
|
59
60
|
-> (taxon) { union_of_taxon_ids taxon.id }
|
61
|
+
|
62
|
+
scope :has_been_finished,
|
63
|
+
-> { where("#{self.table_name}.finished_at < ?", DateTime.now) }
|
64
|
+
|
65
|
+
scope :still_unfinished,
|
66
|
+
-> { where("#{self.table_name}.finished_at is null or #{self.table_name}.finished_at > ?", DateTime.now) }
|
60
67
|
|
61
68
|
scope :union_of_taxon_ids,
|
62
69
|
-> (taxon_ids) { joins(:taxon_relationships).where("#{Apiv1::Listings::TaxonRelationship.table_name}.taxon_id" => taxon_ids) }
|
@@ -97,7 +104,25 @@ class Apiv1::Product < ActiveRecord::Base
|
|
97
104
|
taxons: taxons.map(&:id),
|
98
105
|
attachments: attachments.map(&:id),
|
99
106
|
offers: offers.map(&:id),
|
100
|
-
user_id: user.try(:id)
|
107
|
+
user_id: user.try(:id),
|
108
|
+
thumbnail: pictures.first.try(:thumbnail),
|
109
|
+
isFinished: finished?
|
110
|
+
end
|
111
|
+
|
112
|
+
def mark_finish!
|
113
|
+
update finished_at: DateTime.now
|
114
|
+
end
|
115
|
+
|
116
|
+
def mark_unfinish!
|
117
|
+
update finished_at: nil
|
118
|
+
end
|
119
|
+
|
120
|
+
def unfinished?
|
121
|
+
finished_at.blank? || finished_at > DateTime.now
|
122
|
+
end
|
123
|
+
|
124
|
+
def finished?
|
125
|
+
not unfinished?
|
101
126
|
end
|
102
127
|
|
103
128
|
def rough_summary
|
@@ -18,7 +18,7 @@ class Apiv1::ProductsMachine
|
|
18
18
|
@elastic_query.try(:count) || Apiv1::Product.count
|
19
19
|
end
|
20
20
|
def _filter_pipeline
|
21
|
-
_possible_query_search >> _paginate >> _unify_type >> _process_taxons >> _process_ordering
|
21
|
+
_possible_query_search >> _paginate >> _unify_type >> _process_taxons >> _process_ordering >> _process_finishing
|
22
22
|
end
|
23
23
|
def _possible_query_search
|
24
24
|
lambda do |product|
|
@@ -45,6 +45,13 @@ class Apiv1::ProductsMachine
|
|
45
45
|
-> (product) { product.order_by_created_at }
|
46
46
|
end
|
47
47
|
end
|
48
|
+
def _process_finishing
|
49
|
+
if _user.blank?
|
50
|
+
lambda(&:still_unfinished)
|
51
|
+
else
|
52
|
+
-> (product) { product }
|
53
|
+
end
|
54
|
+
end
|
48
55
|
def _process_taxons
|
49
56
|
lambda do |product|
|
50
57
|
if _taxon_ids.present?
|
@@ -58,7 +65,7 @@ class Apiv1::ProductsMachine
|
|
58
65
|
-> (t) { t.respond_to?(:records) ? t.records.load : t }
|
59
66
|
end
|
60
67
|
def _user
|
61
|
-
Admin::User.find_by_id params[:user_id]
|
68
|
+
@user ||= Admin::User.find_by_id params[:user_id]
|
62
69
|
end
|
63
70
|
def _query
|
64
71
|
params[:query]
|
@@ -9,6 +9,9 @@ if product
|
|
9
9
|
tr-span en=product.material
|
10
10
|
if product.sku
|
11
11
|
span.predash= product.sku
|
12
|
+
if product.isFinished
|
13
|
+
span.parantheses
|
14
|
+
tr-span en="finished"
|
12
15
|
if product.others
|
13
16
|
p.product-summary
|
14
17
|
tr-span en=product.others
|
@@ -36,6 +39,9 @@ if product
|
|
36
39
|
tr-span en=product.material
|
37
40
|
if product.sku
|
38
41
|
span.predash= product.sku
|
42
|
+
if product.isFinished
|
43
|
+
span.parantheses
|
44
|
+
tr-span en="finished"
|
39
45
|
|
40
46
|
.product-specs
|
41
47
|
if product.amount
|
data/generica/app/varissets/javascripts/templates/products/product/offers/_anonymous-form.emblem.erb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
.the-form class=showOfferForm::hidden
|
2
|
+
== partial "products/product/offers/offer-form"
|
1
3
|
.somesort-of-request
|
2
4
|
button.button.secondary.capitalize click="displayModal 'login'"
|
3
5
|
tr-span en="login if you have an account"
|
@@ -6,6 +8,4 @@
|
|
6
8
|
tr-span en="or register an account"
|
7
9
|
.somesort-of-request
|
8
10
|
button.button.secondary.capitalize click="toggleOfferForm"
|
9
|
-
tr-span en="or make
|
10
|
-
.the-form class=showOfferForm::hidden
|
11
|
-
== partial "products/product/offers/offer-form"
|
11
|
+
tr-span en="or make a quick offer"
|
data/generica/config/routes.rb
CHANGED
@@ -5,6 +5,7 @@ Rails.application.routes.draw do
|
|
5
5
|
resources :products, only: [:index], controller: 'products/index'
|
6
6
|
resources :products, only: [:show], controller: 'products/show'
|
7
7
|
resources :products, only: [:destroy], controller: 'products/destroy'
|
8
|
+
resources :products, only: [:update], controller: 'products/update'
|
8
9
|
resource :product_metadatum, only: [:show], controller: 'product_metadatum/show'
|
9
10
|
resource :product_metadata, only: [:show], controller: 'product_metadatum/show'
|
10
11
|
resources :pictures, only: [:show], controller: 'pictures/show'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Use this file to easily define all of your cron jobs.
|
2
|
+
#
|
3
|
+
# It's helpful, but not entirely necessary to understand cron before proceeding.
|
4
|
+
# http://en.wikipedia.org/wiki/Cron
|
5
|
+
|
6
|
+
# Example:
|
7
|
+
#
|
8
|
+
# set :output, "/path/to/my/cron_log.log"
|
9
|
+
#
|
10
|
+
# every 2.hours do
|
11
|
+
# command "/usr/bin/some_great_command"
|
12
|
+
# runner "MyModel.some_method"
|
13
|
+
# rake "some:great:rake:task"
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# every 4.days do
|
17
|
+
# runner "AnotherModel.prune_old_records"
|
18
|
+
# end
|
19
|
+
|
20
|
+
# Learn more: http://github.com/javan/whenever
|
21
|
+
set :job_template, "bash -l -c -i ':job'"
|
22
|
+
set :output, '/home/ia/workspace/<%= project_name %>/log/cron_log.log'
|
23
|
+
|
24
|
+
every 2.hours do
|
25
|
+
rake "email_slave:work_the_queue"
|
26
|
+
end
|
@@ -1,75 +1,81 @@
|
|
1
1
|
module Generica; end
|
2
2
|
class Generica::EmailSlave
|
3
3
|
def dispatch_emails!
|
4
|
-
|
5
|
-
_requests.update_all(status: :attempt_dispatch)
|
6
|
-
_postal_pipeline.call _emails
|
7
|
-
_requests.update_all(status: :delivered)
|
4
|
+
_dispatch_process.call
|
8
5
|
end
|
9
6
|
private
|
10
|
-
def
|
11
|
-
|
7
|
+
def _dispatch_process
|
8
|
+
_all_unfulfilled_requests >> _group_by_target_email >= _crunch_down_emails >= (_dispatch_email! % _manage_failure!)
|
12
9
|
end
|
13
|
-
def
|
14
|
-
|
10
|
+
def _all_unfulfilled_requests
|
11
|
+
Arrows.lift Apiv1::EmailRequest.awaiting_delivery
|
15
12
|
end
|
16
|
-
def
|
17
|
-
-> (
|
13
|
+
def _group_by_target_email
|
14
|
+
Arrows.lift -> (requests) { Sorter.group_by_to requests }
|
18
15
|
end
|
19
|
-
def
|
20
|
-
-> (
|
16
|
+
def _crunch_down_emails
|
17
|
+
Arrows.lift -> (requests) { Cruncher.crunch requests }
|
21
18
|
end
|
22
|
-
def
|
23
|
-
|
19
|
+
def _dispatch_email!
|
20
|
+
Arrows.lift -> (mail) { Dispatcher.dispatch! mail }
|
24
21
|
end
|
25
|
-
def
|
26
|
-
|
27
|
-
end
|
28
|
-
def _consider_email_address
|
29
|
-
-> (scope) { @user.present? ? scope.meant_for_email(@email) : scope }
|
30
|
-
end
|
31
|
-
def _consider_workload
|
32
|
-
-> (klass) { klass.belonging_to_the_user_with_oldest_undelivered }
|
33
|
-
end
|
34
|
-
def _emails
|
35
|
-
@emails ||= _requests.map(&:mailify)
|
36
|
-
end
|
37
|
-
def _hash_map_dispatch(hash)
|
38
|
-
_right_map(hash) { |email| Dispatcher.dispatch email }
|
39
|
-
end
|
40
|
-
def _hash_map_crunch(hash)
|
41
|
-
_right_map(hash) { |emails| Cruncher.crunch emails }
|
42
|
-
end
|
43
|
-
def _right_map(hash, &block)
|
44
|
-
thing = hash.map { |key_and_value| [key_and_value.first, yield(key_and_value.last)] }
|
45
|
-
Hash[thing]
|
22
|
+
def _manage_failure!
|
23
|
+
Arrows.lift -> (requests) { GarbageMan.cleanup! requests }
|
46
24
|
end
|
47
25
|
end
|
48
26
|
|
49
27
|
class Generica::EmailSlave::Sorter
|
50
28
|
class << self
|
51
|
-
def
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
29
|
+
def group_by_to(requests)
|
30
|
+
requests.group_by { |request| request.to }.map(&:last)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Generica::EmailSlave::GarbageMan
|
36
|
+
class << self
|
37
|
+
def cleanup!(requests)
|
38
|
+
return if requests.blank?
|
39
|
+
requests.map(&:mark_as_failed!)
|
57
40
|
end
|
58
41
|
end
|
59
42
|
end
|
60
43
|
|
61
44
|
class Generica::EmailSlave::Cruncher
|
62
45
|
class << self
|
63
|
-
def crunch(
|
64
|
-
|
65
|
-
|
46
|
+
def crunch(requests)
|
47
|
+
mails_or_fails = requests.map { |request| _convert_request request }
|
48
|
+
_crunch_process.call mails_or_fails
|
49
|
+
end
|
50
|
+
private
|
51
|
+
def _crunch_process
|
52
|
+
_separate_good_and_evil >> (_payload % _payload) >> (_crunch_good_emails % Arrows::ID)
|
53
|
+
end
|
54
|
+
def _crunch_good_emails
|
55
|
+
Arrows.lift -> (emails) { emails.count < 2 ? emails.first : Apiv1::AggregateMailer.summary(emails) }
|
56
|
+
end
|
57
|
+
def _payload
|
58
|
+
Arrows.lift -> (eithers) { eithers.map &:payload }
|
59
|
+
end
|
60
|
+
def _separate_good_and_evil
|
61
|
+
Arrows.lift -> (mails_and_fails) { [mails_and_fails.select(&:good?), mails_and_fails.reject(&:good?)] }
|
62
|
+
end
|
63
|
+
def _convert_request(request)
|
64
|
+
begin
|
65
|
+
mail = request.mailify
|
66
|
+
request.mark_as_delivered!
|
67
|
+
Arrows.good mail
|
68
|
+
rescue
|
69
|
+
Arrows.evil request
|
70
|
+
end
|
66
71
|
end
|
67
72
|
end
|
68
73
|
end
|
69
74
|
|
70
75
|
class Generica::EmailSlave::Dispatcher
|
71
76
|
class << self
|
72
|
-
def dispatch(email)
|
77
|
+
def dispatch!(email)
|
78
|
+
return if email.blank?
|
73
79
|
gmail.compose(email).deliver!
|
74
80
|
end
|
75
81
|
def gmail(&block)
|
@@ -1,11 +1,14 @@
|
|
1
|
+
require "generica/email_slave"
|
1
2
|
namespace :email_slave do
|
2
3
|
desc "checks the email job queue for emails that require sending, batches them together, and sends them off"
|
3
4
|
task work_the_queue: :environment do
|
4
|
-
require "generica/email_slave"
|
5
5
|
puts "begin dispatching emails..."
|
6
|
-
Generica::EmailSlave.new.dispatch_emails
|
6
|
+
puts Generica::EmailSlave.new.dispatch_emails!.inspect
|
7
7
|
puts "finished"
|
8
|
-
sleep 1
|
9
8
|
end
|
10
9
|
|
10
|
+
desc "just blandly outputs the emails to be sent"
|
11
|
+
task display_the_queue: :environment do
|
12
|
+
|
13
|
+
end
|
11
14
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
require "generica/email_slave"
|
3
|
+
|
4
|
+
RSpec.describe Generica::EmailSlave do
|
5
|
+
let(:user) { Admin::UserFactory.mock }
|
6
|
+
let(:owner) { Admin::UserFactory.mock }
|
7
|
+
let(:product) { Apiv1::ProductFactory.new.create }
|
8
|
+
before do
|
9
|
+
product.update user: owner
|
10
|
+
@offer_params = {
|
11
|
+
product_id: product.id,
|
12
|
+
price_terms: "Are you feeling lucky, punk?",
|
13
|
+
notes: "Nothing to say here"
|
14
|
+
}
|
15
|
+
end
|
16
|
+
let(:offer) { Users::Products::OfferFactory.new(user, @offer_params).tap(&:satisfy_specifications?).tap(&:save!).offer }
|
17
|
+
let(:postboy) { described_class.new offer }
|
18
|
+
context '#dispatch_emails!' do
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
RSpec.describe Generica::EmailSlave::Cruncher do
|
24
|
+
before do
|
25
|
+
expect(Apiv1::AggregateMailer).to receive(:summary).and_return("first mail")
|
26
|
+
@f = lambda do
|
27
|
+
req = instance_double "Apiv1::EmailRequest"
|
28
|
+
allow(req).to receive(:mailify) { throw :uhoh_fail }
|
29
|
+
allow(req).to receive(:mark_as_delivered!).and_return("mark_as_delivered!")
|
30
|
+
req
|
31
|
+
end
|
32
|
+
@r = lambda do
|
33
|
+
req = instance_double "Apiv1::EmailRequest"
|
34
|
+
allow(req).to receive(:mailify).and_return("mailified")
|
35
|
+
allow(req).to receive(:mark_as_delivered!).and_return("mark_as_delivered!")
|
36
|
+
req
|
37
|
+
end
|
38
|
+
end
|
39
|
+
context ':crunch' do
|
40
|
+
let(:requests) { [@r.call, @r.call, @r.call, @r.call] }
|
41
|
+
let(:expected) { ["first mail", []] }
|
42
|
+
subject { described_class.crunch requests }
|
43
|
+
specify { should eq expected }
|
44
|
+
end
|
45
|
+
context ':crunch2' do
|
46
|
+
before do
|
47
|
+
@f1 = @f.call
|
48
|
+
@f2 = @f.call
|
49
|
+
end
|
50
|
+
let(:requests) { [@r.call, @r.call, @r.call, @r.call, @f1, @f2] }
|
51
|
+
let(:expected) { ["first mail", [@f1, @f2]] }
|
52
|
+
subject { described_class.crunch requests }
|
53
|
+
specify { should eq expected }
|
54
|
+
end
|
55
|
+
end
|
@@ -29,8 +29,11 @@ class Cloudrider::BackendCommander
|
|
29
29
|
def _squash
|
30
30
|
[Cloudrider::Serverside::SquashRuby]
|
31
31
|
end
|
32
|
+
def _schedule
|
33
|
+
[Cloudrider::Serverside::ScheduleRuby]
|
34
|
+
end
|
32
35
|
def _configs
|
33
|
-
_nginx + _unicorn + _database + _squash
|
36
|
+
_nginx + _unicorn + _database + _squash + _schedule
|
34
37
|
end
|
35
38
|
def _related_backends
|
36
39
|
case _backend_name
|
@@ -44,6 +47,8 @@ class Cloudrider::BackendCommander
|
|
44
47
|
_database
|
45
48
|
when "squash"
|
46
49
|
_squash
|
50
|
+
when "schedule"
|
51
|
+
_schedule
|
47
52
|
else
|
48
53
|
raise UnknownOrUnimplmenetedVarisset, _backend_name
|
49
54
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Cloudrider::Serverside::ScheduleRuby < Cloudrider::Serverside::Base
|
2
|
+
class Context
|
3
|
+
attr_accessor :project_name
|
4
|
+
end
|
5
|
+
private
|
6
|
+
def _file_name
|
7
|
+
"schedule.rb"
|
8
|
+
end
|
9
|
+
def _context
|
10
|
+
Context.new.tap do |c|
|
11
|
+
c.project_name = @protosite.project_name
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/cloudrider/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cloudrider
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.23
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas Chen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-12-
|
11
|
+
date: 2014-12-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -429,6 +429,7 @@ files:
|
|
429
429
|
- generica/app/controllers/apiv1/products/destroy_controller.rb
|
430
430
|
- generica/app/controllers/apiv1/products/index_controller.rb
|
431
431
|
- generica/app/controllers/apiv1/products/show_controller.rb
|
432
|
+
- generica/app/controllers/apiv1/products/update_controller.rb
|
432
433
|
- generica/app/controllers/apiv1/taxons/destroy_controller.rb
|
433
434
|
- generica/app/controllers/apiv1/taxons/index_controller.rb
|
434
435
|
- generica/app/controllers/apiv1/taxons/show_controller.rb
|
@@ -564,6 +565,7 @@ files:
|
|
564
565
|
- generica/config/nginx.conf
|
565
566
|
- generica/config/nginx.conf.erb
|
566
567
|
- generica/config/routes.rb
|
568
|
+
- generica/config/schedule.rb.yml
|
567
569
|
- generica/config/secrets.yml
|
568
570
|
- generica/config/unicorn.conf.rb
|
569
571
|
- generica/config/unicorn.conf.rb.erb
|
@@ -591,6 +593,7 @@ files:
|
|
591
593
|
- generica/db/migrate/20141127230304_create_apiv1_email_requests.rb
|
592
594
|
- generica/db/migrate/20141127230455_create_apiv1_email_objects.rb
|
593
595
|
- generica/db/migrate/20141209185352_create_apiv1_user_contacts.rb
|
596
|
+
- generica/db/migrate/20141212162545_add_finished_at_to_apiv1_products.rb
|
594
597
|
- generica/db/schema.rb
|
595
598
|
- generica/db/seeds.rb
|
596
599
|
- generica/db/seeds/dimensions.rb
|
@@ -649,6 +652,7 @@ files:
|
|
649
652
|
- generica/spec/factories/apiv1/product_factory.rb
|
650
653
|
- generica/spec/factories/apiv1/taxon_factory.rb
|
651
654
|
- generica/spec/factories/base_factory.rb
|
655
|
+
- generica/spec/generica/email_slave_spec.rb
|
652
656
|
- generica/spec/generica/image_preloader_spec.rb
|
653
657
|
- generica/spec/mailers/apiv1/aggregate_mailer_spec.rb
|
654
658
|
- generica/spec/models/admin/pictures_factory_spec.rb
|
@@ -724,6 +728,7 @@ files:
|
|
724
728
|
- lib/cloudrider/serverside/base.rb
|
725
729
|
- lib/cloudrider/serverside/database_yaml.rb
|
726
730
|
- lib/cloudrider/serverside/nginx_conf.rb
|
731
|
+
- lib/cloudrider/serverside/schedule_ruby.rb
|
727
732
|
- lib/cloudrider/serverside/squash_ruby.rb
|
728
733
|
- lib/cloudrider/serverside/unicorn_conf_ruby.rb
|
729
734
|
- lib/cloudrider/version.rb
|