FEATURE: Profile Backgrounds

Shares a modified codebase with avatars called "user_image"
This commit is contained in:
Johan Jatko 2014-02-28 21:12:51 +01:00
parent 5e1019adba
commit 98c479c3c4
17 changed files with 292 additions and 39 deletions

View file

@ -60,7 +60,20 @@ Discourse.User = Discourse.Model.extend({
return this.get('website').split("/")[2]; return this.get('website').split("/")[2];
}.property('website'), }.property('website'),
/**
This user's profile background(in CSS).
@property websiteName
@type {String}
**/
profileBackground: function() {
var background = this.get('profile_background');
if(Em.isEmpty(background) || !Discourse.SiteSettings.allow_profile_backgrounds) { return; }
return 'background-image: url(' + background + ')';
}.property('profile_background'),
statusIcon: function() { statusIcon: function() {
var desc; var desc;
if(this.get('admin')) { if(this.get('admin')) {
@ -316,6 +329,22 @@ Discourse.User = Discourse.Model.extend({
data: { use_uploaded_avatar: useUploadedAvatar } data: { use_uploaded_avatar: useUploadedAvatar }
}); });
}, },
/*
Clear profile background
@method clearProfileBackground
@returns {Promise} the result of the clear profile background request
*/
clearProfileBackground: function() {
var user = this;
return Discourse.ajax("/users/" + this.get("username_lower") + "/preferences/profile_background/clear", {
type: 'PUT',
data: { }
}).then(function() {
user.set('profile_background', null);
});
},
/** /**
Determines whether the current user is allowed to upload a file. Determines whether the current user is allowed to upload a file.

View file

@ -43,6 +43,13 @@ Discourse.PreferencesRoute = Discourse.RestrictedUserRoute.extend({
)); ));
user.set('avatar_template', avatarSelector.get('avatarTemplate')); user.set('avatar_template', avatarSelector.get('avatarTemplate'));
avatarSelector.send('closeModal'); avatarSelector.send('closeModal');
},
showProfileBackgroundFileSelector: function() {
$("#profile-background-input").click();
},
clearProfileBackground: function() {
this.modelFor('user').clearProfileBackground();
} }
} }
}); });

View file

@ -73,6 +73,26 @@
{{/if}} {{/if}}
</div> </div>
</div> </div>
{{#if Discourse.SiteSettings.allow_profile_backgrounds}}
<div class="control-group">
<label class="control-label">{{i18n user.change_profile_background.title}}</label>
<div class="controls">
<input type="file" id="profile-background-input" accept="image/*" style="display:none" />
<div id="profile-background-preview" class="input-xxlarge" {{bind-attr style="profileBackground"}}>
<div id="profile-background-controls">
<button {{action showProfileBackgroundFileSelector}} class="btn pad-left no-text"><i class="fa fa-picture-o"></i></button>
{{#if profileBackground}}
<button {{action clearProfileBackground}} class="btn btn-danger pad-left no-text"><i class="fa fa-trash-o"></i></button>
{{/if}}
{{#if view.uploading}}
<span class="btn">{{i18n upload_selector.uploading}} {{view.uploadProgress}}%</span>
{{/if}}
</div>
</div>
</div>
</div>
{{/if}}
{{#if allowUserLocale}} {{#if allowUserLocale}}
<div class="control-group"> <div class="control-group">

View file

@ -40,7 +40,7 @@
</section> </section>
<section class='user-main'> <section class='user-main'>
<section {{bind-attr class="collapsedInfo :about"}}> <section {{bind-attr class="collapsedInfo :about"}} {{bind-attr style="profileBackground"}}>
<div class='details'> <div class='details'>
<div class='primary'> <div class='primary'>

View file

@ -33,9 +33,10 @@ Discourse.AvatarSelectorView = Discourse.ModalBodyView.extend({
// define the upload endpoint // define the upload endpoint
$upload.fileupload({ $upload.fileupload({
url: Discourse.getURL("/users/" + this.get("controller.username") + "/preferences/avatar"), url: Discourse.getURL("/users/" + this.get("controller.username") + "/preferences/user_image"),
dataType: "json", dataType: "json",
fileInput: $upload fileInput: $upload,
formData: { user_image_type: "avatar" }
}); });
// when a file has been selected // when a file has been selected

View file

@ -8,7 +8,48 @@
**/ **/
Discourse.PreferencesView = Discourse.View.extend({ Discourse.PreferencesView = Discourse.View.extend({
templateName: 'user/preferences', templateName: 'user/preferences',
classNames: ['user-preferences'] classNames: ['user-preferences'],
uploading: false,
uploadProgress: 0,
didInsertElement: function() {
var self = this;
var $upload = $("#profile-background-input");
this._super();
$upload.fileupload({
url: Discourse.getURL("/users/" + this.get('controller.model.username') + "/preferences/user_image"),
dataType: "json",
fileInput: $upload,
formData: { user_image_type: "profile_background" }
});
$upload.on("fileuploadadd", function() {
self.set("uploading", true);
});
$upload.on("fileuploadprogressall", function(e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
self.set("uploadProgress", progress);
});
$upload.on("fileuploaddone", function(e, data) {
if(data.result.url) {
self.set("controller.model.profile_background", data.result.url);
} else {
bootbox.alert(I18n.t('post.errors.upload'));
}
});
$upload.on("fileuploadfail", function(e, data) {
Discourse.Utilities.displayErrorForUpload(data);
});
$upload.on("fileuploadalways", function() {
self.setProperties({ uploading: false, uploadProgress: 0});
});
},
willDestroyElement: function() {
$("#profile-background-input").fileupload("destroy");
}
}); });

