Skip to content
This repository has been archived by the owner on Apr 17, 2023. It is now read-only.

Commit

Permalink
Added the the ability to add comments on repositories
Browse files Browse the repository at this point in the history
Fixes: #204

Signed-off-by: Jürgen Löhel <[email protected]>
  • Loading branch information
Jürgen Löhel committed Nov 26, 2015
1 parent 08e1c76 commit 4d780d9
Show file tree
Hide file tree
Showing 27 changed files with 334 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- Team and namespace descriptions can be written using Markdown. See pull
requests: [#546](https://github.com/SUSE/Portus/pull/546) and
[#531](https://github.com/SUSE/Portus/pull/531).
- Team members can comment on repositories. See pull request: [#538](https://github.com/SUSE/Portus/pull/583)

## 2.0.0

Expand Down
8 changes: 8 additions & 0 deletions app/assets/javascripts/comments.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
$(document).on "page:change", ->

# Shows and hides the comment form
$('#write_comment_repository_btn').unbind('click').on 'click', (event) ->
$('#write_comment_form').toggle 400, "swing", ->
if $('#write_comment_form').is(':visible')
$('#comment_body').focus()
layout_resizer()
1 change: 1 addition & 0 deletions app/assets/stylesheets/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@
@import 'tables';
@import 'forms';
@import 'types';
@import 'comments';
@import 'responsive-utilities';
15 changes: 15 additions & 0 deletions app/assets/stylesheets/comments.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#write_comment_repository_btn, .destroy_comments_btn {
padding-top: 0px;
padding-bottom: 0px;
border: 0px;
}

.comment-thumbnail {
padding: 10px 0px;
img {
border-radius: 100%;
border: 4px solid $gray-light;
width: 40px;
}
}

33 changes: 33 additions & 0 deletions app/controllers/comments_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
class CommentsController < ApplicationController
before_action :set_repository
respond_to :js, :html

# POST /repositories/1/comments
# POST /repositories/1/comments.json
def create
@comment = @repository.comments.new(params.require(:comment).permit(:body))
@comment.author = current_user
authorize @comment

if @comment.save
respond_with(@comment)
else
respond_with @comment.errors, status: :unprocessable_entity
end
end

# DELETE /repositories/1/comments/1
# DELETE /repositories/1/comments/1.json
def destroy
@comment = @repository.comments.find(params[:id])
authorize @comment
@comment.destroy
respond_with @comment
end

private

def set_repository
@repository = Repository.find(params[:repository_id])
end
end
1 change: 1 addition & 0 deletions app/controllers/repositories_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def index
def show
authorize @repository
@tags = @repository.tags.order("created_at DESC")
@repository_comments = @repository.comments.all
respond_with(@repository)
end

Expand Down
12 changes: 12 additions & 0 deletions app/models/comment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class Comment < ActiveRecord::Base
include PublicActivity::Common
belongs_to :repository
belongs_to :author, class_name: "User", foreign_key: "user_id"

validates :body, presence: true

# Returns true if the user is the author of the comment
def author?(user)
user_id == user.id
end
end
1 change: 1 addition & 0 deletions app/models/repository.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class Repository < ActiveRecord::Base
belongs_to :namespace
has_many :tags, dependent: :delete_all
has_many :stars, dependent: :delete_all
has_many :comments, dependent: :delete_all

# We don't validate the format because we get that from the registry, and
# it's guaranteed to be well-formatted there.
Expand Down
22 changes: 22 additions & 0 deletions app/policies/comment_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
class CommentPolicy
attr_reader :user, :comment

def initialize(user, comment)
@user = user
@comment = comment
end

# Allows the admin to write comments
# Allows all members of a team to comment on their repos
# Allows all users to comment on repos under a public namespace
def create?
@user.admin? ||
@comment.repository.namespace.public? ||
@comment.repository.namespace.team.team_users.where(user_id: @user.id).any?
end

# Allows only the admin and author to delete comments
def destroy?
@user.admin? || @comment.author?(@user)
end
end
29 changes: 29 additions & 0 deletions app/views/comments/_comment.html.slim
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.row id="comment_#{comment.id}"
.col-md-1.comment-thumbnail
.pull-right
.user-image
= user_image_tag(comment.author.email)
.col-md-11
.panel.panel-default
.panel-heading
h5
strong
= comment.author.username
' commented
= activity_time_tag comment.updated_at
' ago
.pull-right
button.btn.btn-link.btn-x.btn-edit-role.destroy_comments_btn[
data-placement="left"
data-toggle="popover"
data-title="Please confirm"
data-content='<p>Are you sure you want to remove this \
comment?</p><a class="btn btn-default">No</a> <a class="btn \
btn-primary" data-method="delete" rel="nofollow" \
data-remote="true" href="#{url_for([comment.repository, comment])}">Yes</a>'
data-html="true"
role="button"]
if.fa.fa-trash
| Delete comment
.panel-body
= markdown(comment.body)
15 changes: 15 additions & 0 deletions app/views/comments/create.js.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<% if @comment.errors.any? %>
$('#alert p').html("<%= escape_javascript(@comment.errors.full_messages.join('<br/>')) %>");
<% else %>
<% if @repository.comments.count == 1 %>
$(".comment_string").text(" Comment");
$(".container-full").text("")
<% elsif @repository.comments.count == 2 %>
$(".comment_string").text(" Comments");
<% end %>
$(".number_of_comments").text("<%= escape_javascript(@repository.comments.count.to_s) %>");
$("<%= escape_javascript(render @comment) %>").appendTo(".container-full");
$('#alert p').html("New comment posted");
$('#write_comment_form').fadeOut();
<% end %>
$('#alert').fadeIn();
14 changes: 14 additions & 0 deletions app/views/comments/destroy.js.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<% if @comment.errors.any? %>
$('#alert p').html("<%= escape_javascript(@comment.errors.full_messages.join('<br/>')) %>");
<% else %>
<% if @repository.comments.count == 0 %>
$(".comment_string").text(" Comments");
$(".container-full").text("Nobody has left a comment yet.");
<% elsif @repository.comments.count == 1 %>
$(".comment_string").text(" Comment");
<% end %>
$("#comment_<%= escape_javascript(@comment.id.to_s) %>").remove();
$(".number_of_comments").text("<%= escape_javascript(@repository.comments.count.to_s) %>");
$('#alert p').html("Comment successfully deleted");
<% end %>
$('#alert').fadeIn();
34 changes: 33 additions & 1 deletion app/views/repositories/show.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
= link_to toggle_star_repository_path(@repository), method: :post, title: 'Star repository', class: 'btn btn-small btn-default btn-xs', remote: true, id: 'toggle_star' do
i.fa.fa-star-o
span#star-counter.btn.btn-primary.btn-xs
= @repository.stars.count
= @repository.stars.count

.panel-body
.table-responsive
Expand All @@ -34,3 +34,35 @@
= tag.name
td= tag.author.username
td= tag.created_at

#write_comment_form.collapse
= form_for [@repository, @repository.comments.build], remote: true, html: {id: 'new-comment-form', class: 'form-horizontal', role: 'form'} do |f|
.form-group
= f.label :comment, {class: 'control-label col-md-2'}
.col-md-7
= f.text_area(:body, class: 'form-control', required: false, placeholder: "Please write a comment.")
.form-group
.col-md-offset-2.col-md-7
= f.submit('Add', class: 'btn btn-primary')

.panel.panel-default
.panel-heading.clearfix
h5
.pull-right
a#write_comment_repository_btn.btn.btn-xs.btn-link.js-toggle-button role="button"
i.fa.fa-comment
| Write a comment
span class="number_of_comments"
= @repository.comments.count
span class="comment_string"
- if @repository.comments.count == 1
| Comment
- else
| Comments
#comments.panel-body
.container-full
- if @repository_comments.empty?
' Nobody has left a comment yet.
- else
- @repository_comments.each do |comment|
= render(comment)
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

resources :repositories, only: [:index, :show] do
post :toggle_star, on: :member
resources :comments, only: [:create, :destroy]
end

devise_for :users, controllers: { registrations: "auth/registrations",
Expand Down
11 changes: 11 additions & 0 deletions db/migrate/20151113162311_create_comments.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class CreateComments < ActiveRecord::Migration
def change
create_table :comments do |t|
t.string :title
t.text :body
t.references :repository, index: true, foreign_key: true

t.timestamps null: false
end
end
end
5 changes: 5 additions & 0 deletions db/migrate/20151113162513_add_author_to_comment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddAuthorToComment < ActiveRecord::Migration
def change
add_belongs_to :comments, :user, index: true
end
end
5 changes: 5 additions & 0 deletions db/migrate/20151124150353_removetitle.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class Removetitle < ActiveRecord::Migration
def change
remove_column :comments, :title
end
end
14 changes: 13 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 20151112003047) do
ActiveRecord::Schema.define(version: 20151124150353) do

create_table "activities", force: :cascade do |t|
t.integer "trackable_id", limit: 4
Expand All @@ -31,6 +31,17 @@
add_index "activities", ["recipient_id", "recipient_type"], name: "index_activities_on_recipient_id_and_recipient_type", using: :btree
add_index "activities", ["trackable_id", "trackable_type"], name: "index_activities_on_trackable_id_and_trackable_type", using: :btree

create_table "comments", force: :cascade do |t|
t.text "body", limit: 65535
t.integer "repository_id", limit: 4
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id", limit: 4
end

add_index "comments", ["repository_id"], name: "index_comments_on_repository_id", using: :btree
add_index "comments", ["user_id"], name: "index_comments_on_user_id", using: :btree

create_table "crono_jobs", force: :cascade do |t|
t.string "job_id", limit: 255, null: false
t.text "log", limit: 65535
Expand Down Expand Up @@ -150,6 +161,7 @@
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
add_index "users", ["username"], name: "index_users_on_username", unique: true, using: :btree

add_foreign_key "comments", "repositories"
add_foreign_key "stars", "repositories"
add_foreign_key "stars", "users"
end
2 changes: 1 addition & 1 deletion spec/api/v2/token_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@
valid_auth_header
end

it "allows to pull an image in which this user is just a viewer" do
it "allows to pull an image in which this user is just a viewer" do
# Quick way to force a "viewer" policy.
allow_any_instance_of(NamespacePolicy).to receive(:push?).and_return(false)
allow_any_instance_of(NamespacePolicy).to receive(:pull?).and_return(true)
Expand Down
90 changes: 90 additions & 0 deletions spec/controllers/comments_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
require "rails_helper"

describe CommentsController, type: :controller do
let(:admin) { create(:admin) }
let(:owner) { create(:user) }
let(:user) { create(:user) }
let!(:team) { create(:team, owners: [owner]) }
let!(:public_namespace) { create(:namespace, public: 1, team: team) }
let!(:visible_repository) { create(:repository, namespace: public_namespace) }
let!(:private_namespace) { create(:namespace, public: 0, team: team) }
let!(:invisible_repository) { create(:repository, namespace: private_namespace) }
let(:comment) { create(:comment, author: owner) }
let!(:commented_repository) do
create(:repository, comments: [comment], namespace: private_namespace)
end

let(:valid_attributes) do
{ body: "short test comment" }
end

let(:invalid_attributes) do
{ foo: "not valid" }
end

describe "PUT #create" do
context "with valid params" do
it "creates a new comment" do
sign_in owner
expect do
repository_id = invisible_repository.id
post :create, repository_id: repository_id, comment: valid_attributes, format: "js"
expect(response.status).to eq 200
end.to change(Comment, :count).by(1)
expect(assigns(:comment).author.id).to eq owner.id
end

it "does allow everyone to write comments for a repository under a public namespace" do
sign_in user
post :create, repository_id: visible_repository.id, comment: valid_attributes, format: :js
expect(response.status).to eq 200
end

it "does allow the admin to write comments for every repository" do
sign_in admin
post :create, repository_id: invisible_repository.id, comment: valid_attributes, format: :js
expect(response.status).to eq 200
end

it "does not allow a user who has no access to the repository to write comments" do
sign_in user
post :create, repository_id: invisible_repository.id, comment: valid_attributes, format: :js
expect(response.status).to eq 401
end
end

context "with invalid params" do
it "does not allow invalid parameters" do
sign_in owner
repository_id = invisible_repository.id
post :create, repository_id: repository_id, comment: invalid_attributes, format: :js
expect(assigns(:comment)).to be_a_new(Comment)
expect(response.status).to eq 422
end
end
end

describe "DELETE #destroy" do
it "deletes a comment" do
sign_in owner
expect do
delete :destroy, repository_id: commented_repository.id, id: comment.id, format: :js
end.to change(Comment, :count).by(-1)
end

it "does allow to delete a comment if the user is admin" do
sign_in admin
expect do
delete :destroy, repository_id: commented_repository.id, id: comment.id, format: :js
end.to change(Comment, :count).by(-1)
end

it "does not allow to delete the comment if the user is not the author" do
sign_in user
expect do
delete :destroy, repository_id: commented_repository.id, id: comment.id, format: :js
end.to change(Comment, :count).by(0)
expect(response.status).to eq 401
end
end
end
Loading

0 comments on commit 4d780d9

Please sign in to comment.