cloudrider 0.3.0 → 0.3.20

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 (227) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -1
  3. data/cloudrider.gemspec +1 -0
  4. data/generica/.gitignore +18 -0
  5. data/generica/Gemfile +8 -1
  6. data/generica/Gemfile.lock +3 -0
  7. data/generica/app/assets/javascripts/apiv1.js.ls +6 -1
  8. data/generica/app/assets/javascripts/components/offers-summary.js.em +2 -0
  9. data/generica/app/assets/javascripts/components/picture-chunk.js.em +17 -0
  10. data/generica/app/assets/javascripts/components/pictures-group.js.em +21 -0
  11. data/generica/app/assets/javascripts/components/search-and-filter.js.em +4 -0
  12. data/generica/app/assets/javascripts/components/site-nav.js.em +5 -1
  13. data/generica/app/assets/javascripts/components/taggable-field.js.em +28 -0
  14. data/generica/app/assets/javascripts/components/tree/taxon-li.js.em +6 -2
  15. data/generica/app/assets/javascripts/controllers/index_controller.js.em +19 -2
  16. data/generica/app/assets/javascripts/controllers/modals/login_controller.js.em +23 -9
  17. data/generica/app/assets/javascripts/controllers/modals/register_controller.js.em +24 -0
  18. data/generica/app/assets/javascripts/controllers/products/index_controller.js.em +23 -13
  19. data/generica/app/assets/javascripts/controllers/products/product/offers/new_controller.js.em +17 -0
  20. data/generica/app/assets/javascripts/controllers/users/offers/index_controller.js.em +10 -0
  21. data/generica/app/assets/javascripts/controllers/users/product/edit_controller.js.em +31 -0
  22. data/generica/app/assets/javascripts/controllers/users/products/index_controller.js.em +8 -0
  23. data/generica/app/assets/javascripts/controllers/users/products/new_controller.js.em +22 -0
  24. data/generica/app/assets/javascripts/controllers/users/products/slice_controller.js.em +4 -0
  25. data/generica/app/assets/javascripts/helpers/filte_previewer.js.ls +21 -0
  26. data/generica/app/assets/javascripts/helpers/listing_picture.js.em +17 -0
  27. data/generica/app/assets/javascripts/models/offer.js.em +9 -8
  28. data/generica/app/assets/javascripts/models/user.js.em +12 -0
  29. data/generica/app/assets/javascripts/models/users/product.js.em +12 -0
  30. data/generica/app/assets/javascripts/routes/application_route.js.em +10 -2
  31. data/generica/app/assets/javascripts/routes/products/index_route.js.em +23 -2
  32. data/generica/app/assets/javascripts/routes/products/product/offers/new_route.js.em +18 -0
  33. data/generica/app/assets/javascripts/routes/users/offers/index_route.js.em +1 -0
  34. data/generica/app/assets/javascripts/routes/users/product_route.js.em +3 -0
  35. data/generica/app/assets/javascripts/routes/users/products/index_route.js.em +3 -0
  36. data/generica/app/assets/javascripts/routes/users/products/new_route.js.em +3 -0
  37. data/generica/app/assets/javascripts/templates/admin/product/edit.emblem +2 -0
  38. data/generica/app/assets/javascripts/templates/admin/products/_form-core.emblem +44 -0
  39. data/generica/app/assets/javascripts/templates/admin/products/new.emblem +4 -42
  40. data/generica/app/assets/javascripts/templates/components/offers-summary.emblem +9 -0
  41. data/generica/app/assets/javascripts/templates/components/picture-chunk.emblem +9 -0
  42. data/generica/app/assets/javascripts/templates/components/pictures-group.emblem +6 -0
  43. data/generica/app/assets/javascripts/templates/components/search-and-filter.emblem +5 -1
  44. data/generica/app/assets/javascripts/templates/components/taggable-field.emblem +1 -0
  45. data/generica/app/assets/javascripts/templates/modals/register.emblem +57 -0
  46. data/generica/app/assets/javascripts/templates/products/product/offers/_anonymous-form.emblem +11 -0
  47. data/generica/app/assets/javascripts/templates/products/product/offers/_offer-form.emblem +55 -0
  48. data/generica/app/assets/javascripts/templates/products/product/offers/new.emblem +37 -0
  49. data/generica/app/assets/javascripts/templates/products/product/show.emblem +26 -1
  50. data/generica/app/assets/javascripts/templates/users.emblem +24 -0
  51. data/generica/app/assets/javascripts/templates/users/index.emblem +3 -0
  52. data/generica/app/assets/javascripts/templates/users/offers/index.emblem +25 -0
  53. data/generica/app/assets/javascripts/templates/users/offers/slice.emblem +31 -0
  54. data/generica/app/assets/javascripts/templates/users/product/edit.emblem +82 -0
  55. data/generica/app/assets/javascripts/templates/users/products/index.emblem +22 -0
  56. data/generica/app/assets/javascripts/templates/users/products/new.emblem +28 -0
  57. data/generica/app/assets/javascripts/templates/users/products/slice.emblem +27 -0
  58. data/generica/app/assets/stylesheets/apiv1/_application.css.scss +4 -0
  59. data/generica/app/assets/stylesheets/apiv1/components/_offers-summary.css.scss +20 -0
  60. data/generica/app/assets/stylesheets/apiv1/components/_pictures-group.css.scss +32 -0
  61. data/generica/app/assets/stylesheets/apiv1/components/_taggable-field.css.scss +32 -0
  62. data/generica/app/assets/stylesheets/apiv1/products/product/offers/_new.css.scss +8 -0
  63. data/generica/app/controllers/admin/base_controller.rb +1 -1
  64. data/generica/app/controllers/admin/sessions/destroy_controller.rb +16 -0
  65. data/generica/app/controllers/apiv1/offers/create_controller.rb +30 -0
  66. data/generica/app/controllers/apiv1/offers/index_controller.rb +22 -0
  67. data/generica/app/controllers/apiv1/offers/show_controller.rb +12 -0
  68. data/generica/app/controllers/apiv1/products/destroy_controller.rb +6 -0
  69. data/generica/app/controllers/apiv1/products/index_controller.rb +8 -2
  70. data/generica/app/controllers/apiv1/products/show_controller.rb +7 -3
  71. data/generica/app/controllers/apiv1/taxons/index_controller.rb +5 -1
  72. data/generica/app/controllers/apiv1/users/create_controller.rb +17 -0
  73. data/generica/app/controllers/apiv1/users/show_controller.rb +12 -0
  74. data/generica/app/controllers/apiv1/users_controller.rb +9 -0
  75. data/generica/app/controllers/application_controller.rb +3 -0
  76. data/generica/app/controllers/users/offers/create_controller.rb +26 -0
  77. data/generica/app/controllers/users/products/create_controller.rb +26 -0
  78. data/generica/app/controllers/users/products/update_controller.rb +35 -0
  79. data/generica/app/mailers/apiv1/aggregate_mailer.rb +13 -0
  80. data/generica/app/mailers/apiv1/aggregate_mailer/summary_context.rb +121 -0
  81. data/generica/app/mailers/apiv1/notifications_mailer.rb +19 -0
  82. data/generica/app/mailers/apiv1/notifications_mailer/new_offer_context.rb +53 -0
  83. data/generica/app/models/admin/user.rb +43 -2
  84. data/generica/app/models/apiv1/attachment.rb +2 -0
  85. data/generica/app/models/apiv1/email_object.rb +35 -0
  86. data/generica/app/models/apiv1/email_request.rb +107 -0
  87. data/generica/app/models/apiv1/offer_message.rb +53 -0
  88. data/generica/app/models/apiv1/offer_postboy.rb +33 -0
  89. data/generica/app/models/apiv1/picture.rb +2 -0
  90. data/generica/app/models/apiv1/product.rb +28 -6
  91. data/generica/app/models/apiv1/products/offer_message_factory.rb +13 -0
  92. data/generica/app/models/apiv1/products_machine.rb +8 -2
  93. data/generica/app/models/apiv1/taxon.rb +1 -1
  94. data/generica/app/models/apiv1/users/product_factory.rb +22 -0
  95. data/generica/app/models/apiv1/users/product_relationship.rb +19 -0
  96. data/generica/app/models/users/products/offer_factory.rb +40 -0
  97. data/generica/app/varissets/javascripts/templates/application.emblem.erb +2 -2
  98. data/generica/app/varissets/javascripts/templates/components/offers-overview.emblem.erb +1 -0
  99. data/generica/app/varissets/javascripts/templates/components/search-and-filter.emblem.erb +33 -0
  100. data/generica/app/varissets/javascripts/templates/components/site-nav.emblem.erb +8 -0
  101. data/generica/app/varissets/javascripts/templates/components/taggable-field.emblem.erb +11 -0
  102. data/generica/app/varissets/javascripts/templates/modals/login.emblem.erb +36 -0
  103. data/generica/app/varissets/javascripts/templates/products.emblem.erb +4 -0
  104. data/generica/app/varissets/javascripts/templates/products/index.emblem.erb +17 -0
  105. data/generica/app/varissets/javascripts/templates/products/product/offers/_anonymous-form.emblem.erb +11 -0
  106. data/generica/app/varissets/javascripts/templates/products/product/offers/_offer-form.emblem.erb +55 -0
  107. data/generica/app/varissets/javascripts/templates/products/product/offers/new.emblem.erb +37 -0
  108. data/generica/app/varissets/javascripts/templates/products/product/show.emblem.erb +108 -0
  109. data/generica/app/varissets/stylesheets/apiv1/components/_offers-overview.css.scss.erb +3 -0
  110. data/generica/app/varissets/stylesheets/apiv1/components/_offers_overview.css.scss.erb +3 -0
  111. data/generica/app/varissets/stylesheets/apiv1/components/_search-and-filter.css.scss.erb +15 -0
  112. data/generica/app/varissets/stylesheets/apiv1/components/_site-footer.css.scss.erb +23 -0
  113. data/generica/app/varissets/stylesheets/apiv1/components/_taggable-field.css.scss.erb +32 -0
  114. data/generica/app/views/apiv1/home/index.html.haml +3 -1
  115. data/generica/app/views/apiv1/layouts/application.html.haml +37 -2
  116. data/generica/config/{database.yml.erb → database.yml.str} +3 -3
  117. data/generica/config/email.yml +11 -0
  118. data/generica/config/email_slave.god +9 -0
  119. data/generica/config/initializers/premailer.rb +1 -0
  120. data/generica/config/initializers/squash.rb.erb +3 -0
  121. data/generica/config/nginx.conf.erb +4 -4
  122. data/generica/config/routes.rb +14 -0
  123. data/generica/db/migrate/20141114213427_add_user_rank_to_admin_users.rb +9 -0
  124. data/generica/db/migrate/20141114225019_create_apiv1_users_products.rb +9 -0
  125. data/generica/db/migrate/20141116053522_create_apiv1_offer_messages.rb +16 -0
  126. data/generica/db/migrate/20141116235011_add_deleted_at_to_apiv1_products.rb +5 -0
  127. data/generica/db/migrate/20141116235209_add_deleted_at_to_apiv1_offer_messages.rb +5 -0
  128. data/generica/db/migrate/20141116235651_add_deleted_at_to_apiv1_pictures.rb +5 -0
  129. data/generica/db/migrate/20141116235808_add_deleted_at_to_apiv1_attachments.rb +5 -0
  130. data/generica/db/migrate/20141127230304_create_apiv1_email_requests.rb +16 -0
  131. data/generica/db/migrate/20141127230455_create_apiv1_email_objects.rb +11 -0
  132. data/generica/lib/generica/assets_unretardifier.rb +32 -0
  133. data/generica/lib/generica/email_slave.rb +92 -0
  134. data/generica/lib/tasks/email_slave.rake +11 -0
  135. data/generica/lib/tasks/production.rake +1 -1
  136. data/generica/public/tests/bermese.jpg +0 -0
  137. data/generica/public/tests/chow.jpg +0 -0
  138. data/generica/public/tests/malamute.jpg +0 -0
  139. data/generica/public/tests/malamute2.JPG +0 -0
  140. data/generica/spec/controllers/apiv1/users/create_controller_spec.rb +17 -0
  141. data/generica/spec/controllers/users/products/create_controller_spec.rb +31 -0
  142. data/generica/spec/mailers/apiv1/aggregate_mailer_spec.rb +29 -0
  143. data/generica/spec/models/apiv1/email_request_spec.rb +4 -0
  144. data/generica/spec/models/apiv1/offer_postboy_spec.rb +22 -0
  145. data/generica/spec/models/apiv1/product_spec.rb +1 -0
  146. data/generica/spec/models/apiv1/products/offer_message_factory_spec.rb +37 -0
  147. data/generica/spec/models/apiv1/products_machine_spec.rb +77 -0
  148. data/generica/spec/models/apiv1/users/product_factory_spec.rb +47 -0
  149. data/generica/spec/models/users/products/offer_factory_spec.rb +38 -0
  150. data/generica/test/mailers/.keep +0 -0
  151. data/generica/test/mailers/previews/apiv1/aggregate_preview.rb +6 -0
  152. data/generica/test/mailers/previews/apiv1/notifications_preview.rb +5 -0
  153. data/lib/cloudrider.rb +1 -1
  154. data/lib/cloudrider/apiv1/base.rb +2 -2
  155. data/lib/cloudrider/apiv1/login_modal_emblem.rb +25 -0
  156. data/lib/cloudrider/apiv1/offers_anonymous_form_partial_emblem.rb +12 -0
  157. data/lib/cloudrider/apiv1/offers_new_emblem.rb +15 -0
  158. data/lib/cloudrider/apiv1/offers_overview_component.rb +8 -0
  159. data/lib/cloudrider/apiv1/offers_overview_sass.rb +8 -0
  160. data/lib/cloudrider/apiv1/product_show_emblem.rb +31 -0
  161. data/lib/cloudrider/apiv1/products_emblem.rb +11 -0
  162. data/lib/cloudrider/apiv1/products_index_emblem.rb +14 -0
  163. data/lib/cloudrider/apiv1/search_and_filter_component.rb +11 -0
  164. data/lib/cloudrider/apiv1/search_and_filter_sass.rb +11 -0
  165. data/lib/cloudrider/apiv1/site_footer_component.rb +7 -7
  166. data/lib/cloudrider/apiv1/site_footer_sass.rb +9 -0
  167. data/lib/cloudrider/application_commander.rb +46 -34
  168. data/lib/cloudrider/backend_commander.rb +15 -9
  169. data/lib/cloudrider/protofile.rb +18 -2
  170. data/lib/cloudrider/protosite.example.json +26 -0
  171. data/lib/cloudrider/protosite.rb +66 -0
  172. data/lib/cloudrider/serverside/base.rb +2 -2
  173. data/lib/cloudrider/serverside/database_yaml.rb +9 -4
  174. data/lib/cloudrider/serverside/nginx_conf.rb +7 -2
  175. data/lib/cloudrider/serverside/squash_ruby.rb +14 -0
  176. data/lib/cloudrider/serverside/unicorn_conf_ruby.rb +2 -1
  177. data/lib/cloudrider/version.rb +1 -1
  178. data/lib/cloudrider/web_firm.rb +16 -0
  179. data/lib/cloudrider/web_firm/component_architect.rb +23 -0
  180. data/lib/cloudrider/web_firm/page_designer.rb +13 -0
  181. data/lib/cloudrider/web_firm/project_lead.rb +30 -0
  182. data/lib/cloudrider/web_firm/scumbag_engineer.rb +40 -0
  183. data/lib/cloudrider/web_firm/support_intern.rb +113 -0
  184. data/lib/generators/cloudrider/server/server_generator.rb +4 -8
  185. data/lib/generators/cloudrider/website/website_generator.rb +25 -0
  186. data/spec/cloudrider/protosite_spec.rb +63 -0
  187. data/vanilla/.bowerrc +4 -0
  188. data/vanilla/.editorconfig +33 -0
  189. data/vanilla/.ember-cli +9 -0
  190. data/vanilla/.gitignore +17 -0
  191. data/vanilla/.jshintrc +32 -0
  192. data/vanilla/.travis.yml +16 -0
  193. data/vanilla/Brocfile.js +20 -0
  194. data/vanilla/README.md +51 -0
  195. data/vanilla/app/app.js +16 -0
  196. data/vanilla/app/components/.gitkeep +0 -0
  197. data/vanilla/app/controllers/.gitkeep +0 -0
  198. data/vanilla/app/controllers/admin.coffee +5 -0
  199. data/vanilla/app/helpers/.gitkeep +0 -0
  200. data/vanilla/app/index.html +21 -0
  201. data/vanilla/app/models/.gitkeep +0 -0
  202. data/vanilla/app/router.js +11 -0
  203. data/vanilla/app/routes/.gitkeep +0 -0
  204. data/vanilla/app/styles/.gitkeep +0 -0
  205. data/vanilla/app/styles/app.css +3 -0
  206. data/vanilla/app/templates/.gitkeep +0 -0
  207. data/vanilla/app/templates/application.hbs +3 -0
  208. data/vanilla/app/templates/components/.gitkeep +0 -0
  209. data/vanilla/app/views/.gitkeep +0 -0
  210. data/vanilla/bower.json +17 -0
  211. data/vanilla/config/environment.js +47 -0
  212. data/vanilla/package.json +39 -0
  213. data/vanilla/public/.gitkeep +0 -0
  214. data/vanilla/public/crossdomain.xml +15 -0
  215. data/vanilla/public/robots.txt +3 -0
  216. data/vanilla/testem.json +11 -0
  217. data/vanilla/tests/.jshintrc +74 -0
  218. data/vanilla/tests/helpers/resolver.js +11 -0
  219. data/vanilla/tests/helpers/start-app.js +25 -0
  220. data/vanilla/tests/index.html +45 -0
  221. data/vanilla/tests/test-helper.js +12 -0
  222. data/vanilla/tests/unit/.gitkeep +0 -0
  223. data/vanilla/tests/unit/controllers/admin-test.coffee +12 -0
  224. data/vanilla/vendor/.gitkeep +0 -0
  225. metadata +189 -5
  226. data/generica/app/views/layouts/application.html.erb +0 -14
  227. data/lib/cloudrider/master_context.rb +0 -105
