APIv2: some finalization of APIv2. Add new routes, re-rigg

query/param/body requirements. This overhauls the API pretty
substantially, so we'll indeed be moving this to apiv2, using v1 for the
old API, and limiting it's use substantially
This commit is contained in:
Tyler Renelle
2013-12-15 14:49:22 -07:00
parent d0b07ba4db
commit 91e1287d06
7 changed files with 102 additions and 174 deletions

View File

@@ -64,7 +64,7 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', 'User', 'API_URL',
var selected = $scope.selectedEgg ? 'selectedEgg' : $scope.selectedPotion ? 'selectedPotion' : $scope.selectedFood ? 'selectedFood' : undefined; var selected = $scope.selectedEgg ? 'selectedEgg' : $scope.selectedPotion ? 'selectedPotion' : $scope.selectedFood ? 'selectedFood' : undefined;
if (selected) { if (selected) {
var type = $scope.selectedEgg ? 'eggs' : $scope.selectedPotion ? 'hatchingPotions' : $scope.selectedFood ? 'food' : undefined; 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) { if (user.items[type][$scope[selected].name] < 1) {
$scope[selected] = null; $scope[selected] = null;
} }
@@ -77,22 +77,18 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', 'User', 'API_URL',
$scope.hatch = function(egg, potion){ $scope.hatch = function(egg, potion){
if (!confirm('Hatch a ' + potion.name + ' ' + egg.name + '?')) return; 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.selectedEgg = null;
$scope.selectedPotion = null; $scope.selectedPotion = null;
} }
$scope.buy = function(type, item){ $scope.purchase = function(type, item){
var gems = User.user.balance * 4; var gems = User.user.balance * 4;
if(gems < item.value) return $rootScope.modals.buyGems = true; if(gems < item.value) return $rootScope.modals.buyGems = true;
var string = (type == 'hatchingPotion') ? 'hatching potion' : type; // give hatchingPotion a space var string = (type == 'hatchingPotion') ? 'hatching potion' : type; // give hatchingPotion a space
var message = "Buy this " + string + " with " + item.value + " of your " + gems + " Gems?" var message = "Buy this " + string + " with " + item.value + " of your " + gems + " Gems?"
if(confirm(message)){ if(confirm(message))
$http.post(API_URL + '/api/v1/market/buy?type=' + type, item) User.user.ops.purchase({params:{type:type,key:item.name}});
.success(function(data){
User.user.items = data.items;
});
}
} }
$scope.choosePet = function(egg, potion){ $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 + '?')) { } else if (!confirm('Feed ' + pet + ' a ' + food.name + '?')) {
return; return;
} }
User.user.ops.feed({query:{pet: pet, food: food.name}}); User.user.ops.feed({params:{pet: pet, food: food.name}});
$scope.selectedFood = null; $scope.selectedFood = null;
// Selecting Pet // Selecting Pet
} else { } else {
User.user.ops.equip({query:{type: 'pet', key: pet}}); User.user.ops.equip({params:{type: 'pet', key: pet}});
} }
} }
$scope.chooseMount = function(egg, potion) { $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}});
} }
} }
]); ]);

View File

@@ -45,15 +45,9 @@ habitrpg.controller('SettingsCtrl',
} }
$scope.reroll = function(){ $scope.reroll = function(){
User.user.ops.reroll({});
$http.post(API_URL + '/api/v1/user/reroll') $rootScope.modals.reroll = false;
.success(function(){ $rootScope.$state.go('tasks');
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)
})
} }
$scope.changePassword = function(changePass){ $scope.changePassword = function(changePass){
@@ -90,17 +84,13 @@ habitrpg.controller('SettingsCtrl',
}); });
$rootScope.modals.restore = false; $rootScope.modals.restore = false;
} }
$scope.reset = function(){ $scope.reset = function(){
$http.post(API_URL + '/api/v1/user/reset') User.user.ops.reset({});
.success(function(){
User.user._v--;
User.log({});
$rootScope.modals.reset = false; $rootScope.modals.reset = false;
}) $rootScope.$state.go('tasks');
.error(function(data){
alert(data);
});
} }
$scope['delete'] = function(){ $scope['delete'] = function(){
$http['delete'](API_URL + '/api/v1/user') $http['delete'](API_URL + '/api/v1/user')
.success(function(){ .success(function(){

View File

@@ -64,7 +64,7 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
$scope.unlink = function(task, keep) { $scope.unlink = function(task, keep) {
// TODO move this to userServices, turn userSerivces.user into ng-resource // 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(){ .success(function(){
User.log({}); User.log({});
}); });
@@ -81,14 +81,8 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
}) })
$scope.buy = function(item) { $scope.buy = function(item) {
var hasEnough = User.user.ops.buy({query:{key:item.key}}); User.user.ops.buy({params:{key:item.key}});
if (hasEnough) {
Notification.text("Item purchased.");
$scope.itemStore = User.user.fns.updateStore(); $scope.itemStore = User.user.fns.updateStore();
} else {
// Notification.text("Not enough Gold!");
// handled by userServices interceptor
}
}; };

