From ab70d65aab3e7233e0804894411ae506305eb685 Mon Sep 17 00:00:00 2001 From: Connor Turland Date: Fri, 1 Apr 2016 00:01:47 -0400 Subject: [PATCH] enable shared private and public maps --- app/assets/images/exploremaps_sprite.png | Bin 1642 -> 2264 bytes .../javascripts/src/Metamaps.Backbone.js | 18 ++++- .../javascripts/src/Metamaps.GlobalUI.js.erb | 2 + .../javascripts/src/Metamaps.Map.js.erb | 18 ++--- app/assets/javascripts/src/Metamaps.Router.js | 2 +- app/assets/javascripts/src/Metamaps.Topic.js | 3 +- app/assets/javascripts/src/Metamaps.js.erb | 56 ++++++-------- app/assets/stylesheets/clean.css.erb | 7 ++ app/controllers/maps_controller.rb | 69 ++++++++++++++---- app/controllers/synapses_controller.rb | 2 +- app/controllers/topics_controller.rb | 4 +- app/models/map.rb | 9 ++- app/models/synapse.rb | 23 +++++- app/models/topic.rb | 23 +++++- app/models/user.rb | 4 +- app/models/user_map.rb | 4 + app/policies/map_policy.rb | 12 ++- app/policies/synapse_policy.rb | 21 +++++- app/policies/topic_policy.rb | 21 +++++- app/serializers/new_map_serializer.rb | 1 + app/views/maps/_mapinfobox.html.erb | 2 +- app/views/maps/sharedmaps.html.erb | 15 ++++ config/routes.rb | 4 + db/migrate/20160331181959_create_user_maps.rb | 10 +++ ...dd_defers_to_map_to_topics_and_synapses.rb | 6 ++ db/schema.rb | 18 ++++- public/famous/main.js | 2 +- public/famous/templates.js | 8 ++ 28 files changed, 274 insertions(+), 90 deletions(-) mode change 100755 => 100644 app/assets/images/exploremaps_sprite.png create mode 100644 app/models/user_map.rb create mode 100644 app/views/maps/sharedmaps.html.erb create mode 100644 db/migrate/20160331181959_create_user_maps.rb create mode 100644 db/migrate/20160401133937_add_defers_to_map_to_topics_and_synapses.rb diff --git a/app/assets/images/exploremaps_sprite.png b/app/assets/images/exploremaps_sprite.png old mode 100755 new mode 100644 index 1653591859596488e07c4890c090fe3784328e48..492daaa8954731023e5685f1de787a9a567c7f74 GIT binary patch delta 2230 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 the creator, you can change the permission of this map, but the permissions of the topics and synapses on it must be changed independently.", + changePermissionText: "
As the creator, you can change the permission of this map, and the permission of all the topics and synapses you have authority to change will change as well.
", nameHTML: '{{name}}', descHTML: '{{desc}}', init: function () { @@ -623,19 +623,10 @@ Metamaps.Map.InfoBox = { self.selectingPermission = false var permission = $(this).attr('class') - var permBefore = Metamaps.Active.Map.get('permission') Metamaps.Active.Map.save({ permission: permission }) Metamaps.Active.Map.updateMapWrapper() - if (permBefore !== 'commons' && permission === 'commons') { - Metamaps.Realtime.setupSocket() - Metamaps.Realtime.turnOn() - } - else if (permBefore === 'commons' && permission === 'public') { - Metamaps.Realtime.turnOff(true); // true is to 'silence' - // the notification that would otherwise be sent - } shareable = permission === 'private' ? '' : 'shareable' $('.mapPermission').removeClass('commons public private minimize').addClass(permission) $('.mapPermission .permissionSelect').remove() @@ -656,6 +647,7 @@ Metamaps.Map.InfoBox = { Metamaps.Maps.Active.remove(map) Metamaps.Maps.Featured.remove(map) Metamaps.Maps.Mine.remove(map) + Metamaps.Maps.Shared.remove(map) map.destroy() Metamaps.Router.home() Metamaps.GlobalUI.notifyUser('Map eliminated!') diff --git a/app/assets/javascripts/src/Metamaps.Router.js b/app/assets/javascripts/src/Metamaps.Router.js index 6f673b61..5e574637 100644 --- a/app/assets/javascripts/src/Metamaps.Router.js +++ b/app/assets/javascripts/src/Metamaps.Router.js @@ -87,7 +87,7 @@ // either 'featured', 'mapper', or 'active' var capitalize = section.charAt(0).toUpperCase() + section.slice(1) - if (section === 'featured' || section === 'active') { + if (section === 'shared' || section === 'featured' || section === 'active') { document.title = 'Explore ' + capitalize + ' Maps | Metamaps' } else if (section === 'mapper') { $.ajax({ diff --git a/app/assets/javascripts/src/Metamaps.Topic.js b/app/assets/javascripts/src/Metamaps.Topic.js index dc242f7d..da1bfc8c 100644 --- a/app/assets/javascripts/src/Metamaps.Topic.js +++ b/app/assets/javascripts/src/Metamaps.Topic.js @@ -299,7 +299,8 @@ Metamaps.Topic = { var topic = new Metamaps.Backbone.Topic({ name: Metamaps.Create.newTopic.name, - metacode_id: metacode.id + metacode_id: metacode.id, + defer_to_map_id: Metamaps.Active.Map.id }) Metamaps.Topics.add(topic) diff --git a/app/assets/javascripts/src/Metamaps.js.erb b/app/assets/javascripts/src/Metamaps.js.erb index 592c7990..ce88089d 100644 --- a/app/assets/javascripts/src/Metamaps.js.erb +++ b/app/assets/javascripts/src/Metamaps.js.erb @@ -178,8 +178,14 @@ Metamaps.Backbone.init = function () { }, authorizeToEdit: function (mapper) { - if (mapper && (this.get('permission') === "commons" || this.get('user_id') === mapper.get('id'))) return true; - else return false; + if (mapper && + (this.get('calculated_permission') === "commons" || + this.get('collaborator_ids').includes(mapper.get('id')) || + this.get('user_id') === mapper.get('id'))) { + return true + } else { + return false + } }, authorizePermissionChange: function (mapper) { if (mapper && this.get('user_id') === mapper.get('id')) return true; @@ -346,7 +352,7 @@ Metamaps.Backbone.init = function () { return li; }, authorizeToEdit: function (mapper) { - if (mapper && (this.get('permission') === "commons" || this.get('user_id') === mapper.get('id'))) return true; + if (mapper && (this.get('calculated_permission') === "commons" || this.get('collaborator_ids').includes(mapper.get('id')) || this.get('user_id') === mapper.get('id'))) return true; else return false; }, authorizePermissionChange: function (mapper) { @@ -1119,7 +1125,8 @@ Metamaps.TopicCard = { selectingPermission = false; var permission = $(this).attr('class'); topic.save({ - permission: permission + permission: permission, + defer_to_map_id: null }); $('.showcard .mapPerm').removeClass('co pu pr minimize').addClass(permission.substring(0, 2)); $('.showcard .permissionSelect').remove(); @@ -1277,8 +1284,8 @@ Metamaps.TopicCard = { nodeValues.inmaps += '
  • ' + inmapsAr[i]+ '
  • '; } } - nodeValues.permission = topic.get("permission"); - nodeValues.mk_permission = topic.get("permission").substring(0, 2); + nodeValues.permission = topic.get("calculated_permission") + nodeValues.mk_permission = topic.get("calculated_permission").substring(0, 2) nodeValues.map_count = topic.get("map_count").toString(); nodeValues.synapse_count = topic.get("synapse_count").toString(); nodeValues.id = topic.isNew() ? topic.cid : topic.id; @@ -1467,7 +1474,7 @@ Metamaps.SynapseCard = { add_perms_form: function (synapse) { //permissions - if owner, also allow permission editing - $('#editSynLowerBar').append('
    '); + $('#editSynLowerBar').append('
    '); // ability to change permission var selectingPermission = false; @@ -1475,7 +1482,8 @@ Metamaps.SynapseCard = { selectingPermission = false; var permission = $(this).attr('class'); synapse.save({ - permission: permission + permission: permission, + defer_to_map_id: null }); $('#edit_synapse .mapPerm').removeClass('co pu pr minimize').addClass(permission.substring(0, 2)); $('#edit_synapse .permissionSelect').remove(); @@ -2088,14 +2096,12 @@ Metamaps.Realtime = { var self = Metamaps.Realtime; if (Metamaps.Active.Map && Metamaps.Active.Mapper) { - var commonsMap = Metamaps.Active.Map.get('permission') === 'commons'; - var publicMap = Metamaps.Active.Map.get('permission') === 'public'; - if (commonsMap) { + if (Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper)) { self.turnOn(); self.setupSocket(); } - else if (publicMap) { + else { self.attachMapListener(); } self.room.addMessages(new Metamaps.Backbone.MessageCollection(Metamaps.Messages), true); @@ -2114,25 +2120,11 @@ Metamaps.Realtime = { self.room.chat.$container.hide(); self.room.chat.close(); }, - reenableRealtime: function() { - var confirmString = "The layout of your map has fallen out of sync with the saved copy. "; - confirmString += "To save your changes without overwriting the map, hit 'Cancel' and "; - confirmString += "then use 'Save to new map'. "; - confirmString += "Do you want to discard your changes and enable realtime?"; - var c = confirm(confirmString); - if (c) { - Metamaps.Router.maps(Metamaps.Active.Map.id); - } - }, turnOn: function (notify) { var self = Metamaps.Realtime; if (notify) self.sendRealtimeOn(); - //$(".rtMapperSelf").removeClass('littleRtOff').addClass('littleRtOn'); - //$('.rtOn').addClass('active'); - //$('.rtOff').removeClass('active'); self.status = true; - //$(".sidebarCollaborateIcon").addClass("blue"); $(".collabCompass").show(); self.room.chat.$container.show(); self.room.room = 'map-' + Metamaps.Active.Map.id; @@ -2881,21 +2873,21 @@ Metamaps.Realtime = { var map = Metamaps.Active.Map; var isActiveMap = map && data.mapId === map.id; if (isActiveMap) { - var permBefore = map.get('permission'); + var couldEditBefore = map.authorizeToEdit(Metamaps.Active.Mapper) var idBefore = map.id; map.fetch({ success: function (model, response) { var idNow = model.id; - var permNow = model.get('permission'); + var canEditNow = model.authorizeToEdit(Metamaps.Active.Mapper); if (idNow !== idBefore) { Metamaps.Map.leavePrivateMap(); // this means the map has been changed to private } - else if (permNow === 'public' && permBefore === 'commons') { - Metamaps.Map.commonsToPublic(); + else if (couldEditBefore && !canEditNow) { + Metamaps.Map.cantEditNow() } - else if (permNow === 'commons' && permBefore === 'public') { - Metamaps.Map.publicToCommons(); + else if (!couldEditBefore && canEditNow) { + Metamaps.Map.canEditNow() } else { model.fetchContained(); diff --git a/app/assets/stylesheets/clean.css.erb b/app/assets/stylesheets/clean.css.erb index 5665716a..47e18a94 100644 --- a/app/assets/stylesheets/clean.css.erb +++ b/app/assets/stylesheets/clean.css.erb @@ -681,6 +681,10 @@ background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>); background-position: 0 0; } +.exploreMapsCenter .sharedMaps .exploreMapsIcon { + background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>); + background-position: -96px 0; +} .exploreMapsCenter .activeMaps .exploreMapsIcon { background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>); background-position: -32px 0; @@ -698,6 +702,9 @@ .featuredMaps:hover .exploreMapsIcon, .featuredMaps.active .exploreMapsIcon { background-position: -64px -32px; } +.sharedMaps:hover .exploreMapsIcon, .sharedMaps.active .exploreMapsIcon { + background-position: -96px -32px; +} .mapsWrapper { /*overflow-y: auto; */ diff --git a/app/controllers/maps_controller.rb b/app/controllers/maps_controller.rb index 82b87ffb..587eef9e 100644 --- a/app/controllers/maps_controller.rb +++ b/app/controllers/maps_controller.rb @@ -1,8 +1,7 @@ class MapsController < ApplicationController - - before_action :require_user, only: [:create, :update, :screenshot, :events, :destroy] - after_action :verify_authorized, except: [:activemaps, :featuredmaps, :mymaps, :usermaps, :events] - after_action :verify_policy_scoped, only: [:activemaps, :featuredmaps, :mymaps, :usermaps] + before_action :require_user, only: [:create, :update, :access, :screenshot, :events, :destroy] + after_action :verify_authorized, except: [:activemaps, :featuredmaps, :mymaps, :sharedmaps, :usermaps, :events] + after_action :verify_policy_scoped, only: [:activemaps, :featuredmaps, :mymaps, :sharedmaps, :usermaps] respond_to :html, :json, :csv @@ -53,6 +52,21 @@ class MapsController < ApplicationController end end + # GET /explore/shared + def sharedmaps + return redirect_to activemaps_url if !authenticated? + + 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) + + respond_to do |format| + format.html { respond_with(@maps, @user) } + format.json { render json: @maps } + end + end + # GET /explore/mapper/:id def usermaps page = params[:page].present? ? params[:page] : 1 @@ -74,12 +88,9 @@ class MapsController < ApplicationController respond_to do |format| format.html { @allmappers = @map.contributors - @alltopics = @map.topics.to_a.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && current_user.id != t.user_id)) } - @allsynapses = @map.synapses.to_a.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && current_user.id != s.user_id)) } - @allmappings = @map.mappings.to_a.delete_if {|m| - object = m.mappable - !object || (object.permission == "private" && (!authenticated? || (authenticated? && current_user.id != object.user_id))) - } + @alltopics = @map.topics.to_a.delete_if {|t| not policy(t).show? } + @allsynapses = @map.synapses.to_a.delete_if {|s| not policy(s).show? } + @allmappings = @map.mappings.to_a.delete_if {|m| not policy(m).show? } @allmessages = @map.messages.sort_by(&:created_at) respond_with(@allmappers, @allmappings, @allsynapses, @alltopics, @allmessages, @map) @@ -127,12 +138,10 @@ class MapsController < ApplicationController authorize @map @allmappers = @map.contributors - @alltopics = @map.topics.to_a.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && current_user.id != t.user_id)) } - @allsynapses = @map.synapses.to_a.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && current_user.id != s.user_id)) } - @allmappings = @map.mappings.to_a.delete_if {|m| - object = m.mappable - !object || (object.permission == "private" && (!authenticated? || (authenticated? && current_user.id != object.user_id))) - } + @alltopics = @map.topics.to_a.delete_if {|t| not policy(t).show? } + @allsynapses = @map.synapses.to_a.delete_if {|s| not policy(s).show? } + @allmappings = @map.mappings.to_a.delete_if {|m| not policy(m).show? } + @json = Hash.new() @json['map'] = @map @@ -216,6 +225,34 @@ class MapsController < ApplicationController end end + # POST maps/:id/access + def access + @map = Map.find(params[:id]) + authorize @map + userIds = params[:access] || [] + added = userIds.select { |uid| + user = User.find(uid) + if user.nil? || (current_user && user == current_user) + false + else + not @map.collaborators.include?(user) + end + } + removed = @map.collaborators.select { |user| not userIds.include?(user.id) }.map(&:id) + added.each { |uid| + um = UserMap.create({ user_id: uid.to_i, map_id: @map.id }) + } + removed.each { |uid| + @map.user_maps.select{ |um| um.user_id == uid }.each{ |um| um.destroy } + } + + respond_to do |format| + format.json do + render :json => { :message => "Successfully altered edit permissions" } + end + end + end + # POST maps/:id/upload_screenshot def screenshot @map = Map.find(params[:id]) diff --git a/app/controllers/synapses_controller.rb b/app/controllers/synapses_controller.rb index 4440872f..b8ccfc5f 100644 --- a/app/controllers/synapses_controller.rb +++ b/app/controllers/synapses_controller.rb @@ -51,7 +51,7 @@ class SynapsesController < ApplicationController def destroy @synapse = Synapse.find(params[:id]) authorize @synapse - @synapse.delete + @synapse.destroy respond_to do |format| format.json { head :no_content } diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb index 9911a7fe..88e9cef4 100644 --- a/app/controllers/topics_controller.rb +++ b/app/controllers/topics_controller.rb @@ -150,7 +150,7 @@ puts @allsynapses.length @topic = Topic.find(params[:id]) authorize @topic - @topic.delete + @topic.destroy respond_to do |format| format.json { head :no_content } end @@ -159,6 +159,6 @@ puts @allsynapses.length private def topic_params - params.require(:topic).permit(:id, :name, :desc, :link, :permission, :user_id, :metacode_id) + params.require(:topic).permit(:id, :name, :desc, :link, :permission, :user_id, :metacode_id, :defer_to_map_id) end end diff --git a/app/models/map.rb b/app/models/map.rb index d9eb6a18..472d7a79 100644 --- a/app/models/map.rb +++ b/app/models/map.rb @@ -8,6 +8,9 @@ class Map < ActiveRecord::Base has_many :synapses, through: :synapsemappings, source: :mappable, source_type: "Synapse" has_many :messages, as: :resource, dependent: :destroy + has_many :user_maps, dependent: :destroy + has_many :collaborators, through: :user_maps, source: :user + has_many :webhooks, as: :hookable has_many :events, -> { includes :user }, as: :eventable, dependent: :destroy @@ -65,6 +68,10 @@ class Map < ActiveRecord::Base contributors.length end + def collaborator_ids + collaborators.map(&:id) + end + def screenshot_url screenshot.url(:thumb) end @@ -78,7 +85,7 @@ class Map < ActiveRecord::Base end def as_json(options={}) - json = super(:methods =>[:user_name, :user_image, :topic_count, :synapse_count, :contributor_count, :screenshot_url], :except => [:screenshot_content_type, :screenshot_file_size, :screenshot_file_name, :screenshot_updated_at]) + json = super(:methods =>[:user_name, :user_image, :topic_count, :synapse_count, :contributor_count, :collaborator_ids, :screenshot_url], :except => [:screenshot_content_type, :screenshot_file_size, :screenshot_file_name, :screenshot_updated_at]) json[:created_at_clean] = created_at_str json[:updated_at_clean] = updated_at_str json diff --git a/app/models/synapse.rb b/app/models/synapse.rb index 540376bb..33cfab86 100644 --- a/app/models/synapse.rb +++ b/app/models/synapse.rb @@ -1,5 +1,6 @@ class Synapse < ActiveRecord::Base belongs_to :user + belongs_to :defer_to_map, :class_name => 'Map', :foreign_key => 'defer_to_map_id' belongs_to :topic1, :class_name => "Topic", :foreign_key => "node1_id" belongs_to :topic2, :class_name => "Topic", :foreign_key => "node2_id" @@ -32,9 +33,29 @@ class Synapse < ActiveRecord::Base end # :nocov: + # :nocov: + def collaborator_ids + if defer_to_map + defer_to_map.collaborators.map(&:id) + else + [] + end + end + # :nocov: + + # :nocov: + def calculated_permission + if defer_to_map + defer_to_map.permission + else + permission + end + end + # :nocov: + # :nocov: def as_json(options={}) - super(:methods =>[:user_name, :user_image]) + super(:methods =>[:user_name, :user_image, :calculated_permission, :collaborator_ids]) end # :nocov: diff --git a/app/models/topic.rb b/app/models/topic.rb index f1a73c1b..95663055 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -2,6 +2,7 @@ class Topic < ActiveRecord::Base include TopicsHelper belongs_to :user + belongs_to :defer_to_map, :class_name => 'Map', :foreign_key => 'defer_to_map_id' has_many :synapses1, :class_name => 'Synapse', :foreign_key => 'node1_id', dependent: :destroy has_many :synapses2, :class_name => 'Synapse', :foreign_key => 'node2_id', dependent: :destroy @@ -11,6 +12,8 @@ class Topic < ActiveRecord::Base has_many :mappings, as: :mappable, dependent: :destroy has_many :maps, :through => :mappings + belongs_to :metacode + validates :permission, presence: true validates :permission, inclusion: { in: Perm::ISSIONS.map(&:to_s) } @@ -39,8 +42,6 @@ class Topic < ActiveRecord::Base topics1 + topics2 end - belongs_to :metacode - scope :relatives1, ->(topic_id = nil) { includes(:topics1) .where('synapses.node1_id = ?', topic_id) @@ -77,8 +78,24 @@ class Topic < ActiveRecord::Base maps.map(&:id) end + def calculated_permission + if defer_to_map + defer_to_map.permission + else + permission + end + end + def as_json(options={}) - super(:methods =>[:user_name, :user_image, :map_count, :synapse_count, :inmaps, :inmapsLinks]) + super(:methods =>[:user_name, :user_image, :map_count, :synapse_count, :inmaps, :inmapsLinks, :calculated_permission, :collaborator_ids]) + end + + def collaborator_ids + if defer_to_map + defer_to_map.collaborators.map(&:id) + else + [] + end end # TODO move to a decorator? diff --git a/app/models/user.rb b/app/models/user.rb index 10a0a71c..a38d7177 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -7,7 +7,9 @@ class User < ActiveRecord::Base has_many :maps has_many :mappings has_many :tokens - + has_many :user_maps, dependent: :destroy + has_many :shared_maps, through: :user_maps, source: :map + after_create :generate_code devise :database_authenticatable, :recoverable, :rememberable, :trackable, :registerable diff --git a/app/models/user_map.rb b/app/models/user_map.rb new file mode 100644 index 00000000..5e91ecc2 --- /dev/null +++ b/app/models/user_map.rb @@ -0,0 +1,4 @@ +class UserMap < ActiveRecord::Base + belongs_to :map + belongs_to :user +end diff --git a/app/policies/map_policy.rb b/app/policies/map_policy.rb index b1ece0e3..a431e619 100644 --- a/app/policies/map_policy.rb +++ b/app/policies/map_policy.rb @@ -4,7 +4,8 @@ class MapPolicy < ApplicationPolicy visible = ['public', 'commons'] permission = 'maps.permission IN (?)' if user - scope.where(permission + ' OR maps.user_id = ?', visible, user.id) + shared_maps = user.user_maps.map(&:id) + scope.where(permission + ' OR maps.id IN (?) OR maps.user_id = ?', visible, shared_maps, user.id) else scope.where(permission, visible) end @@ -28,7 +29,7 @@ class MapPolicy < ApplicationPolicy end def show? - record.permission == 'commons' || record.permission == 'public' || record.user == user + record.permission == 'commons' || record.permission == 'public' || record.collaborators.include?(user) || record.user == user end def export? @@ -48,7 +49,12 @@ class MapPolicy < ApplicationPolicy end def update? - user.present? && (record.permission == 'commons' || record.user == user) + user.present? && (record.permission == 'commons' || record.collaborators.include?(user) || record.user == user) + end + + def access? + # note that this is to edit access + user.present? && record.user == user end def screenshot? diff --git a/app/policies/synapse_policy.rb b/app/policies/synapse_policy.rb index 042c9a75..9b1a8524 100644 --- a/app/policies/synapse_policy.rb +++ b/app/policies/synapse_policy.rb @@ -4,7 +4,7 @@ class SynapsePolicy < ApplicationPolicy visible = ['public', 'commons'] permission = 'synapses.permission IN (?)' if user - scope.where(permission + ' OR synapses.user_id = ?', visible, user.id) + scope.where(permission + ' OR synapses.defer_to_map_id IN (?) OR synapses.user_id = ?', visible, user.shared_maps.map(&:id), user.id) else scope.where(permission, visible) end @@ -17,14 +17,29 @@ class SynapsePolicy < ApplicationPolicy end def show? - record.permission == 'commons' || record.permission == 'public' || record.user == user + if record.defer_to_map.present? + map_policy.show? + else + record.permission == 'commons' || record.permission == 'public' || record.user == user + end end def update? - user.present? && (record.permission == 'commons' || record.user == user) + if not user.present? + false + elsif record.defer_to_map.present? + map_policy.update? + else + record.permission == 'commons' || record.user == user + end end def destroy? record.user == user || admin_override end + + # Helpers + def map_policy + @map_policy ||= Pundit.policy(user, record.defer_to_map) + end end diff --git a/app/policies/topic_policy.rb b/app/policies/topic_policy.rb index 335a2ed2..2eb2abb6 100644 --- a/app/policies/topic_policy.rb +++ b/app/policies/topic_policy.rb @@ -4,7 +4,7 @@ class TopicPolicy < ApplicationPolicy visible = ['public', 'commons'] permission = 'topics.permission IN (?)' if user - scope.where(permission + ' OR topics.user_id = ?', visible, user.id) + scope.where(permission + ' OR topics.defer_to_map_id IN (?) OR topics.user_id = ?', visible, user.shared_maps.map(&:id), user.id) else scope.where(permission, visible) end @@ -16,11 +16,21 @@ class TopicPolicy < ApplicationPolicy end def show? - record.permission == 'commons' || record.permission == 'public' || record.user == user + if record.defer_to_map.present? + map_policy.show? + else + record.permission == 'commons' || record.permission == 'public' || record.user == user + end end def update? - user.present? && (record.permission == 'commons' || record.user == user) + if not user.present? + false + elsif record.defer_to_map.present? + map_policy.update? + else + record.permission == 'commons' || record.user == user + end end def destroy? @@ -42,4 +52,9 @@ class TopicPolicy < ApplicationPolicy def relatives? show? end + + # Helpers + def map_policy + @map_policy ||= Pundit.policy(user, record.defer_to_map) + end end diff --git a/app/serializers/new_map_serializer.rb b/app/serializers/new_map_serializer.rb index 9b2ff400..3c56f82f 100644 --- a/app/serializers/new_map_serializer.rb +++ b/app/serializers/new_map_serializer.rb @@ -12,5 +12,6 @@ class NewMapSerializer < ActiveModel::Serializer has_many :synapses, serializer: NewSynapseSerializer has_many :mappings, serializer: NewMappingSerializer has_many :contributors, root: :users, serializer: NewUserSerializer + has_many :collaborators, root: :users, serializer: NewUserSerializer end diff --git a/app/views/maps/_mapinfobox.html.erb b/app/views/maps/_mapinfobox.html.erb index ff90532c..4874adaa 100644 --- a/app/views/maps/_mapinfobox.html.erb +++ b/app/views/maps/_mapinfobox.html.erb @@ -35,7 +35,7 @@ <%= @map.synapses.count %>
    -
    As the creator, you can change the permission of this map, but the permissions of the topics and synapses on it must be changed independently.
    +
    As the creator, you can change the permission of this map, and the permission of all the topics and synapses you have authority to change will change as well.
    diff --git a/app/views/maps/sharedmaps.html.erb b/app/views/maps/sharedmaps.html.erb new file mode 100644 index 00000000..99c41828 --- /dev/null +++ b/app/views/maps/sharedmaps.html.erb @@ -0,0 +1,15 @@ +<% # + # @file + # Shows a list of current user's maps + # GET /explore/mine(.:format) + # %> + + diff --git a/config/routes.rb b/config/routes.rb index 345547f4..b64927db 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -44,8 +44,12 @@ Metamaps::Application.routes.draw do get 'explore/active', to: 'maps#activemaps' get 'explore/featured', to: 'maps#featuredmaps' get 'explore/mine', to: 'maps#mymaps' + get 'explore/shared', to: 'maps#sharedmaps' get 'explore/mapper/:id', to: 'maps#usermaps' + 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} devise_for :users, controllers: { registrations: 'users/registrations', passwords: 'users/passwords', sessions: 'devise/sessions' }, :skip => :sessions diff --git a/db/migrate/20160331181959_create_user_maps.rb b/db/migrate/20160331181959_create_user_maps.rb new file mode 100644 index 00000000..2af6e87a --- /dev/null +++ b/db/migrate/20160331181959_create_user_maps.rb @@ -0,0 +1,10 @@ +class CreateUserMaps < ActiveRecord::Migration + def change + create_table :user_maps do |t| + t.references :user, index: true + t.references :map, index: true + + t.timestamps + end + end +end diff --git a/db/migrate/20160401133937_add_defers_to_map_to_topics_and_synapses.rb b/db/migrate/20160401133937_add_defers_to_map_to_topics_and_synapses.rb new file mode 100644 index 00000000..afcd6df2 --- /dev/null +++ b/db/migrate/20160401133937_add_defers_to_map_to_topics_and_synapses.rb @@ -0,0 +1,6 @@ +class AddDefersToMapToTopicsAndSynapses < ActiveRecord::Migration + def change + add_column :topics, :defer_to_map_id, :integer + add_column :synapses, :defer_to_map_id, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index 1ad745a2..d3dc9634 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160318141618) do +ActiveRecord::Schema.define(version: 20160401133937) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -180,8 +180,9 @@ ActiveRecord::Schema.define(version: 20160318141618) do t.integer "node1_id" t.integer "node2_id" t.integer "user_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "defer_to_map_id" end add_index "synapses", ["node1_id", "node1_id"], name: "index_synapses_on_node1_id_and_node1_id", using: :btree @@ -217,11 +218,22 @@ ActiveRecord::Schema.define(version: 20160318141618) do t.string "audio_content_type" t.integer "audio_file_size" t.datetime "audio_updated_at" + t.integer "defer_to_map_id" end add_index "topics", ["metacode_id"], name: "index_topics_on_metacode_id", using: :btree add_index "topics", ["user_id"], name: "index_topics_on_user_id", using: :btree + create_table "user_maps", force: :cascade do |t| + t.integer "user_id" + t.integer "map_id" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "user_maps", ["map_id"], name: "index_user_maps_on_map_id", using: :btree + add_index "user_maps", ["user_id"], name: "index_user_maps_on_user_id", using: :btree + create_table "users", force: :cascade do |t| t.string "name" t.string "email" diff --git a/public/famous/main.js b/public/famous/main.js index 2a147e83..7ec38c5c 100644 --- a/public/famous/main.js +++ b/public/famous/main.js @@ -303,7 +303,7 @@ Metamaps.Famous.build = function () { var loggedIn = Metamaps.Active.Mapper ? 'Auth' : ''; - if (section === "mine" || section === "active" || section === "featured") { + if (section === "mine" || section === "shared" || section === "active" || section === "featured") { f.explore.surf.setContent(templates[section + loggedIn + 'Content']); } else if (section === "mapper") { diff --git a/public/famous/templates.js b/public/famous/templates.js index 66e6b9ac..1365e67a 100644 --- a/public/famous/templates.js +++ b/public/famous/templates.js @@ -19,14 +19,22 @@ t.logoContent += ''; /* logged in explore maps bars */ t.mineAuthContent = '
    My Maps
    '; + t.mineAuthContent += '
    Shared Maps
    '; t.mineAuthContent += '
    Recently Active
    '; t.mineAuthContent += '
    Featured
    '; + t.sharedAuthContent = '
    My Maps
    '; + t.sharedAuthContent += '
    Shared Maps
    '; + t.sharedAuthContent += '
    Recently Active
    '; + t.sharedAuthContent += '
    Featured
    '; + t.activeAuthContent = '
    My Maps
    '; + t.activeAuthContent += '
    Shared Maps
    '; t.activeAuthContent += '
    Recently Active
    '; t.activeAuthContent += '
    Featured
    '; t.featuredAuthContent = '
    My Maps
    '; + t.featuredAuthContent += '
    Shared Maps
    '; t.featuredAuthContent += '
    Recently Active
    '; t.featuredAuthContent += '
    Featured
    ';