diff --git a/public/js/controllers/inventoryCtrl.js b/public/js/controllers/inventoryCtrl.js index 96d99822b1..995f2fad83 100644 --- a/public/js/controllers/inventoryCtrl.js +++ b/public/js/controllers/inventoryCtrl.js @@ -64,7 +64,7 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', 'User', 'API_URL', var selected = $scope.selectedEgg ? 'selectedEgg' : $scope.selectedPotion ? 'selectedPotion' : $scope.selectedFood ? 'selectedFood' : undefined; if (selected) { var type = $scope.selectedEgg ? 'eggs' : $scope.selectedPotion ? 'hatchingPotions' : $scope.selectedFood ? 'food' : undefined; - user.ops.sell({query:{type:type, key: $scope[selected].name}}); + user.ops.sell({params:{type:type, key: $scope[selected].name}}); if (user.items[type][$scope[selected].name] < 1) { $scope[selected] = null; } @@ -77,22 +77,18 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', 'User', 'API_URL', $scope.hatch = function(egg, potion){ if (!confirm('Hatch a ' + potion.name + ' ' + egg.name + '?')) return; - user.ops.hatch({query:{egg:egg.name, hatchingPotion:potion.name}}); + user.ops.hatch({params:{egg:egg.name, hatchingPotion:potion.name}}); $scope.selectedEgg = null; $scope.selectedPotion = null; } - $scope.buy = function(type, item){ + $scope.purchase = function(type, item){ var gems = User.user.balance * 4; if(gems < item.value) return $rootScope.modals.buyGems = true; var string = (type == 'hatchingPotion') ? 'hatching potion' : type; // give hatchingPotion a space var message = "Buy this " + string + " with " + item.value + " of your " + gems + " Gems?" - if(confirm(message)){ - $http.post(API_URL + '/api/v1/market/buy?type=' + type, item) - .success(function(data){ - User.user.items = data.items; - }); - } + if(confirm(message)) + User.user.ops.purchase({params:{type:type,key:item.name}}); } $scope.choosePet = function(egg, potion){ @@ -106,17 +102,17 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', 'User', 'API_URL', } else if (!confirm('Feed ' + pet + ' a ' + food.name + '?')) { return; } - User.user.ops.feed({query:{pet: pet, food: food.name}}); + User.user.ops.feed({params:{pet: pet, food: food.name}}); $scope.selectedFood = null; // Selecting Pet } else { - User.user.ops.equip({query:{type: 'pet', key: pet}}); + User.user.ops.equip({params:{type: 'pet', key: pet}}); } } $scope.chooseMount = function(egg, potion) { - User.user.ops.equip({query:{type: 'mount', key: egg + '-' + potion}}); + User.user.ops.equip({params:{type: 'mount', key: egg + '-' + potion}}); } } ]); \ No newline at end of file diff --git a/public/js/controllers/settingsCtrl.js b/public/js/controllers/settingsCtrl.js index 2925e15372..b4ea919b79 100644 --- a/public/js/controllers/settingsCtrl.js +++ b/public/js/controllers/settingsCtrl.js @@ -45,15 +45,9 @@ habitrpg.controller('SettingsCtrl', } $scope.reroll = function(){ - - $http.post(API_URL + '/api/v1/user/reroll') - .success(function(){ - window.location.href = '/'; - // FIXME, I can't get the tasks to update in the browser, even with _.extend(user,data). refreshing for now - }) - .error(function(data){ - alert(data.err) - }) + User.user.ops.reroll({}); + $rootScope.modals.reroll = false; + $rootScope.$state.go('tasks'); } $scope.changePassword = function(changePass){ @@ -90,17 +84,13 @@ habitrpg.controller('SettingsCtrl', }); $rootScope.modals.restore = false; } + $scope.reset = function(){ - $http.post(API_URL + '/api/v1/user/reset') - .success(function(){ - User.user._v--; - User.log({}); - $rootScope.modals.reset = false; - }) - .error(function(data){ - alert(data); - }); + User.user.ops.reset({}); + $rootScope.modals.reset = false; + $rootScope.$state.go('tasks'); } + $scope['delete'] = function(){ $http['delete'](API_URL + '/api/v1/user') .success(function(){ diff --git a/public/js/controllers/tasksCtrl.js b/public/js/controllers/tasksCtrl.js index fe4bb3641c..42442a3b9b 100644 --- a/public/js/controllers/tasksCtrl.js +++ b/public/js/controllers/tasksCtrl.js @@ -64,7 +64,7 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N $scope.unlink = function(task, keep) { // TODO move this to userServices, turn userSerivces.user into ng-resource - $http.post(API_URL + '/api/v1/user/task/' + task.id + '/unlink?keep=' + keep) + $http.post(API_URL + '/api/v1/user/tasks/' + task.id + '/unlink?keep=' + keep) .success(function(){ User.log({}); }); @@ -81,14 +81,8 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N }) $scope.buy = function(item) { - var hasEnough = User.user.ops.buy({query:{key:item.key}}); - if (hasEnough) { - Notification.text("Item purchased."); - $scope.itemStore = User.user.fns.updateStore(); - } else { -// Notification.text("Not enough Gold!"); - // handled by userServices interceptor - } + User.user.ops.buy({params:{key:item.key}}); + $scope.itemStore = User.user.fns.updateStore(); }; diff --git a/src/controllers/user.js b/src/controllers/user.js index 3189c559cb..66ae0b5730 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -15,23 +15,7 @@ var Challenge = require('./../models/challenge').model; var acceptablePUTPaths; var api = module.exports; -// FIXME put this in a proper location -api.marketBuy = function(req, res, next){ - var user = res.locals.user, - type = req.query.type, - item = req.body; - - if (!_.contains(['hatchingPotion', 'egg', 'food'], type)) - return res.json(400, {err: "Type must be 'hatchingPotion', 'egg', or 'food'"}); - type = (type == 'food' ? type : type + 's'); // I'm stupid, we're passing up 'hatchingPotion' but we need 'hatchingPotions' - if (!user.items[type][item.name]) user.items[type][item.name] = 0; - user.items[type][item.name]++; - user.balance -= (item.value/4); - user.save(function(err, saved){ - if (err) return res.json(500, {err:err}); - res.json(saved); - }) -} +// api.purchase // Shared.ops /* ------------------------------------------------------------------------ @@ -45,15 +29,8 @@ api.marketBuy = function(req, res, next){ --------------- */ -/* -Validate task -*/ -api.verifyTaskExists = function(req, res, next) { - // If we're updating, get the task from the user - var task = res.locals.user.tasks[req.params.id]; - if (_.isEmpty(task)) return res.json(400, {err: "No task found."}); - res.locals.task = task; - return next(); +findTask = function(req, res) { + return task = res.locals.user.tasks[req.params.id]; }; /* @@ -66,6 +43,9 @@ api.verifyTaskExists = function(req, res, next) { Export it also so we can call it from deprecated.coffee */ api.score = function(req, res, next) { + var task = findTask(req,res); + if (!task) return res.json(404, {err: "No task found."}); + var id = req.params.id, direction = req.params.direction, user = res.locals.user, @@ -129,29 +109,17 @@ api.getTasks = function(req, res, next) { * Get Task */ api.getTask = function(req, res, next) { - var task = res.locals.user.tasks[req.params.id]; - if (_.isEmpty(task)) return res.json(400, {err: "No task found."}); + var task = findTask(req,res); + if (!task) return res.json(404, {err: "No task found."}); return res.json(200, task); }; -/** - * Delete Task - */ -api.deleteTask = function(req, res, next) { - api.verifyTaskExists(req, res, function(){ - var user = res.locals.user; - user.deleteTask(res.locals.task.id); - user.save(function(err) { - if (err) return res.json(500, {err: err}); - res.send(204); - }); - }) -}; /* Update Task */ +//api.deleteTask // see Shared.ops // api.updateTask // handled in Shared.ops // api.addTask // handled in Shared.ops // api.sortTask // handled in Shared.ops #TODO updated api, mention in docs @@ -231,44 +199,8 @@ api.cron = function(req, res, next) { user.save(next); }; -api.reroll = function(req, res, next) { - var user = res.locals.user; - if (user.balance < 1) return res.json(401, {err: "Not enough tokens."}); - user.balance -= 1; - _.each(['habits','dailys','todos'], function(type){ - _.each(user[type], function(task){ - task.value = 0; - }) - }) - user.stats.hp = 50; - user.save(function(err, saved) { - if (err) return res.json(500, {err: err}); - return res.json(200, saved); - }); -}; - -api.reset = function(req, res){ - var user = res.locals.user; - user.habits = []; - user.dailys = []; - user.todos = []; - user.rewards = []; - - user.stats.hp = 50; - user.stats.lvl = 1; - user.stats.gp = 0; - user.stats.exp = 0; - - user.items.armor = 0; - user.items.weapon = 0; - user.items.head = 0; - user.items.shield = 0; - - user.save(function(err, saved){ - if (err) return res.json(500,{err:err}); - res.json(saved); - }) -} +// api.reroll // Shared.ops +// api.reset // Shared.ops api['delete'] = function(req, res) { res.locals.user.remove(function(err){ diff --git a/src/models/user.js b/src/models/user.js index 445158d323..bc99fa8565 100644 --- a/src/models/user.js +++ b/src/models/user.js @@ -266,10 +266,7 @@ var UserSchema = new Schema({ }); UserSchema.methods.deleteTask = function(tid) { - //user[t.type+'s'].id(t.id).remove(); - var task = this.tasks[tid]; - var i = this[task.type+'s'].indexOf(task); - if (~i) this[task.type+'s'].splice(i,1); + this.ops.deleteTask(tid); // TODO remove this whole method, since it just proxies, and change all references to this method } UserSchema.methods.toJSON = function() { diff --git a/src/routes/api.js b/src/routes/api.js index 74b7430e1a..b80c092c3a 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -1,3 +1,12 @@ +/*** + * ---------- /api/v2 API ------------ + * Every url added to router is prefaced by /api/v2 + * Note: Many user-route ops exist in habitrpg-shard/script/index.coffee#user.ops, so that they can (1) be called both + * client and server. + * v1 user. Requires x-api-user (user id) and x-api-key (api key) headers, Test with: + * $ mocha test/user.mocha.coffee + */ + var express = require('express'); var router = new express.Router(); var user = require('../controllers/user'); @@ -9,16 +18,6 @@ var dataexport = require('../controllers/dataexport'); var nconf = require('nconf'); var middleware = require('../middleware'); -/* - ---------- /api/v1 API ------------ - Every url added to router is prefaced by /api/v1 - See ./routes/coffee for routes - - v1 user. Requires x-api-user (user id) and x-api-key (api key) headers, Test with: - $ cd node_modules/racer && npm install && cd ../.. - $ mocha test/user.mocha.coffee - */ - var cron = user.cron; router.get('/status', function(req, res) { @@ -27,48 +26,64 @@ router.get('/status', function(req, res) { }); }); -/* Data export */ +// --------------------------------- +// User +// --------------------------------- + +// Data Export router.get('/export/history',auth.auth,dataexport.history); //[todo] encode data output options in the data controller and use these to build routes -/* Scoring*/ -router.post('/user/task/:id/:direction', auth.auth, cron, user.score); +// Scoring router.post('/user/tasks/:id/:direction', auth.auth, cron, user.score); -/* Tasks*/ +// Tasks router.get('/user/tasks', auth.auth, cron, user.getTasks); -router.get('/user/task/:id', auth.auth, cron, user.getTask); -router.put('/user/task/:id', auth.auth, cron, user.updateTask); -router["delete"]('/user/task/:id', auth.auth, cron, user.deleteTask); -router.post('/user/task', auth.auth, cron, user.addTask); -router.put('/user/task/:id/sort', auth.auth, cron, user.sortTask); -router.post('/user/clear-completed', auth.auth, cron, user.clearCompleted); -router.post('/user/task/:id/unlink', auth.auth, challenges.unlink); // removing cron since they may want to remove task first -if (nconf.get('NODE_ENV') == 'development') { - router.post('/user/addTenGems', auth.auth, user.addTenGems); -} +router.get('/user/tasks/:id', auth.auth, cron, user.getTask); +router.put('/user/tasks/:id', auth.auth, cron, user.updateTask); //Shared.ops | body={} +router["delete"]('/user/tasks/:id', auth.auth, cron, user.deleteTask); //Shared.ops +router.post('/user/tasks', auth.auth, cron, user.addTask); //Shared.ops | body={} +router.post('/user/tasks/:id/sort', auth.auth, cron, user.sortTask); //Shared.ops | query={to,from} +router.post('/user/tasks/clear-completed', auth.auth, cron, user.clearCompleted); //Shared.ops +router.post('/user/tasks/:id/unlink', auth.auth, challenges.unlink); // removing cron since they may want to remove task first -/* Items*/ -router.post('/user/buy/:key', auth.auth, cron, user.buy); +// Inventory +router.post('/user/inventory/buy/:key', auth.auth, cron, user.buy); // Shared.ops +router.post('/user/inventory/sell/:type/:key', auth.auth, cron, user.sell); // Shared.ops +router.post('/user/inventory/purchase/:type/:key', auth.auth, user.purchase); //Shared.ops +router.post('/user/inventory/feed/:pet/:food', auth.auth, user.feed); //Shared.ops +router.post('/user/inventory/equip/:type/:key', auth.auth, user.equip); //Shared.ops +router.post('/user/inventory/hatch/:egg/:hatchingPotion', auth.auth, user.hatch); //Shared.ops -/* User*/ +// User router.get('/user', auth.auth, cron, user.getUser); -router.put('/user', auth.auth, cron, user.update); -router.post('/user/revive', auth.auth, cron, user.revive); -router.post('/user/batch-update', middleware.forceRefresh, auth.auth, cron, user.batchUpdate); -router.post('/user/reroll', auth.auth, cron, user.reroll); -router.post('/user/buy-gems', auth.auth, user.buyGems); -router.post('/user/buy-gems/paypal-ipn', user.buyGemsPaypalIPN); -router.post('/user/unlock', auth.auth, cron, user.unlock); -router.post('/user/reset', auth.auth, user.reset); -router.post('/user/cast/:spell', auth.auth, user.cast); +router.put('/user', auth.auth, cron, user.update); // body={} router['delete']('/user', auth.auth, user['delete']); -/* Tags */ -router.post('/user/tags', auth.auth, user.addTag); -router.put('/user/tags/:id', auth.auth, user.updateTag); -router['delete']('/user/tags/:id', auth.auth, user.deleteTag); +router.post('/user/revive', auth.auth, cron, user.revive); // Shared.ops +router.post('/user/reroll', auth.auth, cron, user.reroll); // Shared.ops +router.post('/user/reset', auth.auth, user.reset); // Shared.ops +router.post('/user/sleep', auth.auth, cron, user.sleep); //Shared.opss -/* Groups*/ +router.post('/user/class/change', auth.auth, cron, user.changeClass); //Shared.ops | query={class} +router.post('/user/class/allocate', auth.auth, cron, user.allocate); //Shared.ops | query={stat} +router.post('/user/class/cast/:spell', auth.auth, user.cast); + +router.post('/user/unlock', auth.auth, cron, user.unlock); // Shared.ops +router.post('/user/buy-gems', auth.auth, user.buyGems); +router.post('/user/buy-gems/paypal-ipn', user.buyGemsPaypalIPN); + +router.post('/user/batch-update', middleware.forceRefresh, auth.auth, cron, user.batchUpdate); + +if (nconf.get('NODE_ENV') == 'development') router.post('/user/addTenGems', auth.auth, user.addTenGems); + +// Tags +router.post('/user/tags', auth.auth, user.addTag); //Shared.ops | body={} +router.put('/user/tags/:id', auth.auth, user.updateTag); //Shared.ops | body={} +router['delete']('/user/tags/:id', auth.auth, user.deleteTag); //Shared.ops | body={} + +// --------------------------------- +// Groups +// --------------------------------- router.get('/groups', auth.auth, groups.list); router.post('/groups', auth.auth, groups.create); router.get('/groups/:gid', auth.auth, groups.get); @@ -86,18 +101,22 @@ router.post('/groups/:gid/chat', auth.auth, groups.attachGroup, groups.postChat) router["delete"]('/groups/:gid/chat/:messageId', auth.auth, groups.attachGroup, groups.deleteChatMessage); //PUT /groups/:gid/chat/:messageId -/* Members */ +// --------------------------------- +// Members +// --------------------------------- router.get('/members/:uid', groups.getMember); -/* Admin */ +// --------------------------------- +// Admin +// --------------------------------- router.get('/admin/members', auth.auth, admin.ensureAdmin, admin.listMembers); router.get('/admin/members/:uid', auth.auth, admin.ensureAdmin, admin.getMember); router.post('/admin/members/:uid', auth.auth, admin.ensureAdmin, admin.updateMember); -// Market -router.post('/market/buy', auth.auth, user.marketBuy); +// --------------------------------- +// Challenges +// --------------------------------- -/* Challenges */ // Note: while challenges belong to groups, and would therefore make sense as a nested resource // (eg /groups/:gid/challenges/:cid), they will also be referenced by users from the "challenges" tab // without knowing which group they belong to. So to prevent unecessary lookups, we have them as a top-level resource diff --git a/views/options/inventory/inventory.jade b/views/options/inventory/inventory.jade index 9aa5a7ba03..835eede4e0 100644 --- a/views/options/inventory/inventory.jade +++ b/views/options/inventory/inventory.jade @@ -36,7 +36,7 @@ script(type='text/ng-template', id='partials/options.inventory.inventory.html') li.customize-menu menu.pets-menu(label='{{label}}', ng-repeat='(klass,label) in {base:"Base", warrior:"Warrior", wizard:"Wizard", rogue:"Rogue", special:"Special"}', ng-show='gear[klass]') div(ng-repeat='item in gear[klass]') - button.customize-option(popover='{{item.notes}}', popover-title='{{item.text}}', popover-trigger='mouseenter', popover-placement='right', ng-click='user.ops.equip({query:{key:item.key}})', class='shop_{{item.key}}', ng-class='{selectableInventory: user.items.gear.equipped[item.type] == item.key}') + button.customize-option(popover='{{item.notes}}', popover-title='{{item.text}}', popover-trigger='mouseenter', popover-placement='right', ng-click='user.ops.equip({params:{key:item.key}})', class='shop_{{item.key}}', ng-class='{selectableInventory: user.items.gear.equipped[item.type] == item.key}') label.checkbox.inline input(type="checkbox", ng-model="user.preferences.costume", ng-click='set({"preferences.costume":!user.preferences.costume})') | Use Costume  @@ -44,7 +44,7 @@ script(type='text/ng-template', id='partials/options.inventory.inventory.html') li.customize-menu(ng-if='user.preferences.costume') menu.pets-menu(label='{{label}}', ng-repeat='(klass,label) in {base:"Base", warrior:"Warrior", wizard:"Wizard", rogue:"Rogue", special:"Special"}', ng-show='gear[klass]') div(ng-repeat='item in gear[klass]') - button.customize-option(popover='{{item.notes}}', popover-title='{{item.text}}', popover-trigger='mouseenter', popover-placement='right', ng-click='user.ops.equip({query:{type:"costume", key:item.key}})', class='shop_{{item.key}}', ng-class='{selectableInventory: user.items.gear.costume[item.type] == item.key}') + button.customize-option(popover='{{item.notes}}', popover-title='{{item.text}}', popover-trigger='mouseenter', popover-placement='right', ng-click='user.ops.equip({params:{type:"costume", key:item.key}})', class='shop_{{item.key}}', ng-class='{selectableInventory: user.items.gear.costume[item.type] == item.key}') .span6 h2 Market @@ -73,7 +73,7 @@ script(type='text/ng-template', id='partials/options.inventory.inventory.html') li.customize-menu menu.pets-menu(label='Eggs') div(ng-repeat='egg in Content.eggs') - button.customize-option(popover='{{egg.notes}}', popover-title='{{egg.text}} Egg', popover-trigger='mouseenter', popover-placement='left', ng-click='buy("egg", egg)', class='Pet_Egg_{{egg.name}}') + button.customize-option(popover='{{egg.notes}}', popover-title='{{egg.text}} Egg', popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("eggs", egg)', class='Pet_Egg_{{egg.name}}') p | {{egg.value}} span.Pet_Currency_Gem1x.inline-gems @@ -81,7 +81,7 @@ script(type='text/ng-template', id='partials/options.inventory.inventory.html') li.customize-menu menu.pets-menu(label='Hatching Potions') div(ng-repeat='pot in Content.hatchingPotions') - button.customize-option(popover='{{pot.notes}}', popover-title='{{pot.text}} Potion', popover-trigger='mouseenter', popover-placement='left', ng-click='buy("hatchingPotion", pot)', class='Pet_HatchingPotion_{{pot.name}}') + button.customize-option(popover='{{pot.notes}}', popover-title='{{pot.text}} Potion', popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("hatchingPotions", pot)', class='Pet_HatchingPotion_{{pot.name}}') p | {{pot.value}} span.Pet_Currency_Gem1x.inline-gems @@ -89,13 +89,13 @@ script(type='text/ng-template', id='partials/options.inventory.inventory.html') li.customize-menu menu.pets-menu(label='Food') div(ng-repeat='food in Content.food', ng-show='food.name !== "Saddle"') - button.customize-option(popover='{{food.notes}}', popover-title='{{food.text}}', popover-trigger='mouseenter', popover-placement='left', ng-click='buy("food", food)', class='Pet_Food_{{food.name}}') + button.customize-option(popover='{{food.notes}}', popover-title='{{food.text}}', popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("food", food)', class='Pet_Food_{{food.name}}') p | {{food.value}} span.Pet_Currency_Gem1x.inline-gems menu.pets-menu(label='Saddle') div - button.customize-option(popover='{{Content.food.Saddle.notes}}', popover-title='{{Content.food.Saddle.text}}', popover-trigger='mouseenter', popover-placement='left', ng-click='buy("food", Content.food.Saddle)', class='Pet_Food_{{Content.food.Saddle.name}}') + button.customize-option(popover='{{Content.food.Saddle.notes}}', popover-title='{{Content.food.Saddle.text}}', popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("food", Content.food.Saddle)', class='Pet_Food_{{Content.food.Saddle.name}}') p | {{Content.food.Saddle.value}} span.Pet_Currency_Gem1x.inline-gems