View File

@@ -15,23 +15,7 @@ var Challenge = require('./../models/challenge').model;
var acceptablePUTPaths; var acceptablePUTPaths;
var api = module.exports; var api = module.exports;
// FIXME put this in a proper location // api.purchase // Shared.ops
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);
})
}
/* /*
------------------------------------------------------------------------ ------------------------------------------------------------------------
@@ -45,15 +29,8 @@ api.marketBuy = function(req, res, next){
--------------- ---------------
*/ */
/* findTask = function(req, res) {
Validate task return task = res.locals.user.tasks[req.params.id];
*/
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();
}; };
/* /*
@@ -66,6 +43,9 @@ api.verifyTaskExists = function(req, res, next) {
Export it also so we can call it from deprecated.coffee Export it also so we can call it from deprecated.coffee
*/ */
api.score = function(req, res, next) { 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, var id = req.params.id,
direction = req.params.direction, direction = req.params.direction,
user = res.locals.user, user = res.locals.user,
@@ -129,29 +109,17 @@ api.getTasks = function(req, res, next) {
* Get Task * Get Task
*/ */
api.getTask = function(req, res, next) { api.getTask = function(req, res, next) {
var task = res.locals.user.tasks[req.params.id]; var task = findTask(req,res);
if (_.isEmpty(task)) return res.json(400, {err: "No task found."}); if (!task) return res.json(404, {err: "No task found."});
return res.json(200, task); 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 Update Task
*/ */
//api.deleteTask // see Shared.ops
// api.updateTask // handled in Shared.ops // api.updateTask // handled in Shared.ops
// api.addTask // handled in Shared.ops // api.addTask // handled in Shared.ops
// api.sortTask // handled in Shared.ops #TODO updated api, mention in docs // 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); user.save(next);
}; };
api.reroll = function(req, res, next) { // api.reroll // Shared.ops
var user = res.locals.user; // api.reset // Shared.ops
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['delete'] = function(req, res) { api['delete'] = function(req, res) {
res.locals.user.remove(function(err){ res.locals.user.remove(function(err){

View File

@@ -266,10 +266,7 @@ var UserSchema = new Schema({
}); });
UserSchema.methods.deleteTask = function(tid) { UserSchema.methods.deleteTask = function(tid) {
//user[t.type+'s'].id(t.id).remove(); this.ops.deleteTask(tid); // TODO remove this whole method, since it just proxies, and change all references to this method
var task = this.tasks[tid];
var i = this[task.type+'s'].indexOf(task);
if (~i) this[task.type+'s'].splice(i,1);
} }
UserSchema.methods.toJSON = function() { UserSchema.methods.toJSON = function() {

View File

@@ -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 express = require('express');
var router = new express.Router(); var router = new express.Router();
var user = require('../controllers/user'); var user = require('../controllers/user');
@@ -9,16 +18,6 @@ var dataexport = require('../controllers/dataexport');
var nconf = require('nconf'); var nconf = require('nconf');
var middleware = require('../middleware'); 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; var cron = user.cron;
router.get('/status', function(req, res) { 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 router.get('/export/history',auth.auth,dataexport.history); //[todo] encode data output options in the data controller and use these to build routes
/* Scoring*/ // Scoring
router.post('/user/task/:id/:direction', auth.auth, cron, user.score);
router.post('/user/tasks/:id/:direction', auth.auth, cron, user.score); 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/tasks', auth.auth, cron, user.getTasks);
router.get('/user/task/:id', auth.auth, cron, user.getTask); router.get('/user/tasks/:id', auth.auth, cron, user.getTask);
router.put('/user/task/:id', auth.auth, cron, user.updateTask); router.put('/user/tasks/:id', auth.auth, cron, user.updateTask); //Shared.ops | body={}
router["delete"]('/user/task/:id', auth.auth, cron, user.deleteTask); router["delete"]('/user/tasks/:id', auth.auth, cron, user.deleteTask); //Shared.ops
router.post('/user/task', auth.auth, cron, user.addTask); router.post('/user/tasks', auth.auth, cron, user.addTask); //Shared.ops | body={}
router.put('/user/task/:id/sort', auth.auth, cron, user.sortTask); router.post('/user/tasks/:id/sort', auth.auth, cron, user.sortTask); //Shared.ops | query={to,from}
router.post('/user/clear-completed', auth.auth, cron, user.clearCompleted); router.post('/user/tasks/clear-completed', auth.auth, cron, user.clearCompleted); //Shared.ops
router.post('/user/task/:id/unlink', auth.auth, challenges.unlink); // removing cron since they may want to remove task first router.post('/user/tasks/: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);
}
/* Items*/ // Inventory
router.post('/user/buy/:key', auth.auth, cron, user.buy); 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.get('/user', auth.auth, cron, user.getUser);
router.put('/user', auth.auth, cron, user.update); router.put('/user', auth.auth, cron, user.update); // body={}
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['delete']('/user', auth.auth, user['delete']); router['delete']('/user', auth.auth, user['delete']);
/* Tags */ router.post('/user/revive', auth.auth, cron, user.revive); // Shared.ops
router.post('/user/tags', auth.auth, user.addTag); router.post('/user/reroll', auth.auth, cron, user.reroll); // Shared.ops
router.put('/user/tags/:id', auth.auth, user.updateTag); router.post('/user/reset', auth.auth, user.reset); // Shared.ops
router['delete']('/user/tags/:id', auth.auth, user.deleteTag); 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.get('/groups', auth.auth, groups.list);
router.post('/groups', auth.auth, groups.create); router.post('/groups', auth.auth, groups.create);
router.get('/groups/:gid', auth.auth, groups.get); 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); router["delete"]('/groups/:gid/chat/:messageId', auth.auth, groups.attachGroup, groups.deleteChatMessage);
//PUT /groups/:gid/chat/:messageId //PUT /groups/:gid/chat/:messageId
/* Members */ // ---------------------------------
// Members
// ---------------------------------
router.get('/members/:uid', groups.getMember); router.get('/members/:uid', groups.getMember);
/* Admin */ // ---------------------------------
// Admin
// ---------------------------------
router.get('/admin/members', auth.auth, admin.ensureAdmin, admin.listMembers); router.get('/admin/members', auth.auth, admin.ensureAdmin, admin.listMembers);
router.get('/admin/members/:uid', auth.auth, admin.ensureAdmin, admin.getMember); router.get('/admin/members/:uid', auth.auth, admin.ensureAdmin, admin.getMember);
router.post('/admin/members/:uid', auth.auth, admin.ensureAdmin, admin.updateMember); 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 // 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 // (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 // without knowing which group they belong to. So to prevent unecessary lookups, we have them as a top-level resource

View File

@@ -36,7 +36,7 @@ script(type='text/ng-template', id='partials/options.inventory.inventory.html')
li.customize-menu 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]') 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]') 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 label.checkbox.inline
input(type="checkbox", ng-model="user.preferences.costume", ng-click='set({"preferences.costume":!user.preferences.costume})') input(type="checkbox", ng-model="user.preferences.costume", ng-click='set({"preferences.costume":!user.preferences.costume})')
| Use Costume&nbsp; | Use Costume&nbsp;
@@ -44,7 +44,7 @@ script(type='text/ng-template', id='partials/options.inventory.inventory.html')
li.customize-menu(ng-if='user.preferences.costume') 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]') 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]') 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 .span6
h2 Market h2 Market
@@ -73,7 +73,7 @@ script(type='text/ng-template', id='partials/options.inventory.inventory.html')
li.customize-menu li.customize-menu
menu.pets-menu(label='Eggs') menu.pets-menu(label='Eggs')
div(ng-repeat='egg in Content.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 p
| {{egg.value}} | {{egg.value}}
span.Pet_Currency_Gem1x.inline-gems span.Pet_Currency_Gem1x.inline-gems
@@ -81,7 +81,7 @@ script(type='text/ng-template', id='partials/options.inventory.inventory.html')
li.customize-menu li.customize-menu
menu.pets-menu(label='Hatching Potions') menu.pets-menu(label='Hatching Potions')
div(ng-repeat='pot in Content.hatchingPotions') 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 p
| {{pot.value}} | {{pot.value}}
span.Pet_Currency_Gem1x.inline-gems span.Pet_Currency_Gem1x.inline-gems
@@ -89,13 +89,13 @@ script(type='text/ng-template', id='partials/options.inventory.inventory.html')
li.customize-menu li.customize-menu
menu.pets-menu(label='Food') menu.pets-menu(label='Food')
div(ng-repeat='food in Content.food', ng-show='food.name !== "Saddle"') 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 p
| {{food.value}} | {{food.value}}
span.Pet_Currency_Gem1x.inline-gems span.Pet_Currency_Gem1x.inline-gems
menu.pets-menu(label='Saddle') menu.pets-menu(label='Saddle')
div 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 p
| {{Content.food.Saddle.value}} | {{Content.food.Saddle.value}}
span.Pet_Currency_Gem1x.inline-gems span.Pet_Currency_Gem1x.inline-gems