@@ -0,0 +1,19 @@
1
+ class Apiv1::NotificationsMailer < ActionMailer::Base
2
+ layout 'apiv1/layouts/email'
3
+ default from: "from@example.com"
4
+
5
+ # Subject can be set in your I18n file at config/locales/en.yml
6
+ # with the following lookup:
7
+ #
8
+ # en.notifications.new_offer.subject
9
+ #
10
+ def new_offer(offer_message)
11
+ @ctx = Apiv1::NotificationsMailer::NewOfferContext.new offer_message
12
+
13
+ mail to: @ctx.to,
14
+ from: @ctx.from,
15
+ cc: @ctx.cc,
16
+ bcc: @ctx.bcc,
17
+ subject: @ctx.subject
18
+ end
19
+ end
@@ -0,0 +1,53 @@
1
+ class Apiv1::NotificationsMailer::NewOfferContext
2
+ delegate :created_at,
3
+ :message,
4
+ :product,
5
+ :company_address,
6
+ :sender_email,
7
+ :phone_number,
8
+ to: :_offer
9
+ def initialize(offer_message)
10
+ @offer = offer_message
11
+ end
12
+ def to
13
+ product.try(:user).try(:email)
14
+ end
15
+ def from
16
+ "secretary@plasticscrapmarket.org"
17
+ end
18
+ def cc
19
+ end
20
+ def bcc
21
+ end
22
+ def product_summary
23
+ product.try :rough_summary
24
+ end
25
+ def subject
26
+ ["new offer", "-", price_terms].join " "
27
+ end
28
+ def price_terms
29
+ _offer.price_terms.present? ? _offer.price_terms : "[missing price terms]"
30
+ end
31
+ def product_id
32
+ product.try :id
33
+ end
34
+ def material
35
+ product.try :material
36
+ end
37
+ def link_path(*relative_path)
38
+ File.join "http://www.plasticscrapmarket.org", '#', *relative_path
39
+ end
40
+ def cta_path
41
+ link_path "products/product", "#{product_id}"
42
+ end
43
+ def company_who_made_offer
44
+ _offer.from_company
45
+ end
46
+ def product_owner
47
+ product.try(:user).try(:company_name) || "listing owner"
48
+ end
49
+ private
50
+ def _offer
51
+ @offer
52
+ end
53
+ end
@@ -10,16 +10,57 @@
10
10
  # updated_at :datetime
