follows for maps in the ui for internal testing only still (#1072)

* follows for maps in the ui for testers

* require user for these actions

* match how map follow works
This commit is contained in:
Connor Turland 2017-02-15 23:01:53 -05:00 committed by GitHub
parent 8d771543d8
commit 013e3c7f21
14 changed files with 145 additions and 30 deletions

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
class MapsController < ApplicationController
before_action :require_user, only: [:create, :update, :destroy, :events]
before_action :set_map, only: [:show, :conversation, :update, :destroy, :contains, :events, :export]
before_action :require_user, only: [:create, :update, :destroy, :events, :follow, :unfollow]
before_action :set_map, only: [:show, :conversation, :update, :destroy, :contains, :events, :export, :follow, :unfollow]
after_action :verify_authorized
# GET maps/:id
@ -138,6 +138,32 @@ class MapsController < ApplicationController
end
end
# POST maps/:id/follow
def follow
follow = FollowService.follow(@map, current_user, 'followed')
respond_to do |format|
format.json do
if follow
head :ok
else
head :bad_request
end
end
end
end
# POST maps/:id/unfollow
def unfollow
FollowService.unfollow(@map, current_user)
respond_to do |format|
format.json do
head :ok
end
end
end
private
def set_map

View file

@ -2,7 +2,8 @@
class TopicsController < ApplicationController
include TopicsHelper
before_action :require_user, only: [:create, :update, :destroy]
before_action :require_user, only: [:create, :update, :destroy, :follow, :unfollow]
before_action :set_topic, only: [:show, :update, :relative_numbers, :relatives, :network, :destroy, :follow, :unfollow]
after_action :verify_authorized, except: :autocomplete_topic
respond_to :html, :js, :json
@ -31,9 +32,6 @@ class TopicsController < ApplicationController
# GET topics/:id
def show
@topic = Topic.find(params[:id])
authorize @topic
respond_to do |format|
format.html do
@alltopics = [@topic].concat(policy_scope(Topic.relatives(@topic.id, current_user)).to_a)
@ -49,9 +47,6 @@ class TopicsController < ApplicationController
# GET topics/:id/network
def network
@topic = Topic.find(params[:id])
authorize @topic
@alltopics = [@topic].concat(policy_scope(Topic.relatives(@topic.id, current_user)).to_a)
@allsynapses = policy_scope(Synapse.for_topic(@topic.id))
@ -71,9 +66,6 @@ class TopicsController < ApplicationController
# GET topics/:id/relative_numbers
def relative_numbers
@topic = Topic.find(params[:id])
authorize @topic
topics_already_has = params[:network] ? params[:network].split(',').map(&:to_i) : []
alltopics = policy_scope(Topic.relatives(@topic.id, current_user)).to_a
@ -94,9 +86,6 @@ class TopicsController < ApplicationController
# GET topics/:id/relatives
def relatives
@topic = Topic.find(params[:id])
authorize @topic
topics_already_has = params[:network] ? params[:network].split(',').map(&:to_i) : []
alltopics = policy_scope(Topic.relatives(@topic.id, current_user)).to_a
@ -149,8 +138,6 @@ class TopicsController < ApplicationController
# PUT /topics/1
# PUT /topics/1.json
def update
@topic = Topic.find(params[:id])
authorize @topic
@topic.updated_by = current_user
@topic.assign_attributes(topic_params)
@ -165,8 +152,6 @@ class TopicsController < ApplicationController
# DELETE topics/:id
def destroy
@topic = Topic.find(params[:id])
authorize @topic
@topic.updated_by = current_user
@topic.destroy
respond_to do |format|
@ -174,8 +159,39 @@ class TopicsController < ApplicationController
end
end
# POST topics/:id/follow
def follow
follow = FollowService.follow(@topic, current_user, 'followed')
respond_to do |format|
format.json do
if follow
head :ok
else
head :bad_request
end
end
end
end
# POST topics/:id/unfollow
def unfollow
FollowService.unfollow(@topic, current_user)
respond_to do |format|
format.json do
head :ok
end
end
end
private
def set_topic
@topic = Topic.find(params[:id])
authorize @topic
end
def topic_params
params.require(:topic).permit(:id, :name, :desc, :link, :permission, :metacode_id, :defer_to_map_id)
end

View file

@ -52,10 +52,20 @@ class User < ApplicationRecord
# override default as_json
def as_json(_options = {})
{ id: id,
json = { id: id,
name: name,
image: image.url(:sixtyfour),
admin: admin }
if (_options[:follows])
json['follows'] = {
topics: following.where(followed_type: 'Topic').to_a.map(&:followed_id),
maps: following.where(followed_type: 'Map').to_a.map(&:followed_id)
}
end
if (_options[:email])
json['email'] = email
end
json
end
def as_json_for_autocomplete

View file

@ -90,4 +90,12 @@ class MapPolicy < ApplicationPolicy
def unstar?
user.present?
end
def follow?
show? && user.present?
end
def unfollow?
user.present?
end
end

View file

@ -55,6 +55,14 @@ class TopicPolicy < ApplicationPolicy
show?
end
def follow?
show? && user.present?
end
def unfollow?
user.present?
end
# Helpers
def map_policy
@map_policy ||= Pundit.policy(user, record.defer_to_map)

View file

@ -3,7 +3,7 @@
<%= render :partial => 'shared/metacodeBgColors' %>
<script type="text/javascript" charset="utf-8">
<% if current_user %>
Metamaps.ServerData.ActiveMapper = <%= current_user.to_json.html_safe %>
Metamaps.ServerData.ActiveMapper = <%= current_user.to_json({follows: true, email: true}).html_safe %>
<% else %>
Metamaps.ServerData.ActiveMapper = null
<% end %>

View file

@ -48,6 +48,8 @@ Metamaps::Application.routes.draw do
post :star, to: 'stars#create', default: { format: :json }
post :unstar, to: 'stars#destroy', default: { format: :json }
post :follow, default: { format: :json }
post :unfollow, default: { format: :json }
end
end
@ -83,6 +85,8 @@ Metamaps::Application.routes.draw do
get :network
get :relative_numbers
get :relatives
post :follow, default: { format: :json }
post :unfollow, default: { format: :json }
end
collection do
get :autocomplete_topic

View file

@ -34,6 +34,9 @@ const Map = Backbone.Model.extend({
return false
}
},
isFollowedBy: function(mapper) {
return mapper.get('follows') && mapper.get('follows').maps.indexOf(this.get('id')) > -1
},
getUser: function() {
return Mapper.get(this.get('user_id'))
},

View file

@ -5,7 +5,7 @@ import outdent from 'outdent'
const Mapper = Backbone.Model.extend({
urlRoot: '/users',
blacklist: ['created_at', 'updated_at'],
blacklist: ['created_at', 'updated_at', 'follows'],
toJSON: function(options) {
return _.omit(this.attributes, this.blacklist)
},
@ -15,6 +15,20 @@ const Mapper = Backbone.Model.extend({
<img src="${this.get('image')}" data-id="${this.id}" alt="${this.get('name')}" />
<p>${this.get('name')}</p>
</li>`
},
followMap: function(id) {
this.get('follows').maps.push(id)
},
unfollowMap: function(id) {
const idIndex = this.get('follows').maps.indexOf(id)
if (idIndex > -1) this.get('follows').maps.splice(idIndex, 1)
},
followTopic: function(id) {
this.get('follows').topics.push(id)
},
unfollowTopic: function(id) {
const idIndex = this.get('follows').topics.indexOf(id)
if (idIndex > -1) this.get('follows').topics.splice(idIndex, 1)
}
})

View file

@ -47,6 +47,9 @@ const Topic = Backbone.Model.extend({
if (mapper && this.get('user_id') === mapper.get('id')) return true
else return false
},
isFollowedBy: function(mapper) {
return mapper.get('follows') && mapper.get('follows').topics.indexOf(this.get('id')) > -1
},
getDate: function() {},
getMetacode: function() {
return DataModel.Metacodes.get(this.get('metacode_id'))

View file

@ -181,6 +181,9 @@ const Util = {
})
}
return text
},
isTester: function(currentUser) {
return ['connorturland@gmail.com', 'devin@callysto.com', 'chessscholar@gmail.com', 'solaureum@gmail.com', 'ishanshapiro@gmail.com'].indexOf(currentUser.get('email')) > -1
}
}

View file

@ -53,6 +53,20 @@ const ExploreMaps = {
url: `/maps/${map.id}/access_request`
})
GlobalUI.notifyUser('You will be notified by email if request accepted')
},
onFollow: function(map) {
const isFollowing = map.isFollowedBy(Active.Mapper)
$.post({
url: `/maps/${map.id}/${isFollowing ? 'un' : ''}follow`
})
if (isFollowing) {
GlobalUI.notifyUser('You are no longer following this map')
Active.Mapper.unfollowMap(map.id)
} else {
GlobalUI.notifyUser('You are now following this map')
Active.Mapper.followMap(map.id)
}
self.render()
}
}
ReactDOM.render(

View file

@ -1,5 +1,6 @@
import React, { Component, PropTypes } from 'react'
import { find, values } from 'lodash'
import Util from '../../Metamaps/Util'
const IN_CONVERSATION = 1 // shared with /realtime/reducer.js
@ -23,7 +24,8 @@ class Menu extends Component {
}
render = () => {
const { currentUser, map, onStar, onRequest } = this.props
const { currentUser, map, onStar, onRequest, onFollow } = this.props
const isFollowing = map.isFollowedBy(currentUser)
const style = { display: this.state.open ? 'block' : 'none' }
return <div className='dropdownMenu'>
@ -35,6 +37,7 @@ class Menu extends Component {
<ul className='menuItems' style={ style }>
<li className='star' onClick={ () => { this.toggle() && onStar(map) }}>Star Map</li>
{ !map.authorizeToEdit(currentUser) && <li className='request' onClick={ () => { this.toggle() && onRequest(map) }}>Request Access</li> }
{ Util.isTester(currentUser) && <li className='follow' onClick={ () => { this.toggle() && onFollow(map) }}>{isFollowing ? 'Unfollow' : 'Follow'}</li> }
</ul>
</div>
}
@ -43,7 +46,8 @@ Menu.propTypes = {
currentUser: PropTypes.object.isRequired,
map: PropTypes.object.isRequired,
onStar: PropTypes.func.isRequired,
onRequest: PropTypes.func.isRequired
onRequest: PropTypes.func.isRequired,
onFollow: PropTypes.func.isRequired
}
const Metadata = (props) => {
@ -80,7 +84,7 @@ const checkAndWrapInA = (shouldWrap, classString, mapId, element) => {
class MapCard extends Component {
render = () => {
const { map, mobile, juntoState, currentUser, onRequest, onStar } = this.props
const { map, mobile, juntoState, currentUser, onRequest, onStar, onFollow } = this.props
const hasMap = (juntoState.liveMaps[map.id] && values(juntoState.liveMaps[map.id]).length) || null
const realtimeMap = juntoState.liveMaps[map.id]
@ -131,7 +135,7 @@ class MapCard extends Component {
</div>) }
{ !mobile && hasMapper && <div className='mapHasMapper'><MapperList mappers={ mapperList } /></div> }
{ !mobile && hasConversation && <div className='mapHasConversation'><MapperList mappers={ mapperList } /></div> }
{ !mobile && currentUser && <Menu currentUser={ currentUser } map={ map } onStar= { onStar } onRequest={ onRequest } /> }
{ !mobile && currentUser && <Menu currentUser={ currentUser } map={ map } onStar= { onStar } onRequest={ onRequest } onFollow={ onFollow } /> }
</div>
</div>) }
</div>
@ -145,7 +149,8 @@ MapCard.propTypes = {
juntoState: PropTypes.object,
currentUser: PropTypes.object,
onStar: PropTypes.func.isRequired,
onRequest: PropTypes.func.isRequired
onRequest: PropTypes.func.isRequired,
onFollow: PropTypes.func.isRequired
}
export default MapCard

View file

@ -46,7 +46,7 @@ class Maps extends Component {
}
render = () => {
const { maps, currentUser, juntoState, pending, section, user, onStar, onRequest } = this.props
const { maps, currentUser, juntoState, pending, section, user, onStar, onRequest, onFollow } = this.props
const style = { width: this.state.mapsWidth + 'px' }
const mobile = document && document.body.clientWidth <= MOBILE_VIEW_BREAKPOINT
@ -56,7 +56,7 @@ class Maps extends Component {
<div style={ style }>
{ user ? <MapperCard user={ user } /> : null }
{ currentUser && !user && !(pending && maps.length === 0) ? <div className="map newMap"><a href="/maps/new"><div className="newMapImage"></div><span>Create new map...</span></a></div> : null }
{ maps.models.map(map => <MapCard key={ map.id } map={ map } mobile={ mobile } juntoState={ juntoState } currentUser={ currentUser } onStar={ onStar } onRequest={ onRequest } />) }
{ maps.models.map(map => <MapCard key={ map.id } map={ map } mobile={ mobile } juntoState={ juntoState } currentUser={ currentUser } onStar={ onStar } onRequest={ onRequest } onFollow={ onFollow } />) }
<div className='clearfloat'></div>
</div>
</div>
@ -79,7 +79,8 @@ Maps.propTypes = {
loadMore: PropTypes.func,
pending: PropTypes.bool.isRequired,
onStar: PropTypes.func.isRequired,
onRequest: PropTypes.func.isRequired
onRequest: PropTypes.func.isRequired,
onFollow: PropTypes.func.isRequired
}
export default Maps