diff --git a/app/controllers/explore_controller.rb b/app/controllers/explore_controller.rb index 6f24eba5..59045d5d 100644 --- a/app/controllers/explore_controller.rb +++ b/app/controllers/explore_controller.rb @@ -1,19 +1,15 @@ # frozen_string_literal: true class ExploreController < ApplicationController + before_action :require_authentication, only: [:mine, :shared, :starred] before_action :authorize_explore after_action :verify_authorized after_action :verify_policy_scoped respond_to :html, :json, :csv - # TODO: remove? - # autocomplete :map, :name, full: true, extra_data: [:user_id] - # GET /explore/active def active - page = params[:page].present? ? params[:page] : 1 - @maps = policy_scope(Map).order('updated_at DESC') - .page(page).per(20) + @maps = map_scope(Map) respond_to do |format| format.html do @@ -27,11 +23,7 @@ class ExploreController < ApplicationController # GET /explore/featured def featured - page = params[:page].present? ? params[:page] : 1 - @maps = policy_scope( - Map.where('maps.featured = ? AND maps.permission != ?', - true, 'private') - ).order('updated_at DESC').page(page).per(20) + @maps = map_scope(Map.where(featured: true)) respond_to do |format| format.html { respond_with(@maps, @user) } @@ -41,15 +33,7 @@ class ExploreController < ApplicationController # GET /explore/mine def mine - unless authenticated? - skip_policy_scope - return redirect_to explore_active_path - end - - page = params[:page].present? ? params[:page] : 1 - @maps = policy_scope( - Map.where('maps.user_id = ?', current_user.id) - ).order('updated_at DESC').page(page).per(20) + @maps = map_scope(Map.where(user_id: current_user.id)) respond_to do |format| format.html { respond_with(@maps, @user) } @@ -59,15 +43,7 @@ class ExploreController < ApplicationController # GET /explore/shared def shared - unless authenticated? - skip_policy_scope - return redirect_to explore_active_path - end - - page = params[:page].present? ? params[:page] : 1 - @maps = policy_scope( - Map.where('maps.id IN (?)', current_user.shared_maps.map(&:id)) - ).order('updated_at DESC').page(page).per(20) + @maps = map_scope(Map.where(id: current_user.shared_maps.map(&:id))) respond_to do |format| format.html { respond_with(@maps, @user) } @@ -77,16 +53,7 @@ class ExploreController < ApplicationController # GET /explore/starred def starred - unless authenticated? - skip_policy_scope - return redirect_to explore_active_path - end - - page = params[:page].present? ? params[:page] : 1 - stars = current_user.stars.map(&:map_id) - @maps = policy_scope( - Map.where('maps.id IN (?)', stars) - ).order('updated_at DESC').page(page).per(20) + @maps = map_scope(Map.where(id: current_user.stars.map(&:map_id))) respond_to do |format| format.html { respond_with(@maps, @user) } @@ -96,10 +63,8 @@ class ExploreController < ApplicationController # GET /explore/mapper/:id def mapper - page = params[:page].present? ? params[:page] : 1 @user = User.find(params[:id]) - @maps = policy_scope(Map.where(user: @user)) - .order('updated_at DESC').page(page).per(20) + @maps = map_scope(Map.where(user: @user)) respond_to do |format| format.html { respond_with(@maps, @user) } @@ -109,7 +74,16 @@ class ExploreController < ApplicationController private + def map_scope(scope) + policy_scope(scope).order(updated_at: :desc).page(params[:page]).per(20) + end + def authorize_explore authorize :Explore end + + def require_authentication + # skip_policy_scope + redirect_to explore_active_path unless authenticated? + end end diff --git a/app/controllers/main_controller.rb b/app/controllers/main_controller.rb index 0a5ccb68..d655ea91 100644 --- a/app/controllers/main_controller.rb +++ b/app/controllers/main_controller.rb @@ -1,172 +1,30 @@ # frozen_string_literal: true class MainController < ApplicationController - include TopicsHelper - include MapsHelper - include UsersHelper - include SynapsesHelper + before_action :authorize_main + after_action :verify_authorized + after_action :verify_policy_scoped, only: [:home] - after_action :verify_policy_scoped, except: [:requestinvite, :searchmappers] - - respond_to :html, :json - - # home page + # GET / def home - @maps = policy_scope(Map).order('updated_at DESC').page(1).per(20) respond_to do |format| format.html do if !authenticated? render 'main/home' else + @maps = policy_scope(Map).order(updated_at: :desc).page(1).per(20) render 'explore/active' end end end end - ### SEARCHING ### - - # get /search/topics?term=SOMETERM - def searchtopics - term = params[:term] - user = params[:user] ? params[:user] : false - - if term && !term.empty? && term.downcase[0..3] != 'map:' && term.downcase[0..6] != 'mapper:' && !term.casecmp('topic:').zero? - - # remove "topic:" if appended at beginning - term = term[6..-1] if term.downcase[0..5] == 'topic:' - - # if desc: search desc instead - desc = false - if term.downcase[0..4] == 'desc:' - term = term[5..-1] - desc = true - end - - # if link: search link instead - link = false - if term.downcase[0..4] == 'link:' - term = term[5..-1] - link = true - end - - # check whether there's a filter by metacode as part of the query - filterByMetacode = false - Metacode.all.each do |m| - lOne = m.name.length + 1 - lTwo = m.name.length - - if term.downcase[0..lTwo] == m.name.downcase + ':' - term = term[lOne..-1] - filterByMetacode = m - end - end - - search = '%' + term.downcase + '%' - builder = policy_scope(Topic) - - if filterByMetacode - if term == '' - builder = builder.none - else - builder = builder.where('LOWER("name") like ? OR - LOWER("desc") like ? OR - LOWER("link") like ?', search, search, search) - builder = builder.where(metacode_id: filterByMetacode.id) - end - elsif desc - builder = builder.where('LOWER("desc") like ?', search) - elsif link - builder = builder.where('LOWER("link") like ?', search) - else # regular case, just search the name - builder = builder.where('LOWER("name") like ? OR - LOWER("desc") like ? OR - LOWER("link") like ?', search, search, search) - end - - builder = builder.where(user: user) if user - @topics = builder.order(:name) - else - @topics = [] - end - - render json: autocomplete_array_json(@topics) + # GET /request + def requestinvite end - # get /search/maps?term=SOMETERM - def searchmaps - term = params[:term] - user = params[:user] ? params[:user] : nil + private - if term && !term.empty? && term.downcase[0..5] != 'topic:' && term.downcase[0..6] != 'mapper:' && !term.casecmp('map:').zero? - - # remove "map:" if appended at beginning - term = term[4..-1] if term.downcase[0..3] == 'map:' - - # if desc: search desc instead - desc = false - if term.downcase[0..4] == 'desc:' - term = term[5..-1] - desc = true - end - - search = '%' + term.downcase + '%' - builder = policy_scope(Map) - - builder = if desc - builder.where('LOWER("desc") like ?', search) - else - builder.where('LOWER("name") like ?', search) - end - builder = builder.where(user: user) if user - @maps = builder.order(:name) - else - @maps = [] - end - - render json: autocomplete_map_array_json(@maps) - end - - # get /search/mappers?term=SOMETERM - def searchmappers - term = params[:term] - if term && !term.empty? && term.downcase[0..3] != 'map:' && term.downcase[0..5] != 'topic:' && !term.casecmp('mapper:').zero? - - # remove "mapper:" if appended at beginning - term = term[7..-1] if term.downcase[0..6] == 'mapper:' - search = term.downcase + '%' - - skip_policy_scope # TODO: builder = policy_scope(User) - builder = User.where('LOWER("name") like ?', search) - @mappers = builder.order(:name) - else - @mappers = [] - end - render json: autocomplete_user_array_json(@mappers) - end - - # get /search/synapses?term=SOMETERM OR - # get /search/synapses?topic1id=SOMEID&topic2id=SOMEID - def searchsynapses - term = params[:term] - topic1id = params[:topic1id] - topic2id = params[:topic2id] - - if term && !term.empty? - @synapses = policy_scope(Synapse).where('LOWER("desc") like ?', '%' + term.downcase + '%').order('"desc"') - - @synapses = @synapses.uniq(&:desc) - elsif topic1id && !topic1id.empty? - @one = policy_scope(Synapse).where('topic1_id = ? AND topic2_id = ?', topic1id, topic2id) - @two = policy_scope(Synapse).where('topic2_id = ? AND topic1_id = ?', topic1id, topic2id) - @synapses = @one + @two - @synapses.sort! { |s1, s2| s1.desc <=> s2.desc }.to_a - else - @synapses = [] - end - - # limit to 5 results - @synapses = @synapses.to_a.slice(0, 5) - - render json: autocomplete_synapse_array_json(@synapses) + def authorize_main + authorize :Main end end diff --git a/app/controllers/maps_controller.rb b/app/controllers/maps_controller.rb index 0a0b11d5..b24f85fc 100644 --- a/app/controllers/maps_controller.rb +++ b/app/controllers/maps_controller.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true class MapsController < ApplicationController - before_action :require_user, only: [:create, :update, :destroy, :access, :events, :screenshot, :star, :unstar] - before_action :set_map, only: [:show, :update, :destroy, :access, :contains, :events, :export, :screenshot, :star, :unstar] + before_action :require_user, only: [:create, :update, :destroy, :access, :events, + :screenshot] + before_action :set_map, only: [:show, :update, :destroy, :access, :contains, + :events, :export, :screenshot] after_action :verify_authorized autocomplete :map, :name, full: true, extra_data: [:user_id] @@ -18,7 +20,8 @@ class MapsController < ApplicationController @allmessages = @map.messages.sort_by(&:created_at) @allstars = @map.stars - respond_with(@allmappers, @allcollaborators, @allmappings, @allsynapses, @alltopics, @allmessages, @allstars, @map) + respond_with(@allmappers, @allcollaborators, @allmappings, @allsynapses, + @alltopics, @allmessages, @allstars, @map) end format.json { render json: @map } format.csv { redirect_to action: :export, format: :csv } @@ -41,10 +44,10 @@ class MapsController < ApplicationController # POST maps def create - @user = current_user @map = Map.new(create_map_params) - @map.user = @user + @map.user = current_user @map.arranged = false + authorize @map if params[:topicsToMap].present? create_topics! @@ -52,8 +55,6 @@ class MapsController < ApplicationController @map.arranged = true end - authorize @map - respond_to do |format| if @map.save format.json { render json: @map } @@ -89,8 +90,9 @@ class MapsController < ApplicationController def access user_ids = params[:access] || [] - added = @map.add_new_collaborators(user_ids) - added.each do |user_id| + @map.add_new_collaborators(user_ids).each do |user_id| + # add_new_collaborators returns array of added users, + # who we then send an email to MapMailer.invite_to_edit_email(@map, current_user, User.find(user_id)).deliver_later end @map.remove_old_collaborators(user_ids) @@ -148,29 +150,6 @@ class MapsController < ApplicationController end end - # POST maps/:id/star - def star - star = Star.find_or_create_by(map_id: @map.id, user_id: current_user.id) - - respond_to do |format| - format.json do - render json: { message: 'Successfully starred map' } - end - end - end - - # POST maps/:id/unstar - def unstar - star = Star.find_by(map_id: @map.id, user_id: current_user.id) - star&.delete - - respond_to do |format| - format.json do - render json: { message: 'Successfully unstarred map' } - end - end - end - private def set_map @@ -187,29 +166,20 @@ class MapsController < ApplicationController end def create_topics! - topics = params[:topicsToMap] - topics = topics.split(',') - topics.each do |topic| + params[:topicsToMap].split(',').each do |topic| topic = topic.split('/') - mapping = Mapping.new - mapping.map = @map - mapping.user = @user - mapping.mappable = Topic.find(topic[0]) - mapping.xloc = topic[1] - mapping.yloc = topic[2] + mapping = Mapping.new(map: @map, user: current_user, + mappable: Topic.find(topic[0]), + xloc: topic[1], yloc: topic[2]) authorize mapping, :create? mapping.save end end def create_synapses! - @synAll = params[:synapsesToMap] - @synAll = @synAll.split(',') - @synAll.each do |synapse_id| - mapping = Mapping.new - mapping.map = @map - mapping.user = @user - mapping.mappable = Synapse.find(synapse_id) + params[:synapsesToMap].split(',').each do |synapse_id| + mapping = Mapping.new(map: @map, user: current_user, + mappable: Synapse.find(synapse_id)) authorize mapping, :create? mapping.save end diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb new file mode 100644 index 00000000..91b0b44d --- /dev/null +++ b/app/controllers/search_controller.rb @@ -0,0 +1,162 @@ +# frozen_string_literal: true +class MainController < ApplicationController + include TopicsHelper + include MapsHelper + include UsersHelper + include SynapsesHelper + + before_action :authorize_search + after_action :verify_authorized + after_action :verify_policy_scoped, only: [:maps, :mappers, :synapses, :topics] + + # get /search/topics?term=SOMETERM + def topics + term = params[:term] + user = params[:user] ? params[:user] : false + + if term && !term.empty? && term.downcase[0..3] != 'map:' && term.downcase[0..6] != 'mapper:' && !term.casecmp('topic:').zero? + + # remove "topic:" if appended at beginning + term = term[6..-1] if term.downcase[0..5] == 'topic:' + + # if desc: search desc instead + desc = false + if term.downcase[0..4] == 'desc:' + term = term[5..-1] + desc = true + end + + # if link: search link instead + link = false + if term.downcase[0..4] == 'link:' + term = term[5..-1] + link = true + end + + # check whether there's a filter by metacode as part of the query + filterByMetacode = false + Metacode.all.each do |m| + lOne = m.name.length + 1 + lTwo = m.name.length + + if term.downcase[0..lTwo] == m.name.downcase + ':' + term = term[lOne..-1] + filterByMetacode = m + end + end + + search = '%' + term.downcase + '%' + builder = policy_scope(Topic) + + if filterByMetacode + if term == '' + builder = builder.none + else + builder = builder.where('LOWER("name") like ? OR + LOWER("desc") like ? OR + LOWER("link") like ?', search, search, search) + builder = builder.where(metacode_id: filterByMetacode.id) + end + elsif desc + builder = builder.where('LOWER("desc") like ?', search) + elsif link + builder = builder.where('LOWER("link") like ?', search) + else # regular case, just search the name + builder = builder.where('LOWER("name") like ? OR + LOWER("desc") like ? OR + LOWER("link") like ?', search, search, search) + end + + builder = builder.where(user: user) if user + @topics = builder.order(:name) + else + @topics = [] + end + + render json: autocomplete_array_json(@topics) + end + + # get /search/maps?term=SOMETERM + def maps + term = params[:term] + user = params[:user] ? params[:user] : nil + + if term && !term.empty? && term.downcase[0..5] != 'topic:' && term.downcase[0..6] != 'mapper:' && !term.casecmp('map:').zero? + + # remove "map:" if appended at beginning + term = term[4..-1] if term.downcase[0..3] == 'map:' + + # if desc: search desc instead + desc = false + if term.downcase[0..4] == 'desc:' + term = term[5..-1] + desc = true + end + + search = '%' + term.downcase + '%' + builder = policy_scope(Map) + + builder = if desc + builder.where('LOWER("desc") like ?', search) + else + builder.where('LOWER("name") like ?', search) + end + builder = builder.where(user: user) if user + @maps = builder.order(:name) + else + @maps = [] + end + + render json: autocomplete_map_array_json(@maps) + end + + # get /search/mappers?term=SOMETERM + def mappers + term = params[:term] + if term && !term.empty? && term.downcase[0..3] != 'map:' && term.downcase[0..5] != 'topic:' && !term.casecmp('mapper:').zero? + + # remove "mapper:" if appended at beginning + term = term[7..-1] if term.downcase[0..6] == 'mapper:' + search = term.downcase + '%' + + skip_policy_scope # TODO: builder = policy_scope(User) + builder = User.where('LOWER("name") like ?', search) + @mappers = builder.order(:name) + else + @mappers = [] + end + render json: autocomplete_user_array_json(@mappers) + end + + # get /search/synapses?term=SOMETERM OR + # get /search/synapses?topic1id=SOMEID&topic2id=SOMEID + def synapses + term = params[:term] + topic1id = params[:topic1id] + topic2id = params[:topic2id] + + if term && !term.empty? + @synapses = policy_scope(Synapse).where('LOWER("desc") like ?', '%' + term.downcase + '%').order('"desc"') + + @synapses = @synapses.uniq(&:desc) + elsif topic1id && !topic1id.empty? + @one = policy_scope(Synapse).where(topic1_id: topic1id, topic2_id: topic2id) + @two = policy_scope(Synapse).where(topic2_id: topic1id, topic1_id: topic2id) + @synapses = @one + @two + @synapses.sort! { |s1, s2| s1.desc <=> s2.desc }.to_a + else + @synapses = [] + end + + # limit to 5 results + @synapses = @synapses.to_a.slice(0, 5) + + render json: autocomplete_synapse_array_json(@synapses) + end + + private + + def authorize_search + authorize :Search + end +end diff --git a/app/controllers/stars_controller.rb b/app/controllers/stars_controller.rb new file mode 100644 index 00000000..ec4d1347 --- /dev/null +++ b/app/controllers/stars_controller.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true +class StarsController < ApplicationController + before_action :require_user + before_action :set_map + after_action :verify_authorized + + # POST maps/:id/star + def create + authorize @map, :star? + Star.find_or_create_by(map_id: @map.id, user_id: current_user.id) + + respond_to do |format| + format.json do + render json: { message: 'Successfully starred map' } + end + end + end + + # POST maps/:id/unstar + def destroy + authorize @map, :unstar? + star = Star.find_by(map_id: @map.id, user_id: current_user.id) + star&.delete + + respond_to do |format| + format.json do + render json: { message: 'Successfully unstarred map' } + end + end + end + + private + + def set_map + @map = Map.find(params[:id]) + end +end diff --git a/app/policies/main_policy.rb b/app/policies/main_policy.rb index e0ffc30b..2eb5b3e8 100644 --- a/app/policies/main_policy.rb +++ b/app/policies/main_policy.rb @@ -1,27 +1,10 @@ # frozen_string_literal: true class MainPolicy < ApplicationPolicy - def initialize(user, _record) - @user = user - @record = nil - end - def home? true end - def searchtopics? - true - end - - def searchmaps? - true - end - - def searchmappers? - true - end - - def searchsynapses? + def requestinvite? true end end diff --git a/app/policies/search_policy.rb b/app/policies/search_policy.rb new file mode 100644 index 00000000..5b783457 --- /dev/null +++ b/app/policies/search_policy.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true +class SearchPolicy < ApplicationPolicy + def topics? + true + end + + def maps? + true + end + + def mappers? + true + end + + def synapses? + true + end +end diff --git a/config/routes.rb b/config/routes.rb index 3ad59cb2..d96aa982 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,14 +1,64 @@ # frozen_string_literal: true Metamaps::Application.routes.draw do use_doorkeeper - root to: 'main#home', via: :get + root to: 'main#home', via: :get get 'request', to: 'main#requestinvite', as: :request - get 'search/topics', to: 'main#searchtopics', as: :searchtopics - get 'search/maps', to: 'main#searchmaps', as: :searchmaps - get 'search/mappers', to: 'main#searchmappers', as: :searchmappers - get 'search/synapses', to: 'main#searchsynapses', as: :searchsynapses + namespace :explore do + get 'active' + get 'featured' + get 'mine' + get 'shared' + get 'starred' + get 'mapper/:id', action: 'mapper' + end + + resources :maps, except: [:index, :edit] do + member do + get :export + post 'events/:event', action: :events + get :contains + post :upload_screenshot, action: :screenshot + post :access, default: { format: :json } + post :star, to: 'stars#create', defaults: { format: :json } + post :unstar, to: 'stars#destroy', defaults: { format: :json } + end + end + + resources :mappings, except: [:index, :new, :edit] + + resources :messages, only: [:show, :create, :update, :destroy] + + resources :metacode_sets, except: [:show] + + resources :metacodes, except: [:destroy] + get 'metacodes/:name', to: 'metacodes#show' + + namespace :search do + get :topics + get :maps + get :mappers + get :synapses + end + + resources :synapses, except: [:index, :new, :edit] + + resources :topics, except: [:index, :new, :edit] do + get :autocomplete_topic + member do + get :network + get :relative_numbers + get :relatives + end + end + + resources :users, except: [:index, :destroy] do + member do + get :details + end + end + post 'user/updatemetacodes', to: 'users#updatemetacodes', as: :updatemetacodes namespace :api, path: '/api', default: { format: :json } do namespace :v2, path: '/v2' do @@ -33,39 +83,6 @@ Metamaps::Application.routes.draw do end end - resources :messages, only: [:show, :create, :update, :destroy] - resources :mappings, except: [:index, :new, :edit] - resources :metacode_sets, except: [:show] - - resources :metacodes, except: [:destroy] - get 'metacodes/:name', to: 'metacodes#show' - - resources :synapses, except: [:index, :new, :edit] - resources :topics, except: [:index, :new, :edit] do - get :autocomplete_topic, on: :collection - end - get 'topics/:id/network', to: 'topics#network', as: :network - get 'topics/:id/relative_numbers', to: 'topics#relative_numbers', as: :relative_numbers - get 'topics/:id/relatives', to: 'topics#relatives', as: :relatives - - resources :maps, except: [:index, :edit] - get 'maps/:id/export', to: 'maps#export' - post 'maps/:id/events/:event', to: 'maps#events' - get 'maps/:id/contains', to: 'maps#contains', as: :contains - post 'maps/:id/upload_screenshot', to: 'maps#screenshot', as: :screenshot - post 'maps/:id/access', to: 'maps#access', as: :access, defaults: { format: :json } - post 'maps/:id/star', to: 'maps#star', defaults: { format: :json } - post 'maps/:id/unstar', to: 'maps#unstar', defaults: { format: :json } - - namespace :explore do - get 'active' - get 'featured' - get 'mine' - get 'shared' - get 'starred' - get 'mapper/:id', action: 'mapper' - end - devise_for :users, skip: :sessions, controllers: { registrations: 'users/registrations', passwords: 'users/passwords', @@ -79,10 +96,6 @@ Metamaps::Application.routes.draw do get 'join' => 'devise/registrations#new', :as => :new_user_registration_path end - get 'users/:id/details', to: 'users#details', as: :details - post 'user/updatemetacodes', to: 'users#updatemetacodes', as: :updatemetacodes - resources :users, except: [:index, :destroy] - namespace :hacks do get 'load_url_title' end