11
11
  # remember_me_token :string(255)
12
12
  # remember_me_token_expires_at :datetime
13
+ # user_rank :string(255)
14
+ # company_name :string(255)
15
+ # phone_number :string(255)
16
+ # about_me :text
17
+ # address :string(255)
13
18
  #
14
19
 
15
20
  class Admin::User < ActiveRecord::Base
16
21
  authenticates_with_sorcery!
17
22
  validates :email,
18
- presence: true
23
+ :company_name,
24
+ presence: true,
25
+ uniqueness: true
26
+ validates :password,
27
+ presence: true,
28
+ length: { minimum: 2 },
29
+ confirmation: true
30
+
31
+ has_many :product_relationships,
32
+ class_name: 'Apiv1::Users::ProductRelationship'
33
+
34
+ has_many :products,
35
+ class_name: 'Apiv1::Product',
36
+ through: :product_relationships
37
+
38
+ has_many :offers,
39
+ -> { order "#{Apiv1::OfferMessage.table_name}.created_at desc" },
40
+ through: :products,
41
+ class_name: 'Apiv1::OfferMessage'
42
+
19
43
  def to_ember_hash
20
44
  {
21
45
  id: id,
22
- email: email
46
+ email: email,
47
+ user_rank: user_rank,
48
+ company_name: company_name,
49
+ phone_number: phone_number,
50
+ address: address,
51
+ about_me: about_me
23
52
  }
