2013-02-23 04:41:12 +08:00
|
|
|
/**
|
|
|
|
A data model representing a user on Discourse
|
2013-02-21 02:15:50 +08:00
|
|
|
|
2013-02-23 04:41:12 +08:00
|
|
|
@class User
|
|
|
|
@extends Discourse.Model
|
|
|
|
@namespace Discourse
|
|
|
|
@module Discourse
|
|
|
|
**/
|
|
|
|
Discourse.User = Discourse.Model.extend({
|
|
|
|
|
2013-03-14 04:43:16 +08:00
|
|
|
/**
|
|
|
|
Large version of this user's avatar.
|
|
|
|
|
|
|
|
@property avatarLarge
|
|
|
|
@type {String}
|
|
|
|
**/
|
2013-02-23 04:41:12 +08:00
|
|
|
avatarLarge: (function() {
|
|
|
|
return Discourse.Utilities.avatarUrl(this.get('username'), 'large', this.get('avatar_template'));
|
|
|
|
}).property('username'),
|
|
|
|
|
2013-03-15 02:45:29 +08:00
|
|
|
/**
|
2013-03-14 04:43:16 +08:00
|
|
|
Small version of this user's avatar.
|
|
|
|
|
|
|
|
@property avatarSmall
|
|
|
|
@type {String}
|
|
|
|
**/
|
2013-02-23 04:41:12 +08:00
|
|
|
avatarSmall: (function() {
|
2013-03-14 04:43:16 +08:00
|
|
|
return Discourse.Utilities.avatarUrl(this.get('username'), 'small', this.get('avatar_template'));
|
2013-02-23 04:41:12 +08:00
|
|
|
}).property('username'),
|
|
|
|
|
2013-03-14 04:43:16 +08:00
|
|
|
/**
|
|
|
|
This user's website.
|
|
|
|
|
|
|
|
@property websiteName
|
|
|
|
@type {String}
|
|
|
|
**/
|
2013-02-23 04:41:12 +08:00
|
|
|
websiteName: (function() {
|
|
|
|
return this.get('website').split("/")[2];
|
|
|
|
}).property('website'),
|
|
|
|
|
2013-03-14 04:43:16 +08:00
|
|
|
/**
|
|
|
|
Path to this user.
|
|
|
|
|
|
|
|
@property path
|
|
|
|
@type {String}
|
|
|
|
**/
|
2013-02-23 04:41:12 +08:00
|
|
|
path: (function() {
|
2013-03-14 20:01:52 +08:00
|
|
|
return Discourse.getURL("/users/") + (this.get('username_lower'));
|
|
|
|
}).property('username'),
|
|
|
|
|
|
|
|
/**
|
|
|
|
Path to this user's administration
|
|
|
|
|
|
|
|
@property adminPath
|
|
|
|
@type {String}
|
|
|
|
**/
|
|
|
|
adminPath: (function() {
|
|
|
|
return Discourse.getURL("/admin/users/") + (this.get('username_lower'));
|
2013-02-23 04:41:12 +08:00
|
|
|
}).property('username'),
|
|
|
|
|
2013-03-14 04:43:16 +08:00
|
|
|
/**
|
|
|
|
This user's username in lowercase.
|
|
|
|
|
|
|
|
@property username_lower
|
|
|
|
@type {String}
|
|
|
|
**/
|
2013-02-23 04:41:12 +08:00
|
|
|
username_lower: (function() {
|
|
|
|
return this.get('username').toLowerCase();
|
|
|
|
}).property('username'),
|
|
|
|
|
2013-03-14 04:43:16 +08:00
|
|
|
/**
|
|
|
|
This user's trust level.
|
|
|
|
|
|
|
|
@property trustLevel
|
|
|
|
@type {Integer}
|
|
|
|
**/
|
2013-02-23 04:41:12 +08:00
|
|
|
trustLevel: (function() {
|
|
|
|
return Discourse.get('site.trust_levels').findProperty('id', this.get('trust_level'));
|
|
|
|
}).property('trust_level'),
|
|
|
|
|
2013-03-15 02:45:29 +08:00
|
|
|
/**
|
2013-03-14 04:43:16 +08:00
|
|
|
Changes this user's username.
|
|
|
|
|
|
|
|
@method changeUsername
|
|
|
|
@param {String} newUsername The user's new username
|
|
|
|
@returns Result of ajax call
|
|
|
|
**/
|
2013-02-23 04:41:12 +08:00
|
|
|
changeUsername: function(newUsername) {
|
2013-04-02 04:28:26 +08:00
|
|
|
return Discourse.ajax({
|
2013-03-14 20:01:52 +08:00
|
|
|
url: Discourse.getURL("/users/") + (this.get('username_lower')) + "/preferences/username",
|
2013-02-23 04:41:12 +08:00
|
|
|
type: 'PUT',
|
|
|
|
data: {
|
|
|
|
new_username: newUsername
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2013-03-14 04:43:16 +08:00
|
|
|
/**
|
|
|
|
Changes this user's email address.
|
|
|
|
|
|
|
|
@method changeEmail
|
|
|
|
@param {String} email The user's new email address\
|
|
|
|
@returns Result of ajax call
|
|
|
|
**/
|
2013-02-23 04:41:12 +08:00
|
|
|
changeEmail: function(email) {
|
2013-04-02 04:28:26 +08:00
|
|
|
return Discourse.ajax({
|
2013-03-14 20:01:52 +08:00
|
|
|
url: Discourse.getURL("/users/") + (this.get('username_lower')) + "/preferences/email",
|
2013-02-23 04:41:12 +08:00
|
|
|
type: 'PUT',
|
|
|
|
data: {
|
|
|
|
email: email
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2013-03-14 04:43:16 +08:00
|
|
|
/**
|
|
|
|
Returns a copy of this user.
|
|
|
|
|
|
|
|
@method copy
|
|
|
|
@returns {User}
|
|
|
|
**/
|
|
|
|
copy: function() {
|
2013-02-23 04:41:12 +08:00
|
|
|
return Discourse.User.create(this.getProperties(Ember.keys(this)));
|
|
|
|
},
|
|
|
|
|
2013-03-14 04:43:16 +08:00
|
|
|
/**
|
|
|
|
Save's this user's properties over AJAX via a PUT request.
|
|
|
|
|
|
|
|
@method save
|
|
|
|
@param {Function} finished Function called on completion of AJAX call
|
|
|
|
@returns The result of finished(true) on a success, the result of finished(false) on an error
|
|
|
|
**/
|
2013-02-23 04:41:12 +08:00
|
|
|
save: function(finished) {
|
|
|
|
var _this = this;
|
2013-04-02 04:28:26 +08:00
|
|
|
Discourse.ajax(Discourse.getURL("/users/") + this.get('username').toLowerCase(), {
|
2013-02-23 04:41:12 +08:00
|
|
|
data: this.getProperties('auto_track_topics_after_msecs',
|
|
|
|
'bio_raw',
|
|
|
|
'website',
|
|
|
|
'name',
|
|
|
|
'email_digests',
|
|
|
|
'email_direct',
|
|
|
|
'email_private_messages',
|
|
|
|
'digest_after_days',
|
2013-03-14 04:43:16 +08:00
|
|
|
'new_topic_duration_minutes',
|
2013-03-13 11:06:58 +08:00
|
|
|
'external_links_in_new_tab',
|
|
|
|
'enable_quoting'),
|
2013-02-23 04:41:12 +08:00
|
|
|
type: 'PUT',
|
2013-03-14 04:43:16 +08:00
|
|
|
success: function() {
|
2013-03-13 11:06:58 +08:00
|
|
|
Discourse.set('currentUser.enable_quoting', _this.get('enable_quoting'));
|
|
|
|
Discourse.set('currentUser.external_links_in_new_tab', _this.get('external_links_in_new_tab'));
|
2013-03-14 04:43:16 +08:00
|
|
|
return finished(true);
|
2013-03-13 11:06:58 +08:00
|
|
|
},
|
2013-02-23 04:41:12 +08:00
|
|
|
error: function() { return finished(false); }
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2013-03-14 04:43:16 +08:00
|
|
|
/**
|
|
|
|
Changes the password and calls the callback function on AJAX.complete.
|
|
|
|
|
|
|
|
@method changePassword
|
|
|
|
@param {Function} callback Function called on completion of AJAX call
|
|
|
|
@returns The result of the callback() function on complete
|
|
|
|
**/
|
2013-02-23 04:41:12 +08:00
|
|
|
changePassword: function(callback) {
|
|
|
|
var good;
|
|
|
|
good = false;
|
2013-04-02 04:28:26 +08:00
|
|
|
Discourse.ajax({
|
2013-03-14 20:01:52 +08:00
|
|
|
url: Discourse.getURL("/session/forgot_password"),
|
2013-02-23 04:41:12 +08:00
|
|
|
dataType: 'json',
|
|
|
|
data: {
|
|
|
|
username: this.get('username')
|
|
|
|
},
|
|
|
|
type: 'POST',
|
|
|
|
success: function() { good = true; },
|
|
|
|
complete: function() {
|
|
|
|
var message;
|
|
|
|
message = "error";
|
|
|
|
if (good) {
|
|
|
|
message = "email sent";
|
2013-02-21 02:15:50 +08:00
|
|
|
}
|
2013-02-23 04:41:12 +08:00
|
|
|
return callback(message);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2013-03-14 04:43:16 +08:00
|
|
|
/**
|
|
|
|
Filters out this user's stream of user actions by a given filter
|
|
|
|
|
|
|
|
@method filterStream
|
|
|
|
@param {String} filter
|
|
|
|
**/
|
2013-02-23 04:41:12 +08:00
|
|
|
filterStream: function(filter) {
|
|
|
|
if (Discourse.UserAction.statGroups[filter]) {
|
|
|
|
filter = Discourse.UserAction.statGroups[filter].join(",");
|
|
|
|
}
|
|
|
|
this.set('streamFilter', filter);
|
|
|
|
this.set('stream', Em.A());
|
2013-03-13 17:33:32 +08:00
|
|
|
this.set('totalItems', 0);
|
2013-02-23 04:41:12 +08:00
|
|
|
return this.loadMoreUserActions();
|
|
|
|
},
|
|
|
|
|
2013-03-14 04:43:16 +08:00
|
|
|
/**
|
|
|
|
Loads a single user action by id.
|
|
|
|
|
|
|
|
@method loadUserAction
|
|
|
|
@param {Integer} id The id of the user action being loaded
|
|
|
|
@returns A stream of the user's actions containing the action of id
|
|
|
|
**/
|
2013-02-23 04:41:12 +08:00
|
|
|
loadUserAction: function(id) {
|
|
|
|
var stream,
|
|
|
|
_this = this;
|
|
|
|
stream = this.get('stream');
|
2013-04-02 04:28:26 +08:00
|
|
|
Discourse.ajax({
|
2013-03-14 20:01:52 +08:00
|
|
|
url: Discourse.getURL("/user_actions/") + id + ".json",
|
2013-02-23 04:41:12 +08:00
|
|
|
dataType: 'json',
|
|
|
|
cache: 'false',
|
|
|
|
success: function(result) {
|
|
|
|
if (result) {
|
|
|
|
var action;
|
|
|
|
|
|
|
|
if ((_this.get('streamFilter') || result.action_type) !== result.action_type) {
|
|
|
|
return;
|
2013-02-21 02:15:50 +08:00
|
|
|
}
|
2013-02-23 04:41:12 +08:00
|
|
|
|
|
|
|
action = Em.A();
|
|
|
|
action.pushObject(Discourse.UserAction.create(result));
|
|
|
|
action = Discourse.UserAction.collapseStream(action);
|
|
|
|
|
2013-03-13 12:45:55 +08:00
|
|
|
_this.set('totalItems', _this.get('totalItems') + 1);
|
|
|
|
|
2013-02-23 04:41:12 +08:00
|
|
|
return stream.insertAt(0, action[0]);
|
2013-02-21 02:15:50 +08:00
|
|
|
}
|
|
|
|
}
|
2013-02-23 04:41:12 +08:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2013-03-14 04:43:16 +08:00
|
|
|
/**
|
|
|
|
Loads more user actions, and then calls a callback if defined.
|
|
|
|
|
|
|
|
@method loadMoreUserActions
|
|
|
|
@param {String} callback Called after completion, on success of AJAX call, if it is defined
|
|
|
|
@returns the result of the callback
|
|
|
|
**/
|
2013-02-23 04:41:12 +08:00
|
|
|
loadMoreUserActions: function(callback) {
|
|
|
|
var stream, url,
|
|
|
|
_this = this;
|
|
|
|
stream = this.get('stream');
|
|
|
|
if (!stream) return;
|
|
|
|
|
2013-03-14 20:01:52 +08:00
|
|
|
url = Discourse.getURL("/user_actions?offset=") + this.get('totalItems') + "&user_id=" + (this.get("id"));
|
2013-02-23 04:41:12 +08:00
|
|
|
if (this.get('streamFilter')) {
|
|
|
|
url += "&filter=" + (this.get('streamFilter'));
|
|
|
|
}
|
|
|
|
|
2013-04-02 04:28:26 +08:00
|
|
|
return Discourse.ajax({
|
2013-02-23 04:41:12 +08:00
|
|
|
url: url,
|
|
|
|
dataType: 'json',
|
|
|
|
cache: 'false',
|
|
|
|
success: function(result) {
|
|
|
|
var copy;
|
|
|
|
if (result && result.user_actions && result.user_actions.each) {
|
|
|
|
copy = Em.A();
|
|
|
|
result.user_actions.each(function(i) {
|
|
|
|
return copy.pushObject(Discourse.UserAction.create(i));
|
|
|
|
});
|
|
|
|
copy = Discourse.UserAction.collapseStream(copy);
|
|
|
|
stream.pushObjects(copy);
|
|
|
|
_this.set('stream', stream);
|
2013-03-13 12:45:55 +08:00
|
|
|
_this.set('totalItems', _this.get('totalItems') + result.user_actions.length);
|
2013-02-23 04:41:12 +08:00
|
|
|
}
|
|
|
|
if (callback) {
|
|
|
|
return callback();
|
2013-02-21 02:15:50 +08:00
|
|
|
}
|
|
|
|
}
|
2013-02-23 04:41:12 +08:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2013-03-14 04:43:16 +08:00
|
|
|
/**
|
|
|
|
The user's stat count, excluding PMs.
|
|
|
|
|
|
|
|
@property statsCountNonPM
|
|
|
|
@type {Integer}
|
|
|
|
**/
|
2013-02-23 04:41:12 +08:00
|
|
|
statsCountNonPM: (function() {
|
|
|
|
var stats, total;
|
|
|
|
total = 0;
|
|
|
|
if (!(stats = this.get('stats'))) return 0;
|
|
|
|
this.get('stats').each(function(s) {
|
|
|
|
if (!s.get("isPM")) {
|
|
|
|
total += parseInt(s.count, 10);
|
2013-02-21 02:15:50 +08:00
|
|
|
}
|
2013-02-23 04:41:12 +08:00
|
|
|
});
|
|
|
|
return total;
|
|
|
|
}).property('stats.@each'),
|
|
|
|
|
2013-03-14 04:43:16 +08:00
|
|
|
/**
|
|
|
|
The user's stats, excluding PMs.
|
|
|
|
|
|
|
|
@property statsExcludingPms
|
|
|
|
@type {Array}
|
|
|
|
**/
|
2013-02-23 04:41:12 +08:00
|
|
|
statsExcludingPms: (function() {
|
|
|
|
var r;
|
|
|
|
r = [];
|
|
|
|
if (this.blank('stats')) return r;
|
|
|
|
this.get('stats').each(function(s) {
|
|
|
|
if (!s.get('isPM')) {
|
|
|
|
return r.push(s);
|
2013-02-21 02:15:50 +08:00
|
|
|
}
|
2013-02-23 04:41:12 +08:00
|
|
|
});
|
|
|
|
return r;
|
|
|
|
}).property('stats.@each'),
|
|
|
|
|
2013-03-14 04:43:16 +08:00
|
|
|
/**
|
|
|
|
This user's stats, only including PMs.
|
|
|
|
|
|
|
|
@property statsPmsOnly
|
|
|
|
@type {Array}
|
|
|
|
**/
|
2013-02-23 04:41:12 +08:00
|
|
|
statsPmsOnly: (function() {
|
|
|
|
var r;
|
|
|
|
r = [];
|
|
|
|
if (this.blank('stats')) return r;
|
|
|
|
this.get('stats').each(function(s) {
|
|
|
|
if (s.get('isPM')) return r.push(s);
|
|
|
|
});
|
|
|
|
return r;
|
|
|
|
}).property('stats.@each'),
|
|
|
|
|
2013-03-14 04:43:16 +08:00
|
|
|
/**
|
|
|
|
Number of items in this user's inbox.
|
|
|
|
|
|
|
|
@property inboxCount
|
|
|
|
@type {Integer}
|
|
|
|
**/
|
2013-02-23 04:41:12 +08:00
|
|
|
inboxCount: (function() {
|
|
|
|
var r;
|
|
|
|
r = 0;
|
|
|
|
this.get('stats').each(function(s) {
|
|
|
|
if (s.action_type === Discourse.UserAction.GOT_PRIVATE_MESSAGE) {
|
|
|
|
r = s.count;
|
|
|
|
return false;
|
2013-02-21 02:15:50 +08:00
|
|
|
}
|
2013-02-23 04:41:12 +08:00
|
|
|
});
|
|
|
|
return r;
|
|
|
|
}).property('stats.@each'),
|
|
|
|
|
2013-03-14 04:43:16 +08:00
|
|
|
/**
|
2013-04-05 00:59:44 +08:00
|
|
|
Number of items this user has sent.
|
2013-03-14 04:43:16 +08:00
|
|
|
|
|
|
|
@property sentItemsCount
|
|
|
|
@type {Integer}
|
|
|
|
**/
|
2013-04-05 00:59:44 +08:00
|
|
|
sentItemsCount: function() {
|
2013-02-23 04:41:12 +08:00
|
|
|
var r;
|
|
|
|
r = 0;
|
|
|
|
this.get('stats').each(function(s) {
|
|
|
|
if (s.action_type === Discourse.UserAction.NEW_PRIVATE_MESSAGE) {
|
|
|
|
r = s.count;
|
|
|
|
return false;
|
2013-02-21 02:15:50 +08:00
|
|
|
}
|
2013-02-23 04:41:12 +08:00
|
|
|
});
|
|
|
|
return r;
|
2013-04-05 00:59:44 +08:00
|
|
|
}.property('stats.@each'),
|
|
|
|
|
|
|
|
/**
|
|
|
|
Load extra details for the user
|
|
|
|
|
|
|
|
@method loadDetails
|
|
|
|
**/
|
|
|
|
loadDetails: function() {
|
|
|
|
|
|
|
|
// Check the preload store first
|
|
|
|
var user = this;
|
|
|
|
var username = this.get('username');
|
|
|
|
PreloadStore.getAndRemove("user_" + username, function() {
|
|
|
|
return Discourse.ajax({ url: Discourse.getURL("/users/") + username + '.json' });
|
|
|
|
}).then(function (json) {
|
|
|
|
// Create a user from the resulting JSON
|
|
|
|
json.user.stats = Discourse.User.groupStats(json.user.stats.map(function(s) {
|
|
|
|
var stat = Em.Object.create(s);
|
|
|
|
stat.set('isPM', stat.get('action_type') === Discourse.UserAction.NEW_PRIVATE_MESSAGE ||
|
|
|
|
stat.get('action_type') === Discourse.UserAction.GOT_PRIVATE_MESSAGE);
|
|
|
|
return stat;
|
|
|
|
}));
|
|
|
|
|
|
|
|
var count = 0;
|
|
|
|
if (json.user.stream) {
|
|
|
|
count = json.user.stream.length;
|
|
|
|
json.user.stream = Discourse.UserAction.collapseStream(json.user.stream.map(function(ua) {
|
|
|
|
return Discourse.UserAction.create(ua);
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
user.setProperties(json.user);
|
|
|
|
user.set('totalItems', count);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2013-02-23 04:41:12 +08:00
|
|
|
});
|
2013-02-21 02:15:50 +08:00
|
|
|
|
2013-02-23 04:41:12 +08:00
|
|
|
Discourse.User.reopenClass({
|
2013-03-14 04:43:16 +08:00
|
|
|
/**
|
|
|
|
Checks if given username is valid for this email address
|
2013-02-23 04:41:12 +08:00
|
|
|
|
2013-03-14 04:43:16 +08:00
|
|
|
@method checkUsername
|
|
|
|
@param {String} username A username to check
|
|
|
|
@param {String} email An email address to check
|
|
|
|
**/
|
2013-02-23 04:41:12 +08:00
|
|
|
checkUsername: function(username, email) {
|
2013-04-02 04:28:26 +08:00
|
|
|
return Discourse.ajax({
|
2013-03-14 20:01:52 +08:00
|
|
|
url: Discourse.getURL('/users/check_username'),
|
2013-02-23 04:41:12 +08:00
|
|
|
type: 'GET',
|
|
|
|
data: {
|
|
|
|
username: username,
|
|
|
|
email: email
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2013-03-14 04:43:16 +08:00
|
|
|
/**
|
|
|
|
Groups the user's statistics
|
|
|
|
|
|
|
|
@method groupStats
|
|
|
|
@param {Array} Given stats
|
|
|
|
@returns {Object}
|
|
|
|
**/
|
2013-02-23 04:41:12 +08:00
|
|
|
groupStats: function(stats) {
|
|
|
|
var g,
|
|
|
|
_this = this;
|
|
|
|
g = {};
|
|
|
|
stats.each(function(s) {
|
|
|
|
var c, found, k, v, _ref;
|
|
|
|
found = false;
|
|
|
|
_ref = Discourse.UserAction.statGroups;
|
|
|
|
for (k in _ref) {
|
|
|
|
v = _ref[k];
|
|
|
|
if (v.contains(s.action_type)) {
|
|
|
|
found = true;
|
|
|
|
if (!g[k]) {
|
|
|
|
g[k] = Em.Object.create({
|
|
|
|
description: Em.String.i18n("user_action_descriptions." + k),
|
|
|
|
count: 0,
|
|
|
|
action_type: parseInt(k, 10)
|
|
|
|
});
|
|
|
|
}
|
|
|
|
g[k].count += parseInt(s.count, 10);
|
|
|
|
c = g[k].count;
|
|
|
|
if (s.action_type === k) {
|
|
|
|
g[k] = s;
|
|
|
|
s.count = c;
|
2013-02-21 02:15:50 +08:00
|
|
|
}
|
|
|
|
}
|
2013-02-23 04:41:12 +08:00
|
|
|
}
|
|
|
|
if (!found) {
|
|
|
|
g[s.action_type] = s;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return stats.map(function(s) {
|
|
|
|
return g[s.action_type];
|
|
|
|
}).exclude(function(s) {
|
|
|
|
return !s;
|
|
|
|
});
|
|
|
|
},
|
2013-02-21 02:15:50 +08:00
|
|
|
|
2013-03-14 04:43:16 +08:00
|
|
|
/**
|
|
|
|
Creates a new account over POST
|
|
|
|
|
|
|
|
@method createAccount
|
|
|
|
@param {String} name This user's name
|
|
|
|
@param {String} email This user's email
|
|
|
|
@param {String} password This user's password
|
|
|
|
@param {String} passwordConfirm This user's confirmed password
|
|
|
|
@param {String} challenge
|
|
|
|
@returns Result of ajax call
|
|
|
|
**/
|
2013-02-23 04:41:12 +08:00
|
|
|
createAccount: function(name, email, password, username, passwordConfirm, challenge) {
|
2013-04-02 04:28:26 +08:00
|
|
|
return Discourse.ajax({
|
2013-03-14 20:01:52 +08:00
|
|
|
url: Discourse.getURL("/users"),
|
2013-02-23 04:41:12 +08:00
|
|
|
dataType: 'json',
|
|
|
|
data: {
|
|
|
|
name: name,
|
|
|
|
email: email,
|
|
|
|
password: password,
|
|
|
|
username: username,
|
|
|
|
password_confirmation: passwordConfirm,
|
|
|
|
challenge: challenge
|
|
|
|
},
|
|
|
|
type: 'POST'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|