Commit e22434f3b45cda07048e320cba3cbcfed1799a10
Exists in
master
and in
29 other branches
Merge branch 'master' into redemoinho-improvements
Showing
174 changed files
with
12846 additions
and
7416 deletions
Show diff stats
Too many changes.
To preserve performance only 100 of 174 files displayed.
AUTHORS.md
... | ... | @@ -90,15 +90,17 @@ Matheus Faria <matheus.sousa.faria@gmail.com> |
90 | 90 | Maurilio Atila <cabelotaina@gmail.com> |
91 | 91 | M for Momo <mo@rtnp.org> |
92 | 92 | Michal Čihař <michal@cihar.com> |
93 | -Michel Felipe <mfelipeof@gmail.com> | |
93 | +Michel Felipe de Oliveira Ferreira <michel.ferreira@serpro.gov.br> | |
94 | 94 | Moises Machado <moises@colivre.coop.br> |
95 | 95 | Naíla Alves <naila@colivre.coop.br> |
96 | 96 | Nanda Lopes <nanda.listas+psl@gmail.com> |
97 | 97 | Niemand Jedermann <predatorix@web.de> |
98 | +Omar Junior <omarroinuj@gmail.com> | |
98 | 99 | Parley Martins <parleypachecomartins@gmail.com> |
99 | 100 | Paulo Meirelles <paulo@softwarelivre.org> |
100 | 101 | Pedro de Lyra <pedrodelyra@gmail.com> |
101 | 102 | Pedro Leal |
103 | +Phillip Rohmberger <rohmberger@hotmail.de> | |
102 | 104 | Rafael de Souza Queiroz <querafael@live.com> |
103 | 105 | Rafael Gomes <rafaelgomes@techfree.com.br> |
104 | 106 | Rafael Martins <rmmartins@gmail.com> |
... | ... | @@ -112,6 +114,7 @@ Rodrigo Medeiros <rodrigo.mss01@gmail.com> |
112 | 114 | Rodrigo Souto <rodrigo@colivre.coop.br> |
113 | 115 | Ronny Kursawe <kursawe.ronny@googlemail.com> |
114 | 116 | Samuel R. C. Vale <srcvale@holoscopio.com> |
117 | +Simiao Carvalho <simiaosimis@gmail.com> | |
115 | 118 | Tallys Martins <tallysmartins@yahoo.com.br> |
116 | 119 | Thiago Casotti <thiago.casotti@uol.com.br> |
117 | 120 | Thiago Kairala <thiagor.kairala@gmail.com> |
... | ... | @@ -123,6 +126,7 @@ Valessio Brito <contato@valessiobrito.com.br> |
123 | 126 | Victor Costa <vfcosta@gmail.com> |
124 | 127 | Victor Hugo Alves de Carvalho <victorhugodf.ac@gmail.com> |
125 | 128 | Vinicius Cubas Brand <viniciuscb@gmail.com> |
129 | +Vitor Barbosa <vitornga15@gmail.com> | |
126 | 130 | Wilton Rodrigues <braynwilton@gmail.com> |
127 | 131 | Yann Lugrin <yann.lugrin@liquid-concept.ch> |
128 | 132 | ... | ... |
Gemfile
... | ... | @@ -10,7 +10,7 @@ gem 'RedCloth', '~> 4.2.9' |
10 | 10 | gem 'will_paginate', '~> 3.0.3' |
11 | 11 | gem 'ruby-feedparser', '~> 0.7' |
12 | 12 | gem 'daemons', '~> 1.1.5' |
13 | -gem 'thin', '~> 1.3.1' | |
13 | +gem 'unicorn', '~> 4.8' | |
14 | 14 | gem 'nokogiri', '~> 1.5.5' |
15 | 15 | gem 'rake', :require => false |
16 | 16 | gem 'rest-client', '~> 1.6.7' |
... | ... | @@ -21,6 +21,13 @@ gem 'whenever', :require => false |
21 | 21 | gem 'eita-jrails', '~> 0.9.5', require: 'jrails' |
22 | 22 | gem 'slim' |
23 | 23 | |
24 | +# API dependencies | |
25 | +gem 'grape', '~> 0.12' | |
26 | +gem 'grape-entity' | |
27 | +gem 'grape_logging' | |
28 | +gem 'rack-cors' | |
29 | +gem 'rack-contrib' | |
30 | + | |
24 | 31 | # asset pipeline |
25 | 32 | gem 'uglifier', '>= 1.0.3' |
26 | 33 | gem 'sass-rails' | ... | ... |
Rakefile
... | ... | @@ -15,4 +15,38 @@ Noosfero::Application.load_tasks |
15 | 15 | Dir.glob(pattern).sort |
16 | 16 | end.flatten.each do |taskfile| |
17 | 17 | load taskfile |
18 | +end | |
19 | + | |
20 | +# plugins' tasks | |
21 | +plugins_tasks = Dir.glob("config/plugins/*/{tasks,lib/tasks,rails/tasks}/**/*.rake").sort + | |
22 | + Dir.glob("config/plugins/*/vendor/plugins/*/{tasks,lib/tasks,rails/tasks}/**/*.rake").sort | |
23 | +plugins_tasks.each{ |ext| load ext } | |
24 | + | |
25 | + | |
26 | +desc "Print out grape routes" | |
27 | +task :grape_routes => :environment do | |
28 | + #require 'api/api.rb' | |
29 | + Noosfero::API::API.routes.each do |route| | |
30 | + puts route | |
31 | + method = route.route_method | |
32 | + path = route.route_path | |
33 | + puts " #{method} #{path}" | |
34 | + end | |
35 | +end | |
36 | + | |
37 | +# plugins' tasks | |
38 | +plugins_tasks = Dir.glob("config/plugins/*/{tasks,lib/tasks,rails/tasks}/**/*.rake").sort + | |
39 | + Dir.glob("config/plugins/*/vendor/plugins/*/{tasks,lib/tasks,rails/tasks}/**/*.rake").sort | |
40 | +plugins_tasks.each{ |ext| load ext } | |
41 | + | |
42 | + | |
43 | +desc "Print out grape routes" | |
44 | +task :grape_routes => :environment do | |
45 | + #require 'api/api.rb' | |
46 | + Noosfero::API::API.routes.each do |route| | |
47 | + puts route | |
48 | + method = route.route_method | |
49 | + path = route.route_path | |
50 | + puts " #{method} #{path}" | |
51 | + end | |
18 | 52 | end | ... | ... |
app/controllers/my_profile/cms_controller.rb
... | ... | @@ -27,20 +27,13 @@ class CmsController < MyProfileController |
27 | 27 | |
28 | 28 | helper_method :file_types |
29 | 29 | |
30 | - protect_if :only => :upload_files do |c, user, profile| | |
31 | - article_id = c.params[:parent_id] | |
32 | - (!article_id.blank? && profile.articles.find(article_id).allow_create?(user)) || | |
33 | - (user && (user.has_permission?('post_content', profile) || user.has_permission?('publish_content', profile))) | |
34 | - end | |
35 | - | |
36 | - protect_if :except => [:suggest_an_article, :set_home_page, :edit, :destroy, :publish, :publish_on_portal_community, :publish_on_communities, :search_communities_to_publish, :upload_files, :new] do |c, user, profile| | |
30 | + protect_if :except => [:suggest_an_article, :set_home_page, :edit, :destroy, :publish, :upload_files, :new] do |c, user, profile| | |
37 | 31 | user && (user.has_permission?('post_content', profile) || user.has_permission?('publish_content', profile)) |
38 | 32 | end |
39 | 33 | |
40 | - protect_if :only => :new do |c, user, profile| | |
41 | - article = profile.articles.find_by_id(c.params[:parent_id]) | |
42 | - (!article.nil? && (article.allow_create?(user) || article.parent.allow_create?(user))) || | |
43 | - (user && (user.has_permission?('post_content', profile) || user.has_permission?('publish_content', profile))) | |
34 | + protect_if :only => [:new, :upload_files] do |c, user, profile| | |
35 | + parent = profile.articles.find_by_id(c.params[:parent_id]) | |
36 | + user && user.can_post_content?(profile, parent) | |
44 | 37 | end |
45 | 38 | |
46 | 39 | protect_if :only => :destroy do |c, user, profile| |
... | ... | @@ -118,10 +111,7 @@ class CmsController < MyProfileController |
118 | 111 | end |
119 | 112 | end |
120 | 113 | |
121 | - unless @article.kind_of?(RssFeed) | |
122 | - @escaped_body = CGI::escapeHTML(@article.body || '') | |
123 | - @escaped_abstract = CGI::escapeHTML(@article.abstract || '') | |
124 | - end | |
114 | + escape_fields @article | |
125 | 115 | end |
126 | 116 | |
127 | 117 | def new |
... | ... | @@ -192,6 +182,8 @@ class CmsController < MyProfileController |
192 | 182 | end |
193 | 183 | end |
194 | 184 | |
185 | + escape_fields @article | |
186 | + | |
195 | 187 | render :action => 'edit' |
196 | 188 | end |
197 | 189 | |
... | ... | @@ -541,4 +533,10 @@ class CmsController < MyProfileController |
541 | 533 | end |
542 | 534 | end |
543 | 535 | |
536 | + def escape_fields article | |
537 | + unless article.kind_of?(RssFeed) | |
538 | + @escaped_body = CGI::escapeHTML(article.body || '') | |
539 | + @escaped_abstract = CGI::escapeHTML(article.abstract || '') | |
540 | + end | |
541 | + end | |
544 | 542 | end | ... | ... |
app/controllers/my_profile/profile_editor_controller.rb
... | ... | @@ -133,6 +133,13 @@ class ProfileEditorController < MyProfileController |
133 | 133 | redirect_to_previous_location |
134 | 134 | end |
135 | 135 | |
136 | + def reset_private_token | |
137 | + profile = environment.profiles.find(params[:id]) | |
138 | + profile.user.generate_private_token! | |
139 | + | |
140 | + redirect_to_previous_location | |
141 | + end | |
142 | + | |
136 | 143 | protected |
137 | 144 | |
138 | 145 | def redirect_to_previous_location | ... | ... |
app/controllers/public/account_controller.rb
... | ... | @@ -97,12 +97,9 @@ class AccountController < ApplicationController |
97 | 97 | @block_bot = !!session[:may_be_a_bot] |
98 | 98 | @invitation_code = params[:invitation_code] |
99 | 99 | begin |
100 | - @user = User.new(params[:user]) | |
100 | + @user = User.build(params[:user], params[:profile_data], environment) | |
101 | 101 | @user.session = session |
102 | - @user.terms_of_use = environment.terms_of_use | |
103 | - @user.environment = environment | |
104 | 102 | @terms_of_use = environment.terms_of_use |
105 | - @user.person_data = params[:profile_data] | |
106 | 103 | @user.return_to = session[:return_to] |
107 | 104 | @person = Person.new(params[:profile_data]) |
108 | 105 | @person.environment = @user.environment | ... | ... |
... | ... | @@ -0,0 +1,19 @@ |
1 | +class ApiController < PublicController | |
2 | + | |
3 | + no_design_blocks | |
4 | + | |
5 | + helper_method :endpoints | |
6 | + | |
7 | + def index | |
8 | + end | |
9 | + | |
10 | + def playground | |
11 | + end | |
12 | + | |
13 | + private | |
14 | + | |
15 | + def endpoints | |
16 | + Noosfero::API::API.endpoints(environment) | |
17 | + end | |
18 | + | |
19 | +end | ... | ... |
app/controllers/public/content_viewer_controller.rb
... | ... | @@ -8,6 +8,7 @@ class ContentViewerController < ApplicationController |
8 | 8 | helper TagsHelper |
9 | 9 | |
10 | 10 | def view_page |
11 | + | |
11 | 12 | path = get_path(params[:page], params[:format]) |
12 | 13 | |
13 | 14 | @version = params[:version].to_i |
... | ... | @@ -38,7 +39,7 @@ class ContentViewerController < ApplicationController |
38 | 39 | end |
39 | 40 | |
40 | 41 | # At this point the page will be showed |
41 | - @page.hit unless user_is_a_bot? | |
42 | + @page.hit unless user_is_a_bot? || already_visited?(@page) | |
42 | 43 | |
43 | 44 | @page = FilePresenter.for @page |
44 | 45 | |
... | ... | @@ -272,4 +273,18 @@ class ContentViewerController < ApplicationController |
272 | 273 | @comment_order = params[:comment_order].nil? ? 'oldest' : params[:comment_order] |
273 | 274 | end |
274 | 275 | |
276 | + private | |
277 | + | |
278 | + def already_visited?(element) | |
279 | + user_id = if user.nil? then -1 else current_user.id end | |
280 | + user_id = "#{user_id}_#{element.id}_#{element.class}" | |
281 | + | |
282 | + if cookies.signed[:visited] == user_id | |
283 | + return true | |
284 | + else | |
285 | + cookies.permanent.signed[:visited] = user_id | |
286 | + return false | |
287 | + end | |
288 | + end | |
289 | + | |
275 | 290 | end | ... | ... |
app/controllers/public/profile_controller.rb
... | ... | @@ -66,7 +66,10 @@ class ProfileController < PublicController |
66 | 66 | |
67 | 67 | def members |
68 | 68 | if is_cache_expired?(profile.members_cache_key(params)) |
69 | - @members = profile.members_by_name.includes(relations_to_include).paginate(:per_page => members_per_page, :page => params[:npage], :total_entries => profile.members.count) | |
69 | + sort = (params[:sort] == 'desc') ? params[:sort] : 'asc' | |
70 | + @profile_admins = profile.admins.includes(relations_to_include).order("name #{sort}").paginate(:per_page => members_per_page, :page => params[:npage]) | |
71 | + @profile_members = profile.members.includes(relations_to_include).order("name #{sort}").paginate(:per_page => members_per_page, :page => params[:npage]) | |
72 | + @profile_members_url = url_for(:controller => 'profile', :action => 'members') | |
70 | 73 | end |
71 | 74 | end |
72 | 75 | ... | ... |
app/helpers/article_helper.rb
... | ... | @@ -88,7 +88,7 @@ module ArticleHelper |
88 | 88 | content_tag( 'small', _('Who will be able to create new topics on this forum?')) + |
89 | 89 | content_tag('div', '', slider_options) + |
90 | 90 | hidden_field_tag('article[topic_creation]', article.topic_creation) + |
91 | - javascript_include_tag('topic-creation-config') | |
91 | + javascript_include_tag("#{Noosfero.root}/assets/topic-creation-config.js") | |
92 | 92 | end |
93 | 93 | |
94 | 94 | def privacity_exceptions(article, tokenized_children) | ... | ... |
app/models/add_friend.rb
... | ... | @@ -14,6 +14,9 @@ class AddFriend < Task |
14 | 14 | alias :friend :target |
15 | 15 | alias :friend= :target= |
16 | 16 | |
17 | + validates :requestor, :kind_of => { :kind => Person } | |
18 | + validates :target, :kind_of => { :kind => Person } | |
19 | + | |
17 | 20 | after_create do |task| |
18 | 21 | TaskMailer.invitation_notification(task).deliver unless task.friend |
19 | 22 | remove_from_suggestion_list(task) | ... | ... |
app/models/add_member.rb
app/models/approve_article.rb
1 | 1 | class ApproveArticle < Task |
2 | 2 | validates_presence_of :requestor_id, :target_id |
3 | 3 | |
4 | + validates :requestor, kind_of: {kind: Person} | |
5 | + validate :allowed_requestor | |
6 | + | |
7 | + def allowed_requestor | |
8 | + if target | |
9 | + if target.person? && requestor != target | |
10 | + self.errors.add(:requestor, _('You can not post articles to other users.')) | |
11 | + end | |
12 | + if target.organization? && !target.members.include?(requestor) && target.environment.portal_community != target | |
13 | + self.errors.add(:requestor, _('Only members can post articles on communities.')) | |
14 | + end | |
15 | + end | |
16 | + end | |
17 | + | |
4 | 18 | def article_title |
5 | 19 | article ? article.title : _('(The original text was removed)') |
6 | 20 | end |
7 | - | |
21 | + | |
8 | 22 | def article |
9 | 23 | Article.find_by_id data[:article_id] |
10 | 24 | end |
... | ... | @@ -128,4 +142,9 @@ class ApproveArticle < Task |
128 | 142 | message |
129 | 143 | end |
130 | 144 | |
145 | + def request_is_member_of_target | |
146 | + unless requestor.is_member_of?(target) | |
147 | + errors.add(:approve_article, N_('Requestor must be a member of target.')) | |
148 | + end | |
149 | + end | |
131 | 150 | end | ... | ... |
app/models/article.rb
... | ... | @@ -130,6 +130,8 @@ class Article < ActiveRecord::Base |
130 | 130 | {:include => 'categories_including_virtual', :conditions => { 'categories.id' => category.id }} |
131 | 131 | } |
132 | 132 | |
133 | + include TimeScopes | |
134 | + | |
133 | 135 | scope :by_range, lambda { |range| { |
134 | 136 | :conditions => [ |
135 | 137 | 'articles.published_at BETWEEN :start_date AND :end_date', { :start_date => range.first, :end_date => range.last } |
... | ... | @@ -627,6 +629,11 @@ class Article < ActiveRecord::Base |
627 | 629 | self.hits += 1 |
628 | 630 | end |
629 | 631 | |
632 | + def self.hit(articles) | |
633 | + Article.where(:id => articles.map(&:id)).update_all('hits = hits + 1') | |
634 | + articles.each { |a| a.hits += 1 } | |
635 | + end | |
636 | + | |
630 | 637 | def can_display_hits? |
631 | 638 | true |
632 | 639 | end |
... | ... | @@ -718,6 +725,11 @@ class Article < ActiveRecord::Base |
718 | 725 | person ? person.id : nil |
719 | 726 | end |
720 | 727 | |
728 | + #FIXME make this test | |
729 | + def author_custom_image(size = :icon) | |
730 | + author ? author.profile_custom_image(size) : nil | |
731 | + end | |
732 | + | |
721 | 733 | def version_license(version_number = nil) |
722 | 734 | return license if version_number.nil? |
723 | 735 | profile.environment.licenses.find_by_id(get_version(version_number).license_id) | ... | ... |
app/models/change_password.rb
app/models/comment.rb
... | ... | @@ -20,6 +20,8 @@ class Comment < ActiveRecord::Base |
20 | 20 | |
21 | 21 | scope :without_reply, :conditions => ['reply_of_id IS NULL'] |
22 | 22 | |
23 | + include TimeScopes | |
24 | + | |
23 | 25 | # unauthenticated authors: |
24 | 26 | validates_presence_of :name, :if => (lambda { |record| !record.email.blank? }) |
25 | 27 | validates_presence_of :email, :if => (lambda { |record| !record.name.blank? }) |
... | ... | @@ -67,6 +69,11 @@ class Comment < ActiveRecord::Base |
67 | 69 | author ? author.url : nil |
68 | 70 | end |
69 | 71 | |
72 | + #FIXME make this test | |
73 | + def author_custom_image(size = :icon) | |
74 | + author ? author.profile_custom_image(size) : nil | |
75 | + end | |
76 | + | |
70 | 77 | def url |
71 | 78 | article.view_url.merge(:anchor => anchor) |
72 | 79 | end | ... | ... |
app/models/create_community.rb
... | ... | @@ -3,6 +3,9 @@ class CreateCommunity < Task |
3 | 3 | validates_presence_of :requestor_id, :target_id |
4 | 4 | validates_presence_of :name |
5 | 5 | |
6 | + validates :requestor, kind_of: {kind: Person} | |
7 | + validates :target, kind_of: {kind: Environment} | |
8 | + | |
6 | 9 | alias :environment :target |
7 | 10 | alias :environment= :target= |
8 | 11 | ... | ... |
app/models/create_enterprise.rb
... | ... | @@ -27,6 +27,8 @@ class CreateEnterprise < Task |
27 | 27 | # checks for actual attributes |
28 | 28 | validates_presence_of :requestor_id, :target_id |
29 | 29 | |
30 | + validates :requestor, kind_of: {kind: Person} | |
31 | + | |
30 | 32 | # checks for admins required attributes |
31 | 33 | DATA_FIELDS.each do |attribute| |
32 | 34 | validates_presence_of attribute, :if => lambda { |obj| obj.environment.required_enterprise_fields.include?(attribute) } | ... | ... |
app/models/email_activation.rb
1 | 1 | class EmailActivation < Task |
2 | 2 | |
3 | 3 | validates_presence_of :requestor_id, :target_id |
4 | + | |
5 | + validates :requestor, kind_of: {kind: Person} | |
6 | + validates :target, kind_of: {kind: Environment} | |
7 | + | |
4 | 8 | validate :already_requested, :on => :create |
5 | 9 | |
6 | 10 | alias :environment :target |
7 | 11 | alias :person :requestor |
8 | 12 | |
9 | 13 | def already_requested |
10 | - if !self.requestor.nil? && self.requestor.user.email_activation_pending? | |
14 | + if !self.requestor.nil? && self.requestor.person? && self.requestor.user.email_activation_pending? | |
11 | 15 | self.errors.add(:base, _('You have already requested activation of your mailbox.')) |
12 | 16 | end |
13 | 17 | end | ... | ... |
app/models/enterprise_activation.rb
app/models/invitation.rb
... | ... | @@ -6,6 +6,8 @@ class Invitation < Task |
6 | 6 | |
7 | 7 | validates_presence_of :target_id, :if => Proc.new{|invite| invite.friend_email.blank?} |
8 | 8 | |
9 | + validates :requestor, kind_of: {kind: Person} | |
10 | + | |
9 | 11 | validates_presence_of :friend_email, :if => Proc.new{|invite| invite.target_id.blank?} |
10 | 12 | validates_format_of :friend_email, :with => Noosfero::Constants::EMAIL_FORMAT, :if => Proc.new{|invite| invite.target_id.blank?} |
11 | 13 | |
... | ... | @@ -34,7 +36,7 @@ class Invitation < Task |
34 | 36 | end |
35 | 37 | |
36 | 38 | def not_invite_yourself |
37 | - email = friend ? friend.user.email : friend_email | |
39 | + email = friend && friend.person? ? friend.user.email : friend_email | |
38 | 40 | if person && email && person.user.email == email |
39 | 41 | self.errors.add(:base, _("You can't invite youself")) |
40 | 42 | end |
... | ... | @@ -136,7 +138,11 @@ class Invitation < Task |
136 | 138 | end |
137 | 139 | |
138 | 140 | def environment |
139 | - self.requestor.environment | |
141 | + if self.requestor | |
142 | + self.requestor.environment | |
143 | + else | |
144 | + nil | |
145 | + end | |
140 | 146 | end |
141 | 147 | |
142 | 148 | end | ... | ... |
app/models/moderate_user_registration.rb
app/models/organization.rb
... | ... | @@ -8,6 +8,30 @@ class Organization < Profile |
8 | 8 | :display => %w[compact] |
9 | 9 | } |
10 | 10 | |
11 | + # An Organization is considered visible to a given person if one of the | |
12 | + # following conditions are met: | |
13 | + # 1) The user is an environment administrator. | |
14 | + # 2) The user is an administrator of the organization. | |
15 | + # 3) The user is a member of the organization and the organization is | |
16 | + # visible. | |
17 | + # 4) The user is not a member of the organization but the organization is | |
18 | + # visible, public and enabled. | |
19 | + def self.visible_for_person(person) | |
20 | + joins('LEFT JOIN "role_assignments" ON ("role_assignments"."resource_id" = "profiles"."id" | |
21 | + AND "role_assignments"."resource_type" = \'Profile\') OR ( | |
22 | + "role_assignments"."resource_id" = "profiles"."environment_id" AND | |
23 | + "role_assignments"."resource_type" = \'Environment\' )') | |
24 | + .joins('LEFT JOIN "roles" ON "role_assignments"."role_id" = "roles"."id"') | |
25 | + .where( | |
26 | + ['( (roles.key = ? OR roles.key = ?) AND role_assignments.accessor_type = ? AND role_assignments.accessor_id = ? ) | |
27 | + OR | |
28 | + ( ( ( role_assignments.accessor_type = ? AND role_assignments.accessor_id = ? ) OR | |
29 | + ( profiles.public_profile = ? AND profiles.enabled = ? ) ) AND | |
30 | + ( profiles.visible = ? ) )', | |
31 | + 'profile_admin', 'environment_administrator', Profile.name, person.id, | |
32 | + Profile.name, person.id, true, true, true] | |
33 | + ).uniq | |
34 | + end | |
11 | 35 | |
12 | 36 | settings_items :closed, :type => :boolean, :default => false |
13 | 37 | def closed? | ... | ... |
app/models/person.rb
... | ... | @@ -39,6 +39,19 @@ roles] } |
39 | 39 | { :select => 'DISTINCT profiles.*', :conditions => ['"profiles"."id" NOT IN (SELECT DISTINCT profiles.id FROM "profiles" INNER JOIN "friendships" ON "friendships"."person_id" = "profiles"."id" WHERE "friendships"."friend_id" IN (%s))' % resources.map(&:id)] } |
40 | 40 | } |
41 | 41 | |
42 | + scope :visible_for_person, lambda { |person| | |
43 | + joins('LEFT JOIN "role_assignments" ON | |
44 | + "role_assignments"."resource_id" = "profiles"."environment_id" AND | |
45 | + "role_assignments"."resource_type" = \'Environment\'') | |
46 | + .joins('LEFT JOIN "roles" ON "role_assignments"."role_id" = "roles"."id"') | |
47 | + .joins('LEFT JOIN "friendships" ON "friendships"."friend_id" = "profiles"."id"') | |
48 | + .where( | |
49 | + ['( roles.key = ? AND role_assignments.accessor_type = ? AND role_assignments.accessor_id = ? ) OR ( | |
50 | + ( ( friendships.person_id = ? ) OR (profiles.public_profile = ?)) AND (profiles.visible = ?) )', 'environment_administrator', Profile.name, person.id, person.id, true, true] | |
51 | + ).uniq | |
52 | + } | |
53 | + | |
54 | + | |
42 | 55 | def has_permission_with_admin?(permission, resource) |
43 | 56 | return true if resource.blank? || resource.admins.include?(self) |
44 | 57 | return true if resource.kind_of?(Profile) && resource.environment.admins.include?(self) |
... | ... | @@ -128,6 +141,11 @@ roles] } |
128 | 141 | self.tracked_notifications.exists?(activity) |
129 | 142 | end |
130 | 143 | |
144 | + def can_post_content?(profile, parent=nil) | |
145 | + (!parent.nil? && (parent.allow_create?(self))) || | |
146 | + (self.has_permission?('post_content', profile) || self.has_permission?('publish_content', profile)) | |
147 | + end | |
148 | + | |
131 | 149 | # Sets the identifier for this person. Raises an exception when called on a |
132 | 150 | # existing person (since peoples' identifiers cannot be changed) |
133 | 151 | def identifier=(value) | ... | ... |
app/models/product.rb
... | ... | @@ -56,6 +56,25 @@ class Product < ActiveRecord::Base |
56 | 56 | {:joins => :product_category, :conditions => ['categories.path LIKE ?', "%#{category.slug}%"]} if category |
57 | 57 | } |
58 | 58 | |
59 | + scope :visible_for_person, lambda { |person| | |
60 | + joins('INNER JOIN "profiles" enterprises ON enterprises."id" = "products"."profile_id"') | |
61 | + .joins('LEFT JOIN "role_assignments" ON ("role_assignments"."resource_id" = enterprises."id" | |
62 | + AND "role_assignments"."resource_type" = \'Profile\') OR ( | |
63 | + "role_assignments"."resource_id" = enterprises."environment_id" AND | |
64 | + "role_assignments"."resource_type" = \'Environment\' )') | |
65 | + .joins('LEFT JOIN "roles" ON "role_assignments"."role_id" = "roles"."id"') | |
66 | + .where( | |
67 | + ['( (roles.key = ? OR roles.key = ?) AND role_assignments.accessor_type = \'Profile\' AND role_assignments.accessor_id = ? ) | |
68 | + OR | |
69 | + ( ( ( role_assignments.accessor_type = \'Profile\' AND | |
70 | + role_assignments.accessor_id = ? ) OR | |
71 | + ( enterprises.public_profile = ? AND enterprises.enabled = ? ) ) AND | |
72 | + ( enterprises.visible = ? ) )', | |
73 | + 'profile_admin', 'environment_administrator', person.id, person.id, | |
74 | + true, true, true] | |
75 | + ).uniq | |
76 | + } | |
77 | + | |
59 | 78 | after_update :save_image |
60 | 79 | |
61 | 80 | def lat | ... | ... |
app/models/profile.rb
... | ... | @@ -102,6 +102,50 @@ class Profile < ActiveRecord::Base |
102 | 102 | } |
103 | 103 | scope :no_templates, {:conditions => {:is_template => false}} |
104 | 104 | |
105 | + # Returns a scoped object to select profiles in a given location or in a radius | |
106 | + # distance from the given location center. | |
107 | + # The parameter can be the `request.params` with the keys: | |
108 | + # * `country`: Country code string. | |
109 | + # * `state`: Second-level administrative country subdivisions. | |
110 | + # * `city`: City full name for center definition, or as set by users. | |
111 | + # * `lat`: The latitude to define the center of georef search. | |
112 | + # * `lng`: The longitude to define the center of georef search. | |
113 | + # * `distance`: Define the search radius in kilometers. | |
114 | + # NOTE: This method may return an exception object, to inform filter error. | |
115 | + # When chaining scopes, is hardly recommended you to add this as the last one, | |
116 | + # if you can't be sure about the provided parameters. | |
117 | + def self.by_location(params) | |
118 | + params = params.with_indifferent_access | |
119 | + if params[:distance].blank? | |
120 | + where_code = [] | |
121 | + [ :city, :state, :country ].each do |place| | |
122 | + unless params[place].blank? | |
123 | + # ... So we must to find on this named location | |
124 | + # TODO: convert location attrs to a table collumn | |
125 | + where_code << "(profiles.data like '%#{place}: #{params[place]}%')" | |
126 | + end | |
127 | + end | |
128 | + self.where where_code.join(' AND ') | |
129 | + else # Filter in a georef circle | |
130 | + unless params[:lat].blank? && params[:lng].blank? | |
131 | + lat, lng = [ params[:lat].to_f, params[:lng].to_f ] | |
132 | + end | |
133 | + if !lat | |
134 | + location = [ params[:city], params[:state], params[:country] ].compact.join(', ') | |
135 | + if location.blank? | |
136 | + return Exception.new ( | |
137 | + _('You must to provide `lat` and `lng`, or `city` and `country` to define the center of the search circle, defined by `distance`.') | |
138 | + ) | |
139 | + end | |
140 | + lat, lng = Noosfero::GeoRef.location_to_georef location | |
141 | + end | |
142 | + dist = params[:distance].to_f | |
143 | + self.where "#{Noosfero::GeoRef.sql_dist lat, lng} <= #{dist}" | |
144 | + end | |
145 | + end | |
146 | + | |
147 | + include TimeScopes | |
148 | + | |
105 | 149 | def members |
106 | 150 | scopes = plugins.dispatch_scopes(:organization_members, self) |
107 | 151 | scopes << Person.members_of(self) |
... | ... | @@ -913,7 +957,8 @@ private :generate_url, :url_options |
913 | 957 | |
914 | 958 | def members_cache_key(params = {}) |
915 | 959 | page = params[:npage] || '1' |
916 | - cache_key + '-members-page-' + page | |
960 | + sort = (params[:sort] == 'desc') ? params[:sort] : 'asc' | |
961 | + cache_key + '-members-page-' + page + '-' + sort | |
917 | 962 | end |
918 | 963 | |
919 | 964 | def more_recent_label |
... | ... | @@ -951,6 +996,13 @@ private :generate_url, :url_options |
951 | 996 | image.public_filename(:icon) if image.present? |
952 | 997 | end |
953 | 998 | |
999 | + #FIXME make this test | |
1000 | + def profile_custom_image(size = :icon) | |
1001 | + image_path = profile_custom_icon if size == :icon | |
1002 | + image_path ||= image.public_filename(size) if image.present? | |
1003 | + image_path | |
1004 | + end | |
1005 | + | |
954 | 1006 | def jid(options = {}) |
955 | 1007 | domain = options[:domain] || environment.default_hostname |
956 | 1008 | "#{identifier}@#{domain}" | ... | ... |
app/models/profile_activity.rb
... | ... | @@ -16,6 +16,7 @@ class ProfileActivity < ActiveRecord::Base |
16 | 16 | |
17 | 17 | def self.update_activity activity |
18 | 18 | profile_activity = ProfileActivity.where(activity_id: activity.id, activity_type: activity.class.base_class.name).first |
19 | + return unless profile_activity | |
19 | 20 | profile_activity.send :copy_timestamps |
20 | 21 | profile_activity.save! |
21 | 22 | profile_activity | ... | ... |
app/models/qualifier.rb
... | ... | @@ -11,6 +11,12 @@ class Qualifier < ActiveRecord::Base |
11 | 11 | has_many :qualifier_certifiers, :dependent => :destroy |
12 | 12 | has_many :certifiers, :through => :qualifier_certifiers |
13 | 13 | |
14 | + def used_certs | |
15 | + Certifier.joins('INNER JOIN product_qualifiers' + | |
16 | + ' ON certifiers.id = product_qualifiers.certifier_id') | |
17 | + .where(product_qualifiers: {qualifier_id: self.id}) | |
18 | + end | |
19 | + | |
14 | 20 | has_many :product_qualifiers, :dependent => :destroy |
15 | 21 | has_many :products, :through => :product_qualifiers, :source => :product |
16 | 22 | ... | ... |
app/models/scrap.rb
... | ... | @@ -22,11 +22,11 @@ class Scrap < ActiveRecord::Base |
22 | 22 | |
23 | 23 | scope :not_replies, :conditions => {:scrap_id => nil} |
24 | 24 | |
25 | - track_actions :leave_scrap, :after_create, :keep_params => ['sender.name', 'content', 'receiver.name', 'receiver.url'], :if => Proc.new{|s| s.sender != s.receiver && s.sender != s.top_root.receiver}, :custom_target => :action_tracker_target | |
25 | + track_actions :leave_scrap, :after_create, :keep_params => ['sender.name', 'content', 'receiver.name', 'receiver.url'], :if => Proc.new{|s| s.sender != s.receiver && s.sender != s.top_root.receiver}, :custom_target => :action_tracker_target, :custom_user => :sender | |
26 | 26 | |
27 | - track_actions :leave_scrap_to_self, :after_create, :keep_params => ['sender.name', 'content'], :if => Proc.new{|s| s.sender == s.receiver} | |
27 | + track_actions :leave_scrap_to_self, :after_create, :keep_params => ['sender.name', 'content'], :if => Proc.new{|s| s.sender == s.receiver}, :custom_user => :sender | |
28 | 28 | |
29 | - track_actions :reply_scrap_on_self, :after_create, :keep_params => ['sender.name', 'content'], :if => Proc.new{|s| s.sender != s.receiver && s.sender == s.top_root.receiver} | |
29 | + track_actions :reply_scrap_on_self, :after_create, :keep_params => ['sender.name', 'content'], :if => Proc.new{|s| s.sender != s.receiver && s.sender == s.top_root.receiver}, :custom_user => :sender | |
30 | 30 | |
31 | 31 | after_create :send_notification |
32 | 32 | ... | ... |
app/models/suggest_article.rb
... | ... | @@ -41,6 +41,7 @@ class SuggestArticle < Task |
41 | 41 | return type if type < Article |
42 | 42 | end |
43 | 43 | TinyMceArticle |
44 | + (article[:type] || 'TinyMceArticle').constantize | |
44 | 45 | end |
45 | 46 | |
46 | 47 | def perform |
... | ... | @@ -91,4 +92,5 @@ class SuggestArticle < Task |
91 | 92 | def after_ham! |
92 | 93 | self.delay.marked_as_ham |
93 | 94 | end |
95 | + | |
94 | 96 | end | ... | ... |
app/models/task.rb
... | ... | @@ -116,6 +116,51 @@ class Task < ActiveRecord::Base |
116 | 116 | end |
117 | 117 | end |
118 | 118 | |
119 | + class KindOfValidator < ActiveModel::EachValidator | |
120 | + def validate_each(record, attribute, value) | |
121 | + environment = record.environment || Environment.default | |
122 | + klass = options[:kind] | |
123 | + group = klass.to_s.downcase.pluralize | |
124 | + id = attribute.to_s + "_id" | |
125 | + if environment.respond_to?(group) | |
126 | + attrb = value || environment.send(group).find_by_id(record.send(id)) | |
127 | + else | |
128 | + attrb = value || klass.find_by_id(record.send(id)) | |
129 | + end | |
130 | + if attrb.respond_to?(klass.to_s.downcase + "?") | |
131 | + unless attrb.send(klass.to_s.downcase + "?") | |
132 | + record.errors[attribute] << (options[:message] || "should be "+ klass.to_s.downcase) | |
133 | + end | |
134 | + else | |
135 | + unless attrb.class == klass | |
136 | + record.errors[attribute] << (options[:message] || "should be "+ klass.to_s.downcase) | |
137 | + end | |
138 | + end | |
139 | + end | |
140 | + end | |
141 | + | |
142 | + def requestor_is_of_kind(klass, message = nil) | |
143 | + error_message = message ||= _('Task requestor must be '+klass.to_s.downcase) | |
144 | + group = klass.to_s.downcase.pluralize | |
145 | + if environment.respond_to?(group) and requestor_id | |
146 | + requestor = requestor ||= environment.send(klass.to_s.downcase.pluralize).find_by_id(requestor_id) | |
147 | + end | |
148 | + unless requestor.class == klass | |
149 | + errors.add(error_message) | |
150 | + end | |
151 | + end | |
152 | + | |
153 | + def target_is_of_kind(klass, message = nil) | |
154 | + error_message = message ||= _('Task target must be '+klass.to_s.downcase) | |
155 | + group = klass.to_s.downcase.pluralize | |
156 | + if environment.respond_to?(group) and target_id | |
157 | + target = target ||= environment.send(klass.to_s.downcase.pluralize).find_by_id(target_id) | |
158 | + end | |
159 | + unless target.class == klass | |
160 | + errors.add(error_message) | |
161 | + end | |
162 | + end | |
163 | + | |
119 | 164 | def close(status, closed_by) |
120 | 165 | self.status = status |
121 | 166 | self.end_date = Time.now |
... | ... | @@ -269,6 +314,19 @@ class Task < ActiveRecord::Base |
269 | 314 | |
270 | 315 | include Spammable |
271 | 316 | |
317 | + #FIXME make this test | |
318 | + def display_to?(user = nil) | |
319 | + return true if self.target == user | |
320 | + return false if !self.target.kind_of?(Environment) && self.target.person? | |
321 | + | |
322 | + if self.target.kind_of?(Environment) | |
323 | + user.is_admin?(self.target) | |
324 | + else | |
325 | + self.target.members.by_role(self.target.roles.reject {|r| !r.has_permission?('perform_task')}).include?(user) | |
326 | + end | |
327 | + end | |
328 | + | |
329 | + | |
272 | 330 | protected |
273 | 331 | |
274 | 332 | # This method must be overrided in subclasses, and its implementation must do | ... | ... |
app/models/user.rb
1 | 1 | require 'digest/sha1' |
2 | 2 | require 'user_activation_job' |
3 | +require 'securerandom' | |
3 | 4 | |
4 | 5 | # User models the system users, and is generated by the acts_as_authenticated |
5 | 6 | # Rails generator. |
... | ... | @@ -41,6 +42,14 @@ class User < ActiveRecord::Base |
41 | 42 | alias_method_chain :human_attribute_name, :customization |
42 | 43 | end |
43 | 44 | |
45 | + def self.build(user_data, person_data, environment) | |
46 | + user = User.new(user_data) | |
47 | + user.terms_of_use = environment.terms_of_use | |
48 | + user.environment = environment | |
49 | + user.person_data = person_data | |
50 | + user | |
51 | + end | |
52 | + | |
44 | 53 | before_create do |user| |
45 | 54 | if user.environment.nil? |
46 | 55 | user.environment = Environment.default |
... | ... | @@ -116,6 +125,7 @@ class User < ActiveRecord::Base |
116 | 125 | validates_uniqueness_of :login, :email, :case_sensitive => false, :scope => :environment_id |
117 | 126 | before_save :encrypt_password |
118 | 127 | before_save :normalize_email, if: proc{ |u| u.email.present? } |
128 | + before_save :generate_private_token_if_not_exist | |
119 | 129 | validates_format_of :email, :with => Noosfero::Constants::EMAIL_FORMAT, :if => (lambda {|user| !user.email.blank?}) |
120 | 130 | |
121 | 131 | validates_inclusion_of :terms_accepted, :in => [ '1' ], :if => lambda { |u| ! u.terms_of_use.blank? }, :message => N_('{fn} must be checked in order to signup.').fix_i18n |
... | ... | @@ -131,13 +141,39 @@ class User < ActiveRecord::Base |
131 | 141 | |
132 | 142 | u = self.has_login?(login, login, environment.id) |
133 | 143 | u = u.first if u.is_a?(ActiveRecord::Relation) |
134 | - u && u.authenticated?(password) ? u : nil | |
144 | + | |
145 | + if u && u.authenticated?(password) | |
146 | + u.generate_private_token_if_not_exist | |
147 | + return u | |
148 | + end | |
149 | + return nil | |
135 | 150 | end |
136 | 151 | |
137 | 152 | def register_login |
138 | 153 | self.update_attribute :last_login_at, Time.now |
139 | 154 | end |
140 | 155 | |
156 | + def generate_private_token | |
157 | + self.private_token = SecureRandom.hex | |
158 | + self.private_token_generated_at = DateTime.now | |
159 | + end | |
160 | + | |
161 | + def generate_private_token! | |
162 | + self.generate_private_token | |
163 | + save(:validate => false) | |
164 | + end | |
165 | + | |
166 | + def generate_private_token_if_not_exist | |
167 | + unless self.private_token | |
168 | + self.generate_private_token | |
169 | + end | |
170 | + end | |
171 | + | |
172 | + TOKEN_VALIDITY = 2.weeks | |
173 | + def private_token_expired? | |
174 | + self.private_token.nil? || (self.private_token_generated_at + TOKEN_VALIDITY < DateTime.now) | |
175 | + end | |
176 | + | |
141 | 177 | # Activates the user in the database. |
142 | 178 | def activate |
143 | 179 | return false unless self.person | ... | ... |
... | ... | @@ -0,0 +1,20 @@ |
1 | +<h1>EndPoints</h1> | |
2 | + | |
3 | +<div style="float: right"> | |
4 | +<%= s_('api-playground|Try the %s') % link_to('API Playground', '/api/playground') %> | |
5 | +</div> | |
6 | + | |
7 | +<%= endpoints.map do |endpoint| | |
8 | + app = endpoint.options[:app].to_s | |
9 | + unless app.blank? | |
10 | + content_tag(:h2, app.split('::').last.to_s, title: app) + | |
11 | + (content_tag :ul do | |
12 | + endpoint.routes.map do |route| | |
13 | + content_tag :li do | |
14 | + content_tag(:strong, route.route_method) + ' ' + | |
15 | + route.route_path.gsub(':version', content_tag(:b, route.route_version)) | |
16 | + end | |
17 | + end.join "\n" | |
18 | + end) | |
19 | + end | |
20 | +end.join "\n" %> | ... | ... |
... | ... | @@ -0,0 +1,36 @@ |
1 | +<h1>API Playground</h1> | |
2 | + | |
3 | +<script> | |
4 | +var endpoints = <%= | |
5 | +endpoints.map do |endpoint| | |
6 | + app = endpoint.options[:app].to_s | |
7 | + unless app.blank? | |
8 | + endpoint.routes.map do |route| | |
9 | + { | |
10 | + method: route.route_method, | |
11 | + path: route.route_path.gsub(':version', route.route_version).split('(').first | |
12 | + } | |
13 | + end | |
14 | + end | |
15 | +end.flatten.compact.sort{|a,b| | |
16 | + a[:path]=='/api/v1/login' ? -1 : | |
17 | + b[:path]=='/api/v1/login' ? 1 : | |
18 | + a[:path] <=> b[:path] | |
19 | +}.to_json %>; | |
20 | +</script> | |
21 | + | |
22 | +<form id="api-form"> | |
23 | + <label id="endpoint">Endpoint: | |
24 | + <select name="endpoint" onchange="playground.selEndpoint()"></select> | |
25 | + </label> | |
26 | + <label id="api-token">Token: | |
27 | + <%= tag :input, size: 32, placeholder: _('Use the login endpoint') %> | |
28 | + </label> | |
29 | +</form> | |
30 | +<div id="playground-ctrl"> | |
31 | + <button onclick="playground.addFormParam()"><%=_('Add parameter')%></button> | |
32 | + <button onclick="playground.run()" style="font-weight:bold"> <%=_('Run')%> </button> | |
33 | +</div> | |
34 | +<pre id="api-response" class="empty"></pre> | |
35 | + | |
36 | +<script src="/javascripts/api-playground.js"></script> | ... | ... |
app/views/cms/edit.html.erb
app/views/content_viewer/_article_toolbar.html.erb
... | ... | @@ -29,10 +29,10 @@ |
29 | 29 | <%= expirable_button @page, :locale, content, url %> |
30 | 30 | <% end %> |
31 | 31 | |
32 | - <%= modal_button(:new, label_for_new_article(@page), profile.admin_url.merge(:controller => 'cms', :action => 'new', :parent_id => (@page.folder? ? @page : (@page.parent.nil? ? nil : @page.parent)))) unless remove_content_button(:new, @page) %> | |
32 | + <%= modal_button(:new, label_for_new_article(@page), profile.admin_url.merge(:controller => 'cms', :action => 'new', :parent_id => (@page.folder? ? @page : @page.parent))) unless remove_content_button(:new, @page) %> | |
33 | 33 | |
34 | 34 | <% content = content_tag('span', label_for_clone_article(@page)) %> |
35 | - <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'new', :id => @page.id, :clone => true, :type => @page.class }) %> | |
35 | + <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'new', :id => @page.id, :clone => true, :parent_id => (@page.folder? ? @page : @page.parent), :type => @page.class}) %> | |
36 | 36 | <%= expirable_button @page, :clone, content, url %> |
37 | 37 | <% end %> |
38 | 38 | ... | ... |
... | ... | @@ -0,0 +1,16 @@ |
1 | +<div id="profile-members-sort-options"> | |
2 | + <%= label_tag("sort-#{role}", _("Sort by:")) %> | |
3 | + <%= select_tag("sort-#{role}", | |
4 | + options_for_select([ | |
5 | + [_("Name A-Z"), 'asc'], | |
6 | + [_("Name Z-A"), 'desc'], | |
7 | + ], :selected => params[:sort]) | |
8 | + ) %> | |
9 | +</div> | |
10 | +<ul class="profile-list-<%= role %>" > | |
11 | + <% users.each do |u| %> | |
12 | + <%= profile_image_link(u) %> | |
13 | + <% end %> | |
14 | +</ul> | |
15 | + | |
16 | +<%= pagination_links users, :param_name => 'npage' %> | ... | ... |
app/views/profile/members.html.erb
1 | 1 | <div class="common-profile-list-block"> |
2 | 2 | |
3 | -<h1><%= _("%s's members") % profile.name %></h1> | |
3 | +<h1><%= _("Members (%d)") % @profile_members.total_entries %></h1> | |
4 | +<h2 class="community-name"><%= profile.name %></h2> | |
4 | 5 | |
5 | 6 | <% cache_timeout(profile.members_cache_key(params), 4.hours) do %> |
6 | - <ul class='profile-list'> | |
7 | - <% @members.each do |member| %> | |
8 | - <%= profile_image_link(member) %> | |
9 | - <% end %> | |
10 | - </ul> | |
7 | + <div class="profile-members-tabs-container"> | |
8 | + <% tabs = [] %> | |
9 | + | |
10 | + <% div_members = content_tag :div, :class => "profile-members" do | |
11 | + render :partial => 'profile_members_list', | |
12 | + :locals => { | |
13 | + :users => @profile_members, | |
14 | + :role => "members" | |
15 | + } | |
16 | + end %> | |
17 | + | |
18 | + <% tabs << {:title => _("%d Members") % @profile_members.total_entries, | |
19 | + :id => "members-tab", | |
20 | + :content => div_members | |
21 | + } %> | |
11 | 22 | |
12 | - <div id='pagination-profiles'> | |
13 | - <%= pagination_links @members, :param_name => 'npage' %> | |
14 | - </div> | |
23 | + <% div_admins = content_tag :div, :class => "profile-admins" do | |
24 | + render :partial => 'profile_members_list', | |
25 | + :locals => { | |
26 | + :users => @profile_admins, | |
27 | + :role => "admins" | |
28 | + } | |
29 | + end %> | |
30 | + | |
31 | + <% tabs << {:title => _("%d Administrators") % @profile_admins.total_entries, | |
32 | + :id => "admins-tab", | |
33 | + :content => div_admins | |
34 | + } %> | |
35 | + | |
36 | + <%= render_tabs(tabs) %> | |
37 | + </div><!-- end of class="profile-members-tabs-container" --> | |
15 | 38 | <% end %> |
16 | 39 | |
17 | 40 | <% button_bar do %> |
... | ... | @@ -26,4 +49,7 @@ |
26 | 49 | <% end %> |
27 | 50 | <% end %> |
28 | 51 | |
29 | -</div><!-- fim class="common-profile-list-block" --> | |
52 | +<%= hidden_field_tag "profile_url", @profile_members_url %> | |
53 | +</div><!-- end of class="common-profile-list-block" --> | |
54 | + | |
55 | +<%= javascript_include_tag "members_page.js" %> | ... | ... |
app/views/profile_editor/_person.html.erb
... | ... | @@ -18,6 +18,14 @@ |
18 | 18 | |
19 | 19 | <%= @plugins.dispatch(:profile_info_extra_contents).collect { |content| instance_exec(&content) }.join("") %> |
20 | 20 | |
21 | + <div class="formfieldline"> | |
22 | + <%= label_tag("private_token", _("Private Token")) %> | |
23 | + <div class="formfield type-text"> | |
24 | + <%= text_field_tag("a", @profile.user.private_token, :size => 30) %> | |
25 | + </div> | |
26 | + </div> | |
27 | + <%= link_to("Reset token", {:controller => :profile_editor, :action => :reset_private_token, :id => @profile.id}, :class => "button with-text") %> | |
28 | + | |
21 | 29 | <%= render :partial => 'person_form', :locals => {:f => f} %> |
22 | 30 | |
23 | 31 | <h2><%= _('Notification options') %></h2> | ... | ... |
app/views/tasks/_suggest_article_accept_details.html.erb
... | ... | @@ -14,6 +14,5 @@ |
14 | 14 | <%= labelled_form_field(_('Highlight this article'), a.check_box(:highlighted)) %> |
15 | 15 | |
16 | 16 | <%= a.hidden_field(:type) %> |
17 | - | |
18 | 17 | <%= render :partial => 'shared/lead_and_body', :locals => {:tiny_mce => true, :f => a, :lead_id => task.id} %> |
19 | 18 | <% end %> | ... | ... |
config.ru
1 | 1 | # This file is used by Rack-based servers to start the application. |
2 | 2 | |
3 | 3 | require ::File.expand_path('../config/environment', __FILE__) |
4 | -run Noosfero::Application | |
4 | + | |
5 | +use Rack::Cors do | |
6 | + allow do | |
7 | + origins '*' | |
8 | + resource '/api/*', :headers => :any, :methods => [:get, :post] | |
9 | + end | |
10 | +end | |
11 | + | |
12 | +rails_app = Rack::Builder.new do | |
13 | + if ENV['RAILS_RELATIVE_URL_ROOT'] | |
14 | + map ENV['RAILS_RELATIVE_URL_ROOT'] do | |
15 | + run Noosfero::Application | |
16 | + end | |
17 | + else | |
18 | + run Noosfero::Application | |
19 | + end | |
20 | +end | |
21 | + | |
22 | +run Rack::Cascade.new([ | |
23 | + Noosfero::API::API, | |
24 | + rails_app | |
25 | +]) | ... | ... |
config/initializers/session.rb
config/noosfero.yml.dist
... | ... | @@ -11,7 +11,14 @@ development: |
11 | 11 | max_upload_size: 5MB |
12 | 12 | hours_until_user_activation_check: 72 |
13 | 13 | exclude_profile_identifier_pattern: index(\..*)?|home(\..*)? |
14 | + api_recaptcha_site_key: '6LdsWAcTAAAAAChTUUD6yu9fCDhdIZzNd7F53zf-' | |
15 | + api_recaptcha_private_key: '6LdsWAcTAAAAAB6maB_HalVyCc4asDAxPxloIMvY' | |
16 | + api_recaptcha_verify_uri: 'https://www.google.com/recaptcha/api/siteverify' | |
14 | 17 | |
15 | 18 | test: |
16 | 19 | |
17 | 20 | production: |
21 | + api_recaptcha_site_key: '6LcLPAcTAAAAAKsd0bxY_TArhD_A7OL19SRCW7_i' | |
22 | + api_recaptcha_private_key: '6LcLPAcTAAAAAE36SN1M2w1I7Hn8upwXYZ_YQZ5-' | |
23 | + api_recaptcha_verify_uri: 'https://www.google.com/recaptcha/api/siteverify' | |
24 | + | |
18 | 25 | \ No newline at end of file | ... | ... |
config/routes.rb
... | ... | @@ -22,6 +22,7 @@ Noosfero::Application.routes.draw do |
22 | 22 | root :to => 'home#index', :constraints => EnvironmentDomainConstraint.new |
23 | 23 | |
24 | 24 | match 'site(/:action)', :controller => 'home' |
25 | + match 'api(/:action)', :controller => 'api' | |
25 | 26 | |
26 | 27 | match 'images(/*stuff)' => 'not_found#nothing' |
27 | 28 | match 'stylesheets(/*stuff)' => 'not_found#nothing' | ... | ... |
config/thin.yml.dist
db/migrate/20150223180807_add_private_token_info_to_users.rb
0 → 100644
... | ... | @@ -0,0 +1,11 @@ |
1 | +class AddPrivateTokenInfoToUsers < ActiveRecord::Migration | |
2 | + def self.up | |
3 | + add_column :users, :private_token, :string | |
4 | + add_column :users, :private_token_generated_at, :datetime | |
5 | + end | |
6 | + | |
7 | + def self.down | |
8 | + remove_column :users, :private_token | |
9 | + remove_column :users, :private_token_generated_at | |
10 | + end | |
11 | +end | ... | ... |
db/schema.rb
... | ... | @@ -754,24 +754,26 @@ ActiveRecord::Schema.define(:version => 20150722042714) do |
754 | 754 | create_table "users", :force => true do |t| |
755 | 755 | t.string "login" |
756 | 756 | t.string "email" |
757 | - t.string "crypted_password", :limit => 40 | |
758 | - t.string "salt", :limit => 40 | |
757 | + t.string "crypted_password", :limit => 40 | |
758 | + t.string "salt", :limit => 40 | |
759 | 759 | t.datetime "created_at" |
760 | 760 | t.datetime "updated_at" |
761 | 761 | t.string "remember_token" |
762 | 762 | t.datetime "remember_token_expires_at" |
763 | 763 | t.text "terms_of_use" |
764 | - t.string "terms_accepted", :limit => 1 | |
764 | + t.string "terms_accepted", :limit => 1 | |
765 | 765 | t.integer "environment_id" |
766 | 766 | t.string "password_type" |
767 | - t.boolean "enable_email", :default => false | |
768 | - t.string "last_chat_status", :default => "" | |
769 | - t.string "chat_status", :default => "" | |
767 | + t.boolean "enable_email", :default => false | |
768 | + t.string "last_chat_status", :default => "" | |
769 | + t.string "chat_status", :default => "" | |
770 | 770 | t.datetime "chat_status_at" |
771 | - t.string "activation_code", :limit => 40 | |
771 | + t.string "activation_code", :limit => 40 | |
772 | 772 | t.datetime "activated_at" |
773 | 773 | t.string "return_to" |
774 | 774 | t.datetime "last_login_at" |
775 | + t.string "private_token" | |
776 | + t.datetime "private_token_generated_at" | |
775 | 777 | end |
776 | 778 | |
777 | 779 | create_table "validation_infos", :force => true do |t| | ... | ... |
debian/apache2/conf.d/noosfero-cluster.conf
debian/apache2/virtualhost.conf
... | ... | @@ -15,7 +15,11 @@ RewriteRule ^/$ /index.html [QSA] |
15 | 15 | RewriteRule ^([^.]+)$ $1.html [QSA] |
16 | 16 | |
17 | 17 | RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f |
18 | -RewriteRule ^.*$ balancer://noosfero%{REQUEST_URI} [P,QSA,L] | |
18 | +RewriteRule ^.*$ http://127.0.0.1:50000%{REQUEST_URI} [P,QSA,L] | |
19 | +<Proxy http://127.0.0.1:50000> | |
20 | + Order Allow,Deny | |
21 | + Allow from All | |
22 | +</Proxy> | |
19 | 23 | |
20 | 24 | ErrorDocument 503 /503.html |
21 | 25 | ... | ... |
debian/changelog
debian/control
... | ... | @@ -49,12 +49,17 @@ Depends: adduser, |
49 | 49 | ruby-feedparser, |
50 | 50 | ruby-feedparser (>= 0.7-3~), |
51 | 51 | ruby-gettext, |
52 | + ruby-grape, | |
53 | + ruby-grape-entity, | |
54 | + ruby-grape-logging, | |
52 | 55 | ruby-memcache-client, |
53 | 56 | ruby-minitest, |
54 | 57 | ruby-nokogiri, |
55 | 58 | ruby-pg, |
56 | 59 | ruby-progressbar, |
57 | 60 | ruby-rack (>= 1.4.5-2~), |
61 | + ruby-rack-contrib, | |
62 | + ruby-rack-cors, | |
58 | 63 | ruby-rails-autolink, |
59 | 64 | ruby-redcloth, |
60 | 65 | ruby-rest-client, |
... | ... | @@ -65,7 +70,7 @@ Depends: adduser, |
65 | 70 | ruby-whenever, |
66 | 71 | ruby-will-paginate (>= 2.3.12-1~), |
67 | 72 | tango-icon-theme, |
68 | - thin, | |
73 | + unicorn (>= 4.8), | |
69 | 74 | ${misc:Depends} |
70 | 75 | Recommends: postgresql, postgresql-client |
71 | 76 | Description: free web-based platform for social networks | ... | ... |
debian/noosfero-apache.install
debian/noosfero.install
... | ... | @@ -19,7 +19,7 @@ debian/noosfero-check-dbconfig usr/sbin |
19 | 19 | debian/noosfero-console usr/sbin |
20 | 20 | debian/noosfero-runner usr/sbin |
21 | 21 | debian/noosfero.yml etc/noosfero |
22 | -debian/thin.yml etc/noosfero | |
22 | +debian/unicorn.rb etc/noosfero | |
23 | 23 | doc usr/share/noosfero |
24 | 24 | doc/noosfero usr/share/noosfero/doc |
25 | 25 | etc/init.d/noosfero etc/init.d | ... | ... |
debian/noosfero.links
1 | 1 | var/tmp/noosfero usr/share/noosfero/tmp |
2 | 2 | var/log/noosfero usr/share/noosfero/log |
3 | 3 | etc/noosfero/database.yml usr/share/noosfero/config/database.yml |
4 | -etc/noosfero/thin.yml usr/share/noosfero/config/thin.yml | |
4 | +etc/noosfero/unicorn.rb usr/share/noosfero/config/unicorn.rb | |
5 | 5 | etc/noosfero/plugins usr/share/noosfero/config/plugins |
6 | 6 | etc/noosfero/noosfero.yml usr/share/noosfero/config/noosfero.yml |
7 | 7 | etc/noosfero/local.rb usr/share/noosfero/config/local.rb | ... | ... |
debian/noosfero.postinst
... | ... | @@ -10,23 +10,23 @@ makedir() { |
10 | 10 | } |
11 | 11 | |
12 | 12 | |
13 | -# migrate mongrel configuration to thin | |
14 | -mongrel_config=/etc/noosfero/mongrel_cluster.yml | |
13 | +# migrate thin configuration to unicorn | |
14 | +unicorn_config=/etc/noosfero/unicorn.rb | |
15 | 15 | thin_config=/etc/noosfero/thin.yml |
16 | -mongrel_orig_sha1=602c642c03f79349969c08330112a6f29d4c32aa | |
17 | -if [ -r $mongrel_config ]; then | |
18 | - mongrel_sha1=$(sha1sum $mongrel_config | awk '{print $1}') | |
19 | - if [ "$mongrel_sha1" != "$mongrel_orig_sha1" ]; then | |
20 | - port=$(awk '{ if ($1 == "port:") { print($2) } }' $mongrel_config) | |
21 | - servers=$(awk '{ if ($1 == "servers:") { print($2) } }' $mongrel_config) | |
16 | +thin_orig_sha1=47cee6728a7896a13f4d66544086ab88b02e89a7 | |
17 | +if [ -r $thin_config ]; then | |
18 | + thin_sha1=$(sha1sum $thin_config | awk '{print $1}') | |
19 | + if [ "$thin_sha1" != "$thin_orig_sha1" ]; then | |
20 | + port=$(awk '{ if ($1 == "port:") { print($2) } }' $thin_config) | |
21 | + servers=$(awk '{ if ($1 == "servers:") { print($2) } }' $thin_config) | |
22 | 22 | if test -n "$port" && test "$port" -ne 50000 || test -n "$servers" && test "$servers" -ne 1 ; then |
23 | - # mongrel configuration was changed; update thin configuration | |
23 | + # thin configuration was changed; update unicorn configuration | |
24 | 24 | # accordingly |
25 | - sed -i -e "s/port: .*/port: $port/" $thin_config | |
26 | - sed -i -e "s/servers: .*/servers: $servers/" $thin_config | |
25 | + sed -i -e "s/listen.*/listen '127.0.0.1:$port'/" $unicorn_config | |
26 | + sed -i -e "s/worker_processes.*/worker_processes $servers/" $unicorn_config | |
27 | 27 | fi |
28 | 28 | fi |
29 | - mv $mongrel_config $mongrel_config.bak | |
29 | + mv $thin_config $thin_config.bak | |
30 | 30 | fi |
31 | 31 | |
32 | 32 | # create user noosfero in a portable way, while creating the log directory. | ... | ... |
debian/thin.yml
debian/update-noosfero-apache
... | ... | @@ -2,20 +2,9 @@ |
2 | 2 | |
3 | 3 | set -e |
4 | 4 | |
5 | -# automatically update configuration, but if package noosfero is also installed | |
5 | +# automatically update configuration, but only if package noosfero is also | |
6 | +# installed | |
6 | 7 | if test -x /usr/share/noosfero/script/apacheconf; then |
7 | - datadir="/etc/noosfero/apache" | |
8 | - mongrel_file="$datadir/mongrel.conf" | |
9 | - cluster_file="$datadir/cluster.conf" | |
10 | - if test -e "$cluster_file"; then | |
11 | - echo "Overwriting $cluster_file ..." | |
12 | - fi | |
13 | - /usr/share/noosfero/script/apacheconf thin > "$cluster_file" | |
14 | - if test -e "$mongrel_file"; then | |
15 | - echo "Moving existing $mongrel_file away ..." | |
16 | - mv "$mongrel_file" "$mongrel_file".bak | |
17 | - (cd $datadir && ln -sf cluster.conf mongrel.conf) | |
18 | - fi | |
19 | 8 | |
20 | 9 | apache_site='/etc/apache2/sites-available/noosfero' |
21 | 10 | if ! test -e "$apache_site"; then | ... | ... |
gitignore.example
lib/authenticated_system.rb
... | ... | @@ -2,17 +2,21 @@ module AuthenticatedSystem |
2 | 2 | |
3 | 3 | protected |
4 | 4 | |
5 | - # See impl. from http://stackoverflow.com/a/2513456/670229 | |
6 | - def self.included? base | |
7 | - base.around_filter do | |
5 | + def self.included base | |
6 | + # See impl. from http://stackoverflow.com/a/2513456/670229 | |
7 | + base.around_filter do |&block| | |
8 | 8 | begin |
9 | 9 | User.current = current_user |
10 | - yield | |
10 | + block.call | |
11 | 11 | ensure |
12 | 12 | # to address the thread variable leak issues in Puma/Thin webserver |
13 | 13 | User.current = nil |
14 | 14 | end |
15 | 15 | end |
16 | + | |
17 | + # Inclusion hook to make #current_user and #logged_in? | |
18 | + # available as ActionView helper methods. | |
19 | + base.send :helper_method, :current_user, :logged_in? | |
16 | 20 | end |
17 | 21 | |
18 | 22 | # Returns true or false if the user is logged in. |
... | ... | @@ -134,12 +138,6 @@ module AuthenticatedSystem |
134 | 138 | end |
135 | 139 | end |
136 | 140 | |
137 | - # Inclusion hook to make #current_user and #logged_in? | |
138 | - # available as ActionView helper methods. | |
139 | - def self.included(base) | |
140 | - base.send :helper_method, :current_user, :logged_in? | |
141 | - end | |
142 | - | |
143 | 141 | # When called with before_filter :login_from_cookie will check for an :auth_token |
144 | 142 | # cookie and log the user back in if apropriate |
145 | 143 | def login_from_cookie | ... | ... |
... | ... | @@ -0,0 +1,82 @@ |
1 | +require 'grape' | |
2 | +#require 'rack/contrib' | |
3 | + | |
4 | +Dir["#{Rails.root}/lib/noosfero/api/*.rb"].each {|file| require file unless file =~ /api\.rb/} | |
5 | +module Noosfero | |
6 | + module API | |
7 | + class API < Grape::API | |
8 | + use Rack::JSONP | |
9 | + | |
10 | + logger = Logger.new(File.join(Rails.root, 'log', "#{ENV['RAILS_ENV'] || 'production'}_api.log")) | |
11 | + logger.formatter = GrapeLogging::Formatters::Default.new | |
12 | + #use GrapeLogging::Middleware::RequestLogger, { logger: logger } | |
13 | + | |
14 | + rescue_from :all do |e| | |
15 | + logger.error e | |
16 | + end | |
17 | + | |
18 | + @@NOOSFERO_CONF = nil | |
19 | + | |
20 | + def self.NOOSFERO_CONF | |
21 | + if @@NOOSFERO_CONF | |
22 | + @@NOOSFERO_CONF | |
23 | + else | |
24 | + file = Rails.root.join('config', 'noosfero.yml') | |
25 | + @@NOOSFERO_CONF = File.exists?(file) ? YAML.load_file(file)[Rails.env] || {} : {} | |
26 | + end | |
27 | + end | |
28 | + | |
29 | + before { setup_multitenancy } | |
30 | + before { detect_stuff_by_domain } | |
31 | + before { filter_disabled_plugins_endpoints } | |
32 | + after { set_session_cookie } | |
33 | + | |
34 | + version 'v1' | |
35 | + prefix "api" | |
36 | + format :json | |
37 | + content_type :txt, "text/plain" | |
38 | + | |
39 | + helpers APIHelpers | |
40 | + | |
41 | + mount V1::Articles | |
42 | + mount V1::Comments | |
43 | + mount V1::Communities | |
44 | + mount V1::People | |
45 | + mount V1::Enterprises | |
46 | + mount V1::Categories | |
47 | + mount V1::Tasks | |
48 | + mount Session | |
49 | + | |
50 | + # hook point which allow plugins to add Grape::API extensions to API::API | |
51 | + #finds for plugins which has api mount points classes defined (the class should extends Grape::API) | |
52 | + @plugins = Noosfero::Plugin.all.map { |p| p.constantize } | |
53 | + @plugins.each do |klass| | |
54 | + if klass.public_methods.include? :api_mount_points | |
55 | + klass.api_mount_points.each do |mount_class| | |
56 | + mount mount_class if mount_class && ( mount_class < Grape::API ) | |
57 | + end | |
58 | + end | |
59 | + end | |
60 | + | |
61 | + def self.endpoint_unavailable?(endpoint, environment) | |
62 | + api_class = endpoint.options[:app] || endpoint.options[:for] | |
63 | + if api_class.present? | |
64 | + klass = api_class.name.deconstantize.constantize | |
65 | + return klass < Noosfero::Plugin && !environment.plugin_enabled?(klass) | |
66 | + end | |
67 | + end | |
68 | + | |
69 | + class << self | |
70 | + def endpoints_with_plugins(environment = nil) | |
71 | + if environment.present? | |
72 | + cloned_endpoints = endpoints_without_plugins.dup | |
73 | + cloned_endpoints.delete_if { |endpoint| endpoint_unavailable?(endpoint, environment) } | |
74 | + else | |
75 | + endpoints_without_plugins | |
76 | + end | |
77 | + end | |
78 | + alias_method_chain :endpoints, :plugins | |
79 | + end | |
80 | + end | |
81 | + end | |
82 | +end | ... | ... |
... | ... | @@ -0,0 +1,121 @@ |
1 | +module Noosfero | |
2 | + module API | |
3 | + module Entities | |
4 | + | |
5 | + Entity.format_with :timestamp do |date| | |
6 | + date.strftime('%Y/%m/%d %H:%M:%S') if date | |
7 | + end | |
8 | + | |
9 | + class Image < Entity | |
10 | + root 'images', 'image' | |
11 | + | |
12 | + expose :url do |image, options| | |
13 | + image.public_filename | |
14 | + end | |
15 | + | |
16 | + expose :icon_url do |image, options| | |
17 | + image.public_filename(:icon) | |
18 | + end | |
19 | + | |
20 | + expose :minor_url do |image, options| | |
21 | + image.public_filename(:minor) | |
22 | + end | |
23 | + | |
24 | + expose :portrait_url do |image, options| | |
25 | + image.public_filename(:portrait) | |
26 | + end | |
27 | + | |
28 | + expose :thumb_url do |image, options| | |
29 | + image.public_filename(:thumb) | |
30 | + end | |
31 | + end | |
32 | + | |
33 | + class Profile < Entity | |
34 | + expose :identifier, :name, :id | |
35 | + expose :created_at, :format_with => :timestamp | |
36 | + expose :updated_at, :format_with => :timestamp | |
37 | + expose :image, :using => Image | |
38 | + end | |
39 | + | |
40 | + class User < Entity | |
41 | + expose :id | |
42 | + expose :login | |
43 | + end | |
44 | + | |
45 | + class Person < Profile | |
46 | + root 'people', 'person' | |
47 | + expose :user, :using => User | |
48 | + end | |
49 | + class Enterprise < Profile | |
50 | + root 'enterprises', 'enterprise' | |
51 | + end | |
52 | + class Community < Profile | |
53 | + root 'communities', 'community' | |
54 | + expose :description | |
55 | + expose :categories | |
56 | + expose :members, :using => Person | |
57 | + end | |
58 | + | |
59 | + class CategoryBase < Entity | |
60 | + root 'categories', 'category' | |
61 | + expose :name, :id | |
62 | + end | |
63 | + | |
64 | + class Category < CategoryBase | |
65 | + root 'categories', 'category' | |
66 | + expose :slug | |
67 | + expose :full_name do |category, options| | |
68 | + category.full_name | |
69 | + end | |
70 | + expose :parent, :using => CategoryBase, if: { parent: true } | |
71 | + expose :children, :using => CategoryBase, if: { children: true } | |
72 | + expose :image, :using => Image | |
73 | + end | |
74 | + | |
75 | + class ArticleBase < Entity | |
76 | + root 'articles', 'article' | |
77 | + expose :id | |
78 | + expose :body | |
79 | + expose :abstract | |
80 | + expose :created_at, :format_with => :timestamp | |
81 | + expose :updated_at, :format_with => :timestamp | |
82 | + expose :title, :documentation => {:type => "String", :desc => "Title of the article"} | |
83 | + expose :created_by, :as => :author, :using => Profile | |
84 | + expose :profile, :using => Profile | |
85 | + expose :categories, :using => Category | |
86 | + expose :image, :using => Image | |
87 | + #TODO Apply vote stuff in core and make this test | |
88 | + expose :votes_for | |
89 | + expose :votes_against | |
90 | + expose :setting | |
91 | + expose :position | |
92 | + expose :hits | |
93 | + expose :path | |
94 | + end | |
95 | + | |
96 | + class Article < ArticleBase | |
97 | + root 'articles', 'article' | |
98 | + expose :parent, :using => ArticleBase | |
99 | + expose :children, :using => ArticleBase | |
100 | + end | |
101 | + | |
102 | + class Comment < Entity | |
103 | + root 'comments', 'comment' | |
104 | + expose :body, :title, :id | |
105 | + expose :created_at, :format_with => :timestamp | |
106 | + expose :author, :using => Profile | |
107 | + end | |
108 | + | |
109 | + class UserLogin < User | |
110 | + expose :private_token | |
111 | + end | |
112 | + | |
113 | + class Task < Entity | |
114 | + root 'tasks', 'task' | |
115 | + expose :id | |
116 | + expose :type | |
117 | + end | |
118 | + | |
119 | + end | |
120 | + end | |
121 | +end | ... | ... |
... | ... | @@ -0,0 +1,39 @@ |
1 | +class Noosfero::API::Entity < Grape::Entity | |
2 | + | |
3 | + def initialize(object, options = {}) | |
4 | + object = nil if object.is_a? Exception | |
5 | + super object, options | |
6 | + end | |
7 | + | |
8 | + def self.represent(objects, options = {}) | |
9 | + if options[:has_exception] | |
10 | + data = super objects, options.merge(is_inner_data: true) | |
11 | + if objects.is_a? Exception | |
12 | + data.merge ok: false, error: { | |
13 | + type: objects.class.name, | |
14 | + message: objects.message | |
15 | + } | |
16 | + else | |
17 | + data = data.serializable_hash if data.is_a? Noosfero::API::Entity | |
18 | + data.merge ok: true, error: { type: 'Success', message: '' } | |
19 | + end | |
20 | + else | |
21 | + super objects, options | |
22 | + end | |
23 | + end | |
24 | + | |
25 | + def self.fields_condition(fields) | |
26 | + lambda do |object, options| | |
27 | + return true if options[:fields].blank? | |
28 | + fields.map { |field| options[:fields].include?(field.to_s)}.grep(true).present? | |
29 | + end | |
30 | + end | |
31 | + | |
32 | + def self.expose(*args, &block) | |
33 | + hash = args.last.is_a?(Hash) ? args.pop : {} | |
34 | + hash.merge!({:if => fields_condition(args)}) if hash[:if].blank? | |
35 | + args << hash | |
36 | + super | |
37 | + end | |
38 | + | |
39 | +end | ... | ... |
... | ... | @@ -0,0 +1,314 @@ |
1 | +module Noosfero | |
2 | + module API | |
3 | + module APIHelpers | |
4 | + PRIVATE_TOKEN_PARAM = :private_token | |
5 | + DEFAULT_ALLOWED_PARAMETERS = [:parent_id, :from, :until, :content_type] | |
6 | + | |
7 | + def current_user | |
8 | + private_token = (params[PRIVATE_TOKEN_PARAM] || headers['Private-Token']).to_s | |
9 | + @current_user ||= User.find_by_private_token(private_token) | |
10 | + @current_user = nil if !@current_user.nil? && @current_user.private_token_expired? | |
11 | + @current_user | |
12 | + end | |
13 | + | |
14 | + def current_person | |
15 | + current_user.person unless current_user.nil? | |
16 | + end | |
17 | + | |
18 | + def logout | |
19 | + @current_user = nil | |
20 | + end | |
21 | + | |
22 | + def environment | |
23 | + @environment | |
24 | + end | |
25 | + | |
26 | + def limit | |
27 | + limit = params[:limit].to_i | |
28 | + limit = default_limit if limit <= 0 | |
29 | + limit | |
30 | + end | |
31 | + | |
32 | + def period(from_date, until_date) | |
33 | + return nil if from_date.nil? && until_date.nil? | |
34 | + | |
35 | + begin_period = from_date.nil? ? Time.at(0).to_datetime : from_date | |
36 | + end_period = until_date.nil? ? DateTime.now : until_date | |
37 | + | |
38 | + begin_period..end_period | |
39 | + end | |
40 | + | |
41 | + def parse_content_type(content_type) | |
42 | + return nil if content_type.blank? | |
43 | + content_type.split(',').map do |content_type| | |
44 | + content_type.camelcase | |
45 | + end | |
46 | + end | |
47 | + | |
48 | + ARTICLE_TYPES = ['Article'] + Article.descendants.map{|a| a.to_s} | |
49 | + TASK_TYPES = ['Task'] + Task.descendants.map{|a| a.to_s} | |
50 | + | |
51 | + def find_article(articles, id) | |
52 | + article = articles.find(id) | |
53 | + article.display_to?(current_user.person) ? article : forbidden! | |
54 | + end | |
55 | + | |
56 | + def post_article(asset, params) | |
57 | + return forbidden! unless current_person.can_post_content?(asset) | |
58 | + | |
59 | + klass_type= params[:content_type].nil? ? 'TinyMceArticle' : params[:content_type] | |
60 | + return forbidden! unless ARTICLE_TYPES.include?(klass_type) | |
61 | + | |
62 | + article = klass_type.constantize.new(params[:article]) | |
63 | + article.last_changed_by = current_person | |
64 | + article.created_by= current_person | |
65 | + article.profile = asset | |
66 | + | |
67 | + if !article.save | |
68 | + render_api_errors!(article.errors.full_messages) | |
69 | + end | |
70 | + present article, :with => Entities::Article, :fields => params[:fields] | |
71 | + end | |
72 | + | |
73 | + def present_article(asset) | |
74 | + article = find_article(asset.articles, params[:id]) | |
75 | + present article, :with => Entities::Article, :fields => params[:fields] | |
76 | + end | |
77 | + | |
78 | + def present_articles(asset) | |
79 | + articles = select_filtered_collection_of(asset, 'articles', params) | |
80 | + articles = articles.display_filter(current_person, nil) | |
81 | + present articles, :with => Entities::Article, :fields => params[:fields] | |
82 | + end | |
83 | + | |
84 | + def find_task(asset, id) | |
85 | + task = asset.tasks.find(id) | |
86 | + current_person.has_permission?(task.permission, asset) ? task : forbidden! | |
87 | + end | |
88 | + | |
89 | + def post_task(asset, params) | |
90 | + klass_type= params[:content_type].nil? ? 'Task' : params[:content_type] | |
91 | + return forbidden! unless TASK_TYPES.include?(klass_type) | |
92 | + | |
93 | + task = klass_type.constantize.new(params[:task]) | |
94 | + task.requestor_id = current_person.id | |
95 | + task.target_id = asset.id | |
96 | + task.target_type = 'Profile' | |
97 | + | |
98 | + if !task.save | |
99 | + render_api_errors!(task.errors.full_messages) | |
100 | + end | |
101 | + present task, :with => Entities::Task, :fields => params[:fields] | |
102 | + end | |
103 | + | |
104 | + def present_task(asset) | |
105 | + task = find_task(asset, params[:id]) | |
106 | + present task, :with => Entities::Task, :fields => params[:fields] | |
107 | + end | |
108 | + | |
109 | + def present_tasks(asset) | |
110 | + tasks = select_filtered_collection_of(asset, 'tasks', params) | |
111 | + tasks = tasks.select {|t| current_person.has_permission?(t.permission, asset)} | |
112 | + return forbidden! if tasks.empty? && !current_person.has_permission?(:perform_task, asset) | |
113 | + present tasks, :with => Entities::Task, :fields => params[:fields] | |
114 | + end | |
115 | + | |
116 | + def make_conditions_with_parameter(params = {}) | |
117 | + parsed_params = parser_params(params) | |
118 | + conditions = {} | |
119 | + from_date = DateTime.parse(parsed_params.delete(:from)) if parsed_params[:from] | |
120 | + until_date = DateTime.parse(parsed_params.delete(:until)) if parsed_params[:until] | |
121 | + | |
122 | + conditions[:type] = parse_content_type(parsed_params.delete(:content_type)) unless parsed_params[:content_type].nil? | |
123 | + | |
124 | + conditions[:created_at] = period(from_date, until_date) if from_date || until_date | |
125 | + conditions.merge!(parsed_params) | |
126 | + | |
127 | + conditions | |
128 | + end | |
129 | + | |
130 | + def make_order_with_parameters(params) | |
131 | + params[:order] || "created_at DESC" | |
132 | + end | |
133 | + | |
134 | + def make_page_number_with_parameters(params) | |
135 | + params[:page] || 1 | |
136 | + end | |
137 | + | |
138 | + def make_per_page_with_parameters(params) | |
139 | + params[:per_page] ||= limit | |
140 | + params[:per_page].to_i | |
141 | + end | |
142 | + | |
143 | + def make_timestamp_with_parameters_and_method(params, method) | |
144 | + timestamp = nil | |
145 | + if params[:timestamp] | |
146 | + datetime = DateTime.parse(params[:timestamp]) | |
147 | + table_name = method.to_s.singularize.camelize.constantize.table_name | |
148 | + timestamp = "#{table_name}.updated_at >= '#{datetime}'" | |
149 | + end | |
150 | + | |
151 | + timestamp | |
152 | + end | |
153 | + | |
154 | + def by_reference(scope, params) | |
155 | + if params[:reference_id] | |
156 | + created_at = scope.find(params[:reference_id]).created_at | |
157 | + scope.send("#{params.key?(:oldest) ? 'older_than' : 'younger_than'}", created_at) | |
158 | + else | |
159 | + scope | |
160 | + end | |
161 | + end | |
162 | + | |
163 | + def select_filtered_collection_of(object, method, params) | |
164 | + conditions = make_conditions_with_parameter(params) | |
165 | + order = make_order_with_parameters(params) | |
166 | + page_number = make_page_number_with_parameters(params) | |
167 | + per_page = make_per_page_with_parameters(params) | |
168 | + timestamp = make_timestamp_with_parameters_and_method(params, method) | |
169 | + | |
170 | + objects = object.send(method) | |
171 | + objects = by_reference(objects, params) | |
172 | + | |
173 | + objects = objects.where(conditions).where(timestamp).page(page_number).per_page(per_page).order(order) | |
174 | + | |
175 | + objects | |
176 | + end | |
177 | + | |
178 | + def authenticate! | |
179 | + unauthorized! unless current_user | |
180 | + end | |
181 | + | |
182 | + # Checks the occurrences of uniqueness of attributes, each attribute must be present in the params hash | |
183 | + # or a Bad Request error is invoked. | |
184 | + # | |
185 | + # Parameters: | |
186 | + # keys (unique) - A hash consisting of keys that must be unique | |
187 | + def unique_attributes!(obj, keys) | |
188 | + keys.each do |key| | |
189 | + cant_be_saved_request!(key) if obj.send("find_by_#{key.to_s}", params[key]) | |
190 | + end | |
191 | + end | |
192 | + | |
193 | + def attributes_for_keys(keys) | |
194 | + attrs = {} | |
195 | + keys.each do |key| | |
196 | + attrs[key] = params[key] if params[key].present? or (params.has_key?(key) and params[key] == false) | |
197 | + end | |
198 | + attrs | |
199 | + end | |
200 | + | |
201 | + def verify_recaptcha_v2(remote_ip, g_recaptcha_response, private_key, api_recaptcha_verify_uri) | |
202 | + verify_hash = { | |
203 | + "secret" => private_key, | |
204 | + "remoteip" => remote_ip, | |
205 | + "response" => g_recaptcha_response | |
206 | + } | |
207 | + uri = URI(api_recaptcha_verify_uri) | |
208 | + https = Net::HTTP.new(uri.host, uri.port) | |
209 | + https.use_ssl = true | |
210 | + request = Net::HTTP::Post.new(uri.path) | |
211 | + request.set_form_data(verify_hash) | |
212 | + JSON.parse(https.request(request).body) | |
213 | + end | |
214 | + | |
215 | + ########################################## | |
216 | + # error helpers # | |
217 | + ########################################## | |
218 | + | |
219 | + def not_found! | |
220 | + render_api_error!('404 Not found', 404) | |
221 | + end | |
222 | + | |
223 | + def forbidden! | |
224 | + render_api_error!('403 Forbidden', 403) | |
225 | + end | |
226 | + | |
227 | + def cant_be_saved_request!(attribute) | |
228 | + message = _("(Invalid request) #{attribute} can't be saved") | |
229 | + render_api_error!(message, 400) | |
230 | + end | |
231 | + | |
232 | + def bad_request!(attribute) | |
233 | + message = _("(Bad request) #{attribute} not given") | |
234 | + render_api_error!(message, 400) | |
235 | + end | |
236 | + | |
237 | + def something_wrong! | |
238 | + message = _("Something wrong happened") | |
239 | + render_api_error!(message, 400) | |
240 | + end | |
241 | + | |
242 | + def unauthorized! | |
243 | + render_api_error!(_('Unauthorized'), 401) | |
244 | + end | |
245 | + | |
246 | + def not_allowed! | |
247 | + render_api_error!(_('Method Not Allowed'), 405) | |
248 | + end | |
249 | + | |
250 | + def render_api_error!(message, status) | |
251 | + error!({'message' => message, :code => status}, status) | |
252 | + end | |
253 | + | |
254 | + def render_api_errors!(messages) | |
255 | + render_api_error!(messages.join(','), 400) | |
256 | + end | |
257 | + protected | |
258 | + | |
259 | + def set_session_cookie | |
260 | + cookies['_noosfero_api_session'] = { value: @current_user.private_token, httponly: true } if @current_user.present? | |
261 | + end | |
262 | + | |
263 | + def setup_multitenancy | |
264 | + Noosfero::MultiTenancy.setup!(request.host) | |
265 | + end | |
266 | + | |
267 | + def detect_stuff_by_domain | |
268 | + @domain = Domain.find_by_name(request.host) | |
269 | + if @domain.nil? | |
270 | + @environment = Environment.default | |
271 | + if @environment.nil? && Rails.env.development? | |
272 | + # This should only happen in development ... | |
273 | + @environment = Environment.create!(:name => "Noosfero", :is_default => true) | |
274 | + end | |
275 | + else | |
276 | + @environment = @domain.environment | |
277 | + end | |
278 | + end | |
279 | + | |
280 | + def filter_disabled_plugins_endpoints | |
281 | + not_found! if Noosfero::API::API.endpoint_unavailable?(self, !@environment) | |
282 | + end | |
283 | + | |
284 | + private | |
285 | + | |
286 | + def parser_params(params) | |
287 | + parsed_params = {} | |
288 | + params.map do |k,v| | |
289 | + parsed_params[k.to_sym] = v if DEFAULT_ALLOWED_PARAMETERS.include?(k.to_sym) | |
290 | + end | |
291 | + parsed_params | |
292 | + end | |
293 | + | |
294 | + def default_limit | |
295 | + 20 | |
296 | + end | |
297 | + | |
298 | + def parse_content_type(content_type) | |
299 | + return nil if content_type.blank? | |
300 | + content_type.split(',').map do |content_type| | |
301 | + content_type.camelcase | |
302 | + end | |
303 | + end | |
304 | + | |
305 | + def period(from_date, until_date) | |
306 | + begin_period = from_date.nil? ? Time.at(0).to_datetime : from_date | |
307 | + end_period = until_date.nil? ? DateTime.now : until_date | |
308 | + | |
309 | + begin_period..end_period | |
310 | + end | |
311 | + | |
312 | + end | |
313 | + end | |
314 | +end | ... | ... |
... | ... | @@ -0,0 +1,61 @@ |
1 | +require "uri" | |
2 | + | |
3 | +module Noosfero | |
4 | + module API | |
5 | + | |
6 | + class Session < Grape::API | |
7 | + | |
8 | + # Login to get token | |
9 | + # | |
10 | + # Parameters: | |
11 | + # login (*required) - user login or email | |
12 | + # password (required) - user password | |
13 | + # | |
14 | + # Example Request: | |
15 | + # POST http://localhost:3000/api/v1/login?login=adminuser&password=admin | |
16 | + post "/login" do | |
17 | + user ||= User.authenticate(params[:login], params[:password], environment) | |
18 | + | |
19 | + return unauthorized! unless user | |
20 | + @current_user = user | |
21 | + present user, :with => Entities::UserLogin | |
22 | + end | |
23 | + | |
24 | + # Create user. | |
25 | + # | |
26 | + # Parameters: | |
27 | + # email (required) - Email | |
28 | + # password (required) - Password | |
29 | + # login - login | |
30 | + # Example Request: | |
31 | + # POST /register?email=some@mail.com&password=pas&login=some | |
32 | + params do | |
33 | + requires :email, type: String, desc: _("Email") | |
34 | + requires :login, type: String, desc: _("Login") | |
35 | + requires :password, type: String, desc: _("Password") | |
36 | + end | |
37 | + post "/register" do | |
38 | + unique_attributes! User, [:email, :login] | |
39 | + attrs = attributes_for_keys [:email, :login, :password] | |
40 | + attrs[:password_confirmation] = attrs[:password] | |
41 | + | |
42 | + #Commented for stress tests | |
43 | + | |
44 | + # remote_ip = (request.respond_to?(:remote_ip) && request.remote_ip) || (env && env['REMOTE_ADDR']) | |
45 | + # private_key = API.NOOSFERO_CONF['api_recaptcha_private_key'] | |
46 | + # api_recaptcha_verify_uri = API.NOOSFERO_CONF['api_recaptcha_verify_uri'] | |
47 | + # captcha_result = verify_recaptcha_v2(remote_ip, params['g-recaptcha-response'], private_key, api_recaptcha_verify_uri) | |
48 | + user = User.new(attrs) | |
49 | +# if captcha_result["success"] and user.save | |
50 | + if user.save | |
51 | + user.activate | |
52 | + user.generate_private_token! | |
53 | + present user, :with => Entities::UserLogin | |
54 | + else | |
55 | + message = user.errors.to_json | |
56 | + render_api_error!(message, 400) | |
57 | + end | |
58 | + end | |
59 | + end | |
60 | + end | |
61 | +end | ... | ... |
... | ... | @@ -0,0 +1,121 @@ |
1 | +module Noosfero | |
2 | + module API | |
3 | + module V1 | |
4 | + class Articles < Grape::API | |
5 | + before { authenticate! } | |
6 | + | |
7 | + ARTICLE_TYPES = Article.descendants.map{|a| a.to_s} | |
8 | + | |
9 | + resource :articles do | |
10 | + | |
11 | + # Collect articles | |
12 | + # | |
13 | + # Parameters: | |
14 | + # from - date where the search will begin. If nothing is passed the default date will be the date of the first article created | |
15 | + # oldest - Collect the oldest articles. If nothing is passed the newest articles are collected | |
16 | + # limit - amount of articles returned. The default value is 20 | |
17 | + # | |
18 | + # Example Request: | |
19 | + # GET host/api/v1/articles?from=2013-04-04-14:41:43&until=2015-04-04-14:41:43&limit=10&private_token=e96fff37c2238fdab074d1dcea8e6317 | |
20 | + get do | |
21 | + present_articles(environment) | |
22 | + end | |
23 | + | |
24 | + desc "Return the article id" | |
25 | + get ':id' do | |
26 | + present_article(environment) | |
27 | + end | |
28 | + | |
29 | + get ':id/children' do | |
30 | + article = find_article(environment.articles, params[:id]) | |
31 | + | |
32 | + #TODO make tests for this situation | |
33 | + votes_order = params.delete(:order) if params[:order]=='votes_score' | |
34 | + articles = select_filtered_collection_of(article, 'children', params) | |
35 | + articles = articles.display_filter(current_person, nil) | |
36 | + | |
37 | + | |
38 | + #TODO make tests for this situation | |
39 | + if votes_order | |
40 | + articles = articles.joins('left join votes on articles.id=votes.voteable_id').group('articles.id').reorder('sum(coalesce(votes.vote, 0)) DESC') | |
41 | + end | |
42 | + | |
43 | + Article.hit(articles) | |
44 | + present articles, :with => Entities::Article, :fields => params[:fields] | |
45 | + end | |
46 | + | |
47 | + get ':id/children/:child_id' do | |
48 | + article = find_article(environment.articles, params[:id]) | |
49 | + present find_article(article.children, params[:child_id]), :with => Entities::Article, :fields => params[:fields] | |
50 | + end | |
51 | + | |
52 | + post ':id/children/suggest' do | |
53 | + parent_article = environment.articles.find(params[:id]) | |
54 | + | |
55 | + suggest_article = SuggestArticle.new | |
56 | + suggest_article.article = params[:article] | |
57 | + suggest_article.article[:parent_id] = parent_article.id | |
58 | + suggest_article.target = parent_article.profile | |
59 | + suggest_article.requestor = current_person | |
60 | + | |
61 | + unless suggest_article.save | |
62 | + render_api_errors!(suggest_article.article_object.errors.full_messages) | |
63 | + end | |
64 | + present suggest_article, :with => Entities::Task, :fields => params[:fields] | |
65 | + end | |
66 | + | |
67 | + # Example Request: | |
68 | + # POST api/v1/articles/:id/children?private_token=234298743290432&article[name]=title&article[body]=body | |
69 | + post ':id/children' do | |
70 | + | |
71 | + parent_article = environment.articles.find(params[:id]) | |
72 | + return forbidden! unless parent_article.allow_create?(current_person) | |
73 | + | |
74 | + klass_type= params[:content_type].nil? ? 'TinyMceArticle' : params[:content_type] | |
75 | + #FIXME see how to check the article types | |
76 | + #return forbidden! unless ARTICLE_TYPES.include?(klass_type) | |
77 | + | |
78 | + article = klass_type.constantize.new(params[:article]) | |
79 | + article.parent = parent_article | |
80 | + article.last_changed_by = current_person | |
81 | + article.created_by= current_person | |
82 | + article.author= current_person | |
83 | + article.profile = parent_article.profile | |
84 | + | |
85 | + if !article.save | |
86 | + render_api_errors!(article.errors.full_messages) | |
87 | + end | |
88 | + present article, :with => Entities::Article, :fields => params[:fields] | |
89 | + end | |
90 | + | |
91 | + end | |
92 | + | |
93 | + kinds = %w[community person enterprise] | |
94 | + kinds.each do |kind| | |
95 | + resource kind.pluralize.to_sym do | |
96 | + segment "/:#{kind}_id" do | |
97 | + resource :articles do | |
98 | + get do | |
99 | + profile = environment.send(kind.pluralize).find(params["#{kind}_id"]) | |
100 | + present_articles(profile) | |
101 | + end | |
102 | + | |
103 | + get ':id' do | |
104 | + profile = environment.send(kind.pluralize).find(params["#{kind}_id"]) | |
105 | + present_article(profile) | |
106 | + end | |
107 | + | |
108 | + # Example Request: | |
109 | + # POST api/v1/{people,communities,enterprises}/:asset_id/articles?private_token=234298743290432&article[name]=title&article[body]=body | |
110 | + post do | |
111 | + profile = environment.send(kind.pluralize).find(params["#{kind}_id"]) | |
112 | + post_article(profile, params) | |
113 | + end | |
114 | + end | |
115 | + end | |
116 | + end | |
117 | + end | |
118 | + end | |
119 | + end | |
120 | + end | |
121 | +end | ... | ... |
... | ... | @@ -0,0 +1,28 @@ |
1 | +module Noosfero | |
2 | + module API | |
3 | + module V1 | |
4 | + class Categories < Grape::API | |
5 | + before { authenticate! } | |
6 | + | |
7 | + resource :categories do | |
8 | + | |
9 | + get do | |
10 | + type = params[:category_type] | |
11 | + include_parent = params[:include_parent] == 'true' | |
12 | + include_children = params[:include_children] == 'true' | |
13 | + | |
14 | + categories = type.nil? ? environment.categories : environment.categories.where(:type => type) | |
15 | + present categories, :with => Entities::Category, parent: include_parent, children: include_children | |
16 | + end | |
17 | + | |
18 | + desc "Return the category by id" | |
19 | + get ':id' do | |
20 | + present environment.categories.find(params[:id]), :with => Entities::Category, parent: true, children: true | |
21 | + end | |
22 | + | |
23 | + end | |
24 | + | |
25 | + end | |
26 | + end | |
27 | + end | |
28 | +end | ... | ... |
... | ... | @@ -0,0 +1,41 @@ |
1 | +module Noosfero | |
2 | + module API | |
3 | + module V1 | |
4 | + class Comments < Grape::API | |
5 | + before { authenticate! } | |
6 | + | |
7 | + resource :articles do | |
8 | + # Collect comments from articles | |
9 | + # | |
10 | + # Parameters: | |
11 | + # reference_id - comment id used as reference to collect comment | |
12 | + # oldest - Collect the oldest comments from reference_id comment. If nothing is passed the newest comments are collected | |
13 | + # limit - amount of comments returned. The default value is 20 | |
14 | + # | |
15 | + # Example Request: | |
16 | + # GET /articles/12/comments?oldest&limit=10&reference_id=23 | |
17 | + get ":id/comments" do | |
18 | + article = find_article(environment.articles, params[:id]) | |
19 | + comments = select_filtered_collection_of(article, :comments, params) | |
20 | + | |
21 | + present comments, :with => Entities::Comment | |
22 | + end | |
23 | + | |
24 | + get ":id/comments/:comment_id" do | |
25 | + article = find_article(environment.articles, params[:id]) | |
26 | + present article.comments.find(params[:comment_id]), :with => Entities::Comment | |
27 | + end | |
28 | + | |
29 | + # Example Request: | |
30 | + # POST api/v1/articles/12/comments?private_token=2298743290432&body=new comment&title=New | |
31 | + post ":id/comments" do | |
32 | + article = find_article(environment.articles, params[:id]) | |
33 | + options = params.select { |key,v| !['id','private_token'].include?(key) }.merge(:author => current_person) | |
34 | + present article.comments.create(options), :with => Entities::Comment | |
35 | + end | |
36 | + end | |
37 | + | |
38 | + end | |
39 | + end | |
40 | + end | |
41 | +end | ... | ... |
... | ... | @@ -0,0 +1,73 @@ |
1 | +module Noosfero | |
2 | + module API | |
3 | + module V1 | |
4 | + class Communities < Grape::API | |
5 | + before { authenticate! } | |
6 | + | |
7 | + resource :communities do | |
8 | + | |
9 | + # Collect comments from articles | |
10 | + # | |
11 | + # Parameters: | |
12 | + # from - date where the search will begin. If nothing is passed the default date will be the date of the first article created | |
13 | + # oldest - Collect the oldest comments from reference_id comment. If nothing is passed the newest comments are collected | |
14 | + # limit - amount of comments returned. The default value is 20 | |
15 | + # | |
16 | + # Example Request: | |
17 | + # GET /communities?from=2013-04-04-14:41:43&until=2014-04-04-14:41:43&limit=10 | |
18 | + # GET /communities?reference_id=10&limit=10&oldest | |
19 | + get do | |
20 | + communities = select_filtered_collection_of(environment, 'communities', params) | |
21 | + communities = communities.visible_for_person(current_person) | |
22 | + communities = communities.by_location(params) # Must be the last. May return Exception obj. | |
23 | + present communities, :with => Entities::Community | |
24 | + end | |
25 | + | |
26 | + | |
27 | + # Example Request: | |
28 | + # POST api/v1/communties?private_token=234298743290432&community[name]=some_name | |
29 | + post do | |
30 | + params[:community] ||= {} | |
31 | + begin | |
32 | + community = Community.create_after_moderation(current_person, params[:community].merge({:environment => environment})) | |
33 | + rescue | |
34 | + community = Community.new(params[:community]) | |
35 | + end | |
36 | + | |
37 | + if !community.save | |
38 | + render_api_errors!(community.errors.full_messages) | |
39 | + end | |
40 | + | |
41 | + present community, :with => Entities::Community | |
42 | + end | |
43 | + | |
44 | + get ':id' do | |
45 | + community = environment.communities.visible_for_person(current_person).find_by_id(params[:id]) | |
46 | + present community, :with => Entities::Community | |
47 | + end | |
48 | + | |
49 | + end | |
50 | + | |
51 | + resource :people do | |
52 | + | |
53 | + segment '/:person_id' do | |
54 | + | |
55 | + resource :communities do | |
56 | + | |
57 | + get do | |
58 | + person = environment.people.find(params[:person_id]) | |
59 | + communities = select_filtered_collection_of(person, 'communities', params) | |
60 | + communities = communities.visible | |
61 | + present communities, :with => Entities::Community | |
62 | + end | |
63 | + | |
64 | + end | |
65 | + | |
66 | + end | |
67 | + | |
68 | + end | |
69 | + | |
70 | + end | |
71 | + end | |
72 | + end | |
73 | +end | ... | ... |
... | ... | @@ -0,0 +1,58 @@ |
1 | +module Noosfero | |
2 | + module API | |
3 | + module V1 | |
4 | + class Enterprises < Grape::API | |
5 | + before { authenticate! } | |
6 | + | |
7 | + resource :enterprises do | |
8 | + | |
9 | + # Collect enterprises from environment | |
10 | + # | |
11 | + # Parameters: | |
12 | + # from - date where the search will begin. If nothing is passed the default date will be the date of the first article created | |
13 | + # oldest - Collect the oldest comments from reference_id comment. If nothing is passed the newest comments are collected | |
14 | + # limit - amount of comments returned. The default value is 20 | |
15 | + # georef params - read `Profile.by_location` for more information. | |
16 | + # | |
17 | + # Example Request: | |
18 | + # GET /enterprises?from=2013-04-04-14:41:43&until=2014-04-04-14:41:43&limit=10 | |
19 | + # GET /enterprises?reference_id=10&limit=10&oldest | |
20 | + get do | |
21 | + enterprises = select_filtered_collection_of(environment, 'enterprises', params) | |
22 | + enterprises = enterprises.visible_for_person(current_person) | |
23 | + enterprises = enterprises.by_location(params) # Must be the last. May return Exception obj. | |
24 | + present enterprises, :with => Entities::Enterprise | |
25 | + end | |
26 | + | |
27 | + desc "Return one enterprise by id" | |
28 | + get ':id' do | |
29 | + enterprise = environment.enterprises.visible_for_person(current_person).find_by_id(params[:id]) | |
30 | + present enterprise, :with => Entities::Enterprise | |
31 | + end | |
32 | + | |
33 | + end | |
34 | + | |
35 | + resource :people do | |
36 | + | |
37 | + segment '/:person_id' do | |
38 | + | |
39 | + resource :enterprises do | |
40 | + | |
41 | + get do | |
42 | + person = environment.people.find(params[:person_id]) | |
43 | + enterprises = select_filtered_collection_of(person, 'enterprises', params) | |
44 | + enterprises = enterprises.visible.by_location(params) | |
45 | + present enterprises, :with => Entities::Enterprise | |
46 | + end | |
47 | + | |
48 | + end | |
49 | + | |
50 | + end | |
51 | + | |
52 | + end | |
53 | + | |
54 | + | |
55 | + end | |
56 | + end | |
57 | + end | |
58 | +end | ... | ... |
... | ... | @@ -0,0 +1,96 @@ |
1 | +module Noosfero | |
2 | + module API | |
3 | + module V1 | |
4 | + class People < Grape::API | |
5 | + before { authenticate! } | |
6 | + | |
7 | + desc 'API Root' | |
8 | + | |
9 | + resource :people do | |
10 | + | |
11 | + # -- A note about privacy -- | |
12 | + # We wold find people by location, but we must test if the related | |
13 | + # fields are public. We can't do it now, with SQL, while the location | |
14 | + # data and the fields_privacy are a serialized settings. | |
15 | + # We must build a new table for profile data, where we can set meta-data | |
16 | + # like: | |
17 | + # | id | profile_id | key | value | privacy_level | source | | |
18 | + # | 1 | 99 | city | Salvador | friends | user | | |
19 | + # | 2 | 99 | lng | -38.521 | me only | automatic | | |
20 | + | |
21 | + # Collect people from environment | |
22 | + # | |
23 | + # Parameters: | |
24 | + # from - date where the search will begin. If nothing is passed the default date will be the date of the first article created | |
25 | + # oldest - Collect the oldest comments from reference_id comment. If nothing is passed the newest comments are collected | |
26 | + # limit - amount of comments returned. The default value is 20 | |
27 | + # | |
28 | + # Example Request: | |
29 | + # GET /people?from=2013-04-04-14:41:43&until=2014-04-04-14:41:43&limit=10 | |
30 | + # GET /people?reference_id=10&limit=10&oldest | |
31 | + | |
32 | + desc "Find environment's people" | |
33 | + get do | |
34 | + people = select_filtered_collection_of(environment, 'people', params) | |
35 | + people = people.visible_for_person(current_person) | |
36 | + present people, :with => Entities::Person | |
37 | + end | |
38 | + | |
39 | + desc "Return the logged user information" | |
40 | + get "/me" do | |
41 | + present current_person, :with => Entities::Person | |
42 | + end | |
43 | + | |
44 | + desc "Return the person information" | |
45 | + get ':id' do | |
46 | + person = environment.people.visible_for_person(current_person).find_by_id(params[:id]) | |
47 | + return not_found! if person.blank? | |
48 | + present person, :with => Entities::Person | |
49 | + end | |
50 | + | |
51 | + # Example Request: | |
52 | + # POST api/v1/people?person[login]=some_login&person[password]=some_password&person[name]=Jack | |
53 | + desc "Create person" | |
54 | + post do | |
55 | + user_data = {} | |
56 | + user_data[:login] = params[:person].delete(:login) || params[:person][:identifier] | |
57 | + user_data[:email] = params[:person].delete(:email) | |
58 | + user_data[:password] = params[:person].delete(:password) | |
59 | + user_data[:password_confirmation] = params[:person].delete(:password_confirmation) | |
60 | + user = User.build(user_data, params[:person], environment) | |
61 | + begin | |
62 | + user.signup! | |
63 | + rescue ActiveRecord::RecordInvalid | |
64 | + render_api_errors!(user.errors.full_messages) | |
65 | + end | |
66 | + | |
67 | + present user.person, :with => Entities::Person | |
68 | + end | |
69 | + | |
70 | + desc "Return the person friends" | |
71 | + get ':id/friends' do | |
72 | + person = environment.people.visible_for_person(current_person).find_by_id(params[:id]) | |
73 | + return not_found! if person.blank? | |
74 | + friends = person.friends.visible | |
75 | + present friends, :with => Entities::Person | |
76 | + end | |
77 | + | |
78 | + desc "Return the person permissions on other profiles" | |
79 | + get ":id/permissions" do | |
80 | + person = environment.people.find(params[:id]) | |
81 | + return not_found! if person.blank? | |
82 | + return forbidden! unless current_person == person || environment.admins.include?(current_person) | |
83 | + | |
84 | + output = {} | |
85 | + person.role_assignments.map do |role_assigment| | |
86 | + if role_assigment.resource.respond_to?(:identifier) | |
87 | + output[role_assigment.resource.identifier] = role_assigment.role.permissions | |
88 | + end | |
89 | + end | |
90 | + present output | |
91 | + end | |
92 | + end | |
93 | + end | |
94 | + end | |
95 | + end | |
96 | +end | ... | ... |
... | ... | @@ -0,0 +1,59 @@ |
1 | +module Noosfero | |
2 | + module API | |
3 | + module V1 | |
4 | + class Tasks < Grape::API | |
5 | +# before { authenticate! } | |
6 | + | |
7 | +# ARTICLE_TYPES = Article.descendants.map{|a| a.to_s} | |
8 | + | |
9 | + resource :tasks do | |
10 | + | |
11 | + # Collect tasks | |
12 | + # | |
13 | + # Parameters: | |
14 | + # from - date where the search will begin. If nothing is passed the default date will be the date of the first article created | |
15 | + # oldest - Collect the oldest articles. If nothing is passed the newest articles are collected | |
16 | + # limit - amount of articles returned. The default value is 20 | |
17 | + # | |
18 | + # Example Request: | |
19 | + # GET host/api/v1/tasks?from=2013-04-04-14:41:43&until=2015-04-04-14:41:43&limit=10&private_token=e96fff37c2238fdab074d1dcea8e6317 | |
20 | + get do | |
21 | + tasks = select_filtered_collection_of(environment, 'tasks', params) | |
22 | + tasks = tasks.select {|t| current_person.has_permission?(t.permission, environment)} | |
23 | + present tasks, :with => Entities::Task, :fields => params[:fields] | |
24 | + end | |
25 | + | |
26 | + desc "Return the task id" | |
27 | + get ':id' do | |
28 | + task = find_task(environment, params[:id]) | |
29 | + present task, :with => Entities::Task, :fields => params[:fields] | |
30 | + end | |
31 | + end | |
32 | + | |
33 | + kinds = %w[community person enterprise] | |
34 | + kinds.each do |kind| | |
35 | + resource kind.pluralize.to_sym do | |
36 | + segment "/:#{kind}_id" do | |
37 | + resource :tasks do | |
38 | + get do | |
39 | + profile = environment.send(kind.pluralize).find(params["#{kind}_id"]) | |
40 | + present_tasks(profile) | |
41 | + end | |
42 | + | |
43 | + get ':id' do | |
44 | + profile = environment.send(kind.pluralize).find(params["#{kind}_id"]) | |
45 | + present_task(profile) | |
46 | + end | |
47 | + | |
48 | + post do | |
49 | + profile = environment.send(kind.pluralize).find(params["#{kind}_id"]) | |
50 | + post_task(profile, params) | |
51 | + end | |
52 | + end | |
53 | + end | |
54 | + end | |
55 | + end | |
56 | + end | |
57 | + end | |
58 | + end | |
59 | +end | ... | ... |
lib/noosfero/geo_ref.rb
1 | 1 | module Noosfero::GeoRef |
2 | 2 | |
3 | - KM_LAT = 111.2 # aproximate distance in km for 1 degree latitude | |
4 | - KM_LNG = 85.3 # aproximate distance in km for 1 degree longitude | |
3 | + # May replace this module by http://www.postgresql.org/docs/9.3/static/earthdistance.html | |
4 | + | |
5 | + EARTH_RADIUS = 6378 # aproximate in km | |
6 | + | |
7 | + class << self | |
8 | + | |
9 | + def dist(lat1, lng1, lat2, lng2) | |
10 | + def deg2rad(d); (d*Math::PI)/180; end | |
11 | + def c(n); Math.cos(n); end | |
12 | + def s(n); Math.sin(n); end | |
13 | + lat1 = deg2rad lat1 | |
14 | + lat2 = deg2rad lat2 | |
15 | + dlng = deg2rad(lng2) - deg2rad(lng1) | |
16 | + EARTH_RADIUS * Math.atan2( | |
17 | + Math.sqrt( | |
18 | + ( c(lat2) * s(dlng) )**2 + | |
19 | + ( c(lat1) * s(lat2) - s(lat1) * c(lat2) * c(dlng) )**2 | |
20 | + ), | |
21 | + s(lat1) * s(lat2) + c(lat1) * c(lat2) * c(dlng) | |
22 | + ) | |
23 | + end | |
24 | + | |
25 | + # Write a SQL expression to return the distance from a profile to a | |
26 | + # reference point, in kilometers. | |
27 | + # http://www.plumislandmedia.net/mysql/vicenty-great-circle-distance-formula | |
28 | + def sql_dist(ref_lat, ref_lng) | |
29 | + "2*PI()*#{EARTH_RADIUS}*( | |
30 | + DEGREES( | |
31 | + ATAN2( | |
32 | + SQRT( | |
33 | + POW(COS(RADIANS(#{ref_lat}))*SIN(RADIANS(#{ref_lng}-lng)),2) + | |
34 | + POW( | |
35 | + COS(RADIANS(lat)) * SIN(RADIANS(#{ref_lat})) - ( | |
36 | + SIN(RADIANS(lat)) * COS(RADIANS(#{ref_lat})) * COS(RADIANS(#{ref_lng}-lng)) | |
37 | + ), 2 | |
38 | + ) | |
39 | + ), | |
40 | + SIN(RADIANS(lat)) * SIN(RADIANS(#{ref_lat})) + | |
41 | + COS(RADIANS(lat)) * COS(RADIANS(#{ref_lat})) * COS(RADIANS(#{ref_lng}-lng)) | |
42 | + ) | |
43 | + )/360 | |
44 | + )" | |
45 | + end | |
46 | + | |
47 | + # Asks Google for the georef of a location. | |
48 | + def location_to_georef(location) | |
49 | + key = location.downcase | |
50 | + ll = Rails.cache.read key | |
51 | + return ll + [:CACHE] if ll.kind_of? Array | |
52 | + resp = RestClient.get 'https://maps.googleapis.com/maps/api/geocode/json?' + | |
53 | + 'sensor=false&address=' + url_encode(location) | |
54 | + if resp.nil? || resp.code.to_i != 200 | |
55 | + if ENV['RAILS_ENV'] == 'test' | |
56 | + print " Google Maps API fail (code #{resp ? resp.code : :nil}) " | |
57 | + else | |
58 | + Rails.logger.warn "Google Maps API request information for " + | |
59 | + "\"#{location}\" fail. (code #{resp ? resp.code : :nil})" | |
60 | + end | |
61 | + return [ 0, 0, "HTTP_FAIL_#{resp.code}".to_sym ] # do not cache failed response | |
62 | + else | |
63 | + json = JSON.parse resp.body | |
64 | + if json && (r=json['results']) && (r=r[0]) && (r=r['geometry']) && | |
65 | + (r=r['location']) && r['lat'] | |
66 | + ll = [ r['lat'], r['lng'], :SUCCESS ] | |
67 | + else | |
68 | + status = json['status'] || 'Undefined Error' | |
69 | + message = "Google Maps API cant find \"#{location}\" (#{status})" | |
70 | + if ENV['RAILS_ENV'] == 'test' | |
71 | + print " #{message} " | |
72 | + else | |
73 | + Rails.logger.warn message | |
74 | + end | |
75 | + ll = [ 0, 0, status.to_sym ] | |
76 | + end | |
77 | + Rails.cache.write key, ll | |
78 | + end | |
79 | + ll | |
80 | + end | |
81 | + | |
82 | + end | |
5 | 83 | |
6 | 84 | end | ... | ... |
lib/noosfero/plugin.rb
... | ... | @@ -171,7 +171,6 @@ class Noosfero::Plugin |
171 | 171 | def all |
172 | 172 | @all ||= available_plugins.map{ |dir| (File.basename(dir) + "_plugin").camelize } |
173 | 173 | end |
174 | - | |
175 | 174 | end |
176 | 175 | |
177 | 176 | def expanded_template(file_path, locals = {}) |
... | ... | @@ -241,6 +240,16 @@ class Noosfero::Plugin |
241 | 240 | nil |
242 | 241 | end |
243 | 242 | |
243 | + # -> Customize the way comments are counted for Profiles and Environment | |
244 | + # considering more than just articles comments | |
245 | + # Used on statistic block | |
246 | + # Ex: a plugin may want that Communities receive comments themselves | |
247 | + # as evaluations | |
248 | + # returns = the number of comments to be sum on the statistics | |
249 | + def more_comments_count owner | |
250 | + nil | |
251 | + end | |
252 | + | |
244 | 253 | # -> Adds tabs to the profile |
245 | 254 | # returns = { :title => title, :id => id, :content => content, :start => start } |
246 | 255 | # title = name that will be displayed. | ... | ... |
lib/noosfero/plugin/parent_methods.rb
... | ... | @@ -54,6 +54,11 @@ class Noosfero::Plugin |
54 | 54 | File.exists?(File.join(root_path, 'controllers', "#{self.identifier}_admin_controller.rb")) |
55 | 55 | end |
56 | 56 | |
57 | + # -> define grape class used to map resource api provided by the plugin | |
58 | + def api_mount_points | |
59 | + [] | |
60 | + end | |
61 | + | |
57 | 62 | def controllers |
58 | 63 | @controllers ||= Dir.glob("#{self.root_path}/controllers/*/*").map do |controller_file| |
59 | 64 | next unless controller_file =~ /_controller.rb$/ | ... | ... |
... | ... | @@ -0,0 +1,20 @@ |
1 | +# our defaults | |
2 | +listen "0.0.0.0:3000" | |
3 | +pid 'tmp/pids/unicorn.pid' | |
4 | + | |
5 | +preload_app true | |
6 | +GC.respond_to?(:copy_on_write_friendly=) and | |
7 | + GC.copy_on_write_friendly = true | |
8 | + | |
9 | +before_fork do |server, worker| | |
10 | + ActiveRecord::Base.connection.disconnect! if defined?(ActiveRecord::Base) | |
11 | +end | |
12 | + | |
13 | +after_fork do |server, worker| | |
14 | + ActiveRecord::Base.establish_connection if defined?(ActiveRecord::Base) | |
15 | +end | |
16 | + | |
17 | +# load local configuration file, if it exists | |
18 | +config = File.join(File.dirname(__FILE__), '../../config/unicorn.rb') | |
19 | +instance_eval(File.read(config), config) if File.exists?(config) | |
20 | + | ... | ... |
lib/noosfero/version.rb
... | ... | @@ -0,0 +1,21 @@ |
1 | +module TimeScopes | |
2 | + def self.included(recipient) | |
3 | + recipient.extend(ClassMethods) | |
4 | + end | |
5 | + | |
6 | + module ClassMethods | |
7 | + def self.extended (base) | |
8 | + if base.respond_to?(:scope) && base.attribute_names.include?('created_at') | |
9 | + base.class_eval do | |
10 | + scope :younger_than, lambda { |created_at| | |
11 | + {:conditions => ["#{table_name}.created_at > ?", created_at]} | |
12 | + } | |
13 | + | |
14 | + scope :older_than, lambda { |created_at| | |
15 | + {:conditions => ["#{table_name}.created_at < ?", created_at]} | |
16 | + } | |
17 | + end | |
18 | + end | |
19 | + end | |
20 | + end | |
21 | +end | ... | ... |
plugins/ldap/README.md
plugins/ldap/lib/ldap_authentication.rb
... | ... | @@ -19,6 +19,7 @@ require 'rubygems' |
19 | 19 | require 'iconv' |
20 | 20 | require 'net/ldap' |
21 | 21 | require 'net/ldap/dn' |
22 | +require 'magic' | |
22 | 23 | |
23 | 24 | class LdapAuthentication |
24 | 25 | |
... | ... | @@ -134,7 +135,18 @@ class LdapAuthentication |
134 | 135 | |
135 | 136 | def self.get_attr(entry, attr_name) |
136 | 137 | if !attr_name.blank? |
137 | - entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name] | |
138 | + val = entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name] | |
139 | + if val.nil? | |
140 | + Rails.logger.warn "LDAP entry #{entry.dn} has no attr #{attr_name}." | |
141 | + nil | |
142 | + elsif val == '' || val == ' ' | |
143 | + Rails.logger.warn "LDAP entry #{entry.dn} has attr #{attr_name} empty." | |
144 | + '' | |
145 | + else | |
146 | + charset = Magic.guess_string_mime_encoding(val) | |
147 | + val.encode 'utf-8', charset, invalid: :replace, undef: :replace | |
148 | + end | |
138 | 149 | end |
139 | 150 | end |
151 | + | |
140 | 152 | end | ... | ... |
plugins/ldap/test/unit/ldap_authentication_test.rb
1 | +# encoding: UTF-8 | |
1 | 2 | require File.dirname(__FILE__) + '/../test_helper' |
2 | 3 | |
3 | 4 | class LdapAuthenticationTest < ActiveSupport::TestCase |
4 | 5 | |
6 | + def pseudoEntry(data) | |
7 | + entry = data.clone | |
8 | + def entry.dn; 'testDN'; end | |
9 | + entry | |
10 | + end | |
11 | + | |
5 | 12 | def setup |
6 | 13 | @ldap_config = load_ldap_config |
7 | 14 | end |
8 | 15 | |
9 | - should "host be nil as default" do | |
16 | + should 'host be nil as default' do | |
10 | 17 | ldap = LdapAuthentication.new |
11 | 18 | assert_nil ldap.host |
12 | 19 | end |
13 | 20 | |
14 | - should "create with host passed as parameter" do | |
21 | + should 'create with host passed as parameter' do | |
15 | 22 | value = 'http://myhost.com' |
16 | 23 | ldap = LdapAuthentication.new('host' => value) |
17 | 24 | assert_equal value, ldap.host |
18 | 25 | end |
19 | 26 | |
20 | - should "port be 389 as default" do | |
27 | + should 'port be 389 as default' do | |
21 | 28 | ldap = LdapAuthentication.new |
22 | 29 | assert_equal 389, ldap.port |
23 | 30 | end |
24 | 31 | |
25 | - should "create with port passed as parameter" do | |
32 | + should 'create with port passed as parameter' do | |
26 | 33 | value = 555 |
27 | 34 | ldap = LdapAuthentication.new('port' => value) |
28 | 35 | assert_equal value, ldap.port |
29 | 36 | end |
30 | 37 | |
31 | - should "account be nil as default" do | |
38 | + should 'account be nil as default' do | |
32 | 39 | ldap = LdapAuthentication.new |
33 | 40 | assert_nil ldap.account |
34 | 41 | end |
35 | 42 | |
36 | - should "create with account passed as parameter" do | |
43 | + should 'create with account passed as parameter' do | |
37 | 44 | value = 'uid=sector,ou=Service,ou=corp,dc=company,dc=com,dc=br' |
38 | 45 | ldap = LdapAuthentication.new('account' => value) |
39 | 46 | assert_equal value, ldap.account |
40 | 47 | end |
41 | 48 | |
42 | - should "account_password be nil as default" do | |
49 | + should 'account_password be nil as default' do | |
43 | 50 | ldap = LdapAuthentication.new |
44 | 51 | assert_nil ldap.account_password |
45 | 52 | end |
46 | 53 | |
47 | - should "create with account_password passed as parameter" do | |
54 | + should 'create with account_password passed as parameter' do | |
48 | 55 | value = 'password' |
49 | 56 | ldap = LdapAuthentication.new('account_password' => value) |
50 | 57 | assert_equal value, ldap.account_password |
51 | 58 | end |
52 | 59 | |
53 | - should "base_dn be nil as default" do | |
60 | + should 'base_dn be nil as default' do | |
54 | 61 | ldap = LdapAuthentication.new |
55 | 62 | assert_nil ldap.base_dn |
56 | 63 | end |
57 | 64 | |
58 | - should "create with base_dn passed as parameter" do | |
65 | + should 'create with base_dn passed as parameter' do | |
59 | 66 | value = 'dc=company,dc=com,dc=br' |
60 | 67 | ldap = LdapAuthentication.new('base_dn' => value) |
61 | 68 | assert_equal value, ldap.base_dn |
62 | 69 | end |
63 | 70 | |
64 | - should "attr_login be nil as default" do | |
71 | + should 'attr_login be nil as default' do | |
65 | 72 | ldap = LdapAuthentication.new |
66 | 73 | assert_nil ldap.attr_login |
67 | 74 | end |
68 | 75 | |
69 | - should "create with attr_login passed as parameter" do | |
76 | + should 'create with attr_login passed as parameter' do | |
70 | 77 | value = 'uid' |
71 | 78 | ldap = LdapAuthentication.new('attr_login' => value) |
72 | 79 | assert_equal value, ldap.attr_login |
73 | 80 | end |
74 | 81 | |
75 | - should "attr_fullname be nil as default" do | |
82 | + should 'attr_fullname be nil as default' do | |
76 | 83 | ldap = LdapAuthentication.new |
77 | 84 | assert_nil ldap.attr_fullname |
78 | 85 | end |
79 | 86 | |
80 | - should "create with attr_fullname passed as parameter" do | |
87 | + should 'create with attr_fullname passed as parameter' do | |
81 | 88 | value = 'Noosfero System' |
82 | 89 | ldap = LdapAuthentication.new('attr_fullname' => value) |
83 | 90 | assert_equal value, ldap.attr_fullname |
84 | 91 | end |
85 | 92 | |
86 | - should "attr_mail be nil as default" do | |
93 | + should 'attr_mail be nil as default' do | |
87 | 94 | ldap = LdapAuthentication.new |
88 | 95 | assert_nil ldap.attr_mail |
89 | 96 | end |
90 | 97 | |
91 | - should "create with attr_mail passed as parameter" do | |
98 | + should 'create with attr_mail passed as parameter' do | |
92 | 99 | value = 'test@noosfero.com' |
93 | 100 | ldap = LdapAuthentication.new('attr_mail' => value) |
94 | 101 | assert_equal value, ldap.attr_mail |
95 | 102 | end |
96 | 103 | |
97 | - should "onthefly_register be false as default" do | |
104 | + should 'onthefly_register be false as default' do | |
98 | 105 | ldap = LdapAuthentication.new |
99 | 106 | refute ldap.onthefly_register |
100 | 107 | end |
101 | 108 | |
102 | - should "create with onthefly_register passed as parameter" do | |
109 | + should 'create with onthefly_register passed as parameter' do | |
103 | 110 | value = true |
104 | 111 | ldap = LdapAuthentication.new('onthefly_register' => value) |
105 | 112 | assert_equal value, ldap.onthefly_register |
106 | 113 | end |
107 | 114 | |
108 | - should "filter be nil as default" do | |
115 | + should 'filter be nil as default' do | |
109 | 116 | ldap = LdapAuthentication.new |
110 | 117 | assert_nil ldap.filter |
111 | 118 | end |
112 | 119 | |
113 | - should "create with filter passed as parameter" do | |
120 | + should 'create with filter passed as parameter' do | |
114 | 121 | value = 'test' |
115 | 122 | ldap = LdapAuthentication.new('filter' => value) |
116 | 123 | assert_equal value, ldap.filter |
117 | 124 | end |
118 | 125 | |
119 | - should "tls be false as default" do | |
126 | + should 'tls be false as default' do | |
120 | 127 | ldap = LdapAuthentication.new |
121 | 128 | refute ldap.tls |
122 | 129 | end |
123 | 130 | |
124 | - should "create with tls passed as parameter" do | |
131 | + should 'create with tls passed as parameter' do | |
125 | 132 | value = true |
126 | 133 | ldap = LdapAuthentication.new('tls' => value) |
127 | 134 | assert_equal value, ldap.tls |
128 | 135 | end |
129 | 136 | |
130 | - should "onthefly_register? return true if onthefly_register is true" do | |
137 | + should 'onthefly_register? return true if onthefly_register is true' do | |
131 | 138 | ldap = LdapAuthentication.new('onthefly_register' => true) |
132 | 139 | assert ldap.onthefly_register? |
133 | 140 | end |
134 | 141 | |
135 | - should "onthefly_register? return false if onthefly_register is false" do | |
142 | + should 'onthefly_register? return false if onthefly_register is false' do | |
136 | 143 | ldap = LdapAuthentication.new('onthefly_register' => false) |
137 | 144 | refute ldap.onthefly_register? |
138 | 145 | end |
139 | 146 | |
147 | + should 'detect and convert non utf-8 charset from ldap' do | |
148 | + entry = pseudoEntry('name' => "Jos\xE9 Jo\xE3o") | |
149 | + name = LdapAuthentication.get_attr entry, 'name' | |
150 | + assert_equal name, 'José João' | |
151 | + end | |
152 | + | |
153 | + should 'dont crash when entry key is empty string' do | |
154 | + entry = pseudoEntry('name' => "") | |
155 | + name = LdapAuthentication.get_attr entry, 'name' | |
156 | + assert_equal name, '' | |
157 | + end | |
158 | + | |
159 | + should 'dont crash when entry key has only a space char' do | |
160 | + entry = pseudoEntry('name' => " ") | |
161 | + name = LdapAuthentication.get_attr entry, 'name' | |
162 | + assert_equal name, '' | |
163 | + end | |
164 | + | |
165 | + should 'dont crash when entry key is nil' do | |
166 | + entry = pseudoEntry('name' => nil) | |
167 | + name = LdapAuthentication.get_attr entry, 'name' | |
168 | + assert_equal name, nil | |
169 | + end | |
170 | + | |
171 | + should 'dont crash when entry key does not exists' do | |
172 | + entry = pseudoEntry({}) | |
173 | + name = LdapAuthentication.get_attr entry, 'name' | |
174 | + assert_equal name, nil | |
175 | + end | |
176 | + | |
140 | 177 | if ldap_configured? |
141 | 178 | should 'return the user attributes' do |
142 | 179 | auth = LdapAuthentication.new(@ldap_config['server']) | ... | ... |
plugins/organization_ratings/controllers/organization_ratings_plugin_admin_controller.rb
0 → 100644
... | ... | @@ -0,0 +1,19 @@ |
1 | +class OrganizationRatingsPluginAdminController < PluginAdminController | |
2 | + | |
3 | + include RatingsHelper | |
4 | + helper :ratings | |
5 | + append_view_path File.join(File.dirname(__FILE__) + '/../views') | |
6 | + | |
7 | + def index | |
8 | + end | |
9 | + | |
10 | + def update | |
11 | + if env_organization_ratings_config.update_attributes(params[:organization_ratings_config]) | |
12 | + session[:notice] = _('Configuration updated successfully.') | |
13 | + else | |
14 | + session[:notice] = _('Configuration could not be saved.') | |
15 | + end | |
16 | + render :action => 'index' | |
17 | + end | |
18 | + | |
19 | +end | |
0 | 20 | \ No newline at end of file | ... | ... |
plugins/organization_ratings/controllers/organization_ratings_plugin_profile_controller.rb
0 → 100644
... | ... | @@ -0,0 +1,76 @@ |
1 | +class OrganizationRatingsPluginProfileController < ProfileController | |
2 | + include RatingsHelper | |
3 | + helper :ratings | |
4 | + | |
5 | + def new_rating | |
6 | + @rating_available = user_can_rate_now? | |
7 | + @users_ratings = get_ratings(profile.id).paginate( | |
8 | + :per_page => env_organization_ratings_config.per_page, | |
9 | + :page => params[:npage] | |
10 | + ) | |
11 | + if request.post? | |
12 | + if @rating_available | |
13 | + create_new_rate | |
14 | + else | |
15 | + session[:notice] = _("You can not vote on this %s") % profile.class.name | |
16 | + end | |
17 | + end | |
18 | + end | |
19 | + | |
20 | + private | |
21 | + | |
22 | + def user_can_rate_now? | |
23 | + return false unless user | |
24 | + ratings = OrganizationRating.where( | |
25 | + :organization_id => profile.id, | |
26 | + :person_id => user.id | |
27 | + ) | |
28 | + | |
29 | + return false if (!ratings.empty? && env_organization_ratings_config.vote_once) | |
30 | + | |
31 | + if ratings.empty? | |
32 | + true | |
33 | + else | |
34 | + elapsed_time_since_last_rating = Time.zone.now - ratings.last.created_at | |
35 | + elapsed_time_since_last_rating > env_organization_ratings_config.cooldown.hours | |
36 | + end | |
37 | + end | |
38 | + | |
39 | + def create_new_rate | |
40 | + rating = OrganizationRating.new(params[:organization_rating]) | |
41 | + rating.person = current_user.person | |
42 | + rating.organization = profile | |
43 | + rating.value = params[:organization_rating_value] if params[:organization_rating_value] | |
44 | + | |
45 | + if rating.save | |
46 | + create_rating_comment(rating) | |
47 | + session[:notice] = _("%s successfully rated!") % profile.name | |
48 | + else | |
49 | + session[:notice] = _("Sorry, there were problems rating this profile.") | |
50 | + end | |
51 | + | |
52 | + redirect_to :controller => 'profile', :action => 'index' | |
53 | + end | |
54 | + | |
55 | + def create_rating_comment(rating) | |
56 | + if params[:comments] | |
57 | + comment_task = CreateOrganizationRatingComment.create!( | |
58 | + params[:comments].merge( | |
59 | + :requestor => rating.person, | |
60 | + :organization_rating_id => rating.id, | |
61 | + :target => rating.organization | |
62 | + ) | |
63 | + ) | |
64 | + comment_task.finish if can_perform?(params) | |
65 | + end | |
66 | + end | |
67 | + | |
68 | + def can_perform? (params) | |
69 | + (params[:comments][:body].blank? || | |
70 | + !env_organization_ratings_config.are_moderated) | |
71 | + end | |
72 | + | |
73 | + def permission | |
74 | + :manage_memberships | |
75 | + end | |
76 | +end | ... | ... |
plugins/organization_ratings/db/migrate/20150830225546_create_organization_ratings.rb
0 → 100644
... | ... | @@ -0,0 +1,12 @@ |
1 | +class CreateOrganizationRatings < ActiveRecord::Migration | |
2 | + def change | |
3 | + create_table :organization_ratings do |t| | |
4 | + t.belongs_to :organization | |
5 | + t.belongs_to :person | |
6 | + t.belongs_to :comment | |
7 | + t.integer :value | |
8 | + | |
9 | + t.timestamps | |
10 | + end | |
11 | + end | |
12 | +end | ... | ... |
plugins/organization_ratings/db/migrate/20150830225733_create_organization_ratings_config.rb
0 → 100644
... | ... | @@ -0,0 +1,14 @@ |
1 | +class CreateOrganizationRatingsConfig < ActiveRecord::Migration | |
2 | + | |
3 | + def change | |
4 | + create_table :organization_ratings_configs do |t| | |
5 | + t.belongs_to :environment | |
6 | + t.integer :cooldown, :integer, :default => 24 | |
7 | + t.integer :default_rating, :integer, :default => 1 | |
8 | + t.string :order, :string, :default => "recent" | |
9 | + t.integer :per_page, :integer, :default => 10 | |
10 | + t.boolean :vote_once, :boolean, :default => false | |
11 | + t.boolean :are_moderated, :boolean, :default => true | |
12 | + end | |
13 | + end | |
14 | +end | ... | ... |
plugins/organization_ratings/db/migrate/20150830230047_add_comments_count_to_profile.rb
0 → 100644
... | ... | @@ -0,0 +1,11 @@ |
1 | +class AddCommentsCountToProfile < ActiveRecord::Migration | |
2 | + def self.up | |
3 | + change_table :profiles do |t| | |
4 | + t.integer :comments_count | |
5 | + end | |
6 | + end | |
7 | + | |
8 | + def self.down | |
9 | + remove_column :profiles, :comments_count | |
10 | + end | |
11 | +end | |
0 | 12 | \ No newline at end of file | ... | ... |
plugins/organization_ratings/features/rate_community.feature
0 → 100644
... | ... | @@ -0,0 +1,30 @@ |
1 | +Feature: rate_community | |
2 | + As a user | |
3 | + I want to be able rate a community | |
4 | + So that users can see my feedback about that community | |
5 | + | |
6 | + Background: | |
7 | + Given plugin "OrganizationRatings" is enabled on environment | |
8 | + Given the following user | |
9 | + | login | name | | |
10 | + | joaosilva | Joao Silva | | |
11 | + And the following community | |
12 | + | identifier | name | | |
13 | + | mycommunity | My Community | | |
14 | + And the following blocks | |
15 | + | owner | type | | |
16 | + | mycommunity | AverageRatingBlock | | |
17 | + | mycommunity | OrganizationRatingsBlock | | |
18 | + And the environment domain is "localhost" | |
19 | + And I am logged in as "joaosilva" | |
20 | + | |
21 | + @selenium | |
22 | + Scenario: display rate button inside average block | |
23 | + Given I am on mycommunity's homepage | |
24 | + Then I should see "Rate this Community" within ".average-rating-block" | |
25 | + And I should see "Be the first to rate" within ".average-rating-block" | |
26 | + | |
27 | + @selenium | |
28 | + Scenario: display rate button inside communities ratings block | |
29 | + Given I am on mycommunity's homepage | |
30 | + Then I should see "Rate Community" within ".make-report-block" | ... | ... |
plugins/organization_ratings/lib/average_rating_block.rb
0 → 100644
... | ... | @@ -0,0 +1,30 @@ |
1 | +class AverageRatingBlock < Block | |
2 | + include RatingsHelper | |
3 | + | |
4 | + def self.description | |
5 | + _('Organization Average Rating') | |
6 | + end | |
7 | + | |
8 | + def help | |
9 | + _('This block displays the organization average rating.') | |
10 | + end | |
11 | + | |
12 | + def content(args = {}) | |
13 | + profile_identifier = self.owner.identifier | |
14 | + average_rating = OrganizationRating.average_rating self.owner.id | |
15 | + | |
16 | + proc do | |
17 | + render( | |
18 | + :file => 'blocks/display_organization_average_rating', | |
19 | + :locals => { | |
20 | + :profile_identifier => profile_identifier, | |
21 | + :average_rating => average_rating | |
22 | + } | |
23 | + ) | |
24 | + end | |
25 | + end | |
26 | + | |
27 | + def cacheable? | |
28 | + false | |
29 | + end | |
30 | +end | ... | ... |
plugins/organization_ratings/lib/create_organization_rating_comment.rb
0 → 100644
... | ... | @@ -0,0 +1,131 @@ |
1 | +class CreateOrganizationRatingComment < Task | |
2 | + include Rails.application.routes.url_helpers | |
3 | + | |
4 | + validates_presence_of :requestor_id, :organization_rating_id, :target_id | |
5 | + | |
6 | + settings_items :organization_rating_id, :type => Integer, :default => nil | |
7 | + settings_items :organization_rating_comment_id, :type => Integer, :default => nil | |
8 | + | |
9 | + attr_accessible :organization_rating_id, :body, :requestor | |
10 | + attr_accessible :reject_explanation, :target | |
11 | + | |
12 | + before_save :update_comment_body | |
13 | + | |
14 | + DATA_FIELDS = ['body'] | |
15 | + DATA_FIELDS.each do |field| | |
16 | + settings_items field.to_sym | |
17 | + end | |
18 | + | |
19 | + def update_comment_body | |
20 | + if self.organization_rating_comment_id.nil? | |
21 | + create_comment | |
22 | + else | |
23 | + comment = Comment.find_by_id(self.organization_rating_comment_id) | |
24 | + comment.body = get_comment_message | |
25 | + comment.save | |
26 | + end | |
27 | + end | |
28 | + | |
29 | + def create_comment | |
30 | + if (self.body && !self.body.blank?) | |
31 | + comment_body = _("Comment waiting for approval") | |
32 | + comment = Comment.create!(:source => self.target, :body => comment_body, :author => self.requestor) | |
33 | + | |
34 | + | |
35 | + self.organization_rating_comment_id = comment.id | |
36 | + link_comment_with_its_rating(comment) | |
37 | + end | |
38 | + end | |
39 | + | |
40 | + def link_comment_with_its_rating(user_comment) | |
41 | + rating = OrganizationRating.find(self.organization_rating_id) | |
42 | + rating.comment = user_comment | |
43 | + rating.save | |
44 | + end | |
45 | + | |
46 | + def get_comment_message | |
47 | + if self.status == Status::CANCELLED | |
48 | + _("Comment rejected") | |
49 | + elsif self.status == Status::FINISHED | |
50 | + self.body | |
51 | + else | |
52 | + _("No comment") | |
53 | + end | |
54 | + end | |
55 | + | |
56 | + def accept_details | |
57 | + true | |
58 | + end | |
59 | + | |
60 | + def title | |
61 | + _("New Comment") | |
62 | + end | |
63 | + | |
64 | + def information | |
65 | + message = _("<a href=%{requestor_url}>%{requestor}</a> wants to create a comment in this %{target_class}") % | |
66 | + {:requestor_url => url_for(self.requestor.url), :requestor => self.requestor.name, :target_class => self.target.class.name.downcase} | |
67 | + | |
68 | + {:message => message} | |
69 | + end | |
70 | + | |
71 | + def reject_details | |
72 | + true | |
73 | + end | |
74 | + | |
75 | + def icon | |
76 | + {:type => :profile_image, :profile => requestor, :url => requestor.url} | |
77 | + end | |
78 | + | |
79 | + # tells if this request was rejected | |
80 | + def rejected? | |
81 | + self.status == Task::Status::CANCELLED | |
82 | + end | |
83 | + | |
84 | + # tells if this request was appoved | |
85 | + def approved? | |
86 | + self.status == Task::Status::FINISHED | |
87 | + end | |
88 | + | |
89 | + def target_notification_description | |
90 | + _("%{requestor} wants to create a comment in this \"%{target}\"") % | |
91 | + {:requestor => self.requestor.name, :target => self.target.class.name.downcase } | |
92 | + end | |
93 | + | |
94 | + def target_notification_message | |
95 | + _("User \"%{user}\" just requested to create a comment in the %{target_class} | |
96 | + \"%{target_name}\". | |
97 | + You have to approve or reject it through the \"Pending Validations\" | |
98 | + section in your control panel.\n") % | |
99 | + { :user => self.requestor.name, :target_class => self.target.class.name.downcase, :target_name => self.target.name } | |
100 | + end | |
101 | + | |
102 | + def task_created_message | |
103 | + _("Your request for commenting at %{target} was | |
104 | + just sent. Environment administrator will receive it and will approve or | |
105 | + reject your request according to his methods and criteria. | |
106 | + You will be notified as soon as environment administrator has a position | |
107 | + about your request.") % | |
108 | + { :target => self.target.name } | |
109 | + end | |
110 | + | |
111 | + def task_cancelled_message | |
112 | + _("Your request for commenting at %{target} was | |
113 | + not approved by the environment administrator. The following explanation | |
114 | + was given: \n\n%{explanation}") % | |
115 | + { :target => self.target.name, | |
116 | + :explanation => self.reject_explanation } | |
117 | + end | |
118 | + | |
119 | + def task_finished_message | |
120 | + _('Your request for commenting was approved. | |
121 | + You can access %{url} to see your comment.') % | |
122 | + { :url => ratings_url } | |
123 | + end | |
124 | + | |
125 | + private | |
126 | + | |
127 | + def ratings_url | |
128 | + url = url_for(self.target.public_profile_url) + "/plugin/organization_ratings/new_rating" | |
129 | + end | |
130 | + | |
131 | +end | ... | ... |
... | ... | @@ -0,0 +1,25 @@ |
1 | +class OrganizationRating < ActiveRecord::Base | |
2 | + belongs_to :person | |
3 | + belongs_to :organization | |
4 | + belongs_to :comment | |
5 | + | |
6 | + attr_accessible :value, :person, :organization, :comment | |
7 | + | |
8 | + validates :value, | |
9 | + :presence => true, :inclusion => { | |
10 | + in: 1..5, message: _("must be between 1 and 5") | |
11 | + } | |
12 | + | |
13 | + validates :organization_id, :person_id, | |
14 | + :presence => true | |
15 | + | |
16 | + | |
17 | + def self.average_rating organization_id | |
18 | + average = OrganizationRating.where(organization_id: organization_id).average(:value) | |
19 | + | |
20 | + if average | |
21 | + (average - average.truncate) >= 0.5 ? average.ceil : average.floor | |
22 | + end | |
23 | + end | |
24 | + | |
25 | +end | ... | ... |
plugins/organization_ratings/lib/organization_ratings_block.rb
0 → 100644
... | ... | @@ -0,0 +1,30 @@ |
1 | +class OrganizationRatingsBlock < Block | |
2 | + include RatingsHelper | |
3 | + | |
4 | + def self.description | |
5 | + _('Organization Ratings') | |
6 | + end | |
7 | + | |
8 | + def help | |
9 | + _('This block displays the community ratings.') | |
10 | + end | |
11 | + | |
12 | + def content(args = {}) | |
13 | + block = self | |
14 | + | |
15 | + proc do | |
16 | + render( | |
17 | + :file => 'blocks/organization_ratings_block', | |
18 | + :locals => {:block => block} | |
19 | + ) | |
20 | + end | |
21 | + end | |
22 | + | |
23 | + def limit_number_of_ratings | |
24 | + env_organization_ratings_config.per_page | |
25 | + end | |
26 | + | |
27 | + def cacheable? | |
28 | + false | |
29 | + end | |
30 | +end | ... | ... |
plugins/organization_ratings/lib/organization_ratings_config.rb
0 → 100644
... | ... | @@ -0,0 +1,53 @@ |
1 | +class OrganizationRatingsConfig < ActiveRecord::Base | |
2 | + | |
3 | + belongs_to :environment | |
4 | + | |
5 | + attr_accessible :cooldown, :default_rating, :order, :per_page | |
6 | + attr_accessible :vote_once, :are_moderated, :environment_id | |
7 | + | |
8 | + ORDER_OPTIONS = {recent: _('More Recent'), best: _('Best Ratings')} | |
9 | + | |
10 | + MINIMUM_RATING = 1 | |
11 | + MAX_COOLDOWN = 1000 | |
12 | + | |
13 | + validates :default_rating, | |
14 | + :presence => true, :numericality => { | |
15 | + greater_than_or_equal_to: MINIMUM_RATING, | |
16 | + less_than_or_equal_to: 5 | |
17 | + } | |
18 | + | |
19 | + validates :cooldown, | |
20 | + :presence => true, :numericality => { | |
21 | + greater_than_or_equal_to: 0, | |
22 | + less_than_or_equal_to: MAX_COOLDOWN | |
23 | + } | |
24 | + | |
25 | + validates :per_page, | |
26 | + :presence => true, :numericality => { | |
27 | + :greater_than_or_equal_to => 5, | |
28 | + :less_than_or_equal_to => 20 | |
29 | + } | |
30 | + | |
31 | + | |
32 | + def order_options | |
33 | + ORDER_OPTIONS | |
34 | + end | |
35 | + | |
36 | + def minimum_ratings | |
37 | + MINIMUM_RATING | |
38 | + end | |
39 | + | |
40 | + def max_cooldown | |
41 | + MAX_COOLDOWN | |
42 | + end | |
43 | + | |
44 | + class << self | |
45 | + def instance | |
46 | + environment = Environment.default | |
47 | + environment.organization_ratings_config || create(environment_id: environment.id) | |
48 | + end | |
49 | + | |
50 | + private :new | |
51 | + end | |
52 | + | |
53 | +end | ... | ... |
plugins/organization_ratings/lib/organization_ratings_plugin.rb
0 → 100644
... | ... | @@ -0,0 +1,73 @@ |
1 | +class OrganizationRatingsPlugin < Noosfero::Plugin | |
2 | + include Noosfero::Plugin::HotSpot | |
3 | + | |
4 | + def self.plugin_name | |
5 | + "Organization Ratings" | |
6 | + end | |
7 | + | |
8 | + def self.plugin_description | |
9 | + _("A plugin that allows you to rate a organization and comment about it.") | |
10 | + end | |
11 | + | |
12 | + module Hotspots | |
13 | + def organization_ratings_plugin_comments_extra_fields | |
14 | + nil | |
15 | + end | |
16 | + | |
17 | + def organization_ratings_title | |
18 | + nil | |
19 | + end | |
20 | + | |
21 | + def organization_ratings_plugin_star_message | |
22 | + nil | |
23 | + end | |
24 | + | |
25 | + def organization_ratings_plugin_extra_fields_show_data user_rating | |
26 | + nil | |
27 | + end | |
28 | + end | |
29 | + | |
30 | + # Plugin Hotspot to display the average rating | |
31 | + def display_organization_average_rating organization | |
32 | + unless organization.nil? | |
33 | + average_rating = OrganizationRating.average_rating organization.id | |
34 | + | |
35 | + Proc::new { | |
36 | + render :file => 'blocks/display_organization_average_rating', | |
37 | + :locals => { | |
38 | + :profile_identifier => organization.identifier, | |
39 | + :average_rating => average_rating | |
40 | + } | |
41 | + } | |
42 | + end | |
43 | + end | |
44 | + | |
45 | + def more_comments_count owner | |
46 | + if owner.kind_of?(Environment) then | |
47 | + owner.profiles.sum(:comments_count) | |
48 | + elsif owner.kind_of?(Profile) then | |
49 | + owner.comments_count | |
50 | + else | |
51 | + 0 | |
52 | + end | |
53 | + end | |
54 | + | |
55 | + def self.extra_blocks | |
56 | + { | |
57 | + OrganizationRatingsBlock => {:type => [Enterprise, Community], :position => ['1']}, | |
58 | + AverageRatingBlock => {:type => [Enterprise, Community]} | |
59 | + } | |
60 | + end | |
61 | + | |
62 | + def stylesheet? | |
63 | + true | |
64 | + end | |
65 | + | |
66 | + def js_files | |
67 | + %w( | |
68 | + public/rate.js | |
69 | + public/comunities_rating_management.js | |
70 | + ) | |
71 | + end | |
72 | + | |
73 | +end | ... | ... |
... | ... | @@ -0,0 +1,15 @@ |
1 | +module RatingsHelper | |
2 | + | |
3 | + def env_organization_ratings_config | |
4 | + OrganizationRatingsConfig.instance | |
5 | + end | |
6 | + | |
7 | + def get_ratings (profile_id) | |
8 | + order_options = env_organization_ratings_config.order_options | |
9 | + if env_organization_ratings_config.order.downcase == order_options[:recent] | |
10 | + ratings = OrganizationRating.where(organization_id: profile_id).order("value DESC") | |
11 | + else | |
12 | + ratings = OrganizationRating.where(organization_id: profile_id).order("created_at DESC") | |
13 | + end | |
14 | + end | |
15 | +end | |
0 | 16 | \ No newline at end of file | ... | ... |
529 Bytes