Merge branch 'master' into vagrant-chef

This commit is contained in:
Elliot Murphy 2013-02-07 11:08:17 -05:00
commit d2cebda786
25 changed files with 200 additions and 41 deletions

View File

@ -7,13 +7,11 @@ on Discourse with:
### Getting Started
1. Install the Xcode tools: https://developer.apple.com/xcode/
2. Install VirtualBox: https://www.virtualbox.org/wiki/Downloads
3. Install Ruby 1.9.3. We recommend RVM: https://rvm.io/
4. Open a terminal
5. Clone the project: `git@github.com:discourse/discourse.git`
6. Enter the project directory: `cd discourse`
7. Install vagrant: `gem install vagrant`
1. Install VirtualBox: https://www.virtualbox.org/wiki/Downloads
2. Install Vagrant: https://www.vagrantup.com/
3. Open a terminal
4. Clone the project: `git@github.com:discourse/discourse.git`
5. Enter the project directory: `cd discourse`
### Using Vagrant
@ -22,7 +20,7 @@ When you're ready to start working, boot the VM:
vagrant up
```
It should prompt you for your admin password. This is so it can mount your local files inside the VM for an easy workflow.
On Windows, it will prompt you for your admin password. This is so it can mount your local files inside the VM for an easy workflow.
(The first time you do this, it will take a while as it downloads the VM image and installs it. Go grab a coffee.)

11
Gemfile
View File

@ -6,7 +6,6 @@ gem 'hiredis'
gem 'em-redis'
gem 'rails'
gem 'pg'
gem 'haml'
gem 'sass'
gem 'rake'
# errbit is broken with 3.1.3 for now
@ -20,8 +19,6 @@ gem 'nokogiri'
gem 'seed-fu'
gem 'sanitize'
gem 'slim', '<= 1.3.0'
gem 'sinatra', :require => nil
gem 'clockwork', :require => false
@ -70,11 +67,10 @@ group :assets do
gem 'uglifier'
# gem "asset_sync"
gem 'turbo-sprockets-rails3'
# need this to compile coffee on the fly
gem 'coffee-script'
end
# need this to compile coffee on the fly
gem 'coffee-script'
gem 'hpricot'
gem 'jquery-rails'
@ -97,12 +93,11 @@ group :test, :development do
gem 'guard-rspec'
gem 'guard-spork'
gem 'mocha', :require => false
gem 'test-unit', :require => "test/unit"
gem 'simplecov', :require => false
gem 'image_optim'
gem 'certified'
gem 'rb-fsevent'
gem 'rb-inotify', :require => RUBY_PLATFORM.include?('linux') && 'rb-inotify'
gem 'rb-inotify', '~> 0.8.8', :require => RUBY_PLATFORM.include?('linux') && 'rb-inotify'
gem 'terminal-notifier-guard', :require => RUBY_PLATFORM.include?('darwin') && 'terminal-notifier-guard'
end

View File

@ -256,7 +256,7 @@ GEM
thor (>= 0.14.6, < 2.0)
rake (10.0.3)
rb-fsevent (0.9.3)
rb-inotify (0.9.0)
rb-inotify (0.8.8)
ffi (>= 0.5.0)
rdoc (3.12)
json (~> 1.4)
@ -416,7 +416,7 @@ DEPENDENCIES
rails_multisite!
rake
rb-fsevent
rb-inotify
rb-inotify (~> 0.8.8)
redis
redis-rails
rest-client

View File

@ -1,4 +1,4 @@
guard 'spork' do
guard :spork, wait: 120 do
watch('config/application.rb')
watch('config/environment.rb')
watch(%r{^config/environments/.*\.rb$})
@ -27,7 +27,7 @@ guard 'jasmine', jasmine_options do watch(%r{spec/javascripts/spec\.(js\.coffee|
watch(%r{app/assets/javascripts/(.+?)\.(js\.coffee|js|coffee)$}) { "spec/javascripts" }
end
guard 'rspec', :focus_on_failed => true, :version => 2, :cli => "--drb" do
guard 'rspec', :focus_on_failed => true, :cli => "--drb" do
watch(%r{^spec/.+_spec\.rb$})
#watch(%r{^lib/jobs/(.+)\.rb$}) { |m| "spec/components/jobs/#{m[1]}_spec.rb" }
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/components/#{m[1]}_spec.rb" }

View File

@ -20,6 +20,7 @@ If you're interested in helping us develop Discourse, please start with our **[D
```
git clone git@github.com:discourse/discourse.git
cd discourse
bundle install
rake db:create
rake db:migrate
rake db:seed_fu