24
53
  end
54
+
55
+ def admin?
56
+ user_rank.to_s == "admin"
57
+ end
58
+
59
+ def admin!
60
+ update user_rank: "admin"
61
+ end
62
+
63
+ def owns?(product)
64
+ product_relationships.exists?(product_id: product.id)
65
+ end
25
66
  end
@@ -11,9 +11,11 @@
11
11
  # document_updated_at :datetime
12
12
  # created_at :datetime
13
13
  # updated_at :datetime
14
+ # deleted_at :datetime
14
15
  #
15
16
 
16
17
  class Apiv1::Attachment < ActiveRecord::Base
18
+ acts_as_paranoid
17
19
  DocumentTypes = [
18
20
  "application/pdf",
19
21
  "application/docx",
@@ -0,0 +1,35 @@
1
+ # == Schema Information
2
+ #
3
+ # Table name: apiv1_email_objects
4
+ #
5
+ # id :integer not null, primary key
6
+ # email_id :integer
7
+ # class_name :string(255)
8
+ # permalink_id :string(255)
9
+ # deleted_at :datetime
10
+ # created_at :datetime
11
+ # updated_at :datetime
12
+ #
13
+
14
+ class Apiv1::EmailObject < ActiveRecord::Base
15
+ acts_as_paranoid
16
+ belongs_to :email,
17
+ class_name: 'Apiv1::EmailRequest'
18
+
19
+ validates :class_name,
20
+ :permalink_id,
21
+ presence: true
22
+
23
+ class << self
24
+ def paramify(some_object)
25
+ {
26
+ class_name: some_object.class,
27
+ permalink_id: some_object.id
28
+ }
29
+ end
30
+ end
31
+
32
+ def objectify
33
+ Object.const_get(class_name).find permalink_id
34
+ end
35
+ end
@@ -0,0 +1,107 @@
1
+ # == Schema Information
2
+ #
3
+ # Table name: apiv1_email_requests
4
+ #
5
+ # id :integer not null, primary key
6
+ # to :string(255) not null
7
+ # cc :string(255)
8
+ # bcc :string(255)
9
+ # subject :string(255)
10
+ # status :string(255)
11
+ # from :string(255)
12
+ # mailer_class :string(255) not null
13
+ # deleted_at :datetime
14
+ # created_at :datetime
15
+ # updated_at :datetime
16
+ #
17
+
18
+ class Apiv1::EmailRequest < ActiveRecord::Base
19
+ class MailerClassValidator < ActiveModel::Validator
20
+ def validate(record)
21
+ _enforce_class_existence!(record)
22
+ _enforce_method_existence!(record)
23
+ end
24
+ private
25
+ def _enforce_class_existence!(record)
26
+ begin
27
+ record.send "_mailer_class"
28
+ rescue
29
+ record.errors[:mailer_class] << "There is no #{record.send '_mailer_class_name'} class"
30
+ end
31
+ end
32
+ def _enforce_method_existence!(record)
33
+ begin
34
+ throw :bad unless record.send("_mailer_class").action_methods.include? record.send("_mailer_method")
35
+ rescue
36
+ record.errors[:mailer_class] << "The class #{record.send '_mailer_class_name'} doesn't respond to #{record.send '_mailer_method'}"
37
+ end
38
+ end
39
+ end
40
+ acts_as_paranoid
41
+ has_many :email_objects,
42
+ class_name: 'Apiv1::EmailObject',
43
+ foreign_key: 'email_id'
44
+
45
+ scope :awaiting_delivery,
46
+ -> { where "#{self.table_name}.status is null" }
47
+
48
+ scope :already_delivered,
49
+ -> { where "#{self.table_name}.status is not null" }
50
+
51
+ scope :meant_for_email,
52
+ -> (email) { where "#{self.table_name}.to" => email }
53
+
54
+ scope :last_delivered_email_for,
55
+ -> (email) { meant_for_email(email).already_delivered.latest_first.limit 1 }
56
+
57
+ scope :latest_first,
58
+ -> { order "#{self.table_name}.updated_at desc" }
59
+
60
+ scope :oldest_first,
61
+ -> { order "#{self.table_name}.updated_at asc" }
62
+
63
+ scope :the_null_scope,
64
+ -> { where("id != id").limit 0 }
65
+
66
+ validates :to,
67
+ presence: true
68
+
69
+ validates :mailer_class,
70
+ presence: true,
71
+ format: { with: /[A-Z][a-zA-Z0-9_:]+#[a-z0-9_]+/ }
72
+
73
+ validates_with MailerClassValidator
74
+
75
+ class << self
76
+ def oldest_undelivered_request
77
+ awaiting_delivery.oldest_first.limit(1).first
78
+ end
79
+ def belonging_to_the_user_with_oldest_undelivered
80
+ return the_null_scope if oldest_undelivered_request.blank?
81
+ awaiting_delivery.oldest_first.meant_for_email oldest_undelivered_request.to
82
+ end
83
+ end
84
+
85
+ def mailify
86
+ _mailer_class.send _mailer_method, *objectified_objects
87
+ end
88
+
89
+ def objectified_objects
90
+ email_objects.map(&:objectify)
91
+ end
92
+
93
+ def mark_as_delivered!
94
+ update status: :delivered
95
+ end
96
+
97
+ private
98
+ def _mailer_class
99
+ Object.const_get _mailer_class_name
100
+ end
101
+ def _mailer_class_name
102
+ mailer_class.to_s.split('#').first
103
+ end
104
+ def _mailer_method
105
+ mailer_class.to_s.split('#').last
106
+ end
107
+ end
@@ -0,0 +1,53 @@
1
+ # == Schema Information
2
+ #
3
+ # Table name: apiv1_offer_messages
4
+ #
5
+ # id :integer not null, primary key
6
+ # product_id :integer
7
+ # from_company :string(255)
8
+ # sender_email :string(255)
9
+ # subject_text :string(255)
10
+ # phone_number :string(255)
11
+ # contact_person :string(255)
12
+ # company_address :string(255)
13
+ # status :string(255)
14
+ # message :text
15
+ # created_at :datetime
16
+ # updated_at :datetime
17
+ # deleted_at :datetime
18
+ #
19
+
20
+ class Apiv1::OfferMessage < ActiveRecord::Base
21
+ acts_as_paranoid
22
+ belongs_to :product,
23
+ class_name: 'Apiv1::Product'
24
+
25
+ validates :price_terms,
26
+ :product,
27
+ presence: true
28
+
29
+ def to_ember_hash
30
+ attributes.merge price_terms: price_terms,
31
+ notes: notes
32
+ end
33
+
34
+ def to_public_ember_hash
35
+ to_ember_hash.symbolize_keys.permit(:id, :product_id, :created_at, :price_terms, :status)
36
+ end
37
+
38
+ def price_terms
39
+ read_attribute :subject_text
40
+ end
41
+
42
+ def price_terms=(value)
43
+ write_attribute(:subject_text, value)
44
+ end
45
+
46
+ def notes
47
+ read_attribute :message
48
+ end
49
+
50
+ def notes=(m)
51
+ write_attribute :message, m
52
+ end
53
+ end
@@ -0,0 +1,33 @@
1
+ class Apiv1::OfferPostboy
2
+ def initialize(offer)
3
+ @offer = offer
4
+ end
5
+
6
+ def request_work!
7
+ _email_objects && _email_request.valid? && _email_request.tap(&:save!)
8
+ end
9
+
10
+ private
11
+ def _email_request
12
+ @email_request ||= Apiv1::EmailRequest.new _request_params
13
+ end
14
+ def _email_objects
15
+ @email_objects ||= _email_request.email_objects.new Apiv1::EmailObject.paramify @offer
16
+ end
17
+ def _request_params
18
+ {
19
+ to: _mail_to_with_default,
20
+ cc: _mail.cc.join(","),
21
+ bcc: _mail.bcc.join(","),
22
+ from: _mail.from.first,
23
+ subject: _mail.subject,
24
+ mailer_class: "Apiv1::NotificationsMailer#new_offer"
25
+ }
26
+ end
27
+ def _mail_to_with_default
28
+ _mail.to.first || "missing_destination@mailinator.com"
29
+ end
30
+ def _mail
31
+ @mail ||= Apiv1::NotificationsMailer.new_offer @offer
32
+ end
33
+ end
@@ -11,9 +11,11 @@
11
11
  # pic_content_type :string(255)
12
12
  # pic_file_size :integer
13
13
  # pic_updated_at :datetime
14
+ # deleted_at :datetime
14
15
  #
15
16
 
16
17
  class Apiv1::Picture < ActiveRecord::Base
18
+ acts_as_paranoid
17
19
  belongs_to :depictable,
18
20
  polymorphic: true
19
21
 
@@ -14,9 +14,11 @@
14
14
  # created_at :datetime
15
15
  # updated_at :datetime
16
16
  # showcase_order :integer
17
+ # deleted_at :datetime
17
18
  #
18
19
 
19
20
  class Apiv1::Product < ActiveRecord::Base
21
+ acts_as_paranoid
20
22
  include ::Elasticsearch::Model
21
23
  include ::Elasticsearch::Model::Callbacks
22
24
  Fields = [
@@ -35,6 +37,9 @@ class Apiv1::Product < ActiveRecord::Base
35
37
  has_many :taxons,
36
38
  through: :taxon_relationships,
37
39
  class_name: 'Apiv1::Taxon'
40
+ has_many :offers,
41
+ -> { order "#{Apiv1::OfferMessage.table_name}.created_at desc" },
42
+ class_name: 'Apiv1::OfferMessage'
38
43
  has_many :pictures,
39
44
  class_name: 'Apiv1::Picture',
40
45
  as: :depictable,
@@ -43,15 +48,27 @@ class Apiv1::Product < ActiveRecord::Base
43
48
  class_name: 'Apiv1::Attachment',
44
49
  as: :attachable,
45
50
  dependent: :destroy
51
+ has_one :ownership,
52
+ class_name: 'Apiv1::Users::ProductRelationship',
53
+ foreign_key: 'product_id'
54
+ has_one :user,
55
+ through: :ownership,
56
+ class_name: 'Admin::User'
46
57
 
47
58
  scope :belonging_to_taxon,
48
- -> (taxon) { joins(:taxons).where("#{Apiv1::Taxon.table_name}.id = ?", taxon.id) }
59
+ -> (taxon) { union_of_taxon_ids taxon.id }
60
+
61
+ scope :union_of_taxon_ids,
62
+ -> (taxon_ids) { joins(:taxon_relationships).where("#{Apiv1::Listings::TaxonRelationship.table_name}.taxon_id" => taxon_ids) }
49
63
 
50
- scope :involving_taxon_ids,
51
- -> (taxon_ids) { joins(:taxons).where("#{Apiv1::Taxon.table_name}.id" => taxon_ids) }
64
+ scope :grouped_by_count,
65
+ -> { select("#{Apiv1::Product.table_name}.*, COUNT(#{Apiv1::Product.table_name}.id) as product_count").group("#{Apiv1::Product.table_name}.id") }
52
66
 
53
- scope :involving_taxons,
54
- -> (taxons) { involving_taxon_ids taxons.map(&:id) }
67
+ scope :intersection_of_taxon_ids,
68
+ -> (taxon_ids) { taxon_ids.count > 1 ? intersection_of_two_or_more_taxon_ids(taxon_ids) : union_of_taxon_ids(taxon_ids) }
69
+
70
+ scope :intersection_of_two_or_more_taxon_ids,
71
+ -> (taxon_ids) { union_of_taxon_ids(taxon_ids).grouped_by_count.having("product_count = ?", taxon_ids.count) }
55
72
 
56
73
  scope :order_by_created_at,
57
74
  -> { order "#{self.table_name}.created_at desc"}
@@ -78,9 +95,14 @@ class Apiv1::Product < ActiveRecord::Base
78
95
  def to_ember_hash
79
96
  attributes.merge pictures: pictures.map(&:id),
80
97
  taxons: taxons.map(&:id),
81
- attachments: attachments.map(&:id)
98
+ attachments: attachments.map(&:id),
99
+ offers: offers.map(&:id),
100
+ user_id: user.try(:id)
82
101
  end
83
102
 
103
+ def rough_summary
104
+ [material, sku, quality, place, amount].select(&:present?).join(" - ")
105
+ end
84
106
  private
85
107
  def _make_permalink
86
108
  self.permalink ||= Apiv1::Permalinkifier.permalinkify material