diff --git a/app/assets/images/exploremaps_sprite.png b/app/assets/images/exploremaps_sprite.png
old mode 100755
new mode 100644
index 16535918..492daaa8
Binary files a/app/assets/images/exploremaps_sprite.png and b/app/assets/images/exploremaps_sprite.png differ
diff --git a/app/assets/javascripts/src/Metamaps.Backbone.js b/app/assets/javascripts/src/Metamaps.Backbone.js
index 7b40c3cc..25c92fc9 100644
--- a/app/assets/javascripts/src/Metamaps.Backbone.js
+++ b/app/assets/javascripts/src/Metamaps.Backbone.js
@@ -47,12 +47,21 @@ Metamaps.Backbone.Map = Backbone.Model.extend({
Metamaps.Realtime.sendMapChange(this)
},
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('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
- else return false
+ if (mapper && this.get('user_id') === mapper.get('id')) {
+ return true
+ } else {
+ return false
+ }
},
getUser: function () {
return Metamaps.Mapper.get(this.get('user_id'))
@@ -70,6 +79,7 @@ Metamaps.Backbone.Map = Backbone.Model.extend({
$.ajax({
url: '/maps/' + this.id + '/contains.json',
success: start,
+ error: errorFunc,
async: false
})
},
diff --git a/app/assets/javascripts/src/Metamaps.GlobalUI.js.erb b/app/assets/javascripts/src/Metamaps.GlobalUI.js.erb
index 690bba1f..d7904386 100644
--- a/app/assets/javascripts/src/Metamaps.GlobalUI.js.erb
+++ b/app/assets/javascripts/src/Metamaps.GlobalUI.js.erb
@@ -93,6 +93,7 @@ Metamaps.GlobalUI = {
if (Metamaps.Active.Mapper) Metamaps.Active.Mapper = new Metamaps.Backbone.Mapper(Metamaps.Active.Mapper);
var myCollection = Metamaps.Maps.Mine ? Metamaps.Maps.Mine : [];
+ var sharedCollection = Metamaps.Maps.Shared ? Metamaps.Maps.Shared : [];
var mapperCollection = [];
var mapperOptionsObj = {id: 'mapper', sortBy: 'updated_at' };
if (Metamaps.Maps.Mapper) {
@@ -102,6 +103,7 @@ Metamaps.GlobalUI = {
var featuredCollection = Metamaps.Maps.Featured ? Metamaps.Maps.Featured : [];
var activeCollection = Metamaps.Maps.Active ? Metamaps.Maps.Active : [];
Metamaps.Maps.Mine = new Metamaps.Backbone.MapsCollection(myCollection, {id: 'mine', sortBy: 'updated_at' });
+ Metamaps.Maps.Shared = new Metamaps.Backbone.MapsCollection(sharedCollection, {id: 'shared', sortBy: 'updated_at' });
// 'Mapper' refers to another mapper
Metamaps.Maps.Mapper = new Metamaps.Backbone.MapsCollection(mapperCollection, mapperOptionsObj);
Metamaps.Maps.Featured = new Metamaps.Backbone.MapsCollection(featuredCollection, {id: 'featured', sortBy: 'updated_at' });
diff --git a/app/assets/javascripts/src/Metamaps.Map.js.erb b/app/assets/javascripts/src/Metamaps.Map.js.erb
index 0207d081..84a47700 100644
--- a/app/assets/javascripts/src/Metamaps.Map.js.erb
+++ b/app/assets/javascripts/src/Metamaps.Map.js.erb
@@ -180,13 +180,13 @@ Metamaps.Map = {
Metamaps.Router.home()
Metamaps.GlobalUI.notifyUser('Sorry! That map has been changed to Private.')
},
- commonsToPublic: function () {
+ cantEditNow: function () {
Metamaps.Realtime.turnOff(true); // true is for 'silence'
Metamaps.GlobalUI.notifyUser('Map was changed to Public. Editing is disabled.')
Metamaps.Active.Map.trigger('changeByOther')
},
- publicToCommons: function () {
- var confirmString = 'This map permission has been changed to Commons! '
+ canEditNow: function () {
+ var confirmString = "You've been granted permission to edit this map. "
confirmString += 'Do you want to reload and enable realtime collaboration?'
var c = confirm(confirmString)
if (c) {
@@ -419,7 +419,7 @@ Metamaps.Map.InfoBox = {
isOpen: false,
changing: false,
selectingPermission: false,
- changePermissionText: "
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.
",
+ 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 += '';
}
}
- 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';