21
Vagrantfile vendored
View File

@ -1,14 +1,29 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
# See https://github.com/discourse/core/blob/master/DEVELOPMENT.md
#
Vagrant::Config.run do |config|
config.vm.box = 'discourse-pre'
config.vm.box_url = 'http://www.discourse.org/vms/discourse-pre.box'
# Make this VM reachable on the host network as well, so that other
# VM's running other browsers can access our dev server.
config.vm.network :hostonly, '192.168.10.200'
# Make it so that network access from the vagrant guest is able to
# use SSH private keys that are present on the host without copying
# them into the VM.
config.ssh.forward_agent = true
# This setting gives the VM 512MB of MEMORIES instead of the default 384.
config.vm.customize ["modifyvm", :id, "--memory", 512]
# This setting makes it so that network access from inside the vagrant guest
# is able to resolve DNS using the hosts VPN connection.
config.vm.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
config.vm.forward_port 3000, 4000
config.vm.forward_port 1080, 4080 # Mailcatcher
if RUBY_PLATFORM =~ /darwin/
config.vm.share_folder("v-root", "/vagrant", ".", :nfs => true)
end
config.vm.share_folder("v-root", "/vagrant", ".")
end

View File

@ -1,10 +1,14 @@
window.Discourse.AdminUser = Discourse.Model.extend
deleteAllPosts: ->
@set('can_delete_all_posts', false)
$.ajax "/admin/users/#{@get('id')}/delete_all_posts", type: 'PUT'
# Revoke the user's admin access
revokeAdmin: ->
@set('admin',false)
@set('can_grant_admin',true)
@set('can_revoke_admin',false)
@set('can_revoke_admin',false)
$.ajax "/admin/users/#{@get('id')}/revoke_admin", type: 'PUT'
grantAdmin: ->
@ -18,13 +22,11 @@ window.Discourse.AdminUser = Discourse.Model.extend
type: 'POST'
bootbox.alert("Message sent to all clients!")
approve: ->
@set('can_approve', false)
@set('approved', true)
@set('approved_by', Discourse.get('currentUser'))
$.ajax "/admin/users/#{@get('id')}/approve", type: 'PUT'
$.ajax "/admin/users/#{@get('id')}/approve", type: 'PUT'
username_lower:(->
@get('username').toLowerCase()

View File

@ -38,7 +38,7 @@
<tr>
<td></td>
<td class='message'>
<div>{{avatar user imageSize="small"}} {{message}}</div>
<div><a href="/admin{{unbound user.path}}">{{avatar user imageSize="small"}}</a> {{message}}</div>
</td>
<td></td>
<td></td>

View File

@ -139,6 +139,14 @@
<div class='display-row'>
<div class='field'>{{i18n admin.user.post_count}}</div>
<div class='value'>{{content.post_count}}</div>
<div class='controls'>
{{#if content.can_delete_all_posts}}
<button class='btn' {{action deleteAllPosts target="content"}}>
<i class='icon icon-trash'></i>
{{i18n admin.user.delete_all_posts}}
</button>
{{/if}}
</div>
</div>
<div class='display-row'>
<div class='field'>{{i18n admin.user.posts_read_count}}</div>

View File

@ -19,6 +19,11 @@ class Admin::UsersController < Admin::AdminController
render_serialized(@user, AdminDetailedUserSerializer, root: false)
end
def delete_all_posts
@user = User.where(id: params[:user_id]).first
@user.delete_all_posts!(guardian)
render nothing: true
end
def ban
@user = User.where(id: params[:user_id]).first
guardian.ensure_can_ban!(@user)

View File

@ -319,11 +319,11 @@ class Post < ActiveRecord::Base
Post.transaction do
self.last_version_at = revised_at
updater.call(true)
EditRateLimiter.new(updated_by).performed!
EditRateLimiter.new(updated_by).performed! unless opts[:bypass_rate_limiter]
# If a new version is created of the last post, bump it.
unless Post.where('post_number > ? and topic_id = ?', self.post_number, self.topic_id).exists?
topic.update_column(:bumped_at, Time.now)
topic.update_column(:bumped_at, Time.now) unless opts[:bypass_bump]
end
end

View File

@ -1,5 +1,6 @@
class SiteCustomization < ActiveRecord::Base
ENABLED_KEY = '7e202ef2-56d7-47d5-98d8-a9c8d15e57dd'
CACHE_PATH = 'stylesheet-cache'
@lock = Mutex.new
@ -49,18 +50,46 @@ footer:after{ content: '#{error}' }"
self.remove_from_cache!
end
def self.enabled_key
ENABLED_KEY.dup << RailsMultisite::ConnectionManagement.current_db
end
def self.enabled_style_key
@cache ||= {}
preview_style = @cache[self.enabled_key]
return nil if preview_style == :none
return preview_style if preview_style
@lock.synchronize do
style = self.where(enabled: true).first
if style
@cache[self.enabled_key] = style.key
return style.key
else
@cache[self.enabled_key] = :none
return nil
end
end
end
def self.custom_stylesheet(preview_style)
preview_style ||= enabled_style_key
style = lookup_style(preview_style)
style.stylesheet_link_tag.html_safe if style
end
def self.custom_header(preview_style)
preview_style ||= enabled_style_key
style = lookup_style(preview_style)
style.header.html_safe if style
if style && style.header
style.header.html_safe
else
""
end
end
def self.override_default_style(preview_style)
preview_style ||= enabled_style_key
style = lookup_style(preview_style)
style.override_default_style if style
end
@ -103,6 +132,7 @@ footer:after{ content: '#{error}' }"
end
def remove_from_cache!
self.class.remove_from_cache!(self.class.enabled_key)
self.class.remove_from_cache!(self.key)
end

View File

@ -335,6 +335,18 @@ class User < ActiveRecord::Base
PrettyText.excerpt(bio_cooked, 350)
end
def delete_all_posts!(guardian)
raise Discourse::InvalidAccess unless guardian.can_delete_all_posts? self
posts.order("post_number desc").each do |p|
if p.post_number == 1
p.topic.destroy
else
p.destroy
end
end
end
def is_banned?
!banned_till.nil? && banned_till > DateTime.now
end

View File

@ -8,7 +8,8 @@ class AdminDetailedUserSerializer < AdminUserSerializer
:post_count,
:flags_given_count,
:flags_received_count,
:private_topics_count
:private_topics_count,
:can_delete_all_posts
has_one :approved_by, serializer: BasicUserSerializer, embed: :objects
@ -19,6 +20,10 @@ class AdminDetailedUserSerializer < AdminUserSerializer
def can_grant_admin
scope.can_grant_admin?(object)
end
def can_delete_all_posts
scope.can_delete_all_posts?(object)
end
def moderator
object.has_trust_level?(:moderator)

View File

@ -0,0 +1,9 @@
<html>
<head></head>
<body>
<script type="text/javascript">
window.opener.Discourse.authenticationComplete(#{@data.to_json});
window.close();
</script>
</body>
</html>

View File

@ -50,11 +50,7 @@
</style>
<%- unless SiteCustomization.override_default_style(session[:preview_style]) %>
<%- if params[:shiny] %>
<%=stylesheet_link_tag "shiny/shiny"%>
<%- else %>
<%=stylesheet_link_tag "application"%>
<%- end %>
<%=stylesheet_link_tag "application"%>
<%- end %>
<%- if mini_profiler_enabled? %>

View File

@ -0,0 +1,9 @@
<html>
<head></head>
<body>
<script type="text/javascript">
window.opener.Discourse.authenticationComplete(#{@data.to_json});
window.close();
</script>
</body>
</html>

View File

@ -0,0 +1,9 @@
<html>
<head></head>
<body>
<script type="text/javascript">
window.opener.Discourse.authenticationComplete(#{@data.to_json});
window.close();
</script>
</body>
</html>

View File

@ -369,6 +369,7 @@ en:
ban_failed: "Something went wrong banning this user {{error}}"
unban_failed: "Something went wrong unbanning this user {{error}}"
ban_duration: "How long would you like to ban the user for? (days)"
delete_all_posts: "Delete all posts"
ban: "Ban"
unban: "Unban"
banned: "Banned?"

View File

@ -30,6 +30,7 @@ Discourse::Application.routes.draw do
put 'approve-bulk' => 'users#approve_bulk'
end
put 'ban' => 'users#ban'
put 'delete_all_posts' => 'users#delete_all_posts'
put 'unban' => 'users#unban'
put 'revoke_admin' => 'users#revoke_admin'
put 'grant_admin' => 'users#grant_admin'

View File

@ -167,6 +167,13 @@ class Guardian
@user.id == user_id
end
def can_delete_all_posts?(user)
return false unless is_admin?
return false if user.created_at < 7.days.ago
true
end
# Support for ensure_{blah}! methods.
def method_missing(method, *args, &block)
if method.to_s =~ /^ensure_(.*)\!$/

View File

@ -23,6 +23,7 @@ module Slug
str.gsub!(/[^a-z0-9 -]/, '')
str.gsub!(/\s+/, '-')
str.gsub!(/\-+/, '-')
str.gsub!(/^-|-$/, '')
str
end

View File

@ -27,6 +27,13 @@ describe Slug do
Slug.for("a....b.....c").should == "a-b-c"
end
it 'strips trailing punctuation' do
Slug.for("hello...").should == "hello"
end
it 'strips leading punctuation' do
Slug.for("...hello").should == "hello"
end
end

View File

@ -16,6 +16,35 @@ describe SiteCustomization do
end
context 'caching' do
context 'enabled style' do
before do
@customization = customization
end
it 'finds no style when none enabled' do
SiteCustomization.enabled_style_key.should be_nil
end
it 'finds the enabled style' do
@customization.enabled = true
@customization.save
SiteCustomization.enabled_style_key.should == @customization.key
end
it 'finds no enabled style on other sites' do
@customization.enabled = true
@customization.save
RailsMultisite::ConnectionManagement.expects(:current_db).returns("foo").twice
# the mocking is tricky, lets remove the record so we can properly pretend we are on another db
# this bypasses the before / after stuff
SiteCustomization.exec_sql('delete from site_customizations')
SiteCustomization.enabled_style_key.should be_nil
end
end
it 'should allow me to lookup a filename containing my preview stylesheet' do
SiteCustomization.custom_stylesheet(customization.key).should ==
"<link class=\"custom-css\" rel=\"stylesheet\" href=\"/stylesheet-cache/#{customization.key}.css?#{customization.stylesheet_hash}\" type=\"text/css\" media=\"screen\">"

View File

@ -67,12 +67,8 @@ describe User do
user.reload
user.posts_read_count.should == 1
end
end
end
end
context '.enqueue_welcome_message' do
@ -174,6 +170,29 @@ describe User do
end
describe 'delete posts' do
before do
@post1 = Fabricate(:post)
@user = @post1.user
@post2 = Fabricate(:post, topic: @post1.topic, user: @user)
@post3 = Fabricate(:post, user: @user)
@posts = [@post1, @post2, @post3]
@guardian = Guardian.new(Fabricate(:admin))
end
it 'allows moderator to delete all posts' do
@user.delete_all_posts!(@guardian)
@posts.each do |p|
p.reload
if p
p.topic.should be_nil
else
p.should be_nil
end
end
end
end
describe 'new' do