View file

@ -40,6 +40,19 @@
width: 440px; width: 440px;
} }
} }
#profile-background-preview {
height: 270px;
background-position: center center;
background-size: cover;
background-color: $secondary_background_color;
}
#profile-background-controls {
padding: 10px;
}
.static { .static {
color: $primary_text_color; color: $primary_text_color;
@ -174,6 +187,9 @@
.about { .about {
background-color: $secondary_background_color; background-color: $secondary_background_color;
background-size: cover;
background-position: center center;
margin-bottom: 10px; margin-bottom: 10px;
overflow: hidden; overflow: hidden;
color: $secondary_text_color; color: $secondary_text_color;

View file

@ -64,6 +64,13 @@
width: 100%; width: 100%;
padding: 5px 8px; padding: 5px 8px;
} }
#profile-background-preview {
height: 150px;
background-position: center center;
background-size: cover;
background-color: $secondary_background_color;
}
} }
#about-me { #about-me {
@ -120,6 +127,8 @@
.about { .about {
background-color: $secondary_background_color; background-color: $secondary_background_color;
background-position: center center;
background-size: cover;
margin-bottom: 10px; margin-bottom: 10px;
overflow: hidden; overflow: hidden;
color: $primary_text_color; color: $primary_text_color;

View file

@ -7,7 +7,7 @@ class UsersController < ApplicationController
skip_before_filter :authorize_mini_profiler, only: [:avatar] skip_before_filter :authorize_mini_profiler, only: [:avatar]
skip_before_filter :check_xhr, only: [:show, :password_reset, :update, :activate_account, :authorize_email, :user_preferences_redirect, :avatar] skip_before_filter :check_xhr, only: [:show, :password_reset, :update, :activate_account, :authorize_email, :user_preferences_redirect, :avatar]
before_filter :ensure_logged_in, only: [:username, :update, :change_email, :user_preferences_redirect, :upload_avatar, :toggle_avatar, :destroy] before_filter :ensure_logged_in, only: [:username, :update, :change_email, :user_preferences_redirect, :upload_user_image, :toggle_avatar, :clear_profile_background, :destroy]
before_filter :respond_to_suspicious_request, only: [:create] before_filter :respond_to_suspicious_request, only: [:create]
# we need to allow account creation with bad CSRF tokens, if people are caching, the CSRF token on the # we need to allow account creation with bad CSRF tokens, if people are caching, the CSRF token on the
@ -297,8 +297,16 @@ class UsersController < ApplicationController
size = 128 if size > 128 size = 128 if size > 128
size size
end end
def upload_avatar def upload_avatar
params[:user_image_type] = "avatar"
upload_user_image
end
def upload_user_image
params.require(:user_image_type)
user = fetch_user_from_params user = fetch_user_from_params
guardian.ensure_can_edit!(user) guardian.ensure_can_edit!(user)
@ -308,17 +316,26 @@ class UsersController < ApplicationController
# TODO: Does not protect from huge uploads # TODO: Does not protect from huge uploads
# https://github.com/discourse/discourse/pull/1512 # https://github.com/discourse/discourse/pull/1512
# check the file size (note: this might also be done in the web server) # check the file size (note: this might also be done in the web server)
avatar = build_avatar_from(file) img = build_user_image_from(file)
avatar_policy = AvatarUploadPolicy.new(avatar) upload_policy = AvatarUploadPolicy.new(img)
if avatar_policy.too_big? if upload_policy.too_big?
return render status: 413, text: I18n.t("upload.images.too_large", return render status: 413, text: I18n.t("upload.images.too_large",
max_size_kb: avatar_policy.max_size_kb) max_size_kb: upload_policy.max_size_kb)
end end
raise FastImage::UnknownImageType unless SiteSetting.authorized_image?(avatar.file) raise FastImage::UnknownImageType unless SiteSetting.authorized_image?(img.file)
upload_avatar_for(user, avatar) upload_type = params[:user_image_type]
if upload_type == "avatar"
upload_avatar_for(user, img)
elsif upload_type == "profile_background"
upload_profile_background_for(user, img)
else
render status: 422, text: ""
end
rescue Discourse::InvalidParameters rescue Discourse::InvalidParameters
render status: 422, text: I18n.t("upload.images.unknown_image_type") render status: 422, text: I18n.t("upload.images.unknown_image_type")
@ -340,7 +357,17 @@ class UsersController < ApplicationController
render nothing: true render nothing: true
end end
def clear_profile_background
user = fetch_user_from_params
guardian.ensure_can_edit!(user)
user.profile_background = ""
user.save!
render nothing: true
end
def destroy def destroy
@user = fetch_user_from_params @user = fetch_user_from_params
guardian.ensure_can_delete_user!(@user) guardian.ensure_can_delete_user!(@user)
@ -364,7 +391,7 @@ class UsersController < ApplicationController
challenge challenge
end end
def build_avatar_from(file) def build_user_image_from(file)
source = if file.is_a?(String) source = if file.is_a?(String)
is_api? ? :url : (raise FastImage::UnknownImageType) is_api? ? :url : (raise FastImage::UnknownImageType)
else else
@ -380,7 +407,17 @@ class UsersController < ApplicationController
Jobs.enqueue(:generate_avatars, user_id: user.id, upload_id: upload.id) Jobs.enqueue(:generate_avatars, user_id: user.id, upload_id: upload.id)
render json: { url: upload.url, width: upload.width, height: upload.height } render json: { url: upload.url, width: upload.width, height: upload.height }
end end
def upload_profile_background_for(user, background)
upload = Upload.create_for(user.id, background.file, background.filesize)
user.profile_background = upload.url
user.save!
# TODO: maybe add a resize job here
render json: { url: upload.url, width: upload.width, height: upload.height }
end
def respond_to_suspicious_request def respond_to_suspicious_request
if suspicious?(params) if suspicious?(params)
render( render(

View file

@ -8,11 +8,13 @@ module Jobs
uploads_used_in_posts = PostUpload.uniq.pluck(:upload_id) uploads_used_in_posts = PostUpload.uniq.pluck(:upload_id)
uploads_used_as_avatars = User.uniq.where('uploaded_avatar_id IS NOT NULL').pluck(:uploaded_avatar_id) uploads_used_as_avatars = User.uniq.where('uploaded_avatar_id IS NOT NULL').pluck(:uploaded_avatar_id)
uploads_used_as_profile_backgrounds = User.uniq.where("profile_background IS NOT NULL AND profile_background != ''").pluck(:profile_background)
grace_period = [SiteSetting.clean_orphan_uploads_grace_period_hours, 1].max grace_period = [SiteSetting.clean_orphan_uploads_grace_period_hours, 1].max
Upload.where("created_at < ?", grace_period.hour.ago) Upload.where("created_at < ?", grace_period.hour.ago)
.where("id NOT IN (?)", uploads_used_in_posts + uploads_used_as_avatars) .where("id NOT IN (?)", uploads_used_in_posts + uploads_used_as_avatars)
.where("url NOT IN (?)", uploads_used_as_profile_backgrounds)
.find_each do |upload| .find_each do |upload|
upload.destroy upload.destroy
end end

View file

@ -8,6 +8,7 @@ class UserSerializer < BasicUserSerializer
:bio_cooked, :bio_cooked,
:created_at, :created_at,
:website, :website,
:profile_background,
:can_edit, :can_edit,
:can_edit_username, :can_edit_username,
:can_edit_email, :can_edit_email,

View file

@ -297,6 +297,9 @@ en:
uploaded_avatar_empty: "Add a custom picture" uploaded_avatar_empty: "Add a custom picture"
upload_title: "Upload your picture" upload_title: "Upload your picture"
image_is_not_a_square: "Warning: we've cropped your image; it is not square." image_is_not_a_square: "Warning: we've cropped your image; it is not square."
change_profile_background:
title: "Change Profile Background"
email: email:
title: "Email" title: "Email"

View file

@ -809,7 +809,9 @@ en:
detect_custom_avatars: "Whether or not to check that users have uploaded custom avatars" detect_custom_avatars: "Whether or not to check that users have uploaded custom avatars"
max_daily_gravatar_crawls: "The maximum amount of times Discourse will check gravatar for custom avatars in a day" max_daily_gravatar_crawls: "The maximum amount of times Discourse will check gravatar for custom avatars in a day"
allow_profile_backgrounds: "Allows users to upload profile backgrounds"
sequential_replies_threshold: "The amount of posts a user has to make in a row in a topic before being notified" sequential_replies_threshold: "The amount of posts a user has to make in a row in a topic before being notified"
enable_mobile_theme: "Mobile devices use a mobile-friendly theme, with the ability to switch to the full site. Disable this if you want to use a custom stylesheet that is fully responsive." enable_mobile_theme: "Mobile devices use a mobile-friendly theme, with the ability to switch to the full site. Disable this if you want to use a custom stylesheet that is fully responsive."

View file

@ -181,7 +181,9 @@ Discourse::Application.routes.draw do
put "users/:username/preferences/username" => "users#username", constraints: {username: USERNAME_ROUTE_FORMAT} put "users/:username/preferences/username" => "users#username", constraints: {username: USERNAME_ROUTE_FORMAT}
get "users/:username/avatar(/:size)" => "users#avatar", constraints: {username: USERNAME_ROUTE_FORMAT} # LEGACY ROUTE get "users/:username/avatar(/:size)" => "users#avatar", constraints: {username: USERNAME_ROUTE_FORMAT} # LEGACY ROUTE
post "users/:username/preferences/avatar" => "users#upload_avatar", constraints: {username: USERNAME_ROUTE_FORMAT} post "users/:username/preferences/avatar" => "users#upload_avatar", constraints: {username: USERNAME_ROUTE_FORMAT}
post "users/:username/preferences/user_image" => "users#upload_user_image", constraints: {username: USERNAME_ROUTE_FORMAT}
put "users/:username/preferences/avatar/toggle" => "users#toggle_avatar", constraints: {username: USERNAME_ROUTE_FORMAT} put "users/:username/preferences/avatar/toggle" => "users#toggle_avatar", constraints: {username: USERNAME_ROUTE_FORMAT}
put "users/:username/preferences/profile_background/clear" => "users#clear_profile_background", constraints: {username: USERNAME_ROUTE_FORMAT}
get "users/:username/invited" => "users#invited", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/invited" => "users#invited", constraints: {username: USERNAME_ROUTE_FORMAT}
post "users/:username/send_activation_email" => "users#send_activation_email", constraints: {username: USERNAME_ROUTE_FORMAT} post "users/:username/send_activation_email" => "users#send_activation_email", constraints: {username: USERNAME_ROUTE_FORMAT}
get "users/:username/activity" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/activity" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT}

View file

@ -270,6 +270,9 @@ files:
default: '' default: ''
enum: 'S3RegionSiteSetting' enum: 'S3RegionSiteSetting'
s3_upload_bucket: '' s3_upload_bucket: ''
allow_profile_backgrounds:
client: true
default: true
allow_uploaded_avatars: allow_uploaded_avatars:
client: true client: true
default: true default: true

View file

@ -0,0 +1,5 @@
class AddProfileBackgroundToUser < ActiveRecord::Migration
def change
add_column :users, :profile_background, :string, limit: 255
end
end

View file

@ -1092,55 +1092,65 @@ describe UsersController do
end end
end end
describe '.upload_avatar' do describe '.upload_user_image' do
it 'raises an error when not logged in' do it 'raises an error when not logged in' do
lambda { xhr :put, :upload_avatar, username: 'asdf' }.should raise_error(Discourse::NotLoggedIn) lambda { xhr :put, :upload_user_image, username: 'asdf' }.should raise_error(Discourse::NotLoggedIn)
end end
context 'while logged in' do context 'while logged in' do
let!(:user) { log_in } let!(:user) { log_in }
let(:avatar) do let(:user_image) do
ActionDispatch::Http::UploadedFile.new({ ActionDispatch::Http::UploadedFile.new({
filename: 'logo.png', filename: 'logo.png',
tempfile: File.new("#{Rails.root}/spec/fixtures/images/logo.png") tempfile: File.new("#{Rails.root}/spec/fixtures/images/logo.png")
}) })
end end
it 'raises an error without a user_image_type param' do
lambda { xhr :put, :upload_user_image, username: user.username }.should raise_error(ActionController::ParameterMissing)
end
describe "with uploaded file" do describe "with uploaded file" do
it 'raises an error when you don\'t have permission to upload an avatar' do it 'raises an error when you don\'t have permission to upload an user image' do
Guardian.any_instance.expects(:can_edit?).with(user).returns(false) Guardian.any_instance.expects(:can_edit?).with(user).returns(false)
xhr :post, :upload_avatar, username: user.username xhr :post, :upload_user_image, username: user.username, user_image_type: "avatar"
response.should be_forbidden response.should be_forbidden
end end
it 'rejects large images' do it 'rejects large images' do
AvatarUploadPolicy.any_instance.stubs(:too_big?).returns(true) AvatarUploadPolicy.any_instance.stubs(:too_big?).returns(true)
xhr :post, :upload_avatar, username: user.username, file: avatar xhr :post, :upload_user_image, username: user.username, file: user_image, user_image_type: "avatar"
response.status.should eq 413 response.status.should eq 413
end end
it 'rejects unauthorized images' do it 'rejects unauthorized images' do
SiteSetting.stubs(:authorized_image?).returns(false) SiteSetting.stubs(:authorized_image?).returns(false)
xhr :post, :upload_avatar, username: user.username, file: avatar xhr :post, :upload_user_image, username: user.username, file: user_image, user_image_type: "avatar"
response.status.should eq 422
end
it 'rejects requests with unknown user_image_type' do
xhr :post, :upload_user_image, username: user.username, file: user_image, user_image_type: "asdf"
response.status.should eq 422 response.status.should eq 422
end end
it 'is successful' do it 'is successful for avatars' do
upload = Fabricate(:upload) upload = Fabricate(:upload)
Upload.expects(:create_for).returns(upload) Upload.expects(:create_for).returns(upload)
# enqueues the avatar generator job # enqueues the user_image generator job
Jobs.expects(:enqueue).with(:generate_avatars, { user_id: user.id, upload_id: upload.id }) Jobs.expects(:enqueue).with(:generate_avatars, { user_id: user.id, upload_id: upload.id })
xhr :post, :upload_avatar, username: user.username, file: avatar xhr :post, :upload_user_image, username: user.username, file: user_image, user_image_type: "avatar"
user.reload user.reload
# erase the previous template # erase the previous template
user.uploaded_avatar_template.should == nil user.uploaded_avatar_template.should == nil
# link to the right upload # link to the right upload
user.uploaded_avatar.id.should == upload.id user.uploaded_avatar.id.should == upload.id
# automatically set "use_uploaded_avatar" # automatically set "use_uploaded_user_image"
user.use_uploaded_avatar.should == true user.use_uploaded_avatar.should == true
# returns the url, width and height of the uploaded image # returns the url, width and height of the uploaded image
json = JSON.parse(response.body) json = JSON.parse(response.body)
@ -1148,10 +1158,26 @@ describe UsersController do
json['width'].should == 100 json['width'].should == 100
json['height'].should == 200 json['height'].should == 200
end end
it 'is successful for profile backgrounds' do
upload = Fabricate(:upload)
Upload.expects(:create_for).returns(upload)
xhr :post, :upload_user_image, username: user.username, file: user_image, user_image_type: "profile_background"
user.reload
user.profile_background.should == "/uploads/default/1/1234567890123456.jpg"
# returns the url, width and height of the uploaded image
json = JSON.parse(response.body)
json['url'].should == "/uploads/default/1/1234567890123456.jpg"
json['width'].should == 100
json['height'].should == 200
end
end end
describe "with url" do describe "with url" do
let(:avatar_url) { "http://cdn.discourse.org/assets/logo.png" } let(:user_image_url) { "http://cdn.discourse.org/assets/logo.png" }
before :each do before :each do
UsersController.any_instance.stubs(:is_api?).returns(true) UsersController.any_instance.stubs(:is_api?).returns(true)
@ -1161,40 +1187,63 @@ describe UsersController do
before :each do before :each do
UriAdapter.any_instance.stubs(:open).returns StringIO.new(fixture_file("images/logo.png")) UriAdapter.any_instance.stubs(:open).returns StringIO.new(fixture_file("images/logo.png"))
end end
it 'rejects large images' do it 'rejects large images' do
AvatarUploadPolicy.any_instance.stubs(:too_big?).returns(true) AvatarUploadPolicy.any_instance.stubs(:too_big?).returns(true)
xhr :post, :upload_avatar, username: user.username, file: avatar_url xhr :post, :upload_user_image, username: user.username, file: user_image_url, user_image_type: "profile_background"
response.status.should eq 413 response.status.should eq 413
end end
it 'rejects unauthorized images' do it 'rejects unauthorized images' do
SiteSetting.stubs(:authorized_image?).returns(false) SiteSetting.stubs(:authorized_image?).returns(false)
xhr :post, :upload_avatar, username: user.username, file: avatar_url xhr :post, :upload_user_image, username: user.username, file: user_image_url, user_image_type: "profile_background"
response.status.should eq 422
end
it 'rejects requests with unknown user_image_type' do
xhr :post, :upload_user_image, username: user.username, file: user_image_url, user_image_type: "asdf"
response.status.should eq 422 response.status.should eq 422
end end
it 'is successful' do it 'is successful for avatars' do
upload = Fabricate(:upload) upload = Fabricate(:upload)
Upload.expects(:create_for).returns(upload) Upload.expects(:create_for).returns(upload)
# enqueues the avatar generator job # enqueues the user_image generator job
Jobs.expects(:enqueue).with(:generate_avatars, { user_id: user.id, upload_id: upload.id }) Jobs.expects(:enqueue).with(:generate_avatars, { user_id: user.id, upload_id: upload.id })
xhr :post, :upload_avatar, username: user.username, file: avatar_url xhr :post, :upload_avatar, username: user.username, file: user_image_url, user_image_type: "avatar"
user.reload user.reload
# erase the previous template
user.uploaded_avatar_template.should == nil user.uploaded_avatar_template.should == nil
# link to the right upload
user.uploaded_avatar.id.should == upload.id user.uploaded_avatar.id.should == upload.id
# automatically set "use_uploaded_user_image"
user.use_uploaded_avatar.should == true user.use_uploaded_avatar.should == true
# returns the url, width and height of the uploaded image # returns the url, width and height of the uploaded image
json = JSON.parse(response.body) json = JSON.parse(response.body)
json['url'].should == "/uploads/default/1/1234567890123456.jpg" json['url'].should == "/uploads/default/1/1234567890123456.jpg"
json['width'].should == 100 json['width'].should == 100
json['height'].should == 200 json['height'].should == 200
end end
it 'is successful for profile backgrounds' do
upload = Fabricate(:upload)
Upload.expects(:create_for).returns(upload)
xhr :post, :upload_user_image, username: user.username, file: user_image_url, user_image_type: "profile_background"
user.reload
user.profile_background.should == "/uploads/default/1/1234567890123456.jpg"
# returns the url, width and height of the uploaded image
json = JSON.parse(response.body)
json['url'].should == "/uploads/default/1/1234567890123456.jpg"
json['width'].should == 100
json['height'].should == 200
end
end end
it "should handle malformed urls" do it "should handle malformed urls" do
xhr :post, :upload_avatar, username: user.username, file: "foobar" xhr :post, :upload_user_image, username: user.username, file: "foobar", user_image_type: "profile_background"
response.status.should eq 422 response.status.should eq 422
end end
@ -1233,6 +1282,32 @@ describe UsersController do
end end
end end
describe '.clear_profile_background' do
it 'raises an error when not logged in' do
lambda { xhr :put, :clear_profile_background, username: 'asdf' }.should raise_error(Discourse::NotLoggedIn)
end
context 'while logged in' do
let!(:user) { log_in }
it 'raises an error when you don\'t have permission to clear the profile background' do
Guardian.any_instance.expects(:can_edit?).with(user).returns(false)
xhr :put, :clear_profile_background, username: user.username
response.should be_forbidden
end
it 'it successful' do
xhr :put, :clear_profile_background, username: user.username
user.reload.profile_background.should == ""
response.should be_success
end
end
end
describe '.destroy' do describe '.destroy' do
it 'raises an error when not logged in' do it 'raises an error when not logged in' do