Changeset discussions

Add support for commenting on changesets with RSS feeds and email
notification of comments to other commenters and people that have
chosen to subscribe to a changeset.
This commit is contained in:
Łukasz Gurdek 2014-09-07 14:25:56 +01:00 committed by Tom Hughes
parent 653b4c93bc
commit 14ac1babc2
27 changed files with 1108 additions and 27 deletions

3
.gitignore vendored
View file

@ -8,3 +8,6 @@ tmp
*~ *~
doc doc
.vagrant .vagrant
.ruby-gemset
.ruby-version
.idea

View file

@ -12,6 +12,7 @@
//= require index/history //= require index/history
//= require index/note //= require index/note
//= require index/new_note //= require index/new_note
//= require index/changeset
//= require router //= require router
(function() { (function() {
@ -304,7 +305,7 @@ $(document).ready(function () {
"/node/:id(/history)": OSM.Browse(map, 'node'), "/node/:id(/history)": OSM.Browse(map, 'node'),
"/way/:id(/history)": OSM.Browse(map, 'way'), "/way/:id(/history)": OSM.Browse(map, 'way'),
"/relation/:id(/history)": OSM.Browse(map, 'relation'), "/relation/:id(/history)": OSM.Browse(map, 'relation'),
"/changeset/:id": OSM.Browse(map, 'changeset') "/changeset/:id": OSM.Changeset(map)
}); });
if (OSM.preferred_editor == "remote" && document.location.pathname == "/edit") { if (OSM.preferred_editor == "remote" && document.location.pathname == "/edit") {

View file

@ -0,0 +1,80 @@
OSM.Changeset = function (map) {
var page = {},
content = $('#sidebar_content'),
currentChangesetId;
page.pushstate = page.popstate = function(path, id) {
OSM.loadSidebarContent(path, function() {
page.load(path, id);
});
};
page.load = function(path, id) {
if(id)
currentChangesetId = id;
initialize();
addChangeset(currentChangesetId, true);
};
function addChangeset(id, center) {
var bounds = map.addObject({type: 'changeset', id: parseInt(id)}, function(bounds) {
if (!window.location.hash && bounds.isValid() &&
(center || !map.getBounds().contains(bounds))) {
OSM.router.withoutMoveListener(function () {
map.fitBounds(bounds);
});
}
});
}
function updateChangeset(form, method, url, include_data) {
$(form).find("input[type=submit]").prop("disabled", true);
if(include_data) {
data = {text: $(form.text).val()};
} else {
data = {};
}
$.ajax({
url: url,
type: method,
oauth: true,
data: data,
success: function () {
OSM.loadSidebarContent(window.location.pathname, page.load);
}
});
}
function initialize() {
content.find("input[name=comment]").on("click", function (e) {
e.preventDefault();
var data = $(e.target).data();
updateChangeset(e.target.form, data.method, data.url, true);
});
content.find(".action-button").on("click", function (e) {
e.preventDefault();
var data = $(e.target).data();
updateChangeset(e.target.form, data.method, data.url);
});
content.find("textarea").on("input", function (e) {
var form = e.target.form;
if ($(e.target).val() == "") {
$(form.comment).prop("disabled", true);
} else {
$(form.comment).prop("disabled", false);
}
});
content.find("textarea").val('').trigger("input");
};
page.unload = function() {
map.removeObject();
};
return page;
};

View file

@ -1103,7 +1103,7 @@ header .search_form {
font-size: 90%; font-size: 90%;
} }
.note-comments li { .note-comments li, .changeset-comments li {
margin: $lineheight/2 0; margin: $lineheight/2 0;
p { p {
@ -1111,6 +1111,27 @@ header .search_form {
} }
} }
.comments-header {
float:left;
}
.subscribe-buttons {
float:left;
margin: 18px 10px 10px;
min-width: 80px;
}
.subscribe-buttons input {
font-size: 90%;
line-height: 15px;
min-height: 20px;
}
span.action-button:hover {
cursor:pointer;
text-decoration:underline;
}
.note-description { .note-description {
overflow: hidden; overflow: hidden;
margin: 0 0 10px 10px; margin: 0 0 10px 10px;

View file

@ -58,6 +58,8 @@ class BrowseController < ApplicationController
def changeset def changeset
@type = "changeset" @type = "changeset"
@changeset = Changeset.find(params[:id]) @changeset = Changeset.find(params[:id])
@comments = @changeset.comments.unscope(:where => :visible).includes(:author) if @user and @user.moderator?
@comments ||= @changeset.comments.includes(:author)
@node_pages, @nodes = paginate(:old_nodes, :conditions => {:changeset_id => @changeset.id}, :per_page => 20, :parameter => 'node_page') @node_pages, @nodes = paginate(:old_nodes, :conditions => {:changeset_id => @changeset.id}, :per_page => 20, :parameter => 'node_page')
@way_pages, @ways = paginate(:old_ways, :conditions => {:changeset_id => @changeset.id}, :per_page => 20, :parameter => 'way_page') @way_pages, @ways = paginate(:old_ways, :conditions => {:changeset_id => @changeset.id}, :per_page => 20, :parameter => 'way_page')
@relation_pages, @relations = paginate(:old_relations, :conditions => {:changeset_id => @changeset.id}, :per_page => 20, :parameter => 'relation_page') @relation_pages, @relations = paginate(:old_relations, :conditions => {:changeset_id => @changeset.id}, :per_page => 20, :parameter => 'relation_page')

View file

@ -7,11 +7,12 @@ class ChangesetController < ApplicationController
skip_before_filter :verify_authenticity_token, :except => [:list] skip_before_filter :verify_authenticity_token, :except => [:list]
before_filter :authorize_web, :only => [:list, :feed] before_filter :authorize_web, :only => [:list, :feed]
before_filter :set_locale, :only => [:list, :feed] before_filter :set_locale, :only => [:list, :feed]
before_filter :authorize, :only => [:create, :update, :delete, :upload, :include, :close] before_filter :authorize, :only => [:create, :update, :delete, :upload, :include, :close, :comment, :subscribe, :unsubscribe, :hide_comment, :unhide_comment]
before_filter :require_allow_write_api, :only => [:create, :update, :delete, :upload, :include, :close] before_filter :require_moderator, :only => [:hide_comment, :unhide_comment]
before_filter :require_public_data, :only => [:create, :update, :delete, :upload, :include, :close] before_filter :require_allow_write_api, :only => [:create, :update, :delete, :upload, :include, :close, :comment, :subscribe, :unsubscribe, :hide_comment, :unhide_comment]
before_filter :check_api_writable, :only => [:create, :update, :delete, :upload, :include] before_filter :require_public_data, :only => [:create, :update, :delete, :upload, :include, :close, :comment, :subscribe, :unsubscribe]
before_filter :check_api_readable, :except => [:create, :update, :delete, :upload, :download, :query, :list, :feed] before_filter :check_api_writable, :only => [:create, :update, :delete, :upload, :include, :comment, :subscribe, :unsubscribe, :hide_comment, :unhide_comment]
before_filter :check_api_readable, :except => [:create, :update, :delete, :upload, :download, :query, :list, :feed, :comment, :subscribe, :unsubscribe, :comments_feed]
before_filter(:only => [:list, :feed]) { |c| c.check_database_readable(true) } before_filter(:only => [:list, :feed]) { |c| c.check_database_readable(true) }
after_filter :compress_output after_filter :compress_output
around_filter :api_call_handle_error, :except => [:list, :feed] around_filter :api_call_handle_error, :except => [:list, :feed]
@ -29,6 +30,10 @@ class ChangesetController < ApplicationController
# Assume that Changeset.from_xml has thrown an exception if there is an error parsing the xml # Assume that Changeset.from_xml has thrown an exception if there is an error parsing the xml
cs.user_id = @user.id cs.user_id = @user.id
cs.save_with_tags! cs.save_with_tags!
# Subscribe user to changeset comments
cs.subscribers << @user
render :text => cs.id.to_s, :content_type => "text/plain" render :text => cs.id.to_s, :content_type => "text/plain"
end end
@ -37,7 +42,8 @@ class ChangesetController < ApplicationController
# return anything about the nodes, ways and relations in the changeset. # return anything about the nodes, ways and relations in the changeset.
def read def read
changeset = Changeset.find(params[:id]) changeset = Changeset.find(params[:id])
render :text => changeset.to_xml.to_s, :content_type => "text/xml"
render :text => changeset.to_xml(params[:include_discussion].presence).to_s, :content_type => "text/xml"
end end
## ##
@ -305,6 +311,141 @@ class ChangesetController < ApplicationController
list list
end end
##
# Add a comment to a changeset
def comment
# Check the arguments are sane
raise OSM::APIBadUserInput.new("No id was given") unless params[:id]
raise OSM::APIBadUserInput.new("No text was given") if params[:text].blank?
# Extract the arguments
id = params[:id].to_i
body = params[:text]
# Find the changeset and check it is valid
changeset = Changeset.find(id)
raise OSM::APIChangesetNotYetClosedError.new(changeset) if changeset.is_open?
# Add a comment to the changeset
attributes = {
:changeset => changeset,
:body => body,
:author => @user
}
comment = changeset.comments.create(attributes)
changeset.subscribers.each do |user|
if @user != user
Notifier.changeset_comment_notification(comment, user).deliver
end
end
changeset.subscribers << @user unless changeset.subscribers.exists?(@user)
# Return a copy of the updated changeset
render :text => changeset.to_xml.to_s, :content_type => "text/xml"
end
##
# Adds a subscriber to the changeset
def subscribe
# Check the arguments are sane
raise OSM::APIBadUserInput.new("No id was given") unless params[:id]
# Extract the arguments
id = params[:id].to_i
# Find the changeset and check it is valid
changeset = Changeset.find(id)
raise OSM::APIChangesetNotYetClosedError.new(changeset) if changeset.is_open?
raise OSM::APIChangesetAlreadySubscribedError.new(changeset) if changeset.subscribers.exists?(@user)
changeset.subscribers << @user
# Return a copy of the updated changeset
render :text => changeset.to_xml.to_s, :content_type => "text/xml"
end
##
# Removes a subscriber from the changeset
def unsubscribe
# Check the arguments are sane
raise OSM::APIBadUserInput.new("No id was given") unless params[:id]
# Extract the arguments
id = params[:id].to_i
# Find the changeset and check it is valid
changeset = Changeset.find(id)
raise OSM::APIChangesetNotYetClosedError.new(changeset) if changeset.is_open?
raise OSM::APIChangesetNotSubscribedError.new(changeset) unless changeset.subscribers.exists?(@user)
changeset.subscribers.delete(@user)
# Return a copy of the updated changeset
render :text => changeset.to_xml.to_s, :content_type => "text/xml"
end
##
# Sets visible flag on comment to false
def hide_comment
# Check the arguments are sane
raise OSM::APIBadUserInput.new("No id was given") unless params[:id]
# Extract the arguments
id = params[:id].to_i
# Find the changeset
@comment = ChangesetComment.find(id)
changeset = @comment.changeset
@comment.update(:visible => false)
# Return a copy of the updated changeset
render :text => changeset.to_xml.to_s, :content_type => "text/xml"
end
##
# Sets visible flag on comment to true
def unhide_comment
# Check the arguments are sane
raise OSM::APIBadUserInput.new("No id was given") unless params[:id]
# Extract the arguments
id = params[:id].to_i
# Find the changeset
@comment = ChangesetComment.find(id)
changeset = @comment.changeset
@comment.update :visible => true
# Return a copy of the updated changeset
render :text => changeset.to_xml.to_s, :content_type => "text/xml"
end
##
# Get a feed of recent changeset comments
def comments_feed
if params[:id]
# Extract the arguments
id = params[:id].to_i
# Find the changeset
changeset = Changeset.find(id)
# Find the comments we want to return
@comments = changeset.comments.includes(:author, :changeset).limit(comments_limit)
else
@comments = ChangesetComment.includes(:author, :changeset).where(:visible => :true).order("created_at DESC").limit(comments_limit).preload(:changeset)
end
# Render the result
respond_to do |format|
format.rss
end
end
private private
#------------------------------------------------------------ #------------------------------------------------------------
# utility functions below. # utility functions below.
@ -435,4 +576,18 @@ private
return changesets.where("num_changes > 0") return changesets.where("num_changes > 0")
end end
##
# Get the maximum number of comments to return
def comments_limit
if params[:limit]
if params[:limit].to_i > 0 and params[:limit].to_i <= 10000
params[:limit].to_i
else
raise OSM::APIBadUserInput.new("Comments limit must be between 1 and 10000")
end
else
100
end
end
end end

View file

@ -12,6 +12,9 @@ class Changeset < ActiveRecord::Base
has_many :old_ways has_many :old_ways
has_many :old_relations has_many :old_relations
has_many :comments, -> { where(:visible => true).order(:created_at) }, :class_name => "ChangesetComment"
has_and_belongs_to_many :subscribers, :class_name => 'User', :join_table => 'changesets_subscribers', :association_foreign_key => 'subscriber_id'
validates_presence_of :id, :on => :update validates_presence_of :id, :on => :update
validates_presence_of :user_id, :created_at, :closed_at, :num_changes validates_presence_of :user_id, :created_at, :closed_at, :num_changes
validates_uniqueness_of :id validates_uniqueness_of :id
@ -179,13 +182,13 @@ class Changeset < ActiveRecord::Base
end end
end end
def to_xml def to_xml(include_discussion = false)
doc = OSM::API.new.get_xml_doc doc = OSM::API.new.get_xml_doc
doc.root << to_xml_node() doc.root << to_xml_node(nil, include_discussion)
return doc return doc
end end
def to_xml_node(user_display_name_cache = nil) def to_xml_node(user_display_name_cache = nil, include_discussion = false)
el1 = XML::Node.new 'changeset' el1 = XML::Node.new 'changeset'
el1['id'] = self.id.to_s el1['id'] = self.id.to_s
@ -217,6 +220,23 @@ class Changeset < ActiveRecord::Base
bbox.to_unscaled.add_bounds_to(el1, '_') bbox.to_unscaled.add_bounds_to(el1, '_')
end end
el1['comments_count'] = self.comments.count.to_s
if include_discussion
el2 = XML::Node.new('discussion')
self.comments.includes(:author).each do |comment|
el3 = XML::Node.new('comment')
el3['date'] = comment.created_at.xmlschema
el3['uid'] = comment.author.id.to_s if comment.author.data_public?
el3['user'] = comment.author.display_name.to_s if comment.author.data_public?
el4 = XML::Node.new('text')
el4.content = comment.body.to_s
el3 << el4
el2 << el3
end
el1 << el2
end
# NOTE: changesets don't include the XML of the changes within them, # NOTE: changesets don't include the XML of the changes within them,
# they are just structures for tagging. to get the osmChange of a # they are just structures for tagging. to get the osmChange of a
# changeset, see the download method of the controller. # changeset, see the download method of the controller.

View file

@ -0,0 +1,17 @@
class ChangesetComment < ActiveRecord::Base
belongs_to :changeset
belongs_to :author, :class_name => "User"
validates_presence_of :id, :on => :update # is it necessary?
validates_uniqueness_of :id
validates_presence_of :changeset
validates_associated :changeset
validates_presence_of :author
validates_associated :author
validates :visible, :inclusion => { :in => [true,false] }
# Return the comment text
def body
RichText.new("text", read_attribute(:body))
end
end

View file

@ -146,6 +146,26 @@ class Notifier < ActionMailer::Base
end end
end end
def changeset_comment_notification(comment, recipient)
with_recipient_locale recipient do
@changeset_url = changeset_url(comment.changeset, :host => SERVER_URL)
@comment = comment.body
@owner = recipient == comment.changeset.user
@commenter = comment.author.display_name
@changeset_comment = comment.changeset.tags['comment'].presence
@time = comment.created_at
@changeset_author = comment.changeset.user.display_name
if @owner
subject = I18n.t("notifier.changeset_comment_notification.commented.subject_own", :commenter => @commenter)
else
subject = I18n.t("notifier.changeset_comment_notification.commented.subject_other", :commenter => @commenter)
end
mail :to => recipient.email, :subject => subject
end
end
private private
def with_recipient_locale(recipient) def with_recipient_locale(recipient)

View file

@ -12,6 +12,8 @@ class User < ActiveRecord::Base
has_many :tokens, :class_name => "UserToken" has_many :tokens, :class_name => "UserToken"
has_many :preferences, :class_name => "UserPreference" has_many :preferences, :class_name => "UserPreference"
has_many :changesets, -> { order(:created_at => :desc) } has_many :changesets, -> { order(:created_at => :desc) }
has_many :changeset_comments, :foreign_key => 'author_id'
has_and_belongs_to_many :changeset_subscriptions, :class_name => 'Changeset', :join_table => 'changesets_subscribers', :foreign_key => 'subscriber_id'
has_many :note_comments, :foreign_key => :author_id has_many :note_comments, :foreign_key => :author_id
has_many :notes, :through => :note_comments has_many :notes, :through => :note_comments

View file

@ -11,6 +11,69 @@
<%= render :partial => "tag_details", :object => @changeset.tags.except('comment') %> <%= render :partial => "tag_details", :object => @changeset.tags.except('comment') %>
<h4 class="comments-header"><%= t('browse.changeset.discussion') %></h4>
<div class="buttons clearfix subscribe-buttons">
<form action="#" class="hide_unless_logged_in">
<% if @changeset.subscribers.exists?(@user) %>
<input class="action-button" type="submit" name="unsubscribe" value="<%= t('javascripts.changesets.show.unsubscribe') %>" data-method="POST" data-url="<%= changeset_unsubscribe_url(@changeset) %>" />
<% else %>
<input class="action-button" type="submit" name="subscribe" value="<%= t('javascripts.changesets.show.subscribe') %>" data-method="POST" data-url="<%= changeset_subscribe_url(@changeset) %>" />
<% end %>
</form>
</div>
<div class="clearfix"></div>
<% if @comments.length > 0 %>
<div class='changeset-comments'>
<form action="#">
<ul>
<% @comments.each do |comment| %>
<% if comment.visible %>
<li id="c<%= comment.id %>">
<small class='deemphasize'>
<%= t("browse.changeset.commented_by",
:when => friendly_date(comment.created_at), :exact_time => l(comment.created_at),
:user => link_to(h(comment.author.display_name), {:controller => "user", :action => "view",
:display_name => comment.author.display_name})).html_safe %>
<% if @user and @user.moderator? %>
— <span class="action-button deemphasize" data-comment-id="<%= comment.id %>" data-method="POST" data-url="<%= changeset_comment_hide_url(comment.id) %>"><%= t('javascripts.changesets.show.hide_comment') %></span>
<% end %>
</small>
<%= comment.body.to_html %>
</li>
<% elsif @user and @user.moderator? %>
<li id="c<%= comment.id %>">
<small class='deemphasize'>
<%= t("browse.changeset.hidden_commented_by",
:when => friendly_date(comment.created_at), :exact_time => l(comment.created_at),
:user => link_to(h(comment.author.display_name), {:controller => "user", :action => "view",
:display_name => comment.author.display_name})).html_safe %>
— <span class="action-button deemphasize" data-comment-id="<%= comment.id %>" data-method="POST" data-url="<%= changeset_comment_unhide_url(comment.id) %>"><%= t('javascripts.changesets.show.unhide_comment') %></span>
</small>
<%= comment.body.to_html %>
</li>
<% end %>
<% end %>
</ul>
</form>
</div>
<% end %>
<div class="notice hide_if_logged_in">
<%= link_to(t("browse.changeset.join_discussion"), :controller => 'user', :action => 'login', :referer => request.fullpath) %>
</div>
<% unless @changeset.is_open? %>
<form action="#" class="hide_unless_logged_in">
<textarea class="comment" name="text" cols="40" rows="5"></textarea>
<div class="buttons clearfix">
<input type="submit" name="comment" value="<%= t('javascripts.changesets.show.comment') %>" data-changeset-id="<%= @changeset.id %>" data-method="POST" data-url="<%= changeset_comment_url(@changeset) %>" disabled="1"/>
</div>
</form>
<% end %>
<% unless @ways.empty? %> <% unless @ways.empty? %>
<h4> <h4>
<%= type_and_paginated_count('way', @way_pages) %> <%= type_and_paginated_count('way', @way_pages) %>

View file

@ -0,0 +1,6 @@
<h2><%= t "changeset.rss.comment", :author => comment.author.display_name,
:changeset_id => comment.changeset.id.to_s %></h2>
<div class="changeset-comment" style="margin-top: 5px">
<div class="changeset-comment-description" style="font-size: smaller; color: #999999"><%= t "changeset.rss.commented_at_by_html", :when => friendly_date(comment.created_at), :user => comment.author.display_name %></div>
<div class="changeset-comment-text"><%= comment.body %></div>
</div>

View file

@ -0,0 +1,19 @@
comments.each do |comment|
xml.item do
xml.title t("changeset.rss.comment", :author => comment.author.display_name, :changeset_id => comment.changeset.id.to_s)
xml.link url_for(:controller => "browse", :action => "changeset", :id => comment.changeset.id, :anchor => "c#{comment.id}", :only_path => false)
xml.guid url_for(:controller => "browse", :action => "changeset", :id => comment.changeset.id, :anchor => "c#{comment.id}", :only_path => false)
xml.description do
xml.cdata! render(:partial => "comment", :object => comment, :formats => [ :html ])
end
if comment.author
xml.dc :creator, comment.author.display_name
end
xml.pubDate comment.created_at.to_s(:rfc822)
end
end

View file

@ -0,0 +1,14 @@
xml.rss("version" => "2.0",
"xmlns:dc" => "http://purl.org/dc/elements/1.1/") do
xml.channel do
if @changeset
xml.title t('changeset.rss.title_particular', :changeset_id => @changeset.id)
else
xml.title t('changeset.rss.title_all')
end
xml.link url_for(:controller => "site", :action => "index", :only_path => false)
xml << render(:partial => "comments", :object => @comments)
end
end

View file

@ -0,0 +1,22 @@
<p><%= t 'notifier.changeset_comment_notification.greeting' %></p>
<p>
<% if @owner %>
<%= t "notifier.changeset_comment_notification.commented.your_changeset", :commenter => @commenter, :time => @time %>
<% else %>
<%= t "notifier.changeset_comment_notification.commented.commented_changeset", :commenter => @commenter, :time => @time, :changeset_author => @changeset_author %>
<% end %>
<% if @changeset_comment %>
<%= t "notifier.changeset_comment_notification.commented.partial_changeset_with_comment", :changeset_comment => @changeset_comment %>
<% else %>
<%= t "notifier.changeset_comment_notification.commented.partial_changeset_without_comment" %>
<% end %>
</p>
==
<%= @comment.to_html %>
==
<p><%= raw t 'notifier.changeset_comment_notification.details', :url => link_to(@changeset_url, @changeset_url) %></p>

View file

@ -0,0 +1,18 @@
<%= t 'notifier.changeset_comment_notification.greeting' %>
<% if @owner %>
<%= t "notifier.changeset_comment_notification.commented.your_changeset", :commenter => @commenter, :time => @time %>
<% else %>
<%= t "notifier.changeset_comment_notification.commented.commented_changeset", :commenter => @commenter, :time => @time, :changeset_author => @changeset_author %>
<% end %>
<% if @changeset_comment %>
<%= t "notifier.changeset_comment_notification.commented.partial_changeset_with_comment", :changeset_comment => @changeset_comment %>
<% else %>
<%= t "notifier.changeset_comment_notification.commented.partial_changeset_without_comment" %>
<% end %>
==
<%= @comment.to_text %>
==
<%= t 'notifier.changeset_comment_notification.details', :url => @changeset_url %>

View file

@ -121,11 +121,16 @@ en:
way_paginated: "Ways (%{x}-%{y} of %{count})" way_paginated: "Ways (%{x}-%{y} of %{count})"
relation: "Relations (%{count})" relation: "Relations (%{count})"
relation_paginated: "Relations (%{x}-%{y} of %{count})" relation_paginated: "Relations (%{x}-%{y} of %{count})"
comment: "Comments (%{count})"
hidden_commented_by: "Hidden comment from %{user} <abbr title='%{exact_time}'>%{when} ago</abbr>"
commented_by: "Comment from %{user} <abbr title='%{exact_time}'>%{when} ago</abbr>"
changesetxml: "Changeset XML" changesetxml: "Changeset XML"
osmchangexml: "osmChange XML" osmchangexml: "osmChange XML"
feed: feed:
title: "Changeset %{id}" title: "Changeset %{id}"
title_comment: "Changeset %{id} - %{comment}" title_comment: "Changeset %{id} - %{comment}"
join_discussion: "Log in to join the discussion"
discussion: Discussion
node: node:
title: "Node: %{name}" title: "Node: %{name}"
history_title: "Node History: %{name}" history_title: "Node History: %{name}"
@ -228,6 +233,13 @@ en:
load_more: "Load more" load_more: "Load more"
timeout: timeout:
sorry: "Sorry, the list of changesets you requested took too long to retrieve." sorry: "Sorry, the list of changesets you requested took too long to retrieve."
rss:
title_all: OpenStreetMap changeset discussion
title_particular: "OpenStreetMap changeset #%{changeset_id} discussion"
comment: "New comment on changeset #%{changeset_id} by %{author}"
commented_at_html: "Updated %{when} ago"
commented_at_by_html: "Updated %{when} ago by %{user}"
full: Full discussion
diary_entry: diary_entry:
new: new:
title: New Diary Entry title: New Diary Entry
@ -1234,6 +1246,16 @@ en:
your_note: "%{commenter} has reactivated one of your map notes near %{place}." your_note: "%{commenter} has reactivated one of your map notes near %{place}."
commented_note: "%{commenter} has reactivated a map note you have commented on. The note is near %{place}." commented_note: "%{commenter} has reactivated a map note you have commented on. The note is near %{place}."
details: "More details about the note can be found at %{url}." details: "More details about the note can be found at %{url}."
changeset_comment_notification:
greeting: "Hi,"
commented:
subject_own: "[OpenStreetMap] %{commenter} has commented on one of your changesets"
subject_other: "[OpenStreetMap] %{commenter} has commented on a changeset you are interested in"
your_changeset: "%{commenter} has left a comment on one of your changesets created at %{time}"
commented_changeset: "%{commenter} has left a comment on a map changeset you are watching created by %{changeset_author} at %{time}"
partial_changeset_with_comment: "with comment '%{changeset_comment}'"
partial_changeset_without_comment: "without comment"
details: "More details about the changeset can be found at %{url}."
message: message:
inbox: inbox:
title: "Inbox" title: "Inbox"
@ -2104,6 +2126,13 @@ en:
createnote_disabled_tooltip: Zoom in to add a note to the map createnote_disabled_tooltip: Zoom in to add a note to the map
map_notes_zoom_in_tooltip: Zoom in to see map notes map_notes_zoom_in_tooltip: Zoom in to see map notes
map_data_zoom_in_tooltip: Zoom in to see map data map_data_zoom_in_tooltip: Zoom in to see map data
changesets:
show:
comment: "Comment"
subscribe: "Subscribe"
unsubscribe: "Unsubscribe"
hide_comment: "hide"
unhide_comment: "unhide"
notes: notes:
new: new:
intro: "Spotted a mistake or something missing? Let other mappers know so we can fix it. Move the marker to the correct position and type a note to explain the problem. (Please don't enter personal information or information from copyrighted maps or directory listings.)" intro: "Spotted a mistake or something missing? Let other mappers know so we can fix it. Move the marker to the correct position and type a note to explain the problem. (Please don't enter personal information or information from copyrighted maps or directory listings.)"

View file

@ -8,10 +8,16 @@ OpenStreetMap::Application.routes.draw do
match 'api/0.6/changeset/:id/upload' => 'changeset#upload', :via => :post, :id => /\d+/ match 'api/0.6/changeset/:id/upload' => 'changeset#upload', :via => :post, :id => /\d+/
match 'api/0.6/changeset/:id/download' => 'changeset#download', :via => :get, :as => :changeset_download, :id => /\d+/ match 'api/0.6/changeset/:id/download' => 'changeset#download', :via => :get, :as => :changeset_download, :id => /\d+/
match 'api/0.6/changeset/:id/expand_bbox' => 'changeset#expand_bbox', :via => :post, :id => /\d+/ match 'api/0.6/changeset/:id/expand_bbox' => 'changeset#expand_bbox', :via => :post, :id => /\d+/
match 'api/0.6/changeset/:id' => 'changeset#read', :via => :get, :as => :changeset_read, :via => :get, :id => /\d+/ match 'api/0.6/changeset/:id' => 'changeset#read', :via => :get, :as => :changeset_read, :id => /\d+/
match 'api/0.6/changeset/:id/subscribe' => 'changeset#subscribe', :via => :post, :as => :changeset_subscribe, :id => /\d+/
match 'api/0.6/changeset/:id/unsubscribe' => 'changeset#unsubscribe', :via => :post, :as => :changeset_unsubscribe, :id => /\d+/
match 'api/0.6/changeset/:id' => 'changeset#update', :via => :put, :id => /\d+/ match 'api/0.6/changeset/:id' => 'changeset#update', :via => :put, :id => /\d+/
match 'api/0.6/changeset/:id/close' => 'changeset#close', :via => :put, :id => /\d+/ match 'api/0.6/changeset/:id/close' => 'changeset#close', :via => :put, :id => /\d+/
match 'api/0.6/changesets' => 'changeset#query', :via => :get match 'api/0.6/changesets' => 'changeset#query', :via => :get
post 'api/0.6/changeset/:id/comment' => 'changeset#comment', :as => :changeset_comment, :id => /\d+/
get 'api/0.6/changeset(/:id)/comments/feed' => 'changeset#comments_feed', :as => :changeset_comments_feed, :id => /\d*/, :defaults => { :format => 'rss' }
post 'api/0.6/changeset/comment/:id/hide' => 'changeset#hide_comment', :as => :changeset_comment_hide, :id => /\d+/
post 'api/0.6/changeset/comment/:id/unhide' => 'changeset#unhide_comment', :as => :changeset_comment_unhide, :id => /\d+/
match 'api/0.6/node/create' => 'node#create', :via => :put match 'api/0.6/node/create' => 'node#create', :via => :put
match 'api/0.6/node/:id/ways' => 'way#ways_for_node', :via => :get, :id => /\d+/ match 'api/0.6/node/:id/ways' => 'way#ways_for_node', :via => :get, :id => /\d+/

View file

@ -0,0 +1,16 @@
require 'migrate'
class CreateChangesetComments < ActiveRecord::Migration
def change
create_table :changeset_comments do |t|
t.column :changeset_id, :bigint, :null => false
t.column :author_id, :bigint, :null => false
t.text :body, :null => false
t.timestamp :created_at, :null => false
t.boolean :visible, :null => false
end
add_foreign_key :changeset_comments, [:changeset_id], :changesets, [:id]
add_foreign_key :changeset_comments, [:author_id], :users, [:id]
add_index :changeset_comments, :created_at
end
end

View file

@ -0,0 +1,13 @@
require 'migrate'
class AddJoinTableBetweenUsersAndChangesets < ActiveRecord::Migration
def change
create_table :changesets_subscribers, id: false do |t|
t.column :subscriber_id, :bigint, null: false
t.column :changeset_id, :bigint, null: false
end
add_foreign_key :changesets_subscribers, [:subscriber_id], :users, [:id]
add_foreign_key :changesets_subscribers, [:changeset_id], :changesets, [:id]
add_index :changesets_subscribers, [:subscriber_id, :changeset_id], { :unique => true }
add_index :changesets_subscribers, [:changeset_id]
end
end

View file

@ -126,7 +126,7 @@ CREATE TYPE user_status_enum AS ENUM (
CREATE FUNCTION maptile_for_point(bigint, bigint, integer) RETURNS integer CREATE FUNCTION maptile_for_point(bigint, bigint, integer) RETURNS integer
LANGUAGE c STRICT LANGUAGE c STRICT
AS '/srv/www/master.osm.compton.nu/db/functions/libpgosm.so', 'maptile_for_point'; AS '/home/ukasiu/repos/openstreetmap-website/db/functions/libpgosm', 'maptile_for_point';
-- --
@ -135,7 +135,7 @@ CREATE FUNCTION maptile_for_point(bigint, bigint, integer) RETURNS integer
CREATE FUNCTION tile_for_point(integer, integer) RETURNS bigint CREATE FUNCTION tile_for_point(integer, integer) RETURNS bigint
LANGUAGE c STRICT LANGUAGE c STRICT
AS '/srv/www/master.osm.compton.nu/db/functions/libpgosm.so', 'tile_for_point'; AS '/home/ukasiu/repos/openstreetmap-website/db/functions/libpgosm', 'tile_for_point';
-- --
@ -143,8 +143,8 @@ CREATE FUNCTION tile_for_point(integer, integer) RETURNS bigint
-- --
CREATE FUNCTION xid_to_int4(xid) RETURNS integer CREATE FUNCTION xid_to_int4(xid) RETURNS integer
LANGUAGE c IMMUTABLE STRICT LANGUAGE c STRICT
AS '/srv/www/master.osm.compton.nu/db/functions/libpgosm.so', 'xid_to_int4'; AS '/home/ukasiu/repos/openstreetmap-website/db/functions/libpgosm', 'xid_to_int4';
SET default_tablespace = ''; SET default_tablespace = '';
@ -183,6 +183,39 @@ CREATE SEQUENCE acls_id_seq
ALTER SEQUENCE acls_id_seq OWNED BY acls.id; ALTER SEQUENCE acls_id_seq OWNED BY acls.id;
--
-- Name: changeset_comments; Type: TABLE; Schema: public; Owner: -; Tablespace:
--
CREATE TABLE changeset_comments (
id integer NOT NULL,
changeset_id bigint NOT NULL,
author_id bigint NOT NULL,
body text NOT NULL,
created_at timestamp without time zone NOT NULL,
visible boolean NOT NULL
);
--
-- Name: changeset_comments_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE changeset_comments_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: changeset_comments_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE changeset_comments_id_seq OWNED BY changeset_comments.id;
-- --
-- Name: changeset_tags; Type: TABLE; Schema: public; Owner: -; Tablespace: -- Name: changeset_tags; Type: TABLE; Schema: public; Owner: -; Tablespace:
-- --
@ -230,6 +263,16 @@ CREATE SEQUENCE changesets_id_seq
ALTER SEQUENCE changesets_id_seq OWNED BY changesets.id; ALTER SEQUENCE changesets_id_seq OWNED BY changesets.id;
--
-- Name: changesets_subscribers; Type: TABLE; Schema: public; Owner: -; Tablespace:
--
CREATE TABLE changesets_subscribers (
subscriber_id bigint NOT NULL,
changeset_id bigint NOT NULL
);
-- --
-- Name: client_applications; Type: TABLE; Schema: public; Owner: -; Tablespace: -- Name: client_applications; Type: TABLE; Schema: public; Owner: -; Tablespace:
-- --
@ -698,7 +741,7 @@ CREATE TABLE nodes (
-- --
CREATE TABLE note_comments ( CREATE TABLE note_comments (
id integer NOT NULL, id bigint NOT NULL,
note_id bigint NOT NULL, note_id bigint NOT NULL,
visible boolean NOT NULL, visible boolean NOT NULL,
created_at timestamp without time zone NOT NULL, created_at timestamp without time zone NOT NULL,
@ -733,7 +776,7 @@ ALTER SEQUENCE note_comments_id_seq OWNED BY note_comments.id;
-- --
CREATE TABLE notes ( CREATE TABLE notes (
id integer NOT NULL, id bigint NOT NULL,
latitude integer NOT NULL, latitude integer NOT NULL,
longitude integer NOT NULL, longitude integer NOT NULL,
tile bigint NOT NULL, tile bigint NOT NULL,
@ -851,8 +894,8 @@ CREATE TABLE redactions (
id integer NOT NULL, id integer NOT NULL,
title character varying(255), title character varying(255),
description text, description text,
created_at timestamp without time zone NOT NULL, created_at timestamp without time zone,
updated_at timestamp without time zone NOT NULL, updated_at timestamp without time zone,
user_id bigint NOT NULL, user_id bigint NOT NULL,
description_format format_enum DEFAULT 'markdown'::format_enum NOT NULL description_format format_enum DEFAULT 'markdown'::format_enum NOT NULL
); );
@ -1064,9 +1107,9 @@ CREATE TABLE users (
status user_status_enum DEFAULT 'pending'::user_status_enum NOT NULL, status user_status_enum DEFAULT 'pending'::user_status_enum NOT NULL,
terms_agreed timestamp without time zone, terms_agreed timestamp without time zone,
consider_pd boolean DEFAULT false NOT NULL, consider_pd boolean DEFAULT false NOT NULL,
openid_url character varying(255),
preferred_editor character varying(255), preferred_editor character varying(255),
terms_seen boolean DEFAULT false NOT NULL, terms_seen boolean DEFAULT false NOT NULL,
openid_url character varying(255),
description_format format_enum DEFAULT 'html'::format_enum NOT NULL, description_format format_enum DEFAULT 'html'::format_enum NOT NULL,
image_fingerprint character varying(255), image_fingerprint character varying(255),
changesets_count integer DEFAULT 0 NOT NULL, changesets_count integer DEFAULT 0 NOT NULL,
@ -1141,6 +1184,13 @@ CREATE TABLE ways (
ALTER TABLE ONLY acls ALTER COLUMN id SET DEFAULT nextval('acls_id_seq'::regclass); ALTER TABLE ONLY acls ALTER COLUMN id SET DEFAULT nextval('acls_id_seq'::regclass);
--
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY changeset_comments ALTER COLUMN id SET DEFAULT nextval('changeset_comments_id_seq'::regclass);
-- --
-- Name: id; Type: DEFAULT; Schema: public; Owner: - -- Name: id; Type: DEFAULT; Schema: public; Owner: -
-- --
@ -1289,6 +1339,14 @@ ALTER TABLE ONLY acls
ADD CONSTRAINT acls_pkey PRIMARY KEY (id); ADD CONSTRAINT acls_pkey PRIMARY KEY (id);
--
-- Name: changeset_comments_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
--
ALTER TABLE ONLY changeset_comments
ADD CONSTRAINT changeset_comments_pkey PRIMARY KEY (id);
-- --
-- Name: changesets_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- Name: changesets_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
-- --
@ -1737,6 +1795,34 @@ CREATE INDEX gpx_files_user_id_idx ON gpx_files USING btree (user_id);
CREATE INDEX gpx_files_visible_visibility_idx ON gpx_files USING btree (visible, visibility); CREATE INDEX gpx_files_visible_visibility_idx ON gpx_files USING btree (visible, visibility);
--
-- Name: index_changeset_comments_on_body; Type: INDEX; Schema: public; Owner: -; Tablespace:
--
CREATE INDEX index_changeset_comments_on_body ON changeset_comments USING btree (body);
--
-- Name: index_changeset_comments_on_created_at; Type: INDEX; Schema: public; Owner: -; Tablespace:
--
CREATE INDEX index_changeset_comments_on_created_at ON changeset_comments USING btree (created_at);
--
-- Name: index_changesets_subscribers_on_changeset_id; Type: INDEX; Schema: public; Owner: -; Tablespace:
--
CREATE INDEX index_changesets_subscribers_on_changeset_id ON changesets_subscribers USING btree (changeset_id);
--
-- Name: index_changesets_subscribers_on_subscriber_id_and_changeset_id; Type: INDEX; Schema: public; Owner: -; Tablespace:
--
CREATE UNIQUE INDEX index_changesets_subscribers_on_subscriber_id_and_changeset_id ON changesets_subscribers USING btree (subscriber_id, changeset_id);
-- --
-- Name: index_client_applications_on_key; Type: INDEX; Schema: public; Owner: -; Tablespace: -- Name: index_client_applications_on_key; Type: INDEX; Schema: public; Owner: -; Tablespace:
-- --
@ -1968,6 +2054,22 @@ CREATE INDEX ways_changeset_id_idx ON ways USING btree (changeset_id);
CREATE INDEX ways_timestamp_idx ON ways USING btree ("timestamp"); CREATE INDEX ways_timestamp_idx ON ways USING btree ("timestamp");
--
-- Name: changeset_comments_author_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY changeset_comments
ADD CONSTRAINT changeset_comments_author_id_fkey FOREIGN KEY (author_id) REFERENCES users(id);
--
-- Name: changeset_comments_changeset_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY changeset_comments
ADD CONSTRAINT changeset_comments_changeset_id_fkey FOREIGN KEY (changeset_id) REFERENCES changesets(id);
-- --
-- Name: changeset_tags_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- Name: changeset_tags_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
-- --
@ -1976,6 +2078,22 @@ ALTER TABLE ONLY changeset_tags
ADD CONSTRAINT changeset_tags_id_fkey FOREIGN KEY (changeset_id) REFERENCES changesets(id); ADD CONSTRAINT changeset_tags_id_fkey FOREIGN KEY (changeset_id) REFERENCES changesets(id);
--
-- Name: changesets_subscribers_changeset_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY changesets_subscribers
ADD CONSTRAINT changesets_subscribers_changeset_id_fkey FOREIGN KEY (changeset_id) REFERENCES changesets(id);
--
-- Name: changesets_subscribers_subscriber_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY changesets_subscribers
ADD CONSTRAINT changesets_subscribers_subscriber_id_fkey FOREIGN KEY (subscriber_id) REFERENCES users(id);
-- --
-- Name: changesets_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- Name: changesets_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
-- --
@ -2426,6 +2544,10 @@ INSERT INTO schema_migrations (version) VALUES ('20140117185510');
INSERT INTO schema_migrations (version) VALUES ('20140210003018'); INSERT INTO schema_migrations (version) VALUES ('20140210003018');
INSERT INTO schema_migrations (version) VALUES ('20140507110937');
INSERT INTO schema_migrations (version) VALUES ('20140519141742');
INSERT INTO schema_migrations (version) VALUES ('21'); INSERT INTO schema_migrations (version) VALUES ('21');
INSERT INTO schema_migrations (version) VALUES ('22'); INSERT INTO schema_migrations (version) VALUES ('22');

View file

@ -106,6 +106,57 @@ module OSM
end end
end end
# Raised when the changeset provided is not yet closed
class APIChangesetNotYetClosedError < APIError
def initialize(changeset)
@changeset = changeset
end
attr_reader :changeset
def status
:conflict
end
def to_s
"The changeset #{@changeset.id} is not yet closed."
end
end
# Raised when a user is already subscribed to the changeset
class APIChangesetAlreadySubscribedError < APIError
def initialize(changeset)
@changeset = changeset
end
attr_reader :changeset
def status
:conflict
end
def to_s
"You are already subscribed to changeset #{@changeset.id}."
end
end
# Raised when a user is not subscribed to the changeset
class APIChangesetNotSubscribedError < APIError
def initialize(changeset)
@changeset = changeset
end
attr_reader :changeset
def status
:not_found
end
def to_s
"You are not subscribed to changeset #{@changeset.id}."
end
end
# Raised when a change is expecting a changeset, but the changeset doesn't exist # Raised when a change is expecting a changeset, but the changeset doesn't exist
class APIChangesetMissingError < APIError class APIChangesetMissingError < APIError
def status def status

View file

@ -3,6 +3,7 @@ require 'changeset_controller'
class ChangesetControllerTest < ActionController::TestCase class ChangesetControllerTest < ActionController::TestCase
api_fixtures api_fixtures
fixtures :changeset_comments
## ##
# test all routes which lead to this controller # test all routes which lead to this controller
@ -35,6 +36,30 @@ class ChangesetControllerTest < ActionController::TestCase
{ :path => "/api/0.6/changeset/1/close", :method => :put }, { :path => "/api/0.6/changeset/1/close", :method => :put },
{ :controller => "changeset", :action => "close", :id => "1" } { :controller => "changeset", :action => "close", :id => "1" }
) )
assert_routing(
{ :path => "/api/0.6/changeset/1/subscribe", :method => :post },
{ :controller => "changeset", :action => "subscribe", :id => "1" }
)
assert_routing(
{ :path => "/api/0.6/changeset/1/unsubscribe", :method => :post },
{ :controller => "changeset", :action => "unsubscribe", :id => "1" }
)
assert_routing(
{ :path => "/api/0.6/changeset/comment/1/hide", :method => :post },
{ :controller => "changeset", :action => "hide_comment", :id => "1" }
)
assert_routing(
{ :path => "/api/0.6/changeset/comment/1/unhide", :method => :post },
{ :controller => "changeset", :action => "unhide_comment", :id => "1" }
)
assert_routing(
{ :path => "/api/0.6/changeset/comments/feed", :method => :get },
{ :controller => "changeset", :action => "comments_feed", :format =>"rss" }
)
assert_routing(
{ :path => "/api/0.6/changeset/1/comments/feed", :method => :get },
{ :controller => "changeset", :action => "comments_feed", :id => "1", :format =>"rss" }
)
assert_routing( assert_routing(
{ :path => "/api/0.6/changesets", :method => :get }, { :path => "/api/0.6/changesets", :method => :get },
{ :controller => "changeset", :action => "query" } { :controller => "changeset", :action => "query" }
@ -100,6 +125,9 @@ class ChangesetControllerTest < ActionController::TestCase
# must be number of seconds... # must be number of seconds...
assert_equal 3600, duration.round, "initial idle timeout should be an hour (#{cs.created_at} -> #{cs.closed_at})" assert_equal 3600, duration.round, "initial idle timeout should be an hour (#{cs.created_at} -> #{cs.closed_at})"
end end
# checks if uploader was subscribed
assert_equal 1, cs.subscribers.length
end end
def test_create_invalid def test_create_invalid
@ -149,11 +177,15 @@ class ChangesetControllerTest < ActionController::TestCase
# document structure. # document structure.
def test_read def test_read
changeset_id = changesets(:normal_user_first_change).id changeset_id = changesets(:normal_user_first_change).id
get :read, :id => changeset_id get :read, :id => changeset_id, :format => :xml
assert_response :success, "cannot get first changeset" assert_response :success, "cannot get first changeset"
assert_select "osm[version=#{API_VERSION}][generator=\"OpenStreetMap server\"]", 1 assert_select "osm[version=#{API_VERSION}][generator=\"OpenStreetMap server\"]", 1
assert_select "osm>changeset[id=#{changeset_id}]", 1 assert_select "osm>changeset[id=#{changeset_id}]", 1
assert_select "osm>changeset>discussion", 0
get :read, :id => changeset_id, :format => :xml, :include_discussion => true
assert_select "osm>changeset>discussion", 1
end end
## ##
@ -1382,7 +1414,8 @@ EOF
end end
# get the bounding box back from the changeset # get the bounding box back from the changeset
get :read, :id => changeset_id get :read, :id => changeset_id, :format => :xml
assert_response :success, "Couldn't read back changeset." assert_response :success, "Couldn't read back changeset."
assert_select "osm>changeset[min_lon=1.0]", 1 assert_select "osm>changeset[min_lon=1.0]", 1
assert_select "osm>changeset[max_lon=1.0]", 1 assert_select "osm>changeset[max_lon=1.0]", 1
@ -1397,7 +1430,7 @@ EOF
end end
# get the bounding box back from the changeset # get the bounding box back from the changeset
get :read, :id => changeset_id get :read, :id => changeset_id, :format => :xml
assert_response :success, "Couldn't read back changeset for the second time." assert_response :success, "Couldn't read back changeset for the second time."
assert_select "osm>changeset[min_lon=1.0]", 1 assert_select "osm>changeset[min_lon=1.0]", 1
assert_select "osm>changeset[max_lon=2.0]", 1 assert_select "osm>changeset[max_lon=2.0]", 1
@ -1413,7 +1446,7 @@ EOF
end end
# get the bounding box back from the changeset # get the bounding box back from the changeset
get :read, :id => changeset_id get :read, :id => changeset_id, :format => 'xml'
assert_response :success, "Couldn't read back changeset for the third time." assert_response :success, "Couldn't read back changeset for the third time."
# note that the 3.1 here is because of the bbox overexpansion # note that the 3.1 here is because of the bbox overexpansion
assert_select "osm>changeset[min_lon=1.0]", 1 assert_select "osm>changeset[min_lon=1.0]", 1
@ -1840,6 +1873,234 @@ EOF
assert_select "osmChange node[id=17][version=1]", 0 assert_select "osmChange node[id=17][version=1]", 0
end end
##
# create comment success
def test_create_comment_success
basic_authorization(users(:public_user).email, 'test')
assert_difference('ChangesetComment.count') do
post :comment, { :id => changesets(:normal_user_closed_change).id, :text => 'This is a comment', :format => :xml }
end
assert_response :success
end
##
# create comment fail
def test_create_comment_fail
# unauthorized
post :comment, { :id => changesets(:normal_user_closed_change).id, :text => 'This is a comment' }
assert_response :unauthorized
basic_authorization(users(:public_user).email, 'test')
# bad changeset id
assert_no_difference('ChangesetComment.count') do
post :comment, { :id => 999111, :text => 'This is a comment' }
end
assert_response :not_found
# not closed changeset
assert_no_difference('ChangesetComment.count') do
post :comment, { :id => changesets(:normal_user_first_change).id, :text => 'This is a comment' }
end
assert_response :conflict
# no text
assert_no_difference('ChangesetComment.count') do
post :comment, { :id => changesets(:normal_user_closed_change).id }
end
assert_response :bad_request
# empty text
assert_no_difference('ChangesetComment.count') do
post :comment, { :id => changesets(:normal_user_closed_change).id, :text => '' }
end
assert_response :bad_request
end
##
# test subscribe success
def test_subscribe_success
basic_authorization(users(:public_user).email, 'test')
changeset = changesets(:normal_user_closed_change)
assert_difference('changeset.subscribers.count') do
post :subscribe, { :id => changeset.id, :format => :xml }
end
assert_response :success
end
##
# test subscribe fail
def test_subscribe_fail
changeset = changesets(:normal_user_closed_change)
assert_no_difference('changeset.subscribers.count') do
post :subscribe, { :id => changeset.id, :format => :xml }
end
assert_response :unauthorized
basic_authorization(users(:public_user).email, 'test')
assert_no_difference('changeset.subscribers.count') do
post :subscribe, { :id => 999111 }
end
assert_response :not_found
changeset = changesets(:normal_user_first_change)
assert_no_difference('changeset.subscribers.count') do
post :subscribe, { :id => changeset.id }
end
assert_response :conflict
# subscribing
changeset = changesets(:normal_user_closed_change)
post :subscribe, { :id => changeset.id, :format => :xml }
assert_response :success
# trying to subsrcirbe one more time
assert_no_difference('changeset.subscribers.count') do
post :subscribe, { :id => changeset.id, :format => :xml }
end
assert_response :conflict
end
##
# test unsubscribe success
def test_unsubscribe_success
basic_authorization(users(:public_user).email, 'test')
changeset = changesets(:normal_user_closed_change)
post :subscribe, { :id => changeset.id, :format => :xml }
# unsubscribe
assert_difference('changeset.subscribers.count', -1) do
post :unsubscribe, { :id => changeset.id, :format => :xml }
end
assert_response :success
end
##
# test unsubscribe fail
def test_unsubscribe_fail
changeset = changesets(:normal_user_closed_change)
assert_no_difference('changeset.subscribers.count') do
post :unsubscribe, { :id => changeset.id }
end
assert_response :unauthorized
basic_authorization(users(:public_user).email, 'test')
assert_no_difference('changeset.subscribers.count', -1) do
post :unsubscribe, { :id => 999111 }
end
assert_response :not_found
changeset = changesets(:normal_user_first_change)
assert_no_difference('changeset.subscribers.count', -1) do
post :unsubscribe, { :id => changeset.id }
end
assert_response :conflict
# trying to unsubscribe when not subscribed
changeset = changesets(:normal_user_closed_change)
assert_no_difference('changeset.subscribers.count') do
post :unsubscribe, { :id => changeset.id }
end
assert_response :not_found
end
##
# test hide comment fail
def test_hide_comment_fail
comment = changeset_comments(:normal_comment_1)
assert('comment.visible') do
post :hide_comment, { :id => comment.id, :format => "xml" }
assert_response :unauthorized
end
basic_authorization(users(:public_user).email, 'test')
assert('comment.visible') do
post :hide_comment, { :id => comment.id, :format => "xml" }
assert_response :forbidden
end
basic_authorization(users(:moderator_user).email, 'test')
post :hide_comment, { :id => 999111, :format => "xml" }
assert_response :not_found
end
##
# test hide comment succes
def test_hide_comment_success
comment = changeset_comments(:normal_comment_1)
basic_authorization(users(:moderator_user).email, 'test')
assert('!comment.visible') do
post :hide_comment, { :id => comment.id, :format => "xml" }
end
assert_response :success
end
##
# test unhide comment fail
def test_unhide_comment_fail
comment = changeset_comments(:normal_comment_1)
assert('comment.visible') do
post :unhide_comment, { :id => comment.id, :format => "xml" }
assert_response :unauthorized
end
basic_authorization(users(:public_user).email, 'test')
assert('comment.visible') do
post :unhide_comment, { :id => comment.id, :format => "xml" }
assert_response :forbidden
end
basic_authorization(users(:moderator_user).email, 'test')
post :unhide_comment, { :id => 999111, :format => "xml" }
assert_response :not_found
end
##
# test unhide comment succes
def test_unhide_comment_success
comment = changeset_comments(:normal_comment_1)
basic_authorization(users(:moderator_user).email, 'test')
assert('!comment.visible') do
post :unhide_comment, { :id => comment.id, :format => "xml" }
end
assert_response :success
end
##
# test comments feed
def test_comments_feed
get :comments_feed, {:format => "rss"}
assert_response :success
assert_equal "application/rss+xml", @response.content_type
assert_select "rss", :count => 1 do
assert_select "channel", :count => 1 do
assert_select "item", :count => 3
end
end
get :comments_feed, { :id => changesets(:normal_user_closed_change), :format => "rss"}
assert_response :success
assert_equal "application/rss+xml", @response.content_type
assert_select "rss", :count => 1 do
assert_select "channel", :count => 1 do
assert_select "item", :count => 3
end
end
end
#------------------------------------------------------------ #------------------------------------------------------------
# utility functions # utility functions
#------------------------------------------------------------ #------------------------------------------------------------
@ -1886,5 +2147,4 @@ EOF
xml.find("//osm/way").first[name] = value.to_s xml.find("//osm/way").first[name] = value.to_s
return xml return xml
end end
end end

View file

@ -859,7 +859,7 @@ OSM
# now download the changeset to check its bounding box # now download the changeset to check its bounding box
with_controller(ChangesetController.new) do with_controller(ChangesetController.new) do
get :read, :id => changeset_id get :read, :id => changeset_id, :format => :xml # TODO why it doesn't use defaults?
assert_response :success, "can't re-read changeset for modify test" assert_response :success, "can't re-read changeset for modify test"
assert_select "osm>changeset", 1, "Changeset element doesn't exist in #{@response.body}" assert_select "osm>changeset", 1, "Changeset element doesn't exist in #{@response.body}"
assert_select "osm>changeset[id=#{changeset_id}]", 1, "Changeset id=#{changeset_id} doesn't exist in #{@response.body}" assert_select "osm>changeset[id=#{changeset_id}]", 1, "Changeset id=#{changeset_id} doesn't exist in #{@response.body}"

31
test/fixtures/changeset_comments.yml vendored Normal file
View file

@ -0,0 +1,31 @@
normal_comment_1:
id: 1
changeset_id: 3
created_at: 2007-01-01 00:00:00
author_id: 1
body: 'A comment from a logged-in user'
visible: true
normal_comment_2:
id: 2
changeset_id: 3
created_at: 2007-02-01 00:00:00
author_id: 4
body: 'A comment from another logged-in user'
visible: true
normal_comment_3:
id: 4
changeset_id: 3
created_at: 2007-02-01 00:00:00
author_id: 4
body: 'A comment from another logged-in user'
visible: true
hidden_comment:
id: 3
changeset_id: 3
created_at: 2007-02-01 00:00:00
author_id: 4
body: 'A non-visible comment'
visible: false

View file

@ -0,0 +1,49 @@
require 'test_helper'
class UserChangesetCommentsTest < ActionDispatch::IntegrationTest
fixtures :users, :changesets, :changeset_comments
# Test 'log in to comment' message for nonlogged in user
def test_log_in_message
get "/changeset/#{changesets(:normal_user_closed_change).id}"
assert_response :success
assert_select "div#content" do
assert_select "div#sidebar" do
assert_select "div#sidebar_content" do
assert_select "div.browse-section" do
assert_select "div.notice.hide_if_logged_in"
end
end
end
end
end
# Test if the form is shown
def test_displaying_form
get_via_redirect '/login'
# We should now be at the login page
assert_response :success
assert_template 'user/login'
# We can now login
post '/login', {'username' => "test@openstreetmap.org", 'password' => "test"}
assert_response :redirect
get "/changeset/#{changesets(:normal_user_closed_change).id}"
assert_response :success
assert_template 'browse/changeset'
assert_select "div#content" do
assert_select "div#sidebar" do
assert_select "div#sidebar_content" do
assert_select "div.browse-section" do
assert_select "form[action='#']" do
assert_select "textarea[name=text]"
end
end
end
end
end
end
end

View file

@ -0,0 +1,41 @@
require 'test_helper'
class ChangesetCommentTest < ActiveSupport::TestCase
fixtures :changesets, :changeset_comments
def test_changeset_comment_count
assert_equal 4, ChangesetComment.count
end
# validations
def test_does_not_accept_invalid_author
comment = changeset_comments(:normal_comment_1)
comment.author = nil
assert !comment.valid?
comment.author_id = 999111
assert !comment.valid?
end
def test_does_not_accept_invalid_changeset
comment = changeset_comments(:normal_comment_1)
comment.changeset = nil
assert !comment.valid?
comment.changeset_id = 999111
assert !comment.valid?
end
def test_does_not_accept_empty_visible
comment = changeset_comments(:normal_comment_1)
comment.visible = nil
assert !comment.valid?
end
def test_comments_of_changeset_count
assert_equal 3, Changeset.find(changesets(:normal_user_closed_change)).comments.count
end
end