Merge branch 'develop' of git://github.com/HabitRPG/habitrpg into develop

This commit is contained in:
Blade Barringer 2014-07-27 19:56:09 -05:00
commit d7351932aa
22 changed files with 235 additions and 78 deletions

View file

@ -2,7 +2,7 @@ var _id = '';
var update = {
$push: {
'purchased.plan.mysteryItems':{
$each:['head_mystery_201406','armor_mystery_201406']
$each:['head_mystery_201407','armor_mystery_201407']
}
}
};

View file

@ -85,7 +85,7 @@
"karma-ng-html2js-preprocessor": "~0.1.0",
"karma-chai-plugins": "~0.1.0",
"mocha": "~1.12.1",
"karma-mocha": "~0.1.0",
"karma-mocha": "0.1.3",
"csv": "~0.3.6",
"mongoskin": "~0.6.1",
"expect.js": "~0.2.0",

View file

@ -228,7 +228,12 @@ window.habitrpg = angular.module('habitrpg',
} else if (response.needRefresh) {
$rootScope.$broadcast('responseError', "The site has been updated and the page needs to refresh. The last action has not been recorded, please refresh and try again.");
// 400 range?
} else if (response.data.code && response.data.code === 'ACCOUNT_SUSPENDED') {
confirm(response.data.err);
localStorage.clear();
window.location.href = '/logout';
// 400 range?
} else if (response < 500) {
$rootScope.$broadcast('responseText', response.data.err || response.data);

View file

@ -28,6 +28,7 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
$rootScope.env = window.env;
$rootScope.Math = Math;
$rootScope.Groups = Groups;
$rootScope.toJson = angular.toJson;
// Angular UI Router
$rootScope.$state = $state;
@ -82,6 +83,7 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
scope: options.scope, // optional
keyboard: (options.keyboard === undefined ? true : options.keyboard), // optional
backdrop: (options.backdrop === undefined ? true : options.backdrop) // optional
});
}
@ -121,6 +123,26 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
});
}
$rootScope.showStripeEdit = function(){
StripeCheckout.open({
key: window.env.STRIPE_PUB_KEY,
address: false,
name: 'Update',
description: 'Update the card to be charged for your subscription',
panelLabel: 'Update Card',
token: function(data) {
var url = '/stripe/subscribe/edit?plan=basic_earned';
$scope.$apply(function(){
$http.post(url, data).success(function() {
window.location.reload(true);
}).error(function(err) {
alert(err);
});
})
}
});
}
$rootScope.cancelSubscription = function(){
if (!confirm(window.env.t('sureCancelSub'))) return;
window.location.href = '/' + user.purchased.plan.paymentMethod.toLowerCase() + '/subscribe/cancel?_id=' + user._id + '&apiToken=' + user.apiToken;

View file

@ -65,6 +65,20 @@ habitrpg.controller('SettingsCtrl',
$rootScope.$state.go('tasks');
}
$scope.changeUsername = function(changeUser){
if (!changeUser.newUsername || !changeUser.password) {
return alert(window.env.t('fillAll'));
}
$http.post(API_URL + '/api/v2/user/change-username', changeUser)
.success(function(){
alert(window.env.t('usernameSuccess'));
$scope.changeUser = {};
})
.error(function(data){
alert(data);
});
}
$scope.changePassword = function(changePass){
if (!changePass.oldPassword || !changePass.newPassword || !changePass.confirmNewPassword) {
return alert(window.env.t('fillAll'));

View file

@ -14,6 +14,12 @@ var api = module.exports;
var NO_TOKEN_OR_UID = { err: "You must include a token and uid (user id) in your request"};
var NO_USER_FOUND = {err: "No user found."};
var NO_SESSION_FOUND = { err: "You must be logged in." };
var accountSuspended = function(uuid){
return {
err: 'Account has been suspended, please contact leslie@habitrpg.com with your UUID ('+uuid+') for assistance.',
code: 'ACCOUNT_SUSPENDED'
};
}
api.auth = function(req, res, next) {
var uid = req.headers['x-api-user'];
@ -22,6 +28,7 @@ api.auth = function(req, res, next) {
User.findOne({_id: uid,apiToken: token}, function(err, user) {
if (err) return next(err);
if (_.isEmpty(user)) return res.json(401, NO_USER_FOUND);
if (user.auth.blocked) return res.json(401, accountSuspended(user._id));
res.locals.wasModified = req.query._v ? +user._v !== +req.query._v : true;
res.locals.user = user;
@ -121,9 +128,10 @@ api.loginLocal = function(req, res, next) {
var username = req.body.username;
var password = req.body.password;
if (!(username && password)) return res.json(401, {err:'Missing :username or :password in request body, please provide both'});
User.findOne({'auth.local.username': username}, function(err, user){
User.findOne({'auth.local.username': username}, {auth:1}, function(err, user){
if (err) return next(err);
if (!user) return res.json(401, {err:"Username or password incorrect. Click 'Forgot Password' for help with either. (Note: usernames are case-sensitive)"});
if (user.auth.blocked) return res.json(401, accountSuspended(user._id));
// We needed the whole user object first so we can get his salt to encrypt password comparison
User.findOne({
'auth.local.username': username,
@ -144,30 +152,17 @@ api.loginLocal = function(req, res, next) {
api.loginFacebook = function(req, res, next) {
var email, facebook_id, name, _ref;
_ref = req.body, facebook_id = _ref.facebook_id, email = _ref.email, name = _ref.name;
if (!facebook_id) {
return res.json(401, {
err: 'No facebook id provided'
});
}
return User.findOne({
'auth.facebook.id': facebook_id
}, function(err, user) {
if (!facebook_id)
return res.json(401, {err: 'No facebook id provided'});
User.findOne({'auth.facebook.id': facebook_id}, function(err, user) {
if (err) {
return res.json(401, {
err: err
});
}
if (user) {
return res.json(200, {
id: user.id,
token: user.apiToken
});
return res.json(401, {err: err});
} else if (user) {
if (user.auth.blocked) return res.json(401, accountSuspended(user._id));
return res.json(200, {id: user.id,token: user.apiToken});
} else {
/* FIXME: create a new user instead*/
return res.json(403, {
err: "Please register with Facebook on https://habitrpg.com, then come back here and log in."
});
return res.json(403, {err: "Please register with Facebook on https://habitrpg.com, then come back here and log in."});
}
});
};
@ -195,6 +190,31 @@ api.resetPassword = function(req, res, next){
});
};
api.changeUsername = function(req, res, next) {
var user = res.locals.user,
password = req.body.password
newUsername = req.body.newUsername;
User.findOne({'auth.local.username': newUsername}, function(err, result) {
if (err) next(err);
if(result)
return res.json(401, {err: "Username already taken"});
var salt = user.auth.local.salt,
hashed_password = utils.encryptPassword(password, salt);
if (hashed_password !== user.auth.local.hashed_password)
return res.json(401, {err:"Incorrect password"});
user.auth.local.username = newUsername;
user.save(function(err, saved){
if (err) next(err);
res.send(200);
})
});
}
api.changePassword = function(req, res, next) {
var user = res.locals.user,
oldPassword = req.body.oldPassword,

View file

@ -20,11 +20,11 @@ api.generateCoupons = function(req,res,next) {
api.getCoupons = function(req,res,next) {
var options = {sort:'seq'};
if (req.query.limit) options.limit = req.query.limit;
if (req.query.skip) options.limit = req.query.skip;
if (req.query.skip) options.skip = req.query.skip;
Coupon.find({},{}, options, function(err,coupons){
//res.header('Content-disposition', 'attachment; filename=coupons.csv');
res.csv([['code']].concat(_.map(coupons, function(c){
return ['HabitRPG - To redeem your code, go to goo.gl/azsGaH and enter '+c._id];
return [c._id];
})));
});
}

View file

@ -38,7 +38,8 @@ api.getPatrons = function(req,res,next){
api.getHero = function(req,res,next) {
User.findById(req.params.uid)
.select('contributor balance profile.name purchased')
.select('contributor balance profile.name purchased items')
.select('auth.local.username auth.local.email auth.facebook auth.blocked')
.exec(function(err, user){
if (err) return next(err)
if (!user) return res.json(400,{err:'User not found'});
@ -61,6 +62,12 @@ api.updateHero = function(req,res,next) {
member.contributor = req.body.contributor;
member.purchased.ads = req.body.purchased.ads;
if (member.contributor.level >= 6) member.items.pets['Dragon-Hydra'] = 5;
if (req.body.itemPath && req.body.itemVal
&& req.body.itemPath.indexOf('items.')===0
&& User.schema.paths[req.body.itemPath]) {
shared.dotSet(member, req.body.itemPath, req.body.itemVal); // Sanitization at 5c30944 (deemed unnecessary)
}
if (_.isBoolean(req.body.auth.blocked)) member.auth.blocked = req.body.auth.blocked;
member.save(cb);
}
], function(err, saved){

View file

@ -142,6 +142,32 @@ api.stripeSubscribeCancel = function(req, res, next) {
});
}
api.stripeSubscribeEdit = function(req, res, next) {
var stripe = require("stripe")(nconf.get('STRIPE_API_KEY'));
var token = req.body.id;
var user = res.locals.user;
var user_id = user.purchased.plan.customerId;
var sub_id;
async.waterfall([
function(cb){
stripe.customers.listSubscriptions(user_id, cb);
},
function(response, cb) {
sub_id = response.data[0].id;
console.warn(sub_id);
console.warn([user_id, sub_id, { card: token }]);
stripe.customers.updateSubscription(user_id, sub_id, { card: token }, cb);
},
function(response, cb) {
user.save(cb);
}
], function(err, saved){
if (err) return res.send(500, err.toString()); // don't json this, let toString() handle errors
res.send(200);
});
}
api.paypalSubscribe = function(req,res,next) {
var uuid = res.locals.user._id;
// Authenticate a future subscription of ~5 USD

View file

@ -29,6 +29,13 @@ api.getContent = function(req, res, next) {
res.json(content);
}
api.getModelPaths = function(req,res,next){
res.json(_.reduce(User.schema.paths,function(m,v,k){
m[k] = v.instance || 'Boolean';
return m;
},{}));
}
/*
------------------------------------------------------------------------
Tasks

View file

@ -9,17 +9,19 @@ if (logger == null) {
logger = new (winston.Logger)({});
if (nconf.get('NODE_ENV') == 'production') {
logger.add(winston.transports.newrelic, {});
logger.add(winston.transports.Mail, {
to: nconf.get('ADMIN_EMAIL') || nconf.get('SMTP_USER'),
from: "HabitRPG <" + nconf.get('SMTP_USER') + ">",
subject: "HabitRPG Error",
host: nconf.get('SMTP_HOST'),
port: nconf.get('SMTP_PORT'),
tls: nconf.get('SMTP_TLS'),
username: nconf.get('SMTP_USER'),
password: nconf.get('SMTP_PASS'),
level: 'error'
});
if (!nconf.get('DISABLE_ERROR_EMAILS')) {
logger.add(winston.transports.Mail, {
to: nconf.get('ADMIN_EMAIL') || nconf.get('SMTP_USER'),
from: "HabitRPG <" + nconf.get('SMTP_USER') + ">",
subject: "HabitRPG Error",
host: nconf.get('SMTP_HOST'),
port: nconf.get('SMTP_PORT'),
tls: nconf.get('SMTP_TLS'),
username: nconf.get('SMTP_USER'),
password: nconf.get('SMTP_PASS'),
level: 'error'
});
}
} else {
logger.add(winston.transports.Console, {colorize:true});
logger.add(winston.transports.File, {filename: 'habitrpg.log'});

View file

@ -46,6 +46,7 @@ var UserSchema = new Schema({
valentine: Number
},
auth: {
blocked: Boolean,
facebook: Schema.Types.Mixed,
local: {
email: String,
@ -405,6 +406,7 @@ UserSchema.pre('save', function(next) {
//our own version incrementer
this._v++;
next();
});

View file

@ -46,6 +46,10 @@ module.exports = (swagger, v2) ->
]
action: user.getContent
'/content/paths':
spec:
description: "Show user model tree"
action: user.getModelPaths
"/export/history":
spec:

View file

@ -10,6 +10,7 @@ router.post('/api/v2/user/auth/local', i18n.getUserLanguage, auth.loginLocal);
router.post('/api/v2/user/auth/facebook', i18n.getUserLanguage, auth.loginFacebook);
router.post('/api/v2/user/reset-password', i18n.getUserLanguage, auth.resetPassword);
router.post('/api/v2/user/change-password', i18n.getUserLanguage, auth.auth, auth.changePassword);
router.post('/api/v2/user/change-username', i18n.getUserLanguage, auth.auth, auth.changeUsername);
router.post('/api/v1/register', i18n.getUserLanguage, auth.registerUser);
router.post('/api/v1/user/auth/local', i18n.getUserLanguage, auth.loginLocal);

View file

@ -13,6 +13,7 @@ router.get('/paypal/subscribe/cancel', auth.authWithUrl, i18n.getUserLanguage, p
router.post('/paypal/ipn', i18n.getUserLanguage, payments.paypalIPN); // misc ipn handling
router.post("/stripe/checkout", auth.auth, i18n.getUserLanguage, payments.stripeCheckout);
router.post("/stripe/subscribe/edit", auth.auth, i18n.getUserLanguage, payments.stripeSubscribeEdit)
//router.get("/stripe/subscribe", auth.authWithUrl, i18n.getUserLanguage, payments.stripeSubscribe); // checkout route is used (above) with ?plan= instead
router.get("/stripe/subscribe/cancel", auth.authWithUrl, i18n.getUserLanguage, payments.stripeSubscribeCancel);

View file

@ -83,6 +83,18 @@ script(type='text/ng-template', id='partials/options.settings.settings.html')
input.form-control(type='text', ng-model='_couponCode', placeholder='Enter Coupon Code')
button.btn.btn-primary(type='submit') Submit
div(ng-if='user.auth.local.username')
.panel.panel-default
.panel-heading
span=env.t('changeUsername')
.panel-body
form(ng-submit='changeUsername(changeUser)', ng-show='user.auth.local')
.form-group
input.form-control(type='text', placeholder=env.t('newUsername'), ng-model='changeUser.newUsername', required)
.form-group
input.form-control(type='password', placeholder=env.t('password'), ng-model='changeUser.password', required)
input.btn.btn-default(type='submit', value=env.t('submit'))
//- Why is ng-if='user.auth.local' validating for users *without* user.auth.local (facebook users)? adding .username here for extra
div(ng-if='user.auth.local.username')
.panel.panel-default
@ -207,4 +219,5 @@ script(id='partials/options.settings.subscription.html',type='text/ng-template')
| &nbsp;
span.glyphicon.glyphicon-ok
div(ng-include="'partials/options.settings.subscription.perks.html'")
.btn.btn-sm.btn-danger(ng-if='!user.purchased.plan.dateTerminated', ng-click='cancelSubscription()')=env.t('cancelSub')
.btn.btn-primary(ng-if=':: !user.purchased.plan.dateTerminated && user.purchased.plan.paymentMethod=="Stripe"', ng-click='showStripeEdit()') Update Card
.btn.btn-sm.btn-danger(ng-if=':: !user.purchased.plan.dateTerminated', ng-click='cancelSubscription()')=env.t('cancelSub')

View file

@ -3,7 +3,7 @@ a.pull-right.gem-wallet(popover-trigger='mouseenter', popover-title=env.t('guild
span.task-action-btn.tile.flush.neutral
.Pet_Currency_Gem2x.Gems
| {{group.balance * 4 | number:0 }}
=env.t('guildGems')
=' ' + env.t('guildGems')
.container-fluid
.row

View file

@ -37,24 +37,41 @@ script(type='text/ng-template', id='partials/options.social.hall.heroes.html')
label
input(type='checkbox', ng-model='hero.contributor.admin')
=env.t('admin')
hr
hr
.form-group
label=env.t('balance')
input.form-control(type='number', step="any", ng-model='hero.balance')
small!= '`user.balance`' + env.t('notGems')
.form-group
.checkbox
label
input(type='checkbox', ng-model='hero.purchased.ads')
=env.t('hideAds')
accordion
accordion-group(heading='Items')
h4 Update Item
.form-group.well
input.form-control(type='text',placeholder='Path (eg, items.pets.BearCub-Base)',ng-model='hero.itemPath')
small.muted Enter the <strong>item path</strong>. E.g., <code>items.pets.BearCub-Zombie</code> or <code>items.gear.owned.head_special_0</code> or <code>items.gear.equipped.head</code>. <a href='/api/v2/content/paths' target='_blank'>See all paths here.</a> When in doubt, ask Tyler.
br
input.form-control(type='text',placeholder='Value (eg, 5)',ng-model='hero.itemVal')
small.muted Enter the <strong>item value</strong>. E.g., <code>5</code> or <code>false</code> or <code>head_warrior_3</code> (respectively from above examples).
h4 Current Items
pre {{::toJson(hero.items,true)}}
accordion-group(heading='Auth')
h4 Auth
pre {{::toJson(hero.auth)}}
.form-group
.checkbox
label
input(type='checkbox', ng-model='hero.auth.blocked')
| Blocked
.form-group
label=env.t('balance')
input.form-control(type='number', step="any", ng-model='hero.balance')
small!= '`user.balance`' + env.t('notGems')
.form-group
.checkbox
label
input(type='checkbox', ng-model='hero.purchased.ads')
=env.t('hideAds')
// h4 Backer Status
// Add backer stuff like tier, disable adds, etcs
.form-group
input.form-control.btn.btn-primary(type='submit')=env.t('save')
// h4 Backer Status
// Add backer stuff like tier, disable adds, etcs
.form-group
input.form-control.btn.btn-primary(type='submit')=env.t('save')
table.table.table-striped

View file

@ -8,7 +8,7 @@ script(type='text/ng-template', id='modals/reset.html')
button.btn.btn-default(ng-click='$close();')=env.t('neverMind')
button.btn.btn-danger(ng-click='$close(); reset()')=env.t('resetDo')
script(type='text/ng-template', id='modals/restore.html')
script(type='text/ng-template', id='modals/restore.html')
.modal-header
h4=env.t('fixValues')
.modal-body

View file

@ -11,21 +11,29 @@ table
table.table.table-striped
tr
td
h5 Mobile App Update
p Weve released another update to the mobile app! Now you can feed and select pets from the app. Carry your cute pets with you everywhere you go! The app is available for <a href='https://itunes.apple.com/us/app/habitrpg/id689569235?mt=8' target='_blank'>iOS here</a>, and <a href='https://play.google.com/store/apps/details?id=com.ocdevel.habitrpg' target='_blank'>Android here</a>. Were continuing to release updates on a regular basis, so if you like the direction that weve been taking the app, please do consider leaving us a review. Thank you!
tr
td
h5 Neglect Strike: Tavern Art Swap
p The Dread Drag'on's Rage Bar has filled, and it has unleashed its Neglect Strike, leading to a new look for the Tavern! As a reminder, the Drag'on's rage will NEVER hurt any users or interfere with their ability to be productive, so the chat and inn are still functional. Even so... poor Daniel!
p All users are automatically damaging the Drag'on with their tasks. There is nothing bad that can happen to you or your account by being in this fight!
tr
td
h5 Dread Drag'on Prize Change: Food Reward!
p We've received a lot of feedback due to the weekend's confusion, and it seems that awarding GP and XP for defeating the world boss significantly unbalanced the game for newer players. Based on your feedback, XP and GP will no longer be awarded. Instead, players will receive an assortment of food! The Mantis Shrimps will still be awarded.
p If you were looking forward to receiving the 900XP and 90 GP upon completion of the battle, feel free to award it to yourself using Settings > Site > Fix Character Values when the battle is done!
p Thank you for bearing with us through the confusion. We love you guys.
small.muted 7/16/2014
h5 July Subscriber Item
.promo_mystery_201407.pull-right
p The July Subscriber Item has been revealed: the Undersea Explorer Item Set! All July subscribers will receive the Undersea Explorer Helm and the Undersea Explorer Suit. You still have six days to subscribe and receive the item set! Thank you so much for your support - we really do rely on you to keep HabitRPG free to use and running smoothly.
small.muted 7/25/2014
h5 7/16/2014
table.table.table-striped
tr
td
h5 Mobile App Update
p Weve released another update to the mobile app! Now you can feed and select pets from the app. Carry your cute pets with you everywhere you go! The app is available for <a href='https://itunes.apple.com/us/app/habitrpg/id689569235?mt=8' target='_blank'>iOS here</a>, and <a href='https://play.google.com/store/apps/details?id=com.ocdevel.habitrpg' target='_blank'>Android here</a>. Were continuing to release updates on a regular basis, so if you like the direction that weve been taking the app, please do consider leaving us a review. Thank you!
tr
td
h5 Neglect Strike: Tavern Art Swap
p The Dread Drag'on's Rage Bar has filled, and it has unleashed its Neglect Strike, leading to a new look for the Tavern! As a reminder, the Drag'on's rage will NEVER hurt any users or interfere with their ability to be productive, so the chat and inn are still functional. Even so... poor Daniel!
p All users are automatically damaging the Drag'on with their tasks. There is nothing bad that can happen to you or your account by being in this fight!
tr
td
h5 Dread Drag'on Prize Change: Food Reward!
p We've received a lot of feedback due to the weekend's confusion, and it seems that awarding GP and XP for defeating the world boss significantly unbalanced the game for newer players. Based on your feedback, XP and GP will no longer be awarded. Instead, players will receive an assortment of food! The Mantis Shrimps will still be awarded.
p If you were looking forward to receiving the 900XP and 90 GP upon completion of the battle, feel free to award it to yourself using Settings > Site > Fix Character Values when the battle is done!
p Thank you for bearing with us through the confusion. We love you guys.
hr
h5 7/12/2014
table.table.table-striped
tr
@ -116,7 +124,7 @@ table.table.table-striped
tr
td
h5 June Subscriber Item
img.pull-right(src='/bower_components/habitrpg-shared/img/sprites/spritesmith/gear/events/mystery_201406/promo_mystery_201406.png')
.pull-right.promo_mystery_201406.png
p The June Subscriber Item has been revealed: the Octomage Item Set! All June subscribers will receive the Octopus Robe and the Crown of Tentacles. You still have six days to subscribe and receive the item set! Thank you so much for your support - we really do rely on you to keep HabitRPG free to use and running smoothly.
h5 Mobile App Update
p There's a new mobile app update available! In addition to bug fixes, there are many improvements, including a new button-based menu, tap-and-hold to edit tasks, and the return of stats and in-app avatar customization! Working on the mobile app is our biggest To-Do this summer, so expect more in the coming months. If you feel that the app is improving, we'd love it if you would take the time to give us a review and let us know what you think!
@ -185,7 +193,7 @@ table.table.table-striped
tr
td
h5 May Mystery Outfit Revealed!
img.pull-right(src='/bower_components/habitrpg-shared/img/sprites/spritesmith/gear/events/mystery_201405/promo_mystery_201405.png')
.pull-right.promo_mystery_201405.png
p The May Mystery Item Set has been revealed for all subscribers... <strong>Flame Wielder Item Set</strong>! All people who are subscribed this May will receive two items:
ul
li Flame of Mind (helm)
@ -262,7 +270,7 @@ table.table.table-striped
tr
td
h5 April Mystery Outfit Revealed!
img.pull-right(src='/marketing/promos/April14SAMPLE2.png')
//-img.pull-right(src='/marketing/promos/April14SAMPLE2.png')
p The April Mystery Item Set has been revealed for all subscribers... <strong>Twilight Butterfly Armor Set</strong>! All people who are subscribed this April will receive two items:
ul
li Twilight Butterfly Antennae
@ -317,7 +325,7 @@ table.table.table-striped
tr
td
h5 March Mystery Item Set
img.pull-right(src='/marketing/promos/201403_Forest_Walker.png')
//-img.pull-right(src='/marketing/promos/201403_Forest_Walker.png')
p The March Mystery Item Set has been revealed for all subscribers... The Forest Walker Set! All people who are subscribed this March will receive two items: <strong>Forest Walker Armor</strong> and <strong>Forest Walker Antlers</strong>!
br
p The antlers are a head accessory, so they can be worn with any helmet.

View file

@ -154,3 +154,11 @@ div(ng-if='::profile.achievements.valentine')
small
=env.t('adoringFriendsText', {cards: "{{::profile.achievements.valentine}}"})
hr
div(ng-if='::profile.achievements.quests.dilatory')
.achievement.achievement-dilatory
h5=env.t('achievementDilatory')
small
=env.t('achievementDilatoryText')
hr

View file

@ -80,7 +80,7 @@ html
h2 Extensions / Scripts
p HabitRPG has a simple API for up-scoring and down-scoring third party Habits: <code>POST /api/v2/user/tasks/{id}/{direction}</code> (headers <code>x-api-user</code> and <code>x-api-key</code> required).
h4 Example
p <code>curl -X POST -H "x-api-key: YOUR_API_TOKEN" -H "x-api-user: YOUR_USER_ID" https://beta.habitrpg.com/api/v2/user/tasks/productivity/up</code>
p <code>curl -X POST -H "x-api-key: YOUR_API_TOKEN" -H "x-api-user: YOUR_USER_ID" https://habitrpg.com/api/v2/user/tasks/productivity/up</code>
p Note: You may need to add <code>--compressed -H "Content-Type:application/json"</code> to your curl if you get errors.
ul
li POST to the URL <code>/api/v2/user/tasks/{id}/{direction}</code>
@ -91,7 +91,7 @@ html
h2 Full API
p All API requests should be prefaced by <code>https://habitrpg.com</code>. Every authenticated request should include two headers. Your api key (<code>x-api-key</code>) and your user id (<code>x-api-user</code>). Do not include <code>{}</code> braces in your header (<code>-H 'x-api-user: a94b6d9d-6b64-43ae-856c-2c3f211bd426'</code>)
h2 Requirements:
p The base-url for all routes is /api/v2. So /user actions will be at https://beta.habitrpg.com/api/v2/user/*. You need to send x-api-user and x-api-key headers for each request.
p The base-url for all routes is /api/v2. So /user actions will be at https://habitrpg.com/api/v2/*. You need to send x-api-user and x-api-key headers for each request.
p For create & edit paths (PUT & POST), you'll need to know the schema of the object you're trying to create or edit. See Schema <a href='https://github.com/HabitRPG/habitrpg/tree/develop/src/models' target="_blank">definitions here</a>
p If any of the documentation is lacking or you're having trouble with it, please post an issue to <a href='https://github.com/HabitRPG/habitrpg/issues' target='_blank'>Github</a>
#message-bar.swagger-ui-wrap