mirror of
https://github.com/sudoxnym/habitica-self-host.git
synced 2026-07-13 17:52:22 +00:00
Merge branch 'develop' of github.com:HabitRPG/habitrpg into common-convert
Conflicts: .gitignore .travis.yml
This commit is contained in:
commit
590eded44a
388 changed files with 32815 additions and 5 deletions
8
.bowerrc
Normal file
8
.bowerrc
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"directory": "public/bower_components",
|
||||
"storage" : {
|
||||
"packages" : ".bower-cache",
|
||||
"registry" : ".bower-registry"
|
||||
},
|
||||
"tmp" : ".bower-tmp"
|
||||
}
|
||||
2
.buildpacks
Normal file
2
.buildpacks
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
https://github.com/heroku/heroku-buildpack-nodejs.git
|
||||
https://github.com/stomita/heroku-buildpack-phantomjs.git
|
||||
26
.gitignore
vendored
26
.gitignore
vendored
|
|
@ -1,6 +1,24 @@
|
|||
coverage.html
|
||||
.DS_Store
|
||||
public/gen
|
||||
node_modules
|
||||
*.swp
|
||||
.idea*
|
||||
config.json
|
||||
npm-debug.log
|
||||
.idea
|
||||
.swo
|
||||
.swp
|
||||
lib
|
||||
public/bower_components
|
||||
build
|
||||
newrelic_agent.log
|
||||
.bower-tmp
|
||||
.bower-registry
|
||||
.bower-cache
|
||||
.vagrant
|
||||
|
||||
*.log
|
||||
src/*/*.map
|
||||
src/*/*/*.map
|
||||
test/*.js
|
||||
test/*.map
|
||||
public/docs
|
||||
*.sublime-workspace
|
||||
coverage.html
|
||||
|
|
|
|||
12
.jshintrc
Normal file
12
.jshintrc
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"globals": {
|
||||
"confirm": false
|
||||
},
|
||||
|
||||
"browser": true,
|
||||
"node": true,
|
||||
|
||||
"asi": true,
|
||||
"boss": true,
|
||||
"newcap": false,
|
||||
}
|
||||
15
.nodemonignore
Normal file
15
.nodemonignore
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
node_modules/**
|
||||
.bower-cache/**
|
||||
.bower-tmp/**
|
||||
.bower-registry/**
|
||||
public/**
|
||||
views/**
|
||||
build/**
|
||||
.git/**
|
||||
Gruntfile.js
|
||||
CHANGELOG.md
|
||||
.idea*
|
||||
*.log
|
||||
newrelic_agent.log
|
||||
*.swp
|
||||
*.swx
|
||||
|
|
@ -1,3 +1,9 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
- '0.10'
|
||||
- mongodb
|
||||
before_script:
|
||||
- 'npm install -g grunt-cli mocha'
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
- cp config.json.example config.json
|
||||
|
|
|
|||
282
CHANGELOG.md
Normal file
282
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
<a name="">My app - Changelog</a>
|
||||
# (2014-02-15)
|
||||
|
||||
|
||||
## Documentation
|
||||
|
||||
- **rebirth:** Bullet point about repurchase of limited ed gear after Rebirth
|
||||
([d3f4a561](watch/commits/d3f4a561fdf137e5d8f406bae03be4fef1caff22))
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **#2003:** healer gear not showing
|
||||
([949cd97b](watch/commits/949cd97b91b42e9450eba559bbfea17e239ab100))
|
||||
- **#2375:** merge in @SabreCat's stats.jade changes "More elegant show/hide setup for attribute bonuses"
|
||||
([518f200a](watch/commits/518f200a8fc7373b44ed7d7b5f016d921b0746bd))
|
||||
- **allocateNow:** Send empty object to user.ops per @colegleason suggestion
|
||||
([f6e12fa2](watch/commits/f6e12fa25e4366622db3e6f1b6ab03e848b49e10))
|
||||
- **batch-update:** send errors to client if batch-update found an error, crash pid.
|
||||
([f9679629](watch/commits/f967962996be69a5335454610af76d10e1db08b8))
|
||||
- **beastmaster:** fixes #2557, adds opacity to previously-owned pets after they're mounted. You can earn them back again
|
||||
([5caaff1c](watch/commits/5caaff1cea1a68fe572e7ddf4aac50248b13df5d))
|
||||
- **bosses:** don't reset progress.up when starting a new quest. We want to be able to carry over damage from the same day a boss battle begins, even if the dailies were completed before battle-start. Fixes #2168
|
||||
([4efd0f5e](watch/commits/4efd0f5ed8708f2491dd483f93e3d7a268a6337d))
|
||||
- **bower:** updated jquery directory
|
||||
([191b789d](watch/commits/191b789d760a7bdc7d1b53727f6127b677c78c94))
|
||||
- **bs3:**
|
||||
- fix to MemberModalCtrl parameter
|
||||
([ebd1df93](watch/commits/ebd1df932e28263e5cc01e8a35f545ab26f1e8bd))
|
||||
- port pet feeding bar
|
||||
([5db96ebc](watch/commits/5db96ebca2fbd5b64f49af03a5137ea80f6b1673))
|
||||
- **buffs:** Move help bubble to left of special buffs
|
||||
([4f911a68](watch/commits/4f911a68d805742e6744383948eea6f224f2b0ea))
|
||||
- **challenges:**
|
||||
- better handling of deleted challenges. If !chal, break the task.challenge. Move the function into userController#score so we have access to next, etc. fixes #1883
|
||||
([33b326b5](watch/commits/33b326b59685ea6e50f9950094d009460ce80094))
|
||||
- challenge csv export now has proper filename
|
||||
([36f21196](watch/commits/36f21196f466260b7cd52b283c50b9e16943f668),
|
||||
[#2689](watch/issues/2689))
|
||||
- **classes:**
|
||||
- misc fixes
|
||||
([d2121a85](watch/commits/d2121a858716cb5a532a53ee9c5a1adaa74a7f69))
|
||||
- misc class fixes (not @snicker, ng-if on item store since we dynamically swap it sometimes)
|
||||
([478be611](watch/commits/478be6111337cd200374f7f31b959725c6a0b945))
|
||||
- **css:**
|
||||
- temp fix for bailey height
|
||||
([c8faffcc](watch/commits/c8faffcc7289090990c3a17ab8c07a00069f5ce4))
|
||||
- menu and gems wallet margin
|
||||
([975b5165](watch/commits/975b5165730477310aa64bac27ddc07a34ea6c1d))
|
||||
- lighter columns title
|
||||
([a22e2814](watch/commits/a22e28143f74302c8340c3d33b01af9714875523))
|
||||
- better food tray
|
||||
([1c41c4dd](watch/commits/1c41c4ddb9a5b04297a371bc4d6aba013ce33f17))
|
||||
- **errors:**
|
||||
- `return next(err)` when experiencing errors, instead of res.json(500,{err:err}). Let the top-level error handler handle this (needed for upcoming versionerror discarding)
|
||||
([bf5e9016](watch/commits/bf5e9016a4cb7889b3a9e39b90eb35cb8f7f9ec8))
|
||||
- handle if err.message == undefined, send err
|
||||
([b42dacf2](watch/commits/b42dacf2035d62453b585cfcf453829a423b59de))
|
||||
- **event-tracking:**
|
||||
- typo
|
||||
([ff9d4b88](watch/commits/ff9d4b886ef7a98da0514975441a8bb845496c31))
|
||||
- stripe sub, not pp
|
||||
([0c99976b](watch/commits/0c99976bf5a3c7f04f031d62a8b07c862c85a0a9))
|
||||
- **find_uniq_user:** fix
|
||||
([ecbe780e](watch/commits/ecbe780e70549b1470504efe052f238c89a9db14))
|
||||
- **footer:** ensure window.env is accessible from static pages, so we can get deferred scripts on frontpage (esp google analytics)
|
||||
([67ee011a](watch/commits/67ee011aa35969db93e2d7dc1cd1e1f587f146de))
|
||||
- **groups:**
|
||||
- pass missing next into Like function
|
||||
([afee0968](watch/commits/afee0968f8f6923847e186d3e11b9745ced9606e))
|
||||
- send error if +1 errored
|
||||
([5b6c4427](watch/commits/5b6c4427b504b6143f24bfee314f562b9803c5a4))
|
||||
- **hall:** let's try $gt instead of $ne:null, the query is still slow
|
||||
([a72b0131](watch/commits/a72b013131cfc7fa5d3affdbfe59b5b3cb15ae89))
|
||||
- **i18n:** do not save user language for now
|
||||
([094a4be0](watch/commits/094a4be0015f0f0deaaf94a0734193eb40a8beae))
|
||||
- **misc:**
|
||||
- some styles & translations
|
||||
([8f19f225](watch/commits/8f19f225f104960b3cf27e229a5571e014be697c))
|
||||
- isStaticPage and debug buttons
|
||||
([19139f56](watch/commits/19139f562b8e68ed43f4cab748920f1e0634e86e))
|
||||
- **missing-gems:** remove ad-removal from script, since ads are part of subscription
|
||||
([e1240dde](watch/commits/e1240dde1d3dcaca4235fad384fea5c07a3706bf))
|
||||
- **mongoose:** typo
|
||||
([2786b362](watch/commits/2786b362067efdd245c3efa3a4891021fcfaab2d))
|
||||
- **mounts:**
|
||||
- fix pets & mounts css to position the user based on pet/mount equip
|
||||
([37340d23](watch/commits/37340d23180da02d3742dc9be40a5fb780ecb13b))
|
||||
- Move avatar upward when mounted regardless of pet
|
||||
([bc1adeb1](watch/commits/bc1adeb1277103a5ca1f756e175ed68bbe837a2f))
|
||||
- **nodemon:**
|
||||
- Add another ignore for weirdsauce Windoze dev environments
|
||||
([3fda08c3](watch/commits/3fda08c366793c8fbcbf701a9594ae3b2fd8bbea))
|
||||
- ignore CHANGELOG.md on watch
|
||||
([d6c55952](watch/commits/d6c55952da8b49f36e9d8e4570d80931d081343d))
|
||||
- **party:** Round boss health up instead of to nearest integer
|
||||
([626da568](watch/commits/626da5681f5ea95700f8ddf40587c7184926971c),
|
||||
[#2504](watch/issues/2504))
|
||||
- **paypal:** fixes #2492, remove environment check for now, only have production-mode option. revisit
|
||||
([1dc68112](watch/commits/1dc68112d131e4ebdec32ddff938eb6311d6565f))
|
||||
- **performance:** cache spritesmith image, fix #2633
|
||||
([f03d7d7d](watch/commits/f03d7d7dde4f8cb39babd2b982d77e7f88f349b7))
|
||||
- **pets:** add questPets to UserSchema.items.mounts too fixes #2814
|
||||
([42766125](watch/commits/42766125d5c8870f25c3a0a001473f700b8f6cc1))
|
||||
- **profile:** fix bug where empty profile displayed on username click
|
||||
([0579c432](watch/commits/0579c432489c4a038e8c9f95ea3b285f5abc146f),
|
||||
[#2465](watch/issues/2465))
|
||||
- **quests:**
|
||||
- quests with a level cap cannot be bought before that level.
|
||||
([dab9ddbd](watch/commits/dab9ddbda27f5e10e4545fea703deebfe2dd9975),
|
||||
[#2707](watch/issues/2707))
|
||||
- bug fix to multi-drop
|
||||
([f478d10c](watch/commits/f478d10c20f816cd104b3f0da814c189957f45f5))
|
||||
- list multiple rewards in dialog
|
||||
([e48c7277](watch/commits/e48c7277f8256cf827790aece51e897fe0439374))
|
||||
- **readme:** remove text about translations wip
|
||||
([f2bb1fd2](watch/commits/f2bb1fd26e44a9eb0ba325776bf335e021beeece))
|
||||
- **settings:**
|
||||
- remove unnecessary code
|
||||
([5f0cf657](watch/commits/5f0cf6575c0dc4cfc041956e3dc27898d8b4242d))
|
||||
- reintroduce space between captions and help bubbles stripped during localization
|
||||
([5ddf09fe](watch/commits/5ddf09fe13c7f8d844c8c47be0fb8f8b2fd1df33))
|
||||
- **spells:**
|
||||
- temp workaround for spell & task being undefined. #2649 #2640
|
||||
([241d0414](watch/commits/241d04140f5db77929d9f597d232f55843bb0f5d))
|
||||
- more $rootScope spell-casting bug fixes
|
||||
([47bd6dcb](watch/commits/47bd6dcb79778d90d6f3ddeb003c3d8e45433333))
|
||||
- add some spells tests, don't send up body to spell paths
|
||||
([e0646bb9](watch/commits/e0646bb98d44b6874b5259107c9be5fa34c58933))
|
||||
- some $rootScope.applying action fixes so cast-ending is immediate instead of waiting on response. Also, slim down party population to the essentials to avoid RequestEntityTooLarge
|
||||
([c6f7ab8a](watch/commits/c6f7ab8a5c6f4e382208a928b90ba5f4eba9cd37))
|
||||
- <ESC> to cancel spell-casting
|
||||
([a1df41ad](watch/commits/a1df41ad8165cd9eb6d2d5d59c7fe404edde716c))
|
||||
- **stable:** show hatchable combo when petOwned>0 (fyi @deilann)
|
||||
([51bff238](watch/commits/51bff23885ca0080e7e71ff752daa0950ae923ae))
|
||||
- **stats:** Better layout for attribute point allocation
|
||||
([d782fc6b](watch/commits/d782fc6b6a3cd7e90d327c93a5764626b2990c74))
|
||||
- **swagger:** fix jade script warning in swagger
|
||||
([2e2fcfcf](watch/commits/2e2fcfcf464fbae21bff9e1be1ca915f071b976b))
|
||||
- **tests:**
|
||||
- include select2 in test manifest
|
||||
([38b4cea7](watch/commits/38b4cea73299f51c4db7f6b2eb12533d219745f8))
|
||||
- don't use cluster in tests, else we get "connection refused"
|
||||
([7a479098](watch/commits/7a479098dc6535654e322c737d80813790967941))
|
||||
- **todos:**
|
||||
- add migration for dateCreated & dateCompleted #2478
|
||||
([4cc39f16](watch/commits/4cc39f16a13f5fb9f0e3ddde7d274c0f224f4a0e))
|
||||
- add dateCompleted to todos so they're archived 3 days after completion, not 3 days after creation. Fixes #2478
|
||||
([b1afc177](watch/commits/b1afc177aa4bfc4cbd9b847e40431db91666d9c3))
|
||||
- **toolbar:**
|
||||
- Tweak Settings drop-down
|
||||
([e241429c](watch/commits/e241429cc3d2eca18d2f5a9726f6caa6270a1b02))
|
||||
- Tweak icon popovers
|
||||
([4454204f](watch/commits/4454204f47f80e64119f7896bf246259173d115b))
|
||||
- tweaks
|
||||
([5501d57e](watch/commits/5501d57e107c0bc7085847b0c808f027360fa405))
|
||||
- **translation:** Fix #2585.
|
||||
([06200acc](watch/commits/06200accada462c3234ab407cfb0f6b684e5effe))
|
||||
- **translations:**
|
||||
- fix #2564 and similar ones
|
||||
([42740902](watch/commits/42740902055a3807532028a5dfb39eff905c104f))
|
||||
- add env.t to rootScope
|
||||
([13131087](watch/commits/13131087ff9563d2d174b2c978102f0dc2b87387))
|
||||
- remove translations for privacy & terms
|
||||
([a9095f34](watch/commits/a9095f346479336be13b2bf341666b908fa30b3d))
|
||||
- merge @luveluen pull request, fix some syntax
|
||||
([a6c67f17](watch/commits/a6c67f17815558f19895b8f67d29c40c14689f09))
|
||||
- @lefnire now everything is ok
|
||||
([52decb7e](watch/commits/52decb7edeefb4755ea832b0cf63eaeea5e93259))
|
||||
- correct some variables
|
||||
([fba73953](watch/commits/fba739535bc1b630d73eb469448e9c3706043efd))
|
||||
- revert some views
|
||||
([d000c706](watch/commits/d000c70679ae0e13d9bec749295e42cc8e299c95))
|
||||
- **user:**
|
||||
- make sure next is passed to all routes, and is available in err-back of batch updates
|
||||
([0c21f54c](watch/commits/0c21f54c67b52b07c417fd8216c6b04bce59d0ab))
|
||||
- if need to upgrade site, send 501, not 400
|
||||
([ab86ba11](watch/commits/ab86ba11bdb3379a8d8fa1814879640d61c57227))
|
||||
- PUT user retricted path errors are 401, not 500
|
||||
([0aec4caa](watch/commits/0aec4caa785c3b12e15f1c2e19c5b67b20d1a6e1))
|
||||
- **winston:** typo
|
||||
([83b3739f](watch/commits/83b3739f4671a08466e057242f936140d5c739ef))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **administrators:** start adding features page for admin accounts
|
||||
([f7f4a0c1](watch/commits/f7f4a0c166558ba7e5461732f7bb6d7bcac25f88))
|
||||
- **attributes:** Add backfill button in flat and classbased allocation modes
|
||||
([76a7ab5b](watch/commits/76a7ab5bcce2d486dab3f447f0659ba870d1ff7e))
|
||||
- **bailey:** notif about STWC updates + scroll-purchase deadlines (@colegleason)
|
||||
([90176444](watch/commits/90176444e9c7a318040829e8b71d1493b5d58e9e))
|
||||
- **bug-crushing:** add the critical hammer of bug-crushing
|
||||
([00af5f7d](watch/commits/00af5f7d0258b0f7dddef8ede40bd825b057748a))
|
||||
- **challenges:**
|
||||
- add angular-ui-select2 for simpler find/select challenge winner.
|
||||
([9fa45217](watch/commits/9fa452173989889c48ed696a45cf4a1dc16294a4))
|
||||
- add button for csv export
|
||||
([ae0d758d](watch/commits/ae0d758d8fc751219a693fee7f3e3ebcfbd67590))
|
||||
- add csv export for challenge progress. WIP, will refine this over time - but we need it something like this for the STWC come 1/31.
|
||||
([16a602f9](watch/commits/16a602f94c3b7c99d49e42b47b4835b65a243690))
|
||||
- markdown in challenge-descriptions
|
||||
([41233c7b](watch/commits/41233c7b167905eeccfdff5589789e002ec23f97))
|
||||
- **cheating:** prevent +habit spamming with a 10s timer
|
||||
([ad4ca665](watch/commits/ad4ca6655a3bdd870bb08173530372f81fdc9102))
|
||||
- **event-tracking:**
|
||||
- better page-view tracking via ui-router
|
||||
([b093717b](watch/commits/b093717b8d54b61e5d4b44b0d56a1f43308f078c))
|
||||
- track registration count
|
||||
([72b6c9bc](watch/commits/72b6c9bc9189275909804f9ecab18e9fe1f69d27))
|
||||
- pass ga to server user.ops
|
||||
([9217b517](watch/commits/9217b5174ab9ab4754269263b214f6bfe45d4f1d))
|
||||
- track ecommerce events
|
||||
([d89fb17b](watch/commits/d89fb17b03b2e2c0fb1da77fb13cc660a5b6c9d1))
|
||||
- add server-side GA tracking for ecommerce events
|
||||
([f7b4a04a](watch/commits/f7b4a04a590ade26871abc726ade2c666176488e))
|
||||
- start adding some client-side GA event-tracking
|
||||
([ffb42906](watch/commits/ffb42906e1d7c6bd8f01e715d98d96426bc6d0de))
|
||||
- **groups:** add group chat notifications
|
||||
([ce82be63](watch/commits/ce82be637d1d707e899aeee5f315da69367fa367))
|
||||
- **habitBirthday:** add habitrpg birthday event. includes cakes for all pets, absurd party robes, npc swap, badge, etc. @lemoness
|
||||
([aff885c0](watch/commits/aff885c05c03bd70beeb0db8d68922671fc46309))
|
||||
- **homepage:**
|
||||
- start cleaning up homepage, add navbar for play button & upcoming links
|
||||
([0ddaae4d](watch/commits/0ddaae4d7525277e696a57d20234e49cd6fd1cbc))
|
||||
- use .marketing for centering, add playbutton as static in footer. This is pretty ugly (http://gyazo.com/215e20729569689ab48cf56c71c1fe28), let's iterate / prettify. @deilann
|
||||
([47bcaf83](watch/commits/47bcaf83e760dbb266ae7ff2f7299c2a1cdf3712))
|
||||
- **marketing:**
|
||||
- more copy for mobile
|
||||
([cbb44847](watch/commits/cbb448478edfd0003c43d20ed216bab20d25dadd))
|
||||
- start fleshing out the about page with images, content, etc. Create separate videos page
|
||||
([cb079977](watch/commits/cb079977e6f35f9308ab28158373dd3e1de9f798))
|
||||
- add video tuts on "learn more" page until we have some copy
|
||||
([5028707c](watch/commits/5028707c7b174b5e050c7c1662155e781a6b415b))
|
||||
- some frontpage updates, a screenshot, & "contact us" button mods
|
||||
([a582a054](watch/commits/a582a0546d680d36a21c507deff725a6c38fdb28))
|
||||
- **premium:**
|
||||
- subscriber mystery item (doesn't yet do anything)
|
||||
([d0342628](watch/commits/d0342628340ce7dce95fa20177ccbcfe1ebf93e6))
|
||||
- backport server code for premium subs (it's just ccard handling & uer model stuff)
|
||||
([3660f1a8](watch/commits/3660f1a85c1447de118f334a145d0d7698b93981))
|
||||
- updates to group plans info page
|
||||
([66f95cdd](watch/commits/66f95cdd4cfb698fddc765a77b66d29e31eb1361))
|
||||
- backport client-side premium code to public repo, it's client-side anyway (@colegleason @paglias)
|
||||
([2e18f0eb](watch/commits/2e18f0eb82f5efc77544d33d1db3fbb9cc583124))
|
||||
- **quests:**
|
||||
- add flags.levelDrops for dropping items at certain levels
|
||||
([78315d82](watch/commits/78315d828ba9a1033526b9a72b7c385281e6ad0a))
|
||||
- allow dropping scrolls in quests
|
||||
([54064deb](watch/commits/54064debf3c95390b5507acd826f9db3339b9f09))
|
||||
- allow html in quest notes
|
||||
([800231cf](watch/commits/800231cf6481351032d4e5143edd54f5e7e3a179))
|
||||
- add level requirement for quests
|
||||
([9e69d795](watch/commits/9e69d7959f174955f44429a94f22ce40fc5f7861))
|
||||
- add canBuy so we can exclude certain items from the market (if you can only find them on quest-drop, etc). This isn't the prettiest, change?
|
||||
([f16654d2](watch/commits/f16654d2354dc86cc7c52e1cf0562f850cf203be))
|
||||
- allow quests to drop multiple items
|
||||
([d9e5725e](watch/commits/d9e5725ee13f7e9ad329fc548537d5265cf483ca))
|
||||
- **rainbow-hair:** add rainbow hair colors
|
||||
([82d9233d](watch/commits/82d9233d99167d6704c878884dcc49a55cc7d884))
|
||||
- **restore:** add restore-gp back in. Parly to end the winter event, partly due to the convo at #2681 regarding subscriptions. Fixes #2681
|
||||
([179316e1](watch/commits/179316e10fa7597b08573d94721861baa3dbbb1c))
|
||||
- **toolbar:**
|
||||
- try with icons instead of text, test against prod / beta & get a vote.
|
||||
([7456f00d](watch/commits/7456f00dc6122ad293652b7a32fb4ce671f75241))
|
||||
- add toolbar featuring navigation, gems / subs, bailey, & chat / invite notifications
|
||||
([f72cb213](watch/commits/f72cb21300c078b439b3334bfa3e205ba04dc949))
|
||||
- **tracking:** gems > toolbar separately from wallet
|
||||
([f6abfc67](watch/commits/f6abfc67b31808c0e2d325c235747260855338c9))
|
||||
- **valentine:** valentine event
|
||||
([fd6eb872](watch/commits/fd6eb8724eae38d02849ffccb09f1f9c7d8e490d))
|
||||
- **winter:**
|
||||
- remove purchasable winter hair colors, keep available if they purchased during event
|
||||
([f8796e90](watch/commits/f8796e9028d4f4cd2b5c5ede1734d2876d174dc9))
|
||||
- remove winter scrolls & snowballs
|
||||
([52f8f0d5](watch/commits/52f8f0d5b0fdf4271fcb5f7d497ad3bf544c24e8))
|
||||
|
||||
|
||||
## Docs
|
||||
|
||||
- **rebirth:** Bullet point about repurchase of limited ed gear after Rebirth
|
||||
|
||||
14
CONTRIBUTING.md
Normal file
14
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Reporting Bugs
|
||||
|
||||
[Please see these instructions for reporting bugs](https://github.com/HabitRPG/habitrpg/issues/2760)
|
||||
|
||||
## Frequently Asked Questions
|
||||
You might find help with your issue on the [Frequently Asked Questions](http://habitrpg.wikia.com/wiki/FAQ) page.
|
||||
|
||||
# Requesting a feature
|
||||
|
||||
HabitRPG uses [Trello](https://trello.com/b/EpoYEYod/habitrpg) to track feature requests. [Read more](https://trello.com/c/odmhIqyW/440-read-first-table-of-contents).
|
||||
|
||||
# Contributing Code
|
||||
|
||||
See [Contributing to HabitRPG](http://habitrpg.wikia.com/wiki/Contributing_to_HabitRPG#Coders_.28Web_.26_Mobile.29)
|
||||
77
DOCS-README.md
Normal file
77
DOCS-README.md
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
# HabitRPG Docs Project
|
||||
|
||||
Generated documentation for all of HabitRPG's source files will be kept in the folder and subfolders. If you would like to use the existing documentation, or contribute to the documentation efforts, read on.
|
||||
|
||||
## Viewing Docs
|
||||
|
||||
You're looking at it!
|
||||
|
||||
Unless you are viewing this file directly from GitHub, you should see a list of files and folders to the left of this readme.
|
||||
|
||||
If you are working locally, you can goto `localhost:3000/docs/` and view the Docs.
|
||||
|
||||
All documentation is generated from comments in the code, into HTML files in the `public/docs/` folder. After you have cloned the HabitRPG repo locally, and done all the `npm install` goodness, the Docs should generate automagickly when you run `grunt run:dev`
|
||||
|
||||
## What I do now?
|
||||
|
||||
Well if you know Markdown, simply add detailed comments in the code using Markdown syntax.
|
||||
|
||||
````
|
||||
/*
|
||||
User.js
|
||||
=======
|
||||
|
||||
Defines the user data model (schema) for use via the API.
|
||||
*/
|
||||
|
||||
// Dependencies
|
||||
// ------------
|
||||
var mongoose = require("mongoose");
|
||||
var Schema = mongoose.Schema;
|
||||
var helpers = require('habitrpg-shared/script/helpers');
|
||||
var _ = require('lodash');....
|
||||
````
|
||||
|
||||
As you can see, you can use both multiline style comments `/* fancy stuff */` and inline comments `// Ooooh my`.
|
||||
|
||||
The exception being end of line comments
|
||||
`text: String, // example: Wolf `
|
||||
|
||||
The above will not be on the "pretty print" side of the Docs, but will stay in the code. An example use case for end of line comments would be for FIXME notes.
|
||||
|
||||
Add anything that would be helpful to a developer regarding how to use the functions, variables, and objects associated with HabitRPG.
|
||||
|
||||
**All documentation should be committed as pull request to the `docs-project` branch of HabitRPG.** Since we are adding comments directly to the code, I don't want to be editing files used for beta or master. We can merge in the docs after we're sure we didn't break anything.
|
||||
|
||||
### jsDoc Syntax
|
||||
|
||||
Yes, the generator also supports jsDoc-style comments such as
|
||||
````
|
||||
@param {Array} files Array of file paths relative to the `inDir` to generate documentation for.
|
||||
````
|
||||
|
||||
**Important Note:** If you use the `@param` syntax, you must use multiline comment blocks (ie `/* stuff */`), otherwise they won't be parsed like parameters.
|
||||
|
||||
This may or may not be useful for HabitRPG. Example use cases:
|
||||
- Documenting the API
|
||||
- Javascript Models
|
||||
|
||||
## Okay, I added great comments. Now what?
|
||||
|
||||
If you're running locally, just re-run `grunt run:dev`. Any changed docs will be automagickly updated.
|
||||
|
||||
Once you're satisfied with the output, push your changes to your fork of HabitRPG and issue a Pull Request on the `docs-project` branch.
|
||||
|
||||
It's that easy!
|
||||
|
||||
## Tech Info
|
||||
|
||||
The generator we are using is [Docker](https://github.com/jbt/docker), which is a fork of [Docco](http://jashkenas.github.io/docco/). Docker supports the same wide-range of filetypes, including being able to generate documentation for a whole project, including an index.
|
||||
|
||||
We also use the [Grunt-Docker](https://github.com/Prevole/grunt-docker) node module for automatic processing.
|
||||
|
||||
## Road Map
|
||||
|
||||
- Customize CSS with HabitRPG specific Styling
|
||||
- Explore possibilities of importing Wiki content
|
||||
- Specify style guide for consistency of comments
|
||||
41
Dockerfile
Normal file
41
Dockerfile
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
FROM ubuntu:trusty
|
||||
|
||||
MAINTAINER Thibault Cohen <titilambert@gmail.com>
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
|
||||
### Init
|
||||
|
||||
RUN apt-get update
|
||||
|
||||
### Utils
|
||||
|
||||
RUN apt-get install -y git vim graphicsmagick nodejs phantomjs npm pkgconf libcairo2-dev libjpeg8-dev
|
||||
|
||||
### Installation
|
||||
|
||||
RUN cd /opt && git clone https://github.com/HabitRPG/habitrpg.git
|
||||
|
||||
#RUN cd /opt/habitrpg && git checkout -t origin/develop
|
||||
|
||||
RUN cd /opt/habitrpg && git pull
|
||||
|
||||
RUN cd /opt/habitrpg && npm install -g grunt-cli bower nodemon
|
||||
|
||||
RUN ln -s /usr/bin/nodejs /usr/bin/node
|
||||
|
||||
RUN cd /opt/habitrpg && npm install
|
||||
|
||||
# Add config file
|
||||
|
||||
ADD ./config.json /opt/habitrpg/
|
||||
|
||||
RUN mkdir -p /opt/habitrpg/build
|
||||
|
||||
RUN cd /opt/habitrpg && bower install --allow-root
|
||||
|
||||
# Run server
|
||||
|
||||
RUN cd /opt/habitrpg && grunt build:prod
|
||||
|
||||
CMD cd /opt/habitrpg && grunt nodemon
|
||||
64
EXTENDEDCHANGELOG.md
Normal file
64
EXTENDEDCHANGELOG.md
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<a name="">HabitRPG</a>
|
||||
# (2014-01-28)
|
||||
|
||||
|
||||
## Documentation
|
||||
|
||||
- **rebirth:** Bullet point about repurchase of limited ed gear after Rebirth
|
||||
([d3f4a561](https://github.com/habitrpg/habitrpg/commits/d3f4a561fdf137e5d8f406bae03be4fef1caff22))
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **#2003:** healer gear not showing
|
||||
([949cd97b](https://github.com/habitrpg/habitrpg/commits/949cd97b91b42e9450eba559bbfea17e239ab100))
|
||||
- **#2375:** merge in @SabreCat's stats.jade changes "More elegant show/hide setup for attribute bonuses"
|
||||
([518f200a](https://github.com/habitrpg/habitrpg/commits/518f200a8fc7373b44ed7d7b5f016d921b0746bd))
|
||||
- **beastmaster:** fixes #2557, adds opacity to previously-owned pets after they're mounted. You can earn them back again
|
||||
([5caaff1c](https://github.com/habitrpg/habitrpg/commits/5caaff1cea1a68fe572e7ddf4aac50248b13df5d))
|
||||
- **bosses:** don't reset progress.up when starting a new quest. We want to be able to carry over damage from the same day a boss battle begins, even if the dailies were completed before battle-start. Fixes #2168
|
||||
([4efd0f5e](https://github.com/habitrpg/habitrpg/commits/4efd0f5ed8708f2491dd483f93e3d7a268a6337d))
|
||||
- **classes:**
|
||||
- misc fixes
|
||||
([d2121a85](https://github.com/habitrpg/habitrpg/commits/d2121a858716cb5a532a53ee9c5a1adaa74a7f69))
|
||||
- misc class fixes (not @snicker, ng-if on item store since we dynamically swap it sometimes)
|
||||
([478be611](https://github.com/habitrpg/habitrpg/commits/478be6111337cd200374f7f31b959725c6a0b945))
|
||||
- **find_uniq_user:** fix
|
||||
([ecbe780e](https://github.com/habitrpg/habitrpg/commits/ecbe780e70549b1470504efe052f238c89a9db14))
|
||||
- **mounts:** Move avatar upward when mounted regardless of pet
|
||||
([bc1adeb1](https://github.com/habitrpg/habitrpg/commits/bc1adeb1277103a5ca1f756e175ed68bbe837a2f))
|
||||
- **nodemon:** ignore CHANGELOG.md on watch
|
||||
([d6c55952](https://github.com/habitrpg/habitrpg/commits/d6c55952da8b49f36e9d8e4570d80931d081343d))
|
||||
- **party:** Round boss health up instead of to nearest integer
|
||||
([626da568](https://github.com/habitrpg/habitrpg/commits/626da5681f5ea95700f8ddf40587c7184926971c),
|
||||
[#2504](https://github.com/habitrpg/habitrpg/issues/2504))
|
||||
- **paypal:** fixes #2492, remove environment check for now, only have production-mode option. revisit
|
||||
([1dc68112](https://github.com/habitrpg/habitrpg/commits/1dc68112d131e4ebdec32ddff938eb6311d6565f))
|
||||
- **profile:** fix bug where empty profile displayed on username click
|
||||
([0579c432](https://github.com/habitrpg/habitrpg/commits/0579c432489c4a038e8c9f95ea3b285f5abc146f),
|
||||
[#2465](https://github.com/habitrpg/habitrpg/issues/2465))
|
||||
- **quests:**
|
||||
- bug fix to multi-drop
|
||||
([f478d10c](https://github.com/habitrpg/habitrpg/commits/f478d10c20f816cd104b3f0da814c189957f45f5))
|
||||
- list multiple rewards in dialog
|
||||
([e48c7277](https://github.com/habitrpg/habitrpg/commits/e48c7277f8256cf827790aece51e897fe0439374))
|
||||
- **settings:** reintroduce space between captions and help bubbles stripped during localization
|
||||
([5ddf09fe](https://github.com/habitrpg/habitrpg/commits/5ddf09fe13c7f8d844c8c47be0fb8f8b2fd1df33))
|
||||
- **spells:**
|
||||
- more $rootScope spell-casting bug fixes
|
||||
([47bd6dcb](https://github.com/habitrpg/habitrpg/commits/47bd6dcb79778d90d6f3ddeb003c3d8e45433333))
|
||||
- add some spells tests, don't send up body to spell paths
|
||||
([e0646bb9](https://github.com/habitrpg/habitrpg/commits/e0646bb98d44b6874b5259107c9be5fa34c58933))
|
||||
- some $rootScope.applying action fixes so cast-ending is immediate instead of waiting on response. Also, slim down party population to the essentials to avoid RequestEntityTooLarge
|
||||
([c6f7ab8a](https://github.com/habitrpg/habitrpg/commits/c6f7ab8a5c6f4e382208a928b90ba5f4eba9cd37))
|
||||
- <ESC> to cancel spell-casting
|
||||
([a1df41ad](https://github.com/habitrpg/habitrpg/commits/a1df41ad8165cd9eb6d2d5d59c7fe404edde716c))
|
||||
- **stable:** show hatchable combo when petOwned>0 (fyi @deilann)
|
||||
([51bff238](https://github.com/habitrpg/habitrpg/commits/51bff23885ca0080e7e71ff752daa0950ae923ae))
|
||||
- **stats:** Better layout for attribute point allocation
|
||||
([d782fc6b](https://github.com/habitrpg/habitrpg/commits/d782fc6b6a3cd7e90d327c93a5764626b2990c74))
|
||||
- **tests:**
|
||||
- include select2 in test manifest
|
||||
([38b4cea7](https://github.com/habitrpg/habitrpg/commits/38b4cea73299f51c4db7f6b2eb12533d219745f8))
|
||||
- don't use cluster in tests, else we get "connection refused"
|
||||
([7a479098](https://github.com/habitrpg/habitrpg/commits/7a479098dc6535654e322c737d80813790967941)
|
||||
161
Gruntfile.js
Normal file
161
Gruntfile.js
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
/*global module:false*/
|
||||
var _ = require('lodash');
|
||||
module.exports = function(grunt) {
|
||||
|
||||
// Project configuration.
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
|
||||
git_changelog: {
|
||||
minimal: {
|
||||
options: {
|
||||
repo_url: 'https://github.com/habitrpg/habitrpg',
|
||||
appName : 'HabitRPG',
|
||||
branch_name: 'develop'
|
||||
}
|
||||
},
|
||||
extended: {
|
||||
options: {
|
||||
file: 'EXTENDEDCHANGELOG.md',
|
||||
repo_url: 'https://github.com/habitrpg/habitrpg',
|
||||
appName : 'HabitRPG',
|
||||
branch_name: 'develop',
|
||||
grep_commits: '^perf|^style|^fix|^feat|^docs|^refactor|^chore|BREAKING'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
karma: {
|
||||
unit: {
|
||||
configFile: 'karma.conf.js'
|
||||
},
|
||||
continuous: {
|
||||
configFile: 'karma.conf.js',
|
||||
singleRun: true,
|
||||
autoWatch: false
|
||||
}
|
||||
},
|
||||
|
||||
clean: {
|
||||
build: ['build']
|
||||
},
|
||||
|
||||
stylus: {
|
||||
build: {
|
||||
options: {
|
||||
compress: false, // AFTER
|
||||
'include css': true,
|
||||
paths: ['public']
|
||||
},
|
||||
files: {
|
||||
'build/app.css': ['public/css/index.styl'],
|
||||
'build/static.css': ['public/css/static.styl']
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
copy: {
|
||||
build: {
|
||||
files: [
|
||||
{expand: true, cwd: 'public/', src: 'favicon.ico', dest: 'build/'},
|
||||
{expand: true, cwd: 'public/', src: 'bower_components/habitrpg-shared/dist/spritesmith.png', dest: 'build/'},
|
||||
{expand: true, cwd: 'public/', src: 'bower_components/habitrpg-shared/img/sprites/backer-only/*.gif', dest: 'build/'},
|
||||
{expand: true, cwd: 'public/', src: 'bower_components/habitrpg-shared/img/sprites/npc_ian.gif', dest: 'build/'},
|
||||
{expand: true, cwd: 'public/', src: 'bower_components/bootstrap/dist/fonts/*', dest: 'build/'}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// UPDATE IT WHEN YOU ADD SOME FILES NOT ALREADY MATCHED!
|
||||
hashres: {
|
||||
build: {
|
||||
options: {
|
||||
fileNameFormat: '${name}-${hash}.${ext}'
|
||||
},
|
||||
src: [
|
||||
'build/*.js', 'build/*.css', 'build/favicon.ico',
|
||||
'build/bower_components/habitrpg-shared/dist/*.png',
|
||||
'build/bower_components/habitrpg-shared/img/sprites/backer-only/*.gif',
|
||||
'build/bower_components/habitrpg-shared/img/sprites/npc_ian.gif',
|
||||
'build/bower_components/bootstrap/dist/fonts/*'
|
||||
],
|
||||
dest: 'build/*.css'
|
||||
}
|
||||
},
|
||||
|
||||
nodemon: {
|
||||
dev: {
|
||||
script: '<%= pkg.main %>'
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
dev: {
|
||||
files: ['public/**/*.styl'], // 'public/**/*.js' Not needed because not in production
|
||||
tasks: [ 'build:dev' ],
|
||||
options: {
|
||||
nospawn: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
concurrent: {
|
||||
dev: ['nodemon', 'watch'],
|
||||
options: {
|
||||
logConcurrentOutput: true
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
//Load build files from public/manifest.json
|
||||
grunt.registerTask('loadManifestFiles', 'Load all build files from public/manifest.json', function(){
|
||||
var files = grunt.file.readJSON('./public/manifest.json');
|
||||
var uglify = {};
|
||||
var cssmin = {};
|
||||
|
||||
_.each(files, function(val, key){
|
||||
|
||||
var js = uglify['build/' + key + '.js'] = [];
|
||||
|
||||
_.each(files[key]['js'], function(val){
|
||||
js.push('public/' + val);
|
||||
});
|
||||
|
||||
var css = cssmin['build/' + key + '.css'] = [];
|
||||
|
||||
_.each(files[key]['css'], function(val){
|
||||
var path = (val == 'app.css' || val == 'static.css') ? 'build/' : 'public/';
|
||||
css.push(path + val)
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
grunt.config.set('uglify.build.files', uglify);
|
||||
grunt.config.set('uglify.build.options', {compress: false});
|
||||
|
||||
grunt.config.set('cssmin.build.files', cssmin);
|
||||
// Rewrite urls to relative path
|
||||
grunt.config.set('cssmin.build.options', {'target': 'public/css/whatever-css.css'});
|
||||
});
|
||||
|
||||
// Register tasks.
|
||||
grunt.registerTask('build:prod', ['loadManifestFiles', 'clean:build', 'uglify', 'stylus', 'cssmin', 'copy:build', 'hashres']);
|
||||
grunt.registerTask('build:dev', ['stylus']);
|
||||
|
||||
grunt.registerTask('run:dev', [ 'build:dev', 'concurrent' ]);
|
||||
|
||||
// Load tasks
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||
grunt.loadNpmTasks('grunt-contrib-stylus');
|
||||
grunt.loadNpmTasks('grunt-contrib-cssmin');
|
||||
grunt.loadNpmTasks('grunt-contrib-copy');
|
||||
grunt.loadNpmTasks('grunt-nodemon');
|
||||
grunt.loadNpmTasks('grunt-concurrent');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-hashres');
|
||||
grunt.loadNpmTasks('grunt-karma');
|
||||
grunt.loadNpmTasks('git-changelog');
|
||||
|
||||
};
|
||||
9
LICENSE
Normal file
9
LICENSE
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
* Code is GPL v3 licensed:
|
||||
This Source Code is subject to the terms of the GNU General Public License, v. 3.0.
|
||||
If a copy of the GPL was not distributed with this file, You can obtain one at http://www.gnu.org/licenses/gpl-3.0.txt
|
||||
|
||||
* Assets and content designed for Mozilla BrowserQuest is licensed under CC-BY-SA 3.0:
|
||||
http://creativecommons.org/licenses/by-sa/3.0/
|
||||
|
||||
* Assets and content designed for HabitRPG is licensed under CC-BY-NC-SA 3.0:
|
||||
http://creativecommons.org/licenses/by-nc-sa/3.0/
|
||||
1
Procfile
Normal file
1
Procfile
Normal file
|
|
@ -0,0 +1 @@
|
|||
web: ./node_modules/.bin/grunt build:prod;./node_modules/.bin/grunt nodemon;
|
||||
12
README.md
Normal file
12
README.md
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
HabitRPG [](https://travis-ci.org/HabitRPG/habitrpg) [](https://codeclimate.com/github/HabitRPG/habitrpg) [](https://www.bountysource.com/trackers/68393-habitrpg?utm_source=68393&utm_medium=shield&utm_campaign=TRACKER_BADGE) [](https://gemnasium.com/HabitRPG/habitrpg)
|
||||
===============
|
||||
|
||||
[HabitRPG](https://habitrpg.com) is an open source habit building program which treats your life like a Role Playing Game. Level up as you succeed, lose HP as you fail, earn money to buy weapons and armor.
|
||||
|
||||
We need more programmers! Your assistance will be greatly appreciated.
|
||||
|
||||
For an introduction to the technologies used and how the software is organised, refer to [Contributing to HabitRPG](http://habitrpg.wikia.com/wiki/Contributing_to_HabitRPG#Coders_.28Web_.26_Mobile.29) - "Coders (Web & Mobile)" section.
|
||||
|
||||
To set up a local install of HabitRPG for development and testing, see [Setting up HabitRPG locally](http://habitrpg.wikia.com/wiki/Setting_up_HabitRPG_locally), which contains instructions for Windows, *nix / Mac OS, and Vagrant.
|
||||
|
||||
Then read [Guidance for Blacksmiths](http://habitrpg.wikia.com/wiki/Guidance_for_Blacksmiths) for additional instructions and useful tips.
|
||||
11
VAGRANT.md
Normal file
11
VAGRANT.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# Vagrant #
|
||||
|
||||
Vagrant is a system to create reproducible and portable development
|
||||
environments. Because of the variety of systems used for HabitRPG
|
||||
development and the various issues developers may encounter setting up
|
||||
HabitRPG on them, vagrant provides a single development enviroment with
|
||||
minimal dependencies on the developer's local platform. It can be used
|
||||
on a variety of systems including Windows, Mac OS X, and Linux.
|
||||
|
||||
Instructions for using the HabitRPG Vagrant environment are in
|
||||
[Setting up HabitRPG locally](http://habitrpg.wikia.com/wiki/Setting_up_HabitRPG_locally).
|
||||
15
Vagrantfile
vendored
Normal file
15
Vagrantfile
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
|
||||
VAGRANTFILE_API_VERSION = "2"
|
||||
|
||||
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||
config.vm.box = "thepeopleseason/habitrpg"
|
||||
config.ssh.forward_agent = true
|
||||
|
||||
config.vm.hostname = "habitrpg"
|
||||
config.vm.network "forwarded_port", guest: 3000, host: 3000, auto_correct: true
|
||||
config.vm.usable_port_range = (3000..3050)
|
||||
config.vm.provision :shell, :path => "vagrant.sh"
|
||||
end
|
||||
8
archive/README.md
Normal file
8
archive/README.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# What's This?
|
||||
|
||||
I'm consolidating @litenull's rewrite branch with the old code, and removing files from both sources once they've been
|
||||
successfully merged into the new platform. While @litenull's "from scratch" approach was really clean, it will take
|
||||
us longer to implemente all the original features. This approach will (1) let us leverage code we already have, (2) merge
|
||||
in litenull's hard work from the last few weeks.
|
||||
|
||||
Once this archive/ directory is completely empty, we should be fully merged and ready to deploy the rewrite!
|
||||
46
archive/derby_controllers/browser.coffee
Normal file
46
archive/derby_controllers/browser.coffee
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
_ = require 'lodash'
|
||||
moment = require 'moment'
|
||||
|
||||
###
|
||||
Loads JavaScript files from public/vendor/*
|
||||
Use require() to min / concatinate for faster page load
|
||||
###
|
||||
loadJavaScripts = (model) ->
|
||||
|
||||
# Turns out you can't have expressions in browserify require() statements
|
||||
#vendor = '../../public/vendor'
|
||||
#require "#{vendor}/jquery-ui-1.10.2/jquery-1.9.1"
|
||||
|
||||
###
|
||||
Internal Scripts
|
||||
###
|
||||
require "../vendor/jquery.cookie.min.js"
|
||||
require "../vendor/bootstrap/js/bootstrap.min.js"
|
||||
require "../vendor/datepicker/js/bootstrap-datepicker"
|
||||
require "../vendor/bootstrap-tour/bootstrap-tour"
|
||||
|
||||
unless (model.get('_mobileDevice') is true)
|
||||
require "../vendor/sticky"
|
||||
|
||||
# note: external script loading is handled in app.on('render') near the bottom of this file (see https://groups.google.com/forum/?fromgroups=#!topic/derbyjs/x8FwdTLEuXo)
|
||||
|
||||
|
||||
# jquery sticky header on scroll, no need for position fixed
|
||||
initStickyHeader = (model) ->
|
||||
$('.header-wrap').sticky({topSpacing:0})
|
||||
|
||||
|
||||
module.exports.app = (appExports, model, app) ->
|
||||
|
||||
app.on 'render', (ctx) ->
|
||||
#restoreRefs(model)
|
||||
unless model.get('_mobileDevice')
|
||||
setupTooltips(model)
|
||||
initStickyHeader(model)
|
||||
setupSortable(model)
|
||||
|
||||
$('.datepicker').datepicker({autoclose:true, todayBtn:true})
|
||||
.on 'changeDate', (ev) ->
|
||||
#for some reason selecting a date doesn't fire a change event on the field, meaning our changes aren't saved
|
||||
model.at(ev.target).set 'date', moment(ev.date).format('MM/DD/YYYY')
|
||||
|
||||
239
archive/derby_controllers/challenges.coffee
Normal file
239
archive/derby_controllers/challenges.coffee
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
_ = require 'lodash'
|
||||
{helpers} = require 'habitrpg-shared'
|
||||
async = require 'async'
|
||||
|
||||
module.exports.app = (app) ->
|
||||
|
||||
###
|
||||
Sync any updates to challenges since last refresh. Do it after cron, so we don't punish them for new tasks
|
||||
This is challenge->user sync. user->challenge happens when user interacts with their tasks
|
||||
###
|
||||
app.on 'ready', (model) ->
|
||||
window.setTimeout ->
|
||||
_.each model.get('groups'), (g) ->
|
||||
if (@uid in g.members) and g.challenges
|
||||
_.each(g.challenges, ->app.challenges.syncChalToUser g)
|
||||
true
|
||||
, 500
|
||||
|
||||
###
|
||||
Sync user to challenge (when they score, add to statistics)
|
||||
###
|
||||
app.model.on "change", "_page.user.priv.tasks.*.value", (id, value, previous, passed) ->
|
||||
### Sync to challenge, but do it later ###
|
||||
async.nextTick =>
|
||||
model = app.model
|
||||
ctx = {model: model}
|
||||
task = model.at "_page.user.priv.tasks.#{id}"
|
||||
tobj = task.get()
|
||||
pub = model.get "_page.user.pub"
|
||||
|
||||
if (chalTask = helpers.taskInChallenge.call ctx, tobj)? and chalTask.get()
|
||||
chalTask.increment "value", value - previous
|
||||
chal = model.at "groups.#{tobj.group.id}.challenges.#{tobj.challenge}"
|
||||
chalUser = -> helpers.indexedAt.call(ctx, chal.path(), 'members', {id:pub.id})
|
||||
cu = chalUser()
|
||||
unless cu?.get()
|
||||
chal.push "members", {id: pub.id, name: model.get(pub.profile.name)}
|
||||
cu = model.at chalUser()
|
||||
else
|
||||
cu.set 'name', pub.profile.name # update their name incase it changed
|
||||
cu.set "#{tobj.type}s.#{tobj.id}",
|
||||
value: tobj.value
|
||||
history: tobj.history
|
||||
|
||||
###
|
||||
Render graphs for user scores when the "Challenges" tab is clicked
|
||||
###
|
||||
|
||||
###
|
||||
TODO
|
||||
1) on main tab click or party
|
||||
* sort & render graphs for party
|
||||
2) guild -> all guilds
|
||||
3) public -> all public
|
||||
###
|
||||
|
||||
|
||||
###
|
||||
$('#profile-challenges-tab-link').on 'shown', ->
|
||||
async.each _.toArray(model.get('groups')), (g) ->
|
||||
async.each _.toArray(g.challenges), (chal) ->
|
||||
async.each _.toArray(chal.tasks), (task) ->
|
||||
async.each _.toArray(chal.members), (member) ->
|
||||
if (history = member?["#{task.type}s"]?[task.id]?.history) and !!history
|
||||
data = google.visualization.arrayToDataTable _.map(history, (h)-> [h.date,h.value])
|
||||
options =
|
||||
backgroundColor: { fill:'transparent' }
|
||||
width: 150
|
||||
height: 50
|
||||
chartArea: width: '80%', height: '80%'
|
||||
axisTitlePosition: 'none'
|
||||
legend: position: 'bottom'
|
||||
hAxis: gridlines: color: 'transparent' # since you can't seem to *remove* gridlines...
|
||||
vAxis: gridlines: color: 'transparent'
|
||||
chart = new google.visualization.LineChart $(".challenge-#{chal.id}-member-#{member.id}-history-#{task.id}")[0]
|
||||
chart.draw(data, options)
|
||||
###
|
||||
|
||||
app.fn
|
||||
challenges:
|
||||
|
||||
###
|
||||
Create
|
||||
###
|
||||
create: (e,el) ->
|
||||
[type, gid] = [$(el).attr('data-type'), $(el).attr('data-gid')]
|
||||
cid = @model.id()
|
||||
@model.set '_page.new.challenge',
|
||||
id: cid
|
||||
name: ''
|
||||
habits: []
|
||||
dailys: []
|
||||
todos: []
|
||||
rewards: []
|
||||
user:
|
||||
uid: @uid
|
||||
name: @pub.get('profile.name')
|
||||
group: {type, id:gid}
|
||||
timestamp: +new Date
|
||||
|
||||
###
|
||||
Save
|
||||
###
|
||||
save: ->
|
||||
newChal = @model.get('_page.new.challenge')
|
||||
[gid, cid] = [newChal.group.id, newChal.id]
|
||||
@model.push "_page.lists.challenges.#{gid}", newChal, ->
|
||||
app.browser.growlNotification('Challenge Created','success')
|
||||
app.challenges.discard()
|
||||
app.browser.resetDom() # something is going absolutely haywire here, all model data at end of reflist after chal created
|
||||
|
||||
###
|
||||
Toggle Edit
|
||||
###
|
||||
toggleEdit: (e, el) ->
|
||||
path = "_page.editing.challenges.#{$(el).attr('data-id')}"
|
||||
@model.set path, !@model.get(path)
|
||||
|
||||
###
|
||||
Discard
|
||||
###
|
||||
discard: ->
|
||||
@model.del '_page.new.challenge'
|
||||
|
||||
###
|
||||
Delete
|
||||
###
|
||||
delete: (e) ->
|
||||
return unless confirm("Delete challenge, are you sure?") is true
|
||||
e.at().remove()
|
||||
|
||||
###
|
||||
Add challenge name as a tag for user
|
||||
###
|
||||
syncChalToUser: (chal) ->
|
||||
return unless chal
|
||||
### Sync tags ###
|
||||
tags = @priv.get('tags') or []
|
||||
idx = _.findIndex tags, {id: chal.id}
|
||||
if ~idx and (tags[idx].name isnt chal.name)
|
||||
### update the name - it's been changed since ###
|
||||
@priv.set "tags.#{idx}.name", chal.name
|
||||
else
|
||||
@priv.push 'tags', {id: chal.id, name: chal.name, challenge: true}
|
||||
|
||||
tags = {}; tags[chal.id] = true
|
||||
_.each chal.habits.concat(chal.dailys.concat(chal.todos.concat(chal.rewards))), (task) =>
|
||||
_.defaults task, { tags, challenge: chal.id, group: {id: chal.group.id, type: chal.group.type} }
|
||||
path = "tasks.#{task.id}"
|
||||
if @priv.get path
|
||||
@priv.set path, _.defaults(@priv.get(path), task)
|
||||
else
|
||||
@model.push "_page.lists.tasks.#{@uid}.#{task.type}s", task
|
||||
true
|
||||
|
||||
###
|
||||
Subscribe
|
||||
###
|
||||
subscribe: (e) ->
|
||||
chal = e.get()
|
||||
### Add all challenge's tasks to user's tasks ###
|
||||
currChallenges = @pub.get('challenges')
|
||||
@pub.unshift('challenges', chal.id) unless currChallenges and ~currChallenges.indexOf(chal.id)
|
||||
e.at().push "members",
|
||||
id: @uid
|
||||
name: @pub.get('profile.name')
|
||||
app.challenges.syncChalToUser(chal)
|
||||
|
||||
###
|
||||
--------------------------
|
||||
Unsubscribe functions
|
||||
--------------------------
|
||||
###
|
||||
|
||||
unsubscribe: (chal, keep=true) ->
|
||||
|
||||
### Remove challenge from user ###
|
||||
i = @pub.get('challenges')?.indexOf(chal.id)
|
||||
if i? and ~i
|
||||
@pub.remove("challenges", i, 1)
|
||||
|
||||
### Remove user from challenge ###
|
||||
if ~(i = _.findIndex chal.members, {id: @uid})
|
||||
@model.remove "groups.#{chal.group.id}.challenges.#{chal.id}.members", i, 1
|
||||
|
||||
### Remove tasks from user ###
|
||||
async.each chal.habits.concat(chal.dailys.concat(chal.todos.concat(chal.rewards))), (task) =>
|
||||
if keep is true
|
||||
@priv.del "tasks.#{task.id}.challenge"
|
||||
else
|
||||
path = "_page.lists.tasks.#{@uid}.#{task.type}s"
|
||||
if ~(i = _.findIndex(@model.get(path), {id:task.id}))
|
||||
@model.remove(path, i, 1)
|
||||
true
|
||||
|
||||
taskUnsubscribe: (e, el) ->
|
||||
|
||||
###
|
||||
since the challenge was deleted, we don't have its data to unsubscribe from - but we have the vestiges on the task
|
||||
FIXME this is a really dumb way of doing this
|
||||
###
|
||||
tasks = @priv.get('tasks')
|
||||
tobj = tasks[$(el).attr("data-tid")]
|
||||
deletedChal =
|
||||
id: tobj.challenge
|
||||
members: [@uid]
|
||||
habits: _.where tasks, {type: 'habit', challenge: tobj.challenge}
|
||||
dailys: _.where tasks, {type: 'daily', challenge: tobj.challenge}
|
||||
todos: _.where tasks, {type: 'todo', challenge: tobj.challenge}
|
||||
rewards: _.where tasks, {type: 'reward', challenge: tobj.challenge}
|
||||
|
||||
switch $(el).attr('data-action')
|
||||
when 'keep'
|
||||
@priv.del "tasks.#{tobj.id}.challenge"
|
||||
@priv.del "tasks.#{tobj.id}.group"
|
||||
when 'keep-all'
|
||||
app.challenges.unsubscribe.call @, deletedChal, true
|
||||
when 'remove'
|
||||
path = "_page.lists.tasks.#{@uid}.#{tobj.type}s"
|
||||
if ~(i = _.findIndex @model.get(path), {id: tobj.id})
|
||||
@model.remove path, i
|
||||
when 'remove-all'
|
||||
app.challenges.unsubscribe.call @, deletedChal, false
|
||||
|
||||
challengeUnsubscribe: (e, el) ->
|
||||
$(el).popover('destroy').popover({
|
||||
html: true
|
||||
placement: 'top'
|
||||
trigger: 'manual'
|
||||
title: 'Unsubscribe From Challenge And:'
|
||||
content: """
|
||||
<a class=challenge-unsubscribe-and-remove>Remove Tasks</a><br/>
|
||||
<a class=challenge-unsubscribe-and-keep>Keep Tasks</a><br/>
|
||||
<a class=challenge-unsubscribe-cancel>Cancel</a><br/>
|
||||
"""
|
||||
}).popover('show')
|
||||
$('.challenge-unsubscribe-and-remove').click => app.challenges.unsubscribe.call @, e.get(), false
|
||||
$('.challenge-unsubscribe-and-keep').click => app.challenges.unsubscribe.call @, e.get(), true
|
||||
$('[class^=challenge-unsubscribe]').click => $(el).popover('destroy')
|
||||
22
archive/derby_controllers/filters.coffee
Normal file
22
archive/derby_controllers/filters.coffee
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
_ = require 'lodash'
|
||||
|
||||
module.exports.app = (appExports, model) ->
|
||||
user = model.at('_user')
|
||||
|
||||
appExports.filtersDeleteTag = (e, el) ->
|
||||
tags = user.get('tags')
|
||||
tag = e.at "_user.tags." + $(el).attr('data-index')
|
||||
tagId = tag.get('id')
|
||||
|
||||
###something got corrupted, let's clear the corrupt tags###
|
||||
unless tagId
|
||||
user.set 'tags', _.filter( tags, ((t)-> t?.id) )
|
||||
user.set 'filters', {}
|
||||
return
|
||||
|
||||
model.del "_user.filters.#{tagId}"
|
||||
tag.remove()
|
||||
|
||||
### remove tag from all tasks###
|
||||
_.each user.get("tasks"), (task) -> user.del "tasks.#{task.id}.tags.#{tagId}"; true
|
||||
|
||||
139
archive/derby_controllers/groups.coffee
Normal file
139
archive/derby_controllers/groups.coffee
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
_ = require('lodash')
|
||||
helpers = require('habitrpg-shared/script/helpers')
|
||||
|
||||
module.exports.app = (appExports, model, app) ->
|
||||
browser = require './browser'
|
||||
|
||||
_currentTime = model.at '_currentTime'
|
||||
_currentTime.setNull +new Date
|
||||
# Every 60 seconds, reset the current time so that the chat can update relative times
|
||||
setInterval (->_currentTime.set +new Date), 60000
|
||||
|
||||
appExports.groupCreate = (e,el) ->
|
||||
type = $(el).attr('data-type')
|
||||
newGroup =
|
||||
name: model.get("_new.group.name")
|
||||
description: model.get("_new.group.description")
|
||||
leader: user.get('id')
|
||||
members: [user.get('id')]
|
||||
type: type
|
||||
|
||||
# parties - free
|
||||
if type is 'party'
|
||||
return model.add 'groups', newGroup, ->location.reload()
|
||||
|
||||
# guilds - 4G
|
||||
unless user.get('balance') >= 1
|
||||
return $('#more-gems-modal').modal 'show'
|
||||
if confirm "Create Guild for 4 Gems?"
|
||||
newGroup.privacy = (model.get("_new.group.privacy") || 'public') if type is 'guild'
|
||||
newGroup.balance = 1 # they spent $ to open the guild, it goes into their guild bank
|
||||
model.add 'groups', newGroup, ->
|
||||
user.incr 'balance', -1, ->location.reload()
|
||||
|
||||
appExports.toggleGroupEdit = (e, el) ->
|
||||
path = "_editing.groups.#{$(el).attr('data-gid')}"
|
||||
model.set path, !model.get(path)
|
||||
|
||||
appExports.toggleLeaderMessageEdit = (e, el) ->
|
||||
path = "_editing.leaderMessage.#{$(el).attr('data-gid')}"
|
||||
model.set path, !model.get(path)
|
||||
|
||||
appExports.groupAddWebsite = (e, el) ->
|
||||
test = e.get()
|
||||
e.at().unshift 'websites', model.get('_newGroupWebsite')
|
||||
model.del '_newGroupWebsite'
|
||||
|
||||
appExports.groupInvite = (e,el) ->
|
||||
uid = model.get('_groupInvitee').replace(/[\s"]/g, '')
|
||||
model.set '_groupInvitee', ''
|
||||
return if _.isEmpty(uid)
|
||||
|
||||
model.query('users').publicInfo([uid]).fetch (err, profiles) ->
|
||||
throw err if err
|
||||
profile = profiles.at(0).get()
|
||||
return model.set("_groupError", "User with id #{uid} not found.") unless profile
|
||||
model.query('groups').withMember(uid).fetch (err, g) ->
|
||||
throw err if err
|
||||
group = e.get(); groups = g.get()
|
||||
{type, name} = group; gid = group.id
|
||||
groupError = (msg) -> model.set("_groupError", msg)
|
||||
invite = ->
|
||||
$.bootstrapGrowl "Invitation Sent."
|
||||
switch type
|
||||
when 'guild' then model.push "users.#{uid}.invitations.guilds", {id:gid, name}, ->location.reload()
|
||||
when 'party' then model.set "users.#{uid}.invitations.party", {id:gid, name}, ->location.reload()
|
||||
|
||||
switch type
|
||||
when 'guild'
|
||||
if profile.invitations?.guilds and _.find(profile.invitations.guilds, {id:gid})
|
||||
return groupError("User already invited to that group")
|
||||
else if uid in group.members
|
||||
return groupError("User already in that group")
|
||||
else invite()
|
||||
when 'party'
|
||||
if profile.invitations?.party
|
||||
return groupError("User already pending invitation.")
|
||||
else if _.find(groups, {type:'party'})
|
||||
return groupError("User already in a party.")
|
||||
else invite()
|
||||
|
||||
|
||||
appExports.acceptInvitation = (e,el) ->
|
||||
gid = e.get('id')
|
||||
if $(el).attr('data-type') is 'party'
|
||||
user.set 'invitations.party', null, ->joinGroup(gid)
|
||||
else
|
||||
e.at().remove ->joinGroup(gid)
|
||||
|
||||
appExports.rejectInvitation = (e, el) ->
|
||||
clear = -> browser.resetDom(model)
|
||||
if e.at().path().indexOf('party') != -1
|
||||
model.del e.at().path(), clear
|
||||
else e.at().remove clear
|
||||
|
||||
appExports.groupLeave = (e,el) ->
|
||||
if confirm("Leave this group, are you sure?") is true
|
||||
uid = user.get('id')
|
||||
group = model.at "groups.#{$(el).attr('data-id')}"
|
||||
index = group.get('members').indexOf(uid)
|
||||
if index != -1
|
||||
group.remove 'members', index, 1, ->
|
||||
updated = group.get()
|
||||
# last member out, delete the party
|
||||
if _.isEmpty(updated.members) and (updated.type is 'party')
|
||||
group.del ->location.reload()
|
||||
# assign new leader, so the party is editable #TODO allow old leader to assign new leader, this is just random
|
||||
else if (updated.leader is uid)
|
||||
group.set "leader", updated.members[0], ->location.reload()
|
||||
else location.reload()
|
||||
|
||||
###
|
||||
Chat Functionality
|
||||
###
|
||||
|
||||
model.on 'unshift', '_party.chat', -> $('.chat-message').tooltip()
|
||||
model.on 'unshift', '_habitrpg.chat', -> $('.chat-message').tooltip()
|
||||
|
||||
appExports.chatKeyup = (e, el, next) ->
|
||||
return next() unless e.keyCode is 13
|
||||
appExports.sendChat(e, el)
|
||||
|
||||
appExports.deleteChatMessage = (e) ->
|
||||
if confirm("Delete chat message?") is true
|
||||
e.at().remove() #requires the {#with}
|
||||
|
||||
app.on 'render', (ctx) ->
|
||||
$('#party-tab-link').on 'shown', (e) ->
|
||||
messages = model.get('_party.chat')
|
||||
return false unless messages?.length > 0
|
||||
model.set '_user.party.lastMessageSeen', messages[0].id
|
||||
|
||||
appExports.gotoPartyChat = ->
|
||||
model.set '_gamePane', true, ->
|
||||
$('#party-tab-link').tab('show')
|
||||
|
||||
appExports.assignGroupLeader = (e, el) ->
|
||||
newLeader = model.get('_new.groupLeader')
|
||||
if newLeader and (confirm("Assign new leader, you sure?") is true)
|
||||
e.at().set('leader', newLeader, ->browser.resetDom(model)) if newLeader
|
||||
7
archive/derby_controllers/i18n.coffee
Normal file
7
archive/derby_controllers/i18n.coffee
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
i18n = require 'derby-i18n'
|
||||
|
||||
i18n.plurals.add 'he', (n) -> n
|
||||
i18n.plurals.add 'bg', (n) -> n
|
||||
i18n.plurals.add 'nl', (n) -> n
|
||||
|
||||
module.exports = i18n
|
||||
20
archive/derby_controllers/index.coffee
Normal file
20
archive/derby_controllers/index.coffee
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Translations
|
||||
i18n = require './i18n'
|
||||
i18n.localize app,
|
||||
availableLocales: ['en', 'he', 'bg', 'nl']
|
||||
defaultLocale: 'en'
|
||||
urlScheme: false
|
||||
checkHeader: true
|
||||
|
||||
|
||||
# ========== CONTROLLER FUNCTIONS ==========
|
||||
|
||||
ready (model) ->
|
||||
misc.fixCorruptUser(model) # https://github.com/lefnire/habitrpg/issues/634
|
||||
|
||||
# used for things like remove website, chat, etc
|
||||
exports.removeAt = (e, el) ->
|
||||
if (confirmMessage = $(el).attr 'data-confirm')?
|
||||
return unless confirm(confirmMessage) is true
|
||||
e.at().remove()
|
||||
browser.resetDom(model) if $(el).attr('data-refresh')
|
||||
39
archive/derby_controllers/items.coffee
Normal file
39
archive/derby_controllers/items.coffee
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
items = require 'habitrpg-shared/script/items'
|
||||
_ = require 'lodash'
|
||||
|
||||
updateStore = (model) ->
|
||||
nextItems = items.updateStore(model.get('_user'))
|
||||
_.each nextItems, (v,k) -> model.set("_items.next.#{k}",v); true
|
||||
|
||||
###
|
||||
server exports
|
||||
###
|
||||
module.exports.server = (model) ->
|
||||
model.set '_items', items.items
|
||||
updateStore(model)
|
||||
|
||||
###
|
||||
app exports
|
||||
###
|
||||
module.exports.app = (appExports, model) ->
|
||||
misc = require './misc'
|
||||
|
||||
model.on "set", "_user.items.*", -> updateStore(model)
|
||||
|
||||
appExports.buyItem = (e, el) ->
|
||||
misc.batchTxn model, (uObj, paths) ->
|
||||
ret = items.buyItem uObj, $(el).attr('data-type'), {paths}
|
||||
alert("Not enough GP") if ret is false
|
||||
|
||||
appExports.activateRewardsTab = ->
|
||||
model.set '_activeTabRewards', true
|
||||
model.set '_activeTabPets', false
|
||||
appExports.activatePetsTab = ->
|
||||
model.set '_activeTabPets', true
|
||||
model.set '_activeTabRewards', false
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
152
archive/derby_controllers/misc.coffee
Normal file
152
archive/derby_controllers/misc.coffee
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
_ = require 'lodash'
|
||||
algos = require 'habitrpg-shared/script/algos'
|
||||
items = require('habitrpg-shared/script/items').items
|
||||
helpers = require('habitrpg-shared/script/helpers')
|
||||
|
||||
#TODO put this in habitrpg-shared
|
||||
###
|
||||
We can't always use refLists, but we often still need to get a positional path by id: eg, users.1234.tasks.5678.value
|
||||
For arrays (which use indexes, not id-paths), here's a helper function so we can run indexedPath('users',:user.id,'tasks',:task.id,'value)
|
||||
###
|
||||
indexedPath = ->
|
||||
_.reduce arguments, (m,v) =>
|
||||
return v if !m #first iteration
|
||||
return "#{m}.#{v}" if _.isString v #string paths
|
||||
return "#{m}." + _.findIndex(@model.get(m),v)
|
||||
, ''
|
||||
|
||||
taskInChallenge = (task) ->
|
||||
return undefined unless task?.challenge
|
||||
@model.at indexedPath.call(@, "groups.#{task.group.id}.challenges", {id:task.challenge}, "#{task.type}s", {id:task.id})
|
||||
|
||||
###
|
||||
algos.score wrapper for habitrpg-helpers to work in Derby. We need to do model.set() instead of simply setting the
|
||||
object properties, and it's very difficult to diff the two objects and find dot-separated paths to set. So we to first
|
||||
clone our user object (if we don't do that, it screws with model.on() listeners, ping Tyler for an explaination),
|
||||
perform the updates while tracking paths, then all the values at those paths
|
||||
###
|
||||
module.exports.score = (model, taskId, direction, allowUndo=false) ->
|
||||
drop = undefined
|
||||
delta = batchTxn model, (uObj, paths) ->
|
||||
tObj = uObj.tasks[taskId]
|
||||
|
||||
# Stuff for undo
|
||||
if allowUndo
|
||||
tObjBefore = _.cloneDeep tObj
|
||||
tObjBefore.completed = !tObjBefore.completed if tObjBefore.type in ['daily', 'todo']
|
||||
previousUndo = model.get('_undo')
|
||||
clearTimeout(previousUndo.timeoutId) if previousUndo?.timeoutId
|
||||
timeoutId = setTimeout (-> model.del('_undo')), 20000
|
||||
model.set '_undo', {stats:_.cloneDeep(uObj.stats), task:tObjBefore, timeoutId: timeoutId}
|
||||
|
||||
delta = algos.score(uObj, tObj, direction, {paths})
|
||||
model.set('_streakBonus', uObj._tmp.streakBonus) if uObj._tmp?.streakBonus
|
||||
drop = uObj._tmp?.drop
|
||||
|
||||
# Update challenge statistics
|
||||
# FIXME put this in it's own batchTxn, make batchTxn model.at() ref aware (not just _user)
|
||||
# FIXME use reflists for users & challenges
|
||||
if (chalTask = taskInChallenge.call({model}, tObj)) and chalTask?.get()
|
||||
model._dontPersist = false
|
||||
chalTask.incr "value", delta
|
||||
chal = model.at indexedPath.call({model}, "groups.#{tObj.group.id}.challenges", {id:tObj.challenge})
|
||||
chalUser = -> indexedPath.call({model}, chal.path(), 'users', {id:uObj.id})
|
||||
cu = model.at chalUser()
|
||||
unless cu?.get()
|
||||
chal.push "users", {id: uObj.id, name: helpers.username(uObj.auth, uObj.profile?.name)}
|
||||
cu = model.at chalUser()
|
||||
else
|
||||
cu.set 'name', helpers.username(uObj.auth, uObj.profile?.name) # update their name incase it changed
|
||||
cu.set "#{tObj.type}s.#{tObj.id}",
|
||||
value: tObj.value
|
||||
history: tObj.history
|
||||
model._dontPersist = true
|
||||
, done:->
|
||||
if drop and $?
|
||||
model.set '_drop', drop
|
||||
$('#item-dropped-modal').modal 'show'
|
||||
|
||||
delta
|
||||
|
||||
###
|
||||
Cleanup task-corruption (null tasks, rogue/invisible tasks, etc)
|
||||
Obviously none of this should be happening, but we'll stop-gap until we can find & fix
|
||||
Gotta love refLists! see https://github.com/lefnire/habitrpg/issues/803 & https://github.com/lefnire/habitrpg/issues/6343
|
||||
###
|
||||
module.exports.fixCorruptUser = (model) ->
|
||||
user = model.at('_user')
|
||||
tasks = user.get('tasks')
|
||||
|
||||
## Remove corrupted tasks
|
||||
_.each tasks, (task, key) ->
|
||||
unless task?.id? and task?.type?
|
||||
user.del("tasks.#{key}")
|
||||
delete tasks[key]
|
||||
true
|
||||
resetDom = false
|
||||
batchTxn model, (uObj, paths, batch) ->
|
||||
## fix https://github.com/lefnire/habitrpg/issues/1086
|
||||
uniqPets = _.uniq(uObj.items.pets)
|
||||
batch.set('items.pets', uniqPets) if !_.isEqual(uniqPets, uObj.items.pets)
|
||||
|
||||
if uObj.invitations?.guilds
|
||||
uniqInvites = _.uniq(uObj.invitations.guilds)
|
||||
batch.set('invitations.guilds', uniqInvites) if !_.isEqual(uniqInvites, uObj.invitations.guilds)
|
||||
|
||||
## Task List Cleanup
|
||||
['habit','daily','todo','reward'].forEach (type) ->
|
||||
|
||||
# 1. remove duplicates
|
||||
# 2. restore missing zombie tasks back into list
|
||||
idList = uObj["#{type}Ids"]
|
||||
taskIds = _.pluck( _.where(tasks, {type}), 'id')
|
||||
union = _.union idList, taskIds
|
||||
|
||||
# 2. remove empty (grey) tasks
|
||||
preened = _.filter union, (id) -> id and _.contains(taskIds, id)
|
||||
|
||||
# There were indeed issues found, set the new list
|
||||
if !_.isEqual(idList, preened)
|
||||
batch.set("#{type}Ids", preened)
|
||||
console.error uObj.id + "'s #{type}s were corrupt."
|
||||
true
|
||||
resetDom = !_.isEmpty(paths)
|
||||
require('./browser').resetDom(model) if resetDom
|
||||
|
||||
module.exports.viewHelpers = (view) ->
|
||||
|
||||
#misc
|
||||
view.fn "percent", (x, y) ->
|
||||
x=1 if x==0
|
||||
Math.round(x/y*100)
|
||||
view.fn 'indexOf', (str1, str2) ->
|
||||
return false unless str1 && str2
|
||||
str1.indexOf(str2) != -1
|
||||
view.fn "round", Math.round
|
||||
view.fn "floor", Math.floor
|
||||
view.fn "ceil", Math.ceil
|
||||
view.fn "truarr", (num) -> num-1
|
||||
view.fn 'count', (arr) -> arr?.length or 0
|
||||
view.fn 'int',
|
||||
get: (num) -> num
|
||||
set: (num) -> [parseInt(num)]
|
||||
view.fn 'indexedPath', indexedPath
|
||||
|
||||
|
||||
#iCal
|
||||
view.fn "encodeiCalLink", helpers.encodeiCalLink
|
||||
|
||||
#User
|
||||
view.fn "gems", (balance) -> balance * 4
|
||||
|
||||
#Challenges
|
||||
view.fn 'taskInChallenge', (task) ->
|
||||
taskInChallenge.call(@,task)?.get()
|
||||
view.fn 'taskAttrFromChallenge', (task, attr) ->
|
||||
taskInChallenge.call(@,task)?.get(attr)
|
||||
view.fn 'brokenChallengeLink', (task) ->
|
||||
task?.challenge and !(taskInChallenge.call(@,task)?.get())
|
||||
|
||||
view.fn 'challengeMemberScore', (member, tType, tid) ->
|
||||
Math.round(member["#{tType}s"]?[tid]?.value)
|
||||
|
||||
73
archive/derby_controllers/pets.coffee
Normal file
73
archive/derby_controllers/pets.coffee
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
_ = require 'lodash'
|
||||
{ randomVal } = require 'habitrpg-shared/script/helpers'
|
||||
{ pets, hatchingPotions } = require('habitrpg-shared/script/items').items
|
||||
|
||||
###
|
||||
app exports
|
||||
###
|
||||
module.exports.app = (appExports, model) ->
|
||||
user = model.at '_user'
|
||||
|
||||
appExports.chooseEgg = (e, el) ->
|
||||
model.ref '_hatchEgg', e.at()
|
||||
|
||||
appExports.hatchEgg = (e, el) ->
|
||||
hatchingPotionName = $(el).children('select').val()
|
||||
myHatchingPotion = user.get 'items.hatchingPotions'
|
||||
egg = model.get '_hatchEgg'
|
||||
eggs = user.get 'items.eggs'
|
||||
myPets = user.get 'items.pets'
|
||||
|
||||
hatchingPotionIdx = myHatchingPotion.indexOf hatchingPotionName
|
||||
eggIdx = eggs.indexOf egg
|
||||
|
||||
return alert "You don't own that hatching potion yet, complete more tasks!" if hatchingPotionIdx is -1
|
||||
return alert "You don't own that egg yet, complete more tasks!" if eggIdx is -1
|
||||
return alert "You already have that pet, hatch a different combo." if myPets and myPets.indexOf("#{egg.name}-#{hatchingPotionName}") != -1
|
||||
|
||||
user.push 'items.pets', egg.name + '-' + hatchingPotionName, ->
|
||||
eggs.splice eggIdx, 1
|
||||
myHatchingPotion.splice hatchingPotionIdx, 1
|
||||
user.set 'items.eggs', eggs
|
||||
user.set 'items.hatchingPotions', myHatchingPotion
|
||||
|
||||
alert 'Your egg hatched! Visit your stable to equip your pet.'
|
||||
|
||||
#FIXME Bug: this removes from the array properly in the browser, but on refresh is has removed all items from the arrays
|
||||
# user.remove 'items.hatchingPotions', hatchingPotionIdx, 1
|
||||
# user.remove 'items.eggs', eggIdx, 1
|
||||
|
||||
appExports.choosePet = (e, el, next) ->
|
||||
petStr = $(el).attr('data-pet')
|
||||
|
||||
return next() if user.get('items.pets').indexOf(petStr) == -1
|
||||
# If user's pet is already active, deselect it
|
||||
return user.set 'items.currentPet', {} if user.get('items.currentPet.str') is petStr
|
||||
|
||||
[name, modifier] = petStr.split('-')
|
||||
pet = _.find pets, {name: name}
|
||||
pet.modifier = modifier
|
||||
pet.str = petStr
|
||||
user.set 'items.currentPet', pet
|
||||
|
||||
appExports.buyHatchingPotion = (e, el) ->
|
||||
name = $(el).attr 'data-hatchingPotion'
|
||||
newHatchingPotion = _.find hatchingPotions, {name: name}
|
||||
gems = user.get('balance') * 4
|
||||
if gems >= newHatchingPotion.value
|
||||
if confirm "Buy this hatching potion with #{newHatchingPotion.value} of your #{gems} Gems?"
|
||||
user.push 'items.hatchingPotions', newHatchingPotion.name
|
||||
user.set 'balance', (gems - newHatchingPotion.value) / 4
|
||||
else
|
||||
$('#more-gems-modal').modal 'show'
|
||||
|
||||
appExports.buyEgg = (e, el) ->
|
||||
name = $(el).attr 'data-egg'
|
||||
newEgg = _.find pets, {name: name}
|
||||
gems = user.get('balance') * 4
|
||||
if gems >= newEgg.value
|
||||
if confirm "Buy this egg with #{newEgg.value} of your #{gems} Gems?"
|
||||
user.push 'items.eggs', newEgg
|
||||
user.set 'balance', (gems - newEgg.value) / 4
|
||||
else
|
||||
$('#more-gems-modal').modal 'show'
|
||||
29
archive/derby_controllers/tasks.coffee
Normal file
29
archive/derby_controllers/tasks.coffee
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
algos = require 'habitrpg-shared/script/algos'
|
||||
helpers = require 'habitrpg-shared/script/helpers'
|
||||
_ = require 'lodash'
|
||||
moment = require 'moment'
|
||||
misc = require './misc'
|
||||
|
||||
appExports.clearCompleted = (e, el) ->
|
||||
completedIds = _.pluck( _.where(model.get('_todoList'), {completed:true}), 'id')
|
||||
todoIds = user.get('todoIds')
|
||||
_.each completedIds, (id) -> user.del "tasks.#{id}"; true
|
||||
user.set 'todoIds', _.difference(todoIds, completedIds)
|
||||
|
||||
|
||||
###
|
||||
Undo
|
||||
###
|
||||
appExports.undo = () ->
|
||||
undo = model.get '_undo'
|
||||
clearTimeout(undo.timeoutId) if undo?.timeoutId
|
||||
model.del '_undo'
|
||||
_.each undo.stats, (val, key) -> user.set "stats.#{key}", val; true
|
||||
taskPath = "tasks.#{undo.task.id}"
|
||||
_.each undo.task, (val, key) ->
|
||||
return true if key in ['id', 'type'] # strange bugs in this world: https://workflowy.com/shared/a53582ea-43d6-bcce-c719-e134f9bf71fd/
|
||||
if key is 'completed'
|
||||
user.pass({cron:true}).set("#{taskPath}.completed",val)
|
||||
else
|
||||
user.set "#{taskPath}.#{key}", val
|
||||
true
|
||||
95
archive/derby_controllers/unlock.coffee
Normal file
95
archive/derby_controllers/unlock.coffee
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
_ = require 'lodash'
|
||||
{ randomVal } = require 'habitrpg-shared/script/helpers'
|
||||
{ pets, hatchingPotions } = require('habitrpg-shared/script/items').items
|
||||
|
||||
###
|
||||
Listeners to enabled flags, set notifications to the user when they've unlocked features
|
||||
###
|
||||
|
||||
module.exports.app = (appExports, model) ->
|
||||
user = model.at('_user')
|
||||
|
||||
alreadyShown = (before, after) -> !(!before and after is true)
|
||||
|
||||
showPopover = (selector, title, html, placement='bottom') ->
|
||||
$(selector).popover('destroy')
|
||||
html += " <a href='#' onClick=\"$('#{selector}').popover('hide');return false;\">[Close]</a>"
|
||||
$(selector).popover({
|
||||
title: title
|
||||
placement: placement
|
||||
trigger: 'manual'
|
||||
html: true
|
||||
content: html
|
||||
}).popover 'show'
|
||||
|
||||
|
||||
user.on 'set', 'flags.customizationsNotification', (after, before) ->
|
||||
return if alreadyShown(before,after)
|
||||
$('.main-herobox').popover('destroy') #remove previous popovers
|
||||
html = "Click your avatar to customize your appearance."
|
||||
showPopover '.main-herobox', 'Customize Your Avatar', html, 'bottom'
|
||||
|
||||
user.on 'set', 'flags.itemsEnabled', (after, before) ->
|
||||
return if alreadyShown(before,after)
|
||||
html = """
|
||||
<img src='/vendor/BrowserQuest/client/img/1/chest.png' />
|
||||
Congratulations, you have unlocked the Item Store! You can now buy weapons, armor, potions, etc. Read each item's comment for more information.
|
||||
"""
|
||||
showPopover 'div.rewards', 'Item Store Unlocked', html, 'left'
|
||||
|
||||
user.on 'set', 'flags.petsEnabled', (after, before) ->
|
||||
return if alreadyShown(before,after)
|
||||
html = """
|
||||
<img src='/img/sprites/wolf_border.png' style='width:30px;height:30px;float:left;padding-right:5px' />
|
||||
You have unlocked Pets! You can now buy pets with Gems (note, you replenish Gems with real-life money - so chose your pets wisely!)
|
||||
"""
|
||||
showPopover '#rewardsTabs', 'Pets Unlocked', html, 'left'
|
||||
|
||||
user.on 'set', 'flags.partyEnabled', (after, before) ->
|
||||
return if user.get('party.current') or alreadyShown(before,after)
|
||||
html = """
|
||||
Be social, join a party and play Habit with your friends! You'll be better at your habits with accountability partners. Click User -> Options -> Party, and follow the instructions. LFG anyone?
|
||||
"""
|
||||
showPopover '.user-menu', 'Party System', html, 'bottom'
|
||||
|
||||
user.on 'set', 'flags.dropsEnabled', (after, before) ->
|
||||
return if alreadyShown(before,after)
|
||||
|
||||
egg = randomVal pets
|
||||
|
||||
dontPersist = model._dontPersist
|
||||
|
||||
model._dontPersist = false
|
||||
user.push 'items.eggs', egg
|
||||
model._dontPersist = dontPersist
|
||||
|
||||
$('#drops-enabled-modal').modal 'show'
|
||||
|
||||
user.on 'push', 'items.pets', (after, before) ->
|
||||
return if user.get('achievements.beastMaster')
|
||||
if before >= 90 # evidently before is the count?
|
||||
dontPersist = model._dontPersist; model._dontPersist = false
|
||||
user.set 'achievements.beastMaster', true, (-> model._dontPersist = dontPersist)
|
||||
$('#beastmaster-achievement-modal').modal('show')
|
||||
|
||||
user.on 'set', 'items.*', (after, before) ->
|
||||
return if user.get('achievements.ultimateGear')
|
||||
items = user.get('items')
|
||||
if parseInt(items.weapon) >= 6 and parseInt(items.armor) >= 5 and parseInt(items.head) >= 5 and parseInt(items.shield) >= 5
|
||||
dontPersist = model._dontPersist; model._dontPersist = false
|
||||
user.set 'achievements.ultimateGear', true, (->model._dontPersist = dontPersist)
|
||||
$('#max-gear-achievement-modal').modal('show')
|
||||
|
||||
user.on 'set', 'tasks.*.streak', (id, after, before) ->
|
||||
if after > 0
|
||||
|
||||
# 21-day streak, as per the old philosophy of doign a thing 21-days in a row makes a habit
|
||||
if (after % 21) is 0
|
||||
dontPersist = model._dontPersist; model._dontPersist = false
|
||||
user.incr 'achievements.streak', 1, (-> model._dontPersist = dontPersist)
|
||||
$('#streak-achievement-modal').modal('show')
|
||||
|
||||
# they're undoing a task at the 21 mark, take back their badge
|
||||
else if (before - after is 1) and (before % 21 is 0)
|
||||
dontPersist = model._dontPersist; model._dontPersist = false
|
||||
user.incr 'achievements.streak', -1, (-> model._dontPersist = dontPersist)
|
||||
54
bower.json
Normal file
54
bower.json
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"name": "HabitRPG",
|
||||
"version": "0.1.1",
|
||||
"homepage": "https://github.com/lefnire/habitrpg",
|
||||
"authors": [
|
||||
"Tyler Renelle <tylerrenelle@gmail.com>"
|
||||
],
|
||||
"private": true,
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"public/bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"dependencies": {
|
||||
"jquery": "~2.1.0",
|
||||
"jquery.cookie": "~1.4.0",
|
||||
"angular": "1.3.9",
|
||||
"angular-ui": "~0.4.0",
|
||||
"angular-sanitize": "1.3.9",
|
||||
"angular-resource": "1.3.9",
|
||||
"angular-ui-utils": "~0.1.0",
|
||||
"angular-ui-select2": "git://github.com/angular-ui/ui-select2.git",
|
||||
"angular-bootstrap": "~0.12.0",
|
||||
"angular-loading-bar": "~0.6.0",
|
||||
"bootstrap": "~3.1.0",
|
||||
"bootstrap-growl": "git://github.com/ifightcrime/bootstrap-growl.git#master",
|
||||
"bootstrap-tour": "~0.8.1",
|
||||
"habitrpg-shared": "git://github.com/HabitRPG/habitrpg-shared.git#develop",
|
||||
"BrowserQuest": "git://github.com/browserquest/BrowserQuest.git",
|
||||
"github-buttons": "git://github.com/mdo/github-buttons.git",
|
||||
"marked": "~0.2.9",
|
||||
"Angular-At-Directive": "git://github.com/snicker/Angular-At-Directive#master",
|
||||
"js-emoji": "https://github.com/iamcal/js-emoji.git",
|
||||
"sticky": "*",
|
||||
"swagger-ui": "git://github.com/wordnik/swagger-ui.git",
|
||||
"ngInfiniteScroll": "1.0.0",
|
||||
"jquery-colorbox": "~1.4.36",
|
||||
"pnotify": "~1.3.1",
|
||||
"jquery-ui": "~1.10.3",
|
||||
"hello": "~1.3.1",
|
||||
"css-social-buttons": "https://github.com/samcollins/css-social-buttons.git",
|
||||
"angular-filter": "~0.5.1",
|
||||
"angular-ui-router": "~0.2.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-mocks": "1.3.9"
|
||||
},
|
||||
"resolutions": {
|
||||
"angular": "1.3.9",
|
||||
"jquery": ">=1.9.0"
|
||||
}
|
||||
}
|
||||
55
config.json.example
Normal file
55
config.json.example
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"PORT":3000,
|
||||
"IP":"0.0.0.0",
|
||||
"CORES":1,
|
||||
"BASE_URL":"http://localhost",
|
||||
"FACEBOOK_KEY":"123456789012345",
|
||||
"FACEBOOK_SECRET":"aaaabbbbccccddddeeeeffff00001111",
|
||||
"NODE_DB_URI":"mongodb://localhost/habitrpg",
|
||||
"NODE_ENV":"development",
|
||||
"SESSION_SECRET":"YOUR SECRET HERE",
|
||||
"ADMIN_EMAIL": "you@yours.com",
|
||||
"SMTP_USER":"user@domain.com",
|
||||
"SMTP_PASS":"password",
|
||||
"SMTP_SERVICE":"Gmail",
|
||||
"SMTP_HOST":"smtp.gmail.com",
|
||||
"SMTP_PORT": 587,
|
||||
"SMTP_TLS": true,
|
||||
"STRIPE_API_KEY":"aaaabbbbccccddddeeeeffff00001111",
|
||||
"STRIPE_PUB_KEY":"22223333444455556666777788889999",
|
||||
"NEW_RELIC_LICENSE_KEY":"NEW_RELIC_LICENSE_KEY",
|
||||
"NEW_RELIC_APPLICATION_ID":"NEW_RELIC_APPLICATION_ID",
|
||||
"NEW_RELIC_API_KEY":"NEW_RELIC_API_KEY",
|
||||
"GA_ID": "GA_ID",
|
||||
"FLAG_REPORT_EMAIL": "email@mod.com",
|
||||
"EMAIL_SERVER": {
|
||||
"url": "http://example.com",
|
||||
"authUser": "user",
|
||||
"authPassword": "password"
|
||||
},
|
||||
"S3":{
|
||||
"bucket":"bucket",
|
||||
"accessKeyId":"accessKeyId",
|
||||
"secretAccessKey":"secretAccessKey"
|
||||
},
|
||||
"PAYPAL":{
|
||||
"billing_plans": {
|
||||
"basic_earned":"basic_earned",
|
||||
"basic_3mo":"basic_3mo",
|
||||
"basic_6mo":"basic_6mo",
|
||||
"google_6mo":"google_6mo",
|
||||
"basic_12mo":"basic_12mo"
|
||||
},
|
||||
"mode":"sandbox",
|
||||
"client_id":"client_id",
|
||||
"client_secret":"client_secret"
|
||||
},
|
||||
"IAP_GOOGLE_KEYDIR": "/path/to/google/public/key/dir/",
|
||||
"LOGGLY": {
|
||||
"enabled": false,
|
||||
"subdomain": "subdomain",
|
||||
"token": "token",
|
||||
"username": "username",
|
||||
"password": "password"
|
||||
}
|
||||
}
|
||||
197
database_reports/count_users_who_own_specified_gear.js
Normal file
197
database_reports/count_users_who_own_specified_gear.js
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
var authorName = 'Alys'; // in case script author needs to know when their ...
|
||||
var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
|
||||
|
||||
/**
|
||||
* database_reports/count_users_who_own_specified_gear.js
|
||||
* https://github.com/HabitRPG/habitrpg/pull/3884
|
||||
*/
|
||||
|
||||
var thingsOfInterest = {
|
||||
'Unconventional Armor ownership': {
|
||||
'data_path': 'items.gear.owned',
|
||||
'identifyOwnershipWith': 'exists',
|
||||
'items': [
|
||||
'eyewear_special_wondercon_red',
|
||||
'eyewear_special_wondercon_black',
|
||||
'back_special_wondercon_black',
|
||||
'back_special_wondercon_red',
|
||||
'body_special_wondercon_red',
|
||||
'body_special_wondercon_black',
|
||||
'body_special_wondercon_gold'
|
||||
],
|
||||
},
|
||||
'Spooky Skins purchases': {
|
||||
'data_path': 'purchased.skin',
|
||||
'identifyOwnershipWith': 'true',
|
||||
'items': [
|
||||
'monster',
|
||||
'pumpkin',
|
||||
'skeleton',
|
||||
'zombie',
|
||||
'ghost',
|
||||
'shadow'
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
var mongo = require('mongoskin');
|
||||
var _ = require('lodash');
|
||||
|
||||
var dbUsers = mongo.db('localhost:27017/habitrpg?auto_reconnect').collection('users');
|
||||
|
||||
var thingsFound = {}; // each key is one "thing" from thingsOfInterest,
|
||||
// and the value for that key is the number of users who own it
|
||||
// (for items, 'owned' values of both true and false are counted
|
||||
// to include items lost on death)
|
||||
|
||||
var query = {}; // Not worth limiting search data with query and fields since
|
||||
var fields = {}; // this will be run over a local copy of the database?
|
||||
|
||||
console.warn('Finding data...');
|
||||
var progressCount = 1000;
|
||||
var count = 0;
|
||||
dbUsers.findEach(query, fields, {batchSize:250}, function(err, user) {
|
||||
if (err) { return exiting(1, 'ERROR! ' + err); }
|
||||
if (!user) {
|
||||
console.warn('All users found.');
|
||||
return displayData();
|
||||
}
|
||||
count++;
|
||||
|
||||
_.each(thingsOfInterest,function(obj,label){
|
||||
var data_path = obj['data_path'];
|
||||
var items = obj['items'];
|
||||
var identifyOwnershipWith = obj['identifyOwnershipWith'];
|
||||
var userOwns = path(user, data_path, {});
|
||||
|
||||
_.each(items,function(item){
|
||||
if ( (identifyOwnershipWith == 'exists' && item in userOwns) ||
|
||||
(identifyOwnershipWith == 'true' && userOwns[item])
|
||||
) {
|
||||
if (! thingsFound[label]) { thingsFound[label] = {}; }
|
||||
thingsFound[label][item] = (thingsFound[label][item] || 0) + 1;
|
||||
// console.warn(user.auth.local.username + ": " + label + ": " + item); // good for testing, bad for privacy
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (count%progressCount == 0) console.warn(count + ' ' + user._id);
|
||||
if (user._id == authorUuid) console.warn(authorName + ' processed');
|
||||
if (user._id == '9' ) console.warn('lefnire' + ' processed');
|
||||
});
|
||||
|
||||
|
||||
function displayData() {
|
||||
var today = yyyymmdd(new Date());
|
||||
var csvReport = '';
|
||||
var textReport = today + '\n';
|
||||
|
||||
_.each(thingsFound,function(obj,label){
|
||||
csvReport += '\n"' + label + '"' + '\n';
|
||||
textReport += '\n' + label + ':\n';
|
||||
var csvHeader = '"date"'; // heading row in CSV data
|
||||
var csvData = '"' + today + '"'; // data row in CSV data
|
||||
|
||||
var sortedKeys = _.sortBy(_.keys(obj), function(key){ return key; });
|
||||
_.each(sortedKeys,function(key){
|
||||
var value = obj[key];
|
||||
csvHeader += ',"' + key + '"';
|
||||
csvData += ',"' + (value || 0) + '"';
|
||||
textReport += '\t' + key + ': ' + value + '\n';
|
||||
});
|
||||
csvReport += csvHeader + '\n' + csvData + '\n';
|
||||
});
|
||||
|
||||
console.log('\nCSV DATA:\n' + csvReport + '\n\n' +
|
||||
'READABLE DATA:\n\n' + textReport + '\n\n');
|
||||
|
||||
console.warn(count + ' users searched (should be >400k)\n');
|
||||
// NB: "should be" assumes that no query filter was applied to findEach
|
||||
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
|
||||
function path(obj, path, def) {
|
||||
/**
|
||||
* Retrieve nested item from object/array
|
||||
* @param {Object|Array} obj
|
||||
* @param {String} path dot separated
|
||||
* @param {*} def default value ( if result undefined )
|
||||
* @returns {*}
|
||||
* http://stackoverflow.com/a/16190716
|
||||
* Usage: console.log(path(someObject, pathname));
|
||||
*/
|
||||
for(var i = 0,path = path.split('.'),len = path.length; i < len; i++){
|
||||
if(!obj || typeof obj !== 'object') return def;
|
||||
obj = obj[path[i]];
|
||||
}
|
||||
if(!obj || typeof obj === 'undefined') return def;
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
function yyyymmdd(date) {
|
||||
var yyyy = date.getFullYear().toString();
|
||||
var mm = (date.getMonth()+1).toString();
|
||||
var dd = date.getDate().toString();
|
||||
return yyyy + "-" + (mm[1]?mm:"0"+mm[0]) + "-" + (dd[1]?dd:"0"+dd[0]);
|
||||
}
|
||||
|
||||
|
||||
function exiting(code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
if (code && !msg) { msg = 'ERROR!'; }
|
||||
if (msg) {
|
||||
if (code) { console.error(msg); }
|
||||
else { console.log( msg); }
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
|
||||
/* SAMPLE OUTPUT (STDOUT and STDERR):
|
||||
$ node database_reports/count_users_who_own_specified_gear.js
|
||||
|
||||
Finding data...
|
||||
Alys processed
|
||||
lefnire processed
|
||||
All users found.
|
||||
|
||||
CSV DATA:
|
||||
|
||||
"Unconventional Armor ownership"
|
||||
"date","back_special_wondercon_black","back_special_wondercon_red","body_special_wondercon_black","body_special_wondercon_gold","body_special_wondercon_red","eyewear_special_wondercon_black","eyewear_special_wondercon_red"
|
||||
"2014-09-01","7","7","7","7","7","7","9"
|
||||
|
||||
"Spooky Skins purchases"
|
||||
"date","ghost","monster","pumpkin","shadow","skeleton","zombie"
|
||||
"2014-09-01","2","3","3","6","4","3"
|
||||
|
||||
|
||||
READABLE DATA:
|
||||
|
||||
2014-09-01
|
||||
|
||||
Unconventional Armor ownership:
|
||||
back_special_wondercon_black: 7
|
||||
back_special_wondercon_red: 7
|
||||
body_special_wondercon_black: 7
|
||||
body_special_wondercon_gold: 7
|
||||
body_special_wondercon_red: 7
|
||||
eyewear_special_wondercon_black: 7
|
||||
eyewear_special_wondercon_red: 9
|
||||
|
||||
Spooky Skins purchases:
|
||||
ghost: 2
|
||||
monster: 3
|
||||
pumpkin: 3
|
||||
shadow: 6
|
||||
skeleton: 4
|
||||
zombie: 3
|
||||
|
||||
|
||||
|
||||
400100 users searched (should be >400k)
|
||||
|
||||
*/
|
||||
14
database_reports/duplicate_indexes.js
Normal file
14
database_reports/duplicate_indexes.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
db.users.aggregate([
|
||||
{ $group: {
|
||||
// _id: { "auth.local.username": "$auth.local.username" },
|
||||
// _id: { "auth.facebook.id": "$auth.facebook.id" },
|
||||
_id: { "auth.local.email": "$auth.local.email" },
|
||||
uniqueIds: { $addToSet: "$_id" },
|
||||
count: { $sum: 1 }
|
||||
}},
|
||||
{ $match: {
|
||||
count: { $gt: 1 }
|
||||
}}
|
||||
]).forEach(function(user){
|
||||
// handle duplicates. likely, delete the accounts with older lastCron, or with the starter tasks.
|
||||
})
|
||||
73
karma.conf.js
Normal file
73
karma.conf.js
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
// Karma configuration
|
||||
// http://karma-runner.github.io/0.10/config/configuration-file.html
|
||||
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
// base path, that will be used to resolve files and exclude
|
||||
basePath: '',
|
||||
|
||||
// testing framework to use (jasmine/mocha/qunit/...)
|
||||
frameworks: ['mocha', 'chai', 'chai-as-promised', 'sinon-chai'],
|
||||
|
||||
// list of files / patterns to load in the browser
|
||||
files: [
|
||||
'public/bower_components/jquery/jquery.js',
|
||||
'public/bower_components/angular/angular.js',
|
||||
'public/bower_components/angular-loading-bar/build/loading-bar.min.js',
|
||||
'public/bower_components/angular-resource/angular-resource.min.js',
|
||||
'public/bower_components/angular-sanitize/angular-sanitize.js',
|
||||
'public/bower_components/bootstrap/docs/assets/js/bootstrap.js',
|
||||
'public/bower_components/angular-bootstrap/ui-bootstrap.js',
|
||||
'public/bower_components/angular-bootstrap/ui-bootstrap-tpls.js',
|
||||
'public/bower_components/angular-ui-router/release/angular-ui-router.js',
|
||||
'public/bower_components/angular-ui/build/angular-ui.js',
|
||||
'public/bower_components/angular-ui-utils/ui-utils.min.js',
|
||||
'public/bower_components/Angular-At-Directive/src/at.js',
|
||||
'public/bower_components/Angular-At-Directive/src/caret.js',
|
||||
'public/bower_components/angular-mocks/angular-mocks.js',
|
||||
'public/bower_components/angular-bindonce/bindonce.js',
|
||||
'public/bower_components/ngInfiniteScroll/ng-infinite-scroll.js',
|
||||
"public/bower_components/select2/select2.js",
|
||||
"public/bower_components/angular-ui-select2/src/select2.js",
|
||||
'public/bower_components/marked/lib/marked.js',
|
||||
'public/bower_components/js-emoji/emoji.js',
|
||||
'public/bower_components/habitrpg-shared/dist/habitrpg-shared.js',
|
||||
'public/bower_components/habitrpg-shared/script/userServices.js',
|
||||
'public/js/*.js',
|
||||
'public/js/**/*.js',
|
||||
'test/mock/**/*.js',
|
||||
'test/spec/*.js',
|
||||
'test/spec/**/*.js'
|
||||
],
|
||||
|
||||
// list of files / patterns to exclude
|
||||
exclude: [],
|
||||
|
||||
// web server port
|
||||
port: 8080,
|
||||
|
||||
// level of logging
|
||||
// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
|
||||
logLevel: config.LOG_INFO,
|
||||
|
||||
|
||||
// enable / disable watching file and executing tests whenever any file changes
|
||||
autoWatch: true,
|
||||
|
||||
|
||||
// Start these browsers, currently available:
|
||||
// - Chrome
|
||||
// - ChromeCanary
|
||||
// - Firefox
|
||||
// - Opera
|
||||
// - Safari (only Mac)
|
||||
// - PhantomJS
|
||||
// - IE (only Windows)
|
||||
browsers: ['Firefox'],
|
||||
|
||||
|
||||
// Continuous Integration mode
|
||||
// if true, it capture browsers, run tests and exit
|
||||
singleRun: false
|
||||
});
|
||||
};
|
||||
1
keys/google/iap-live
Normal file
1
keys/google/iap-live
Normal file
|
|
@ -0,0 +1 @@
|
|||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk6EVccD7D6y5vLmsUxUXVYTMyByq+NjfTzjMBaKCk93fq8Ahn6Ipha1K4oBIvArUj3CsP5Tb/Yf6c6SrEsSnE3eqgKQgeq+fYyfIm+Fb+tFGTttLonZ2Sqemqeyx2PjOG2CVZBXj1+Zr7jb0ocMNYraYUTvklWnpcK6/5ZaZLLent1PGiU9l5jIfRAsxmJH/UpssgpFPnCs+8PRKUALkJATTfVCCF+PJef6mcKAafAArKUEo0lR2/NeaE62Wpii33DLuKDPfMqL3g4EHZTEVE1ezFl/3WPeLlxuQm38hZLpEfEYKp17qzplU+GmWIfjZt6OdtVdonlYxIxNAZLnWEQIDAQAB
|
||||
5
migrations/20130128_add_missing_crons.js
Normal file
5
migrations/20130128_add_missing_crons.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
db.users.update(
|
||||
{ lastCron: { $exists: false} },
|
||||
{ $set: { lastCron: +new Date } },
|
||||
{ multi: true }
|
||||
);
|
||||
15
migrations/20130128_merge_completed_todo_ids.js
Normal file
15
migrations/20130128_merge_completed_todo_ids.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
db.users.find({ completedIds: { $exists: true } }).forEach(function(user) {
|
||||
var newTodoIds = user.todoIds;
|
||||
user.completedIds.forEach(function(value) {
|
||||
if (newTodoIds.indexOf(value) === -1) {
|
||||
newTodoIds.push(value)
|
||||
}
|
||||
});
|
||||
db.users.update(
|
||||
{ _id: user._id },
|
||||
{
|
||||
$set: { todoIds: newTodoIds },
|
||||
$unset: { completedIds: 1 }
|
||||
}
|
||||
);
|
||||
});
|
||||
5
migrations/20130129_add_missing_preferences.js
Normal file
5
migrations/20130129_add_missing_preferences.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
db.users.update(
|
||||
{preferences:{$exists:false}},
|
||||
{$set:{preferences:{gender: 'm', armorSet: 'v1'}}},
|
||||
{multi:true}
|
||||
)
|
||||
13
migrations/20130204_count_habits.js
Normal file
13
migrations/20130204_count_habits.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// %mongo server:27017/dbname underscore.js my_commands.js
|
||||
// %mongo server:27017/dbname underscore.js --shell
|
||||
var habits = 0,
|
||||
dailies = 0,
|
||||
todos = 0,
|
||||
registered = { $or: [ { 'auth.local': { $exists: true } }, { 'auth.facebook': { $exists: true} } ]};
|
||||
|
||||
db.user.find(registered).forEach(function(u){
|
||||
//TODO this isn't working??
|
||||
habits += _.where(u.tasks, {type:'habit'}).length;
|
||||
dailies += _.where(u.tasks, {type:'daily'}).length;
|
||||
todos += _.where(u.tasks, {type:'todo'}).length;
|
||||
})
|
||||
102
migrations/20130204_user_public_private_paths.js
Normal file
102
migrations/20130204_user_public_private_paths.js
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
// %mongo server:27017/dbname underscore.js my_commands.js
|
||||
// %mongo server:27017/dbname underscore.js --shell
|
||||
|
||||
//db.users.find({'auth.facebook.email': 'tylerrenelle@gmail.com'}).forEach(function(user){
|
||||
db.users.find().forEach(function(user){
|
||||
|
||||
if (!user._id) {
|
||||
print("User has null _id");
|
||||
return; // need to figure out how to delete these buggers if they don't have an id to delete from
|
||||
}
|
||||
|
||||
if (!!user.idLists) {
|
||||
print("User " + user._id + " has already been migrated")
|
||||
return
|
||||
}
|
||||
|
||||
if (user._id.indexOf("$") === 0) {
|
||||
print("User id starts with $ (" + user._id + ")")
|
||||
return;
|
||||
}
|
||||
|
||||
// even though we're clobbering user later, sometimes these are undefined and crash the script
|
||||
// this saves us some ternaries
|
||||
user.stats = user.stats || {};
|
||||
user.items = user.items || {};
|
||||
user.preferences = user.preferences || {};
|
||||
user.notifications = user.notifications || {};
|
||||
user.flags = user.flags || {};
|
||||
user.habitIds = user.habitIds || [];
|
||||
user.dailyIds = user.dailyIds || [];
|
||||
user.todoIds = user.todoIds || [];
|
||||
user.rewardIds = user.rewardIds|| [];
|
||||
|
||||
_.each(user.tasks, function(task, key){
|
||||
if (!task.type) {
|
||||
delete user.tasks[key];
|
||||
// idList will take care of itself on page-load
|
||||
return
|
||||
}
|
||||
if (key == '$spec') {
|
||||
print("$spec was found: " + user._id);
|
||||
return
|
||||
}
|
||||
if (key.indexOf("$_") === 0) {
|
||||
var newKey = key.replace("$_", ''),
|
||||
index = user[task.type + "Ids"].indexOf(key)
|
||||
user[task.type + "Ids"][index] = newKey;
|
||||
task.id = newKey
|
||||
user.tasks[newKey] = task
|
||||
// TODO make sure this is ok, that we're not deleting the original
|
||||
// Otherwise use lodash.cloneDeep
|
||||
delete user.tasks[key]
|
||||
}
|
||||
});
|
||||
|
||||
// New user schema has public and private paths, so we can setup proper access control with racer
|
||||
// Note 'public' and 'private' are reserved words
|
||||
var newUser = {
|
||||
auth: user.auth, // we need this top-level due to derby-auth
|
||||
apiToken: user.preferences.api_token || null, // set on update, we need derby.uuid()
|
||||
preferences: {
|
||||
armorSet: user.preferences.armorSet || 'v1',
|
||||
gender: user.preferences.gender || 'm'
|
||||
},
|
||||
balance: user.balance || 2,
|
||||
lastCron: user.lastCron || +new Date,
|
||||
history: user.history || [],
|
||||
stats: {
|
||||
gp: user.stats.money || 0,
|
||||
hp: user.stats.hp || 50,
|
||||
exp: user.stats.exp || 0,
|
||||
lvl: user.stats.lvl || 1
|
||||
},
|
||||
items: {
|
||||
armor: user.items.armor || 0,
|
||||
weapon: user.items.weapon || 0
|
||||
},
|
||||
tasks: user.tasks || {},
|
||||
idLists: {
|
||||
habit: user.habitIds || [],
|
||||
daily: user.dailyIds || [],
|
||||
todo: user.todoIds || [],
|
||||
reward: user.rewardIds || []
|
||||
},
|
||||
flags: {
|
||||
partyEnabled: false,
|
||||
itemsEnabled: user.items.itemsEnabled || false,
|
||||
kickstarter: user.notifications.kickstarter || 'show',
|
||||
ads: user.flags.ads || null // null because it's set on registration
|
||||
},
|
||||
party: {
|
||||
current: null,
|
||||
invitation: null
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
db.users.update({_id:user._id}, newUser);
|
||||
} catch(e) {
|
||||
print(e);
|
||||
}
|
||||
})
|
||||
19
migrations/20130208_idLists_to_typeIds.js
Normal file
19
migrations/20130208_idLists_to_typeIds.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// move idList back to root-level, is what's causing the sort bug - see https://github.com/codeparty/racer/pull/73
|
||||
|
||||
// We could just delete user.idLists, since it's re-created on refresh. However, users's first refresh will scare them
|
||||
// since everything will dissappear - second refresh will bring everything back.
|
||||
db.users.find().forEach(function(user){
|
||||
if (!user.idLists) return;
|
||||
db.users.update(
|
||||
{_id:user._id},
|
||||
{
|
||||
$set:{
|
||||
'habitIds':user.idLists.habit,
|
||||
'dailyIds':user.idLists.daily,
|
||||
'todoIds':user.idLists.todo,
|
||||
'rewardIds':user.idLists.reward
|
||||
}
|
||||
//$unset:{idLists:true} // run this after the code has been pushed
|
||||
}
|
||||
)
|
||||
})
|
||||
20
migrations/20130208_user_customizations.js
Normal file
20
migrations/20130208_user_customizations.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
db.users.update(
|
||||
{items:{$exists:0}},
|
||||
{$set:{items:{weapon: 0, armor: 0, head: 0, shield: 0 }}},
|
||||
{multi:true}
|
||||
);
|
||||
|
||||
db.users.find().forEach(function(user){
|
||||
|
||||
var updates = {
|
||||
// I'm not racist, these were just the defaults before ;)
|
||||
'preferences.skin': 'white',
|
||||
'preferences.hair': 'blond',
|
||||
|
||||
'items.head': user.items.armor,
|
||||
'items.shield': user.items.armor,
|
||||
}
|
||||
|
||||
db.users.update({_id:user._id}, {$set:updates});
|
||||
|
||||
})
|
||||
39
migrations/20130307_exp_overflow.js
Normal file
39
migrations/20130307_exp_overflow.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
// mongo habitrpg ./node_modules/underscore/underscore.js ./migrations/20130307_normalize_algo_values.js
|
||||
|
||||
/**
|
||||
* Make sure people aren't overflowing their exp with the new system
|
||||
*/
|
||||
db.users.find().forEach(function(user){
|
||||
function oldTnl(level) {
|
||||
return (Math.pow(level,2)*10)+(level*10)+80
|
||||
}
|
||||
|
||||
function newTnl(level) {
|
||||
var value = 0;
|
||||
if (level >= 100) {
|
||||
value = 0
|
||||
} else {
|
||||
value = Math.round(((Math.pow(level,2)*0.25)+(10 * level) + 139.75)/10)*10; // round to nearest 10
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
var newTnl = newTnl(user.stats.lvl);
|
||||
if (user.stats.exp > newTnl) {
|
||||
var percent = user.stats.exp / oldTnl(user.stats.lvl);
|
||||
percent = (percent>1) ? 1 : percent;
|
||||
user.stats.exp = newTnl * percent;
|
||||
|
||||
try {
|
||||
db.users.update(
|
||||
{_id:user._id},
|
||||
{$set: {'stats.exp': user.stats.exp}},
|
||||
{multi:true}
|
||||
);
|
||||
} catch(e) {
|
||||
print(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
47
migrations/20130307_normalize_algo_values.js
Normal file
47
migrations/20130307_normalize_algo_values.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// mongo habitrpg ./node_modules/underscore/underscore.js ./migrations/20130307_normalize_algo_values.js
|
||||
|
||||
/**
|
||||
* Users were experiencing a lot of extreme Exp multiplication (https://github.com/lefnire/habitrpg/issues/594).
|
||||
* This sets things straight, and in preparation for another algorithm overhaul
|
||||
*/
|
||||
db.users.find().forEach(function(user){
|
||||
if (user.stats.exp >= 3580) {
|
||||
user.stats.exp = 0;
|
||||
}
|
||||
|
||||
if (user.stats.lvl > 100) {
|
||||
user.stats.lvl = 100;
|
||||
}
|
||||
|
||||
_.each(user.tasks, function(task, key){
|
||||
// remove corrupt tasks
|
||||
if (!task) {
|
||||
delete user.tasks[key];
|
||||
return;
|
||||
}
|
||||
|
||||
// Fix busted values
|
||||
if (task.value > 21.27) {
|
||||
task.value = 21.27;
|
||||
}
|
||||
else if (task.value < -47.27) {
|
||||
task.value = -47.27;
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
db.users.update(
|
||||
{_id:user._id},
|
||||
{$set:
|
||||
{
|
||||
'stats.lvl': user.stats.lvl,
|
||||
'stats.exp': user.stats.exp,
|
||||
'tasks' : user.tasks
|
||||
}
|
||||
},
|
||||
{multi:true}
|
||||
);
|
||||
} catch(e) {
|
||||
print(e);
|
||||
}
|
||||
})
|
||||
28
migrations/20130307_remove_duff_histories.js
Normal file
28
migrations/20130307_remove_duff_histories.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* Remove duff histories for dailies
|
||||
*/
|
||||
// mongo habitrpg ./node_modules/underscore/underscore.js ./migrations/20130307_remove_duff_histories.js
|
||||
db.users.find().forEach(function(user){
|
||||
|
||||
|
||||
_.each(user.tasks, function(task, key){
|
||||
if (task.type === "daily") {
|
||||
// remove busted history entries
|
||||
task.history = _.filter(task.history, function(h){return !!h.value})
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
db.users.update(
|
||||
{_id:user._id},
|
||||
{$set:
|
||||
{
|
||||
'tasks' : user.tasks
|
||||
}
|
||||
},
|
||||
{multi:true}
|
||||
);
|
||||
} catch(e) {
|
||||
print(e);
|
||||
}
|
||||
})
|
||||
92
migrations/20130326_migrate_pets.js
Normal file
92
migrations/20130326_migrate_pets.js
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/**
|
||||
* Migrate old pets to new system
|
||||
*/
|
||||
// mongo habitrpg ./node_modules/underscore/underscore.js ./migrations/20130326_migrate_pets.js
|
||||
|
||||
var mapping = {
|
||||
bearcub: {name:'BearCub', modifier: 'Base'},
|
||||
cactus: {name:'Cactus', modifier:'Base'},
|
||||
dragon: {name:'Dragon', modifier:'Base'},
|
||||
flyingpig: {name:'FlyingPig', modifier:'Base'},
|
||||
fox: {name:'Fox', modifier:'Base'},
|
||||
lioncub: {name:'LionCub', modifier:'Base'},
|
||||
pandacub: {name:'PandaCub', modifier:'Base'},
|
||||
tigercub: {name:'TigerCub', modifier:'Base'},
|
||||
wolfBorder: {name:'Wolf', modifier:'Base'},
|
||||
wolfDesert: {name:'Wolf', modifier:'Desert'},
|
||||
wolfGolden: {name:'Wolf', modifier:'Golden'},
|
||||
wolfRed: {name:'Wolf', modifier:'Red'},
|
||||
wolfShade: {name:'Wolf', modifier:'Shade'},
|
||||
wolfSkeleton: {name:'Wolf', modifier:'Skeleton'},
|
||||
wolfVeteran: {name:'Wolf', modifier:'Veteran'},
|
||||
wolfWhite: {name:'Wolf', modifier:'White'},
|
||||
wolfZombie: {name:'Wolf', modifier:'Zombie'}
|
||||
}
|
||||
|
||||
/**
|
||||
== Old Style ==
|
||||
pet: Object
|
||||
icon: "Pet-Wolf-White.png"
|
||||
index: 14
|
||||
name: "wolfWhite"
|
||||
text: "White Wolf"
|
||||
value: 3
|
||||
pets: Object
|
||||
bearcub: true
|
||||
cactus: true
|
||||
|
||||
== New Style ==
|
||||
currentPet: Object
|
||||
modifier: "Red"
|
||||
name: "Wolf"
|
||||
notes: "Find some Hatching Powder to sprinkle on this egg, and one day it will hatch into a loyal pet."
|
||||
str: "Wolf-Red"
|
||||
text: "Wolf"
|
||||
value: 3
|
||||
pets: Array
|
||||
0: "PandaCub-Base"
|
||||
1: "Wolf-Base"
|
||||
*/
|
||||
|
||||
|
||||
db.users.find().forEach(function(user){
|
||||
if (!user.items || (!user.items.pets && !user.items.pet)) return;
|
||||
|
||||
// migrate items.pet to items.currentPet
|
||||
if (!!user.items.pet) {
|
||||
var mapped = mapping[user.items.pet.name];
|
||||
delete user.items.pet;
|
||||
user.items.currentPet = {
|
||||
modifier: mapped.modifier,
|
||||
name: mapped.name,
|
||||
str: mapped.name + "-" + mapped.modifier,
|
||||
text: '' // FIXME?
|
||||
}
|
||||
}
|
||||
|
||||
// migrate items.pets
|
||||
if (!!user.items.pets) {
|
||||
var newPets = [];
|
||||
_.each(user.items.pets, function(val, key){
|
||||
if (_.isNumber(key)) {
|
||||
newPets.push(val)
|
||||
//FIXME why is this happening? seems the user gets migrated already...
|
||||
//throw "Error: User appears already migrated, this shouldn't be happening!"
|
||||
} else {
|
||||
newPets.push(mapping[key].name + "-" + mapping[key].modifier);
|
||||
}
|
||||
});
|
||||
user.items.pets = newPets;
|
||||
}
|
||||
|
||||
try {
|
||||
db.users.update(
|
||||
{_id:user._id},
|
||||
{$set:
|
||||
{ 'items' : user.items }
|
||||
}
|
||||
);
|
||||
} catch(e) {
|
||||
print(e);
|
||||
}
|
||||
})
|
||||
104
migrations/20130327_apply_tokens.js
Normal file
104
migrations/20130327_apply_tokens.js
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/**
|
||||
* Applies backer tokens & items (this file will be updated periodically
|
||||
*/
|
||||
|
||||
// mongo habitrpg ./node_modules/underscore/underscore.js migrations/20130327_apply_tokens.js
|
||||
|
||||
var mapping = [
|
||||
{
|
||||
tier: 1,
|
||||
tokens: 0,
|
||||
users: []
|
||||
},
|
||||
{
|
||||
tier: 5,
|
||||
tokens: 20,
|
||||
users: []
|
||||
},
|
||||
{
|
||||
tier: 10,
|
||||
tokens: 50,
|
||||
users: []
|
||||
},
|
||||
{
|
||||
tier: 15,
|
||||
tokens: 100,
|
||||
users: []
|
||||
},
|
||||
{
|
||||
tier: 30,
|
||||
tokens: 150,
|
||||
users: []
|
||||
},
|
||||
{
|
||||
tier: 45,
|
||||
tokens: 170,
|
||||
users: []
|
||||
},
|
||||
{
|
||||
tier: 60,
|
||||
tokens: 200,
|
||||
users: []
|
||||
},
|
||||
{
|
||||
tier: 70,
|
||||
tokens: 240,
|
||||
users: []
|
||||
},
|
||||
{
|
||||
tier: 80,
|
||||
tokens: 240,
|
||||
users: []
|
||||
},
|
||||
{
|
||||
tier: 90,
|
||||
tokens: 280,
|
||||
users: []
|
||||
},
|
||||
{
|
||||
tier: 300,
|
||||
tokens: 500,
|
||||
users: []
|
||||
},
|
||||
{
|
||||
tier: 800,
|
||||
tokens: 500,
|
||||
users: []
|
||||
}
|
||||
];
|
||||
|
||||
db.users.find().forEach(function(user){
|
||||
if (!user._id) return;
|
||||
|
||||
var possibleUserIds = [user._id];
|
||||
if (!!user.local) {
|
||||
if (!!user.local.username) possibleUserIds.push(user.local.username);
|
||||
if (!!user.local.email) possibleUserIds.push(user.local.email);
|
||||
}
|
||||
|
||||
_.each(mapping, function(tier){
|
||||
var userInTier = !_.isEmpty(_.intersection(tier.users, possibleUserIds));
|
||||
if (userInTier) {
|
||||
var tokenInc = 0,
|
||||
backer = user.backer || {};
|
||||
if (!backer.tokensApplied) {
|
||||
tokenInc = tier.tokens;
|
||||
backer.tokensApplied = true;
|
||||
}
|
||||
backer.tier = tier.tier;
|
||||
|
||||
try {
|
||||
db.users.update(
|
||||
{_id:user._id},
|
||||
{
|
||||
$set: { backer: backer, 'flags.ads': 'hide' },
|
||||
$inc: { balance: (tokenInc/4) }
|
||||
}
|
||||
);
|
||||
} catch(e) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
22
migrations/20130503_max_gear_achievement.js
Normal file
22
migrations/20130503_max_gear_achievement.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* For users who already have max gear, they earned the achievement
|
||||
*/
|
||||
// mongo habitrpg ./node_modules/underscore/underscore.js ./migrations/20130503_max_gear_achievement.js
|
||||
db.users.find().forEach(function(user){
|
||||
var items = user.items;
|
||||
if (!items) { return; }
|
||||
if ( parseInt(items.armor) == 5 &&
|
||||
parseInt(items.head) == 5 &&
|
||||
parseInt(items.shield) == 5 &&
|
||||
parseInt(items.weapon) == 6) {
|
||||
|
||||
try {
|
||||
db.users.update(
|
||||
{_id:user._id},
|
||||
{$set: {'achievements.ultimateGear':true}}
|
||||
);
|
||||
} catch(e) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
})
|
||||
12
migrations/20130507_fix_broken_tags.js
Normal file
12
migrations/20130507_fix_broken_tags.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* users getting broken tags when they try to edit the first blank tag on accident
|
||||
*
|
||||
* mongo habitrpg ./node_modules/underscore/underscore.js ./migrations/20130507_fix_broken_tags.js
|
||||
*/
|
||||
|
||||
db.users.find().forEach(function(user){
|
||||
if(!_.isArray(user.tags)) {
|
||||
db.users.update({_id:user._id}, {$set:{tags:[]}});
|
||||
}
|
||||
|
||||
})
|
||||
1
migrations/20130508_add_backer_pets.js
Normal file
1
migrations/20130508_add_backer_pets.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
db.users.update({'backer.tier':{$gte:80}}, {$push:{'items.pets':'Wolf-Cerberus'}}, {multi:true});
|
||||
36
migrations/20130508_fix_duff_party_subscriptions.js
Normal file
36
migrations/20130508_fix_duff_party_subscriptions.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* 745612d and fedc5b6 added a db-subscription optimization to the initial subscribe.
|
||||
* However, it requires the user only be to one party. That should be the case anyway, but user.party.current was letting
|
||||
* us look past the fact that a user was erroneously subscribed to multiple parties. This fixes
|
||||
*
|
||||
* mongo habitrpg ./node_modules/underscore/underscore.js ./migrations/20130508_fix_duff_party_subscriptions.js
|
||||
*/
|
||||
|
||||
|
||||
// since our primary subscription will first hit parties now, we *definitely* need an index there
|
||||
db.parties.ensureIndex( { 'members': 1}, {background: true} );
|
||||
|
||||
db.parties.find().forEach(function(party){
|
||||
|
||||
if(!party.members) {
|
||||
return db.parties.remove({_id:party._id});
|
||||
}
|
||||
|
||||
// Find all members
|
||||
db.users.find( {_id: {$in:party.members} }, {_id:1,party:1} ).forEach(function(user){
|
||||
// user somehow is subscribed to this party in the background, but they're it's not their primary party
|
||||
if (user.party && user.party.current !== party._id) {
|
||||
var i = party.members.indexOf(user._id);
|
||||
party.members.splice(i, 1);
|
||||
}
|
||||
|
||||
// if after we remove the user, the party is empty - delete this party
|
||||
if (_.isEmpty(party.members)) {
|
||||
db.parties.remove({_id:party._id});
|
||||
|
||||
// else just set it
|
||||
} else {
|
||||
db.parties.update({_id:party._id}, {$set:{members:party.members}});
|
||||
}
|
||||
})
|
||||
})
|
||||
48
migrations/20130518_setup_groups.js
Normal file
48
migrations/20130518_setup_groups.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* In adding the Guilds feature (which supports the Challenges feature), we are consolidating parties and guilds
|
||||
* into one collection: groups, with group.type either 'party' or 'guild'. We are also creating the 'habitrpg' guild,
|
||||
* which everyone is auto-subscribed to, and moving tavern chat into that guild
|
||||
*
|
||||
* mongo habitrpg ./node_modules/lodash/lodash.js ./migrations/20130518_setup_groups.js
|
||||
*/
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* 1) rename collection parties => groups
|
||||
* 2) add group.type = 'party' for each current group
|
||||
* 3) create habitrpg group, .type='guild'
|
||||
* 4) move tavern.chat.chat into habitrpg guild
|
||||
* 5) subscribe everyone to habitrpg (be sure to set that for default user too!)
|
||||
*/
|
||||
|
||||
db.parties.renameCollection('groups',true);
|
||||
//db.parties.dropCollection(); // doesn't seem to do this step during rename...
|
||||
//db.parties.ensureIndex( { 'members': 1, 'background': 1} );
|
||||
|
||||
db.groups.update({}, {$set:{type:'party'}}, {multi:true});
|
||||
|
||||
//migrate invitation mechanisms
|
||||
db.users.update(
|
||||
{},
|
||||
{
|
||||
$remove:{party:1},
|
||||
$set:{invitations:{party:null,guilds:[]}}
|
||||
},
|
||||
{multi:1}
|
||||
);
|
||||
|
||||
tavern = db.tavern.findOne();
|
||||
db.tavern.drop();
|
||||
|
||||
//TODO make as a callback of previous, or make sure group.type is still 'guild' for habitrpg in the end
|
||||
db.groups.insert({
|
||||
_id: "habitrpg",
|
||||
leader: '9',
|
||||
type: 'guild',
|
||||
name: "HabitRPG",
|
||||
chat: tavern.messages,
|
||||
info: {
|
||||
blurb: '',
|
||||
websites: []
|
||||
}
|
||||
});
|
||||
25
migrations/20130602_survey_rewards.js
Normal file
25
migrations/20130602_survey_rewards.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
//mongo habitrpg ./node_modules/lodash/lodash.js migrations/20130602_survey_rewards.js
|
||||
|
||||
var members = []
|
||||
members = _.uniq(members);
|
||||
|
||||
var query = {
|
||||
_id: {$exists:1},
|
||||
$or:[
|
||||
{_id: {$in: members}},
|
||||
//{'profile.name': {$in: members}},
|
||||
{'auth.facebook.name': {$in: members}},
|
||||
{'auth.local.username': {$in: members}},
|
||||
{'auth.local.email': {$in: members}}
|
||||
]
|
||||
};
|
||||
|
||||
print(db.users.count(query));
|
||||
|
||||
db.users.update(query,
|
||||
{
|
||||
$set: { 'achievements.helpedHabit': true },
|
||||
$inc: { balance: 2.5 }
|
||||
},
|
||||
{multi:true}
|
||||
)
|
||||
9
migrations/20130612_survey_rewards_individual.js
Normal file
9
migrations/20130612_survey_rewards_individual.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
//mongo habitrpg migrations/20130612_survey_rewards_individual.js
|
||||
|
||||
var query = {_id: ""};
|
||||
|
||||
db.users.update(query,
|
||||
{
|
||||
$set: { 'achievements.helpedHabit': true },
|
||||
$inc: { balance: 2.5 }
|
||||
})
|
||||
4
migrations/20130615_add_extra_indexes.js
Normal file
4
migrations/20130615_add_extra_indexes.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
db.users.ensureIndex( { _id: 1, apiToken: 1 }, {background: true} )
|
||||
db.groups.ensureIndex( { members: 1 }, {background: true} )
|
||||
db.groups.ensureIndex( { type: 1 }, {background: true} )
|
||||
db.groups.ensureIndex( { type: 1, privacy: 1 }, {background: true} )
|
||||
16
migrations/20130908_cleanup_corrupt_tags.js
Normal file
16
migrations/20130908_cleanup_corrupt_tags.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
//mongo habitrpg ./node_modules/lodash/lodash.js migrations/20130908_cleanup_corrupt_tags.js
|
||||
|
||||
// Racer was notorious for adding duplicates, randomly deleting documents, etc. Once we pull the plug on old.habit,
|
||||
// run this migration to cleanup all the corruption
|
||||
|
||||
db.users.find().forEach(function(user){
|
||||
user.tags = _.filter(user.tags, (function(t) {
|
||||
return !!t ? t.id : false;
|
||||
}));
|
||||
|
||||
try {
|
||||
db.users.update({_id:user._id}, {$set:{tags:user.tags}});
|
||||
} catch(e) {
|
||||
print(e);
|
||||
}
|
||||
})
|
||||
51
migrations/20130908_cleanup_derby_corruption.js
Normal file
51
migrations/20130908_cleanup_derby_corruption.js
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
//mongo habitrpg ./node_modules/lodash/lodash.js migrations/20130908_cleanup_derby_corruption.js
|
||||
|
||||
// Racer was notorious for adding duplicates, randomly deleting documents, etc. Once we pull the plug on old.habit,
|
||||
// run this migration to cleanup all the corruption
|
||||
|
||||
db.users.find().forEach(function(user){
|
||||
|
||||
// remove corrupt tasks, which will either be null-value or no id
|
||||
user.tasks = _.reduce(user.tasks, function(m,task,k) {
|
||||
if (!task || !task.id) return m;
|
||||
if (isNaN(+task.value)) task.value = 0;
|
||||
m[k] = task;
|
||||
return m;
|
||||
}, {});
|
||||
|
||||
// fix NaN stats
|
||||
_.each(user.stats, function(v,k) {
|
||||
if (!v || isNaN(+v)) user.stats[k] = 0;
|
||||
return true;
|
||||
});
|
||||
|
||||
// remove duplicates, restore ghost tasks
|
||||
['habit', 'daily', 'todo', 'reward'].forEach(function(type) {
|
||||
var idList = user[type + "Ids"];
|
||||
var taskIds = _.pluck(_.where(user.tasks, {type: type}), 'id');
|
||||
var union = _.union(idList, taskIds);
|
||||
var preened = _.filter(union, function(id) {
|
||||
return id && _.contains(taskIds, id);
|
||||
});
|
||||
if (!_.isEqual(idList, preened)) {
|
||||
user[type + "Ids"] = preened;
|
||||
}
|
||||
});
|
||||
|
||||
// temporarily remove broken eggs. we'll need to write a migration script to grant gems for and remove these instead
|
||||
if (user.items && user.items.eggs) {
|
||||
user.items.eggs = _.filter(user.items.eggs,function(egg){
|
||||
if (_.isString(egg)) {
|
||||
user.balance += 0.75; // give them 3 gems for each broken egg
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
db.users.update({_id:user._id}, user);
|
||||
} catch(e) {
|
||||
print(e);
|
||||
}
|
||||
})
|
||||
37
migrations/20130908_remove_staged_users.js
Normal file
37
migrations/20130908_remove_staged_users.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* Set this up as a midnight cron script
|
||||
*
|
||||
* mongo habitrpg ./migrations/20130908_remove_staged_users.js
|
||||
*/
|
||||
|
||||
/**
|
||||
* If we experience any troubles with removed staging users, come back to a snapshot and restore accounts. This will
|
||||
* give a peak into possible conflict accounts:
|
||||
*/
|
||||
/*db.users.count({
|
||||
"auth.local": {$exists: false},
|
||||
"auth.facebook": {$exists: false},
|
||||
"history.exp.5": {$exists: 1},
|
||||
$where: "this.todoIds.length != 1 && this.dailyIds.length != 3 && this.habitIds.length != 3 && this.rewardIds.length != 2"
|
||||
})*/
|
||||
|
||||
/**
|
||||
* Users used to be allowed to experiment with the site before registering. Every time a new browser visits habitrpg, a new
|
||||
* "staged" account is created - and if the user later registers, that staged account is considered a "production" account.
|
||||
* This function removes all staged accounts, since the new site doesn't supported staged accounts, and when we add that feature
|
||||
* in we'll be using localStorage anyway instead of creating a new database record
|
||||
*/
|
||||
db.users.remove({
|
||||
// Un-registered users
|
||||
"auth.local": {$exists: false},
|
||||
"auth.facebook": {$exists: false}
|
||||
});
|
||||
|
||||
/**
|
||||
* Remove empty parties
|
||||
* Another vestige of Racer. Empty parties shouldn't be being created anymore in the new site
|
||||
*/
|
||||
db.groups.remove({
|
||||
'type': 'party',
|
||||
$where: "return this.members.length === 0"
|
||||
});
|
||||
5
migrations/20131022_purchased_and_newStuff.js
Normal file
5
migrations/20131022_purchased_and_newStuff.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
db.users.find().forEach(function(user){
|
||||
if (!user.purchased) user.purchased = {hair: {}, skin: {}};
|
||||
user.purchased.ads = user.flags && !!user.flags.ads;
|
||||
db.users.update({_id:user._id}, {$set:{'purchased': user.purchased, 'flags.newStuff': true}, $unset: {'flags.ads':1}});
|
||||
});
|
||||
12
migrations/20131022_restore_ads.js
Normal file
12
migrations/20131022_restore_ads.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// node .migrations/20131022_restore_ads.js
|
||||
var mongo = require('mongoskin');
|
||||
var _ = require('lodash');
|
||||
var dbBackup = mongo.db('localhost:27017/habitrpg?auto_reconnect');
|
||||
var dbLive = mongo.db('localhost:27017/habitrpg2?auto_reconnect');
|
||||
var count = 89474;
|
||||
dbBackup.collection('users').findEach({$or: [{'flags.ads':'show'}, {'flags.ads': null}]}, {batchSize:10}, function(err, item) {
|
||||
if (err) return console.error({err:err});
|
||||
if (!item || !item._id) return console.error('blank user');
|
||||
dbLive.collection('users').update({_id:item._id}, {$set:{'purchased.ads':false}, $unset: {'flags.ads': 1}});
|
||||
if (--count <= 0) console.log("DONE!");
|
||||
});
|
||||
126
migrations/20131028_task_subdocs_tags_invites.js
Normal file
126
migrations/20131028_task_subdocs_tags_invites.js
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
// mongo habitrpg ./node_modules/lodash/lodash.js ./migrations/20131028_task_subdocs_tags_invites.js
|
||||
|
||||
// TODO it might be better we just find() and save() all user objects using mongoose, and rely on our defined pre('save')
|
||||
// and default values to "migrate" users. This way we can make sure those parts are working properly too
|
||||
// @see http://stackoverflow.com/questions/14867697/mongoose-full-collection-scan
|
||||
//Also, what do we think of a Mongoose Migration module? something like https://github.com/madhums/mongoose-migrate
|
||||
|
||||
db.users.find().forEach(function(user){
|
||||
|
||||
// Add invites to groups
|
||||
// -------------------------
|
||||
if(user.invitations){
|
||||
if(user.invitations.party){
|
||||
db.groups.update({_id: user.invitations.party.id}, {$addToSet:{invites:user._id}});
|
||||
}
|
||||
|
||||
if(user.invitations.guilds){
|
||||
_.each(user.invitations.guilds, function(guild){
|
||||
db.groups.update({_id: guild.id}, {$addToSet:{invites:user._id}});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup broken tags
|
||||
// -------------------------
|
||||
_.each(user.tasks, function(task){
|
||||
_.each(task.tags, function(val, key){
|
||||
_.each(user.tags, function(tag){
|
||||
if(key == tag.id) delete task.tags[key];
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Fix corrupt dates
|
||||
// -------------------------
|
||||
user.lastCron = new Date(user.lastCron);
|
||||
if (user.lastCron == 'Invalid Date') user.lastCron = new Date();
|
||||
if (user.auth) { // what to do with !auth?
|
||||
_.defaults(user.auth, {timestamps: {created:undefined, loggedin: undefined}});
|
||||
_.defaults(user.auth.timestamps, {created: new Date(user.lastCron), loggedin: new Date(user.lastCron)});
|
||||
}
|
||||
|
||||
// Fix missing history
|
||||
// -------------------------
|
||||
_.defaults(user, {history:{}});
|
||||
_.defaults(user.history,{exp:[], todos:[]});
|
||||
|
||||
// Add username
|
||||
// -------------------------
|
||||
if (!user.profile) user.profile = {name:undefined};
|
||||
if (_.isEmpty(user.profile.name) && user.auth) {
|
||||
var fb = user.auth.facebook;
|
||||
user.profile.name =
|
||||
(user.auth.local && user.auth.local.username) ||
|
||||
(fb && (fb.displayName || fb.name || fb.username || (fb.first_name && fb.first_name + ' ' + fb.last_name))) ||
|
||||
'Anonymous';
|
||||
}
|
||||
|
||||
// Migrate to TaskSchema Sub-Docs!
|
||||
// -------------------------
|
||||
if (!user.tasks) {
|
||||
// So evidentaly users before 02/2013 were ALREADY setup based on habits[], dailys[], etcs... I don't remember our schema
|
||||
// ever being that way... Anyway, print busted users here (they don't have tasks, but also don't have the right schema)
|
||||
if (!user.habits || !user.dailys || !user.todos || !user.rewards) {
|
||||
print(user._id);
|
||||
}
|
||||
} else {
|
||||
_.each(['habit', 'daily', 'todo', 'reward'], function(type) {
|
||||
// we use _.transform instead of a simple _.where in order to maintain sort-order
|
||||
user[type + "s"] = _.reduce(user[type + "Ids"], function(m, tid) {
|
||||
var task = user.tasks[tid],
|
||||
newTask = {};
|
||||
if (!task) return m; // remove null tasks
|
||||
|
||||
// Cleanup tasks for TaskSchema
|
||||
newTask._id = newTask.id = task.id;
|
||||
newTask.text = (_.isString(task.text)) ? task.text : '';
|
||||
if (_.isString(task.notes)) newTask.notes = task.notes;
|
||||
newTask.tags = (_.isObject(task.tags)) ? task.tags : {};
|
||||
newTask.type = (_.isString(task.type)) ? task.type : 'habit';
|
||||
newTask.value = (_.isNumber(task.value)) ? task.value : 0;
|
||||
newTask.priority = (_.isString(task.priority)) ? task.priority : '!';
|
||||
switch (newTask.type) {
|
||||
case 'habit':
|
||||
newTask.up = (_.isBoolean(task.up)) ? task.up : true;
|
||||
newTask.down = (_.isBoolean(task.down)) ? task.down : true;
|
||||
newTask.history = (_.isArray(task.history)) ? task.history : [];
|
||||
break;
|
||||
case 'daily':
|
||||
newTask.repeat = (_.isObject(task.repeat)) ? task.repeat : {m:1, t:1, w:1, th:1, f:1, s:1, su:1};
|
||||
newTask.streak = (_.isNumber(task.streak)) ? task.streak : 0;
|
||||
newTask.completed = (_.isBoolean(task.completed)) ? task.completed : false;
|
||||
newTask.history = (_.isArray(task.history)) ? task.history : [];
|
||||
break;
|
||||
case 'todo':
|
||||
newTask.completed = (_.isBoolean(task.completed)) ? task.completed : false;
|
||||
break;
|
||||
}
|
||||
|
||||
m.push(newTask);
|
||||
return m;
|
||||
}, []);
|
||||
delete user[type + 'Ids'];
|
||||
});
|
||||
delete user.tasks;
|
||||
}
|
||||
|
||||
try {
|
||||
db.users.update({_id:user._id}, user);
|
||||
} catch(e) {
|
||||
print(e);
|
||||
}
|
||||
});
|
||||
|
||||
// Remove old groups.*.challenges, they're not compatible with the new system, set member counts
|
||||
// -------------------------
|
||||
db.groups.find().forEach(function(group){
|
||||
db.groups.update({_id:group._id}, {
|
||||
$set:{memberCount: _.size(group.members)},
|
||||
$pull:{challenges:1}
|
||||
})
|
||||
});
|
||||
|
||||
// HabitRPG => Tavern
|
||||
// -------------------------
|
||||
db.groups.update({_id:'habitrpg'}, {$set:{name:'Tavern'}});
|
||||
25
migrations/20131102_restore_task_ids.js
Normal file
25
migrations/20131102_restore_task_ids.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// mongo habitrpg ./node_modules/lodash/lodash.js ./migrations/20131028_task_subdocs_tags_invites.js
|
||||
|
||||
db.challenges.find().forEach(function(chal){
|
||||
_.each(chal.habits.concat(chal.dailys).concat(chal.todos).concat(chal.rewards), function(task){
|
||||
task.id = task.id || task._id;
|
||||
})
|
||||
try {
|
||||
db.challenges.update({_id:chal._id}, chal);
|
||||
db.groups.update({_id:chal.group}, {$addToSet:{challenges:chal._id}})
|
||||
} catch(e) {
|
||||
print(e);
|
||||
}
|
||||
});
|
||||
|
||||
db.users.find().forEach(function(user){
|
||||
_.each(user.habits.concat(user.dailys).concat(user.todos).concat(user.rewards), function(task){
|
||||
task.id = task.id || task._id;
|
||||
})
|
||||
try {
|
||||
db.users.update({_id:user._id}, user);
|
||||
} catch(e) {
|
||||
print(e);
|
||||
}
|
||||
});
|
||||
|
||||
7
migrations/20131104_remove_invalid_dues.js
Normal file
7
migrations/20131104_remove_invalid_dues.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
db.users.find({},{todos:1}).forEach(function(user){
|
||||
_.each(user.todos, function(task){
|
||||
if (moment(task.date).toDate() == 'Invalid Date')
|
||||
task.date = moment().format('MM/DD/YYYY');
|
||||
})
|
||||
db.users.update({_id:user._id}, {$set:{todos: user.todos}});
|
||||
});
|
||||
54
migrations/20131104_restore_lost_task_data.js
Normal file
54
migrations/20131104_restore_lost_task_data.js
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
// node .migrations/20131104_restore_lost_task_data.js
|
||||
|
||||
/**
|
||||
* After the great challenges migration, quite a few things got inadvertently dropped from tasks since their
|
||||
* schemas became more strict. See conversation at https://github.com/HabitRPG/habitrpg/issues/1712 ,
|
||||
* this restores task tags, streaks, due-dates, values
|
||||
*/
|
||||
var mongo = require('mongoskin');
|
||||
var _ = require('lodash');
|
||||
|
||||
var backupUsers = mongo.db('localhost:27017/habitrpg_old?auto_reconnect').collection('users');
|
||||
var liveUsers = mongo.db('localhost:27017/habitrpg_new?auto_reconnect').collection('users');
|
||||
|
||||
backupUsers.count(function(err, count){
|
||||
if (err) return console.error(err);
|
||||
backupUsers.findEach({}, {batchSize:250}, function(err, before){
|
||||
if (err) return console.error(err);
|
||||
if (!before) return console.log('!before');
|
||||
liveUsers.findById(before._id, function(err, after){
|
||||
if (err) return console.error(err);
|
||||
if (!after) {
|
||||
count--;
|
||||
return console.log(before._id + ' deleted?');
|
||||
}
|
||||
if (before._id == '9') console.log('lefnire processed');
|
||||
_.each(before.tasks, function(tBefore){
|
||||
var tAfter = _.find(after[tBefore.type+'s'], {id:tBefore.id});
|
||||
if (!tAfter) return; // task has been deleted since launch
|
||||
|
||||
// Restore deleted tags
|
||||
if (!_.isEmpty(tBefore.tags) && _.isEmpty(tAfter.tags))
|
||||
tAfter.tags = tBefore.tags;
|
||||
// Except tags which are no longer available on the updated user
|
||||
_.each(tAfter.tags, function(v,k){ //value is true, key is tag.id
|
||||
if (!_.find(after.tags,{id:k})) delete tAfter.tags[k];
|
||||
})
|
||||
|
||||
// Restore deleted streaks
|
||||
if (+tBefore.streak > tAfter.streak)
|
||||
tAfter.streak = +tBefore.streak;
|
||||
|
||||
if (!!tBefore.date && !tAfter.date)
|
||||
tAfter.date = tBefore.date;
|
||||
|
||||
// Restore deleted values
|
||||
if (+tBefore.value != 0 && tAfter.value == 0)
|
||||
tAfter.value = +tBefore.value;
|
||||
})
|
||||
after._v++;
|
||||
liveUsers.update({_id:after._id}, after);
|
||||
if (--count <= 0) console.log("DONE!");
|
||||
})
|
||||
});
|
||||
});
|
||||
21
migrations/20131105_remove_history_ids.js
Normal file
21
migrations/20131105_remove_history_ids.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
function deleteId(h){
|
||||
delete h._id;
|
||||
}
|
||||
|
||||
db.users.find({},{habits:1,dailys:1,history:1}).forEach(function(user){
|
||||
if (user.history) {
|
||||
_.each(['todos','exp'], function(type){
|
||||
if (user.history[type]) {
|
||||
_.each(user.history.exp, deleteId);
|
||||
}
|
||||
})
|
||||
} else {
|
||||
user.history = {exp:[],todos:[]};
|
||||
}
|
||||
|
||||
_.each(['habits', 'dailys'], function(type){
|
||||
_.each(user[type].history, deleteId);
|
||||
});
|
||||
|
||||
db.users.update({_id:user._id}, {$set:{history: user.history, habits: user.habits, dailys: user.dailys}});
|
||||
});
|
||||
18
migrations/20131107_from_backer_to_contributor.js
Normal file
18
migrations/20131107_from_backer_to_contributor.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
db.users.find({
|
||||
$or: [
|
||||
{'backer.admin':{$exists:1}},
|
||||
{'backer.contributor':{$exists:1}}
|
||||
]
|
||||
},{backer:1}).forEach(function(user){
|
||||
user.contributor = {};
|
||||
user.contributor.admin = user.backer.admin;
|
||||
delete user.backer.admin;
|
||||
|
||||
// this isnt' the proper storage format, but I'm going to be going through the admin utility manually and setting things properly
|
||||
if (user.backer.contributor) {
|
||||
user.contributor.text = user.backer.contributor;
|
||||
delete user.backer.contributor;
|
||||
}
|
||||
|
||||
db.users.update({_id:user._id}, {$set:{backer:user.backer, contributor:user.contributor}});
|
||||
});
|
||||
4
migrations/20131108_add_gems_for_contribs.js
Normal file
4
migrations/20131108_add_gems_for_contribs.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// Increase everyone's gems per their contribution level
|
||||
db.users.find({'contributor.level':{$gt:0}},{contributor:1, balance:1}).forEach(function(user){
|
||||
db.users.update({_id:user._id}, {$inc: {balance: (user.contributor.level * .5)} });
|
||||
});
|
||||
39
migrations/20131109_refactor_pets.js
Normal file
39
migrations/20131109_refactor_pets.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
db.users.find(
|
||||
{$where: "Array.isArray(this.items.pets) || Array.isArray(this.items.eggs) || Array.isArray(this.items.hatchingPotions)"},
|
||||
{backer: 1, items:1}
|
||||
).forEach(function(user){
|
||||
|
||||
if (_.isArray(user.items.pets)) {
|
||||
user.items.pets = _.reduce(user.items.pets, function(m,v){ m[v] = 5; return m;}, {});
|
||||
}
|
||||
|
||||
if (!_.isString(user.items.currentPet)) {
|
||||
user.items.currentPet = user.items.currentPet ? user.items.currentPet.str : '';
|
||||
}
|
||||
|
||||
if (_.isArray(user.items.eggs)) {
|
||||
user.items.eggs = _.reduce(user.items.eggs, function(m,v){
|
||||
if (!m[v.name]) m[v.name] = 0;
|
||||
m[v.name]++;
|
||||
return m;
|
||||
}, {});
|
||||
}
|
||||
|
||||
if (_.isArray(user.items.hatchingPotions)) {
|
||||
user.items.hatchingPotions = _.reduce(user.items.hatchingPotions, function(m,v){
|
||||
if (!m[v]) m[v] = 0;
|
||||
m[v]++;
|
||||
return m;
|
||||
}, {});
|
||||
}
|
||||
|
||||
user.items.food = {};
|
||||
user.items.mounts = {};
|
||||
user.items.currentMount = '';
|
||||
|
||||
if (user.backer && user.backer.tier && user.backer.tier >= 90) {
|
||||
user.items.mounts['LionCub-Ethereal'] = true;
|
||||
}
|
||||
|
||||
db.users.update({_id:user._id}, {$set:{items:user.items}});
|
||||
});
|
||||
15
migrations/20131111_task_NaN.js
Normal file
15
migrations/20131111_task_NaN.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// This migration has already been run in the past. It's vital to fix these users presently, but we need to find
|
||||
// out why task values are ever getting in as NaN. My guess is API PUT /tasks/:tid routes
|
||||
db.users.find({},{habits:1,dailys:1,todos:1,rewards:1}).forEach(function(user){
|
||||
_.each(['habits','dailys','todos','rewards'], function(type){
|
||||
_.each(user[type], function(task){
|
||||
task.value = +task.value;
|
||||
if (_.isNaN(task.value)) {
|
||||
task.value = 0;
|
||||
print(user._id);
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
db.users.update({_id:user._id}, {$set:{habits: user.habits, dailys: user.dailys, todos: user.todos, rewards: user.rewards}});
|
||||
});
|
||||
14
migrations/20131114_migrate_websites_to_blurb.js
Normal file
14
migrations/20131114_migrate_websites_to_blurb.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// Migrate all users websites to the profile blurb field
|
||||
db.users.find({'profile.websites':{$exists: true}}).forEach(function(user){
|
||||
db.users.update({_id: user._id}, {
|
||||
$set: {"profile.blurb": user.profile.blurb + '\n * ' + user.profile.websites.join('\n * ')},
|
||||
$unset: {'profile.websites': 1}
|
||||
})
|
||||
})
|
||||
|
||||
db.groups.find({'websites.0':{$exists: true}}).forEach(function(group){
|
||||
db.groups.update({_id: group._id}, {
|
||||
$set: {"description": group.description + '\n * ' + group.websites.join('\n * ')},
|
||||
$unset: {websites: 1}
|
||||
})
|
||||
})
|
||||
10
migrations/20131115_update_gear_preferences.js
Normal file
10
migrations/20131115_update_gear_preferences.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
//Add defaults to show gears in all users
|
||||
db.users.update(
|
||||
{},
|
||||
{$set:{
|
||||
'preferences.showWeapon': true,
|
||||
'preferences.showShield': true,
|
||||
'preferences.showArmor': true,
|
||||
}},
|
||||
{multi:true}
|
||||
)
|
||||
18
migrations/20131117_fix_task_types.js
Normal file
18
migrations/20131117_fix_task_types.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
// TODO figure out why this is happening in the first place
|
||||
|
||||
db.users.find({},{habits:1, dailys:1, todos:1, rewards:1}).forEach(function(user){
|
||||
_.each(user.habits, function(task){
|
||||
task.type = 'habit';
|
||||
})
|
||||
_.each(user.dailys, function(task){
|
||||
task.type = 'daily';
|
||||
})
|
||||
_.each(user.todos, function(task){
|
||||
task.type = 'todo';
|
||||
})
|
||||
_.each(user.rewards, function(task){
|
||||
task.type = 'reward';
|
||||
})
|
||||
|
||||
db.users.update({_id:user._id}, {$set:{habits: user.habits, dailys: user.dailys, todos: user.todos, rewards: user.rewards}});
|
||||
});
|
||||
12
migrations/20131117_remove_undefined_pets.js
Normal file
12
migrations/20131117_remove_undefined_pets.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// once and for all!
|
||||
|
||||
db.users.find({'items.pets':{$exists:1}},{'items.pets':1}).forEach(function(user){
|
||||
_.reduce(user.items.pets, function(m,v,k){
|
||||
if (!k.indexOf('undefined')) m.push(k);
|
||||
return m;
|
||||
}, []).forEach(function(key){
|
||||
delete user.items.pets[key];
|
||||
})
|
||||
|
||||
db.users.update({_id:user._id}, { $set:{'items.pets':user.items.pets} });
|
||||
});
|
||||
13
migrations/20131122_deleted_tags.js
Normal file
13
migrations/20131122_deleted_tags.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// Cleanup broken tags
|
||||
// -------------------------
|
||||
db.users.find().forEach(function(user){
|
||||
var tasks = user.habits.concat(user.dailys).concat(user.todos).concat(user.rewards);
|
||||
|
||||
_.each(tasks, function(task){
|
||||
_.each(task.tags, function(value, key){ //value is true, key is tag.id
|
||||
if (!_.find(user.tags,{id:key})) delete task.tags[key];
|
||||
});
|
||||
});
|
||||
|
||||
db.users.update({_id:user._id}, user);
|
||||
});
|
||||
8
migrations/20131123_set_default_party_order.js
Normal file
8
migrations/20131123_set_default_party_order.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
//Add default to randomize party members list
|
||||
db.users.update(
|
||||
{},
|
||||
{$set:{
|
||||
'party.order': 'random',
|
||||
}},
|
||||
{multi:true}
|
||||
)
|
||||
5
migrations/20131126_clean_dayStart.js
Normal file
5
migrations/20131126_clean_dayStart.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
db.users.find({'preferences.dayStart':{$exists:1}},{'preferences.dayStart':1}).forEach(function(user){
|
||||
var dayStart = +user.preferences.dayStart;
|
||||
dayStart = (_.isNaN(dayStart) || dayStart < 0 || dayStart > 24) ? 0 : dayStart;
|
||||
db.users.update({_id:user._id}, {$set:{'preferences.dayStart':dayStart}});
|
||||
});
|
||||
1
migrations/20131126_turkey_pet.js
Normal file
1
migrations/20131126_turkey_pet.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
db.users.update({},{$set:{'items.pets.Turkey-Base':5, 'flags.newStuff':true}}, {multi:true});
|
||||
32
migrations/20131127_restore_dayStart.js
Normal file
32
migrations/20131127_restore_dayStart.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// node .migrations/20131127_restore_dayStart.js
|
||||
|
||||
var mongo = require('mongoskin');
|
||||
var _ = require('lodash');
|
||||
|
||||
var backupUsers = mongo.db('localhost:27017/habitrpg_old?auto_reconnect').collection('users');
|
||||
var liveUsers = mongo.db('localhost:27017/habitrpg_new?auto_reconnect').collection('users');
|
||||
|
||||
var query = {'preferences.dayStart':{$exists:1,$ne:0}};
|
||||
var select = {'preferences.dayStart': 1};
|
||||
|
||||
backupUsers.count(query, function(err, count){
|
||||
if (err) return console.error(err);
|
||||
backupUsers.findEach(query, select, {batchSize:20}, function(err, before){
|
||||
if (err) return console.error(err);
|
||||
if (!before) { count--; return console.log('!before'); }
|
||||
liveUsers.findById(before._id, function(err, after){
|
||||
if (err) return console.error(err);
|
||||
if (!after) { count--; return console.log(before._id + ' deleted?'); }
|
||||
|
||||
var dayStart = +before.preferences.dayStart;
|
||||
if (after.preferences.dayStart == 0 && dayStart != 0){
|
||||
dayStart = (_.isNaN(dayStart) || dayStart < 0 || dayStart > 24) ? 0 : dayStart;
|
||||
} else {
|
||||
dayStart = after.preferences.dayStart;
|
||||
}
|
||||
|
||||
liveUsers.update({_id:after._id}, {$inc:{_v:1}, $set:{'preferences.dayStart':dayStart}});
|
||||
if (--count <= 0) console.log("DONE!");
|
||||
})
|
||||
});
|
||||
});
|
||||
133
migrations/20131214_classes.coffee
Normal file
133
migrations/20131214_classes.coffee
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
###
|
||||
Huge migration:
|
||||
- Customization Redo: https://trello.com/c/YKXmHNjY/306-customization-redo
|
||||
- Armory: https://trello.com/c/83M5RqQB/299-armory
|
||||
- Classes
|
||||
###
|
||||
mongo = require('mongoskin')
|
||||
_ = require('lodash')
|
||||
async = require('async')
|
||||
|
||||
db = mongo.db('localhost:27017/habitrpg?auto_reconnect')
|
||||
|
||||
###
|
||||
Migrate users
|
||||
###
|
||||
query = {migration:{$ne:'20131214_classes'}}
|
||||
users = db.collection('users')
|
||||
users.count query, (err, count) ->
|
||||
console.log {count}
|
||||
return console.error(err) if err
|
||||
users.findEach query, {batchSize:500}, (err, user) ->
|
||||
unless user then err = 'Blank user';count--
|
||||
return console.log(err) if err
|
||||
|
||||
# --------- Misc ---------
|
||||
user.stats ?= {}
|
||||
_.defaults user.purchased, {hair: {}, skin: {}, balance: 0}
|
||||
user.preferences.sleep = user.flags.rest
|
||||
delete user.flags.rest
|
||||
delete user.preferences.armorSet
|
||||
|
||||
# --------- Class System ---------
|
||||
_.defaults user.stats,
|
||||
class: "warrior"
|
||||
str: 0
|
||||
con: 0
|
||||
int: 0
|
||||
per: 0
|
||||
buffs: {}
|
||||
points: user.stats.lvl
|
||||
mp: (user.stats.lvl - 1) / 2 + 10
|
||||
user.flags.classSelected = false
|
||||
|
||||
# --------- Gender ---------
|
||||
user.preferences.size = if (user.preferences.gender is "f") then "slim" else "broad"
|
||||
user.preferences.shirt = if (user.preferences.gender is "f") then "pink" else "white"
|
||||
user.preferences.hair =
|
||||
color: user.preferences.hair
|
||||
base: if user.preferences.gender is "f" then 1 else 0
|
||||
bangs: if user.preferences.gender is "f" then 1 else 3
|
||||
beard: 0
|
||||
mustache: 0
|
||||
|
||||
delete user.preferences.gender
|
||||
|
||||
# --------- Skin (see https://trello.com/c/YKXmHNjY/306-customization-redo) ---------
|
||||
user.preferences.skin = switch user.preferences.skin
|
||||
when "asian" then "ddc994"
|
||||
when "white" then "f5a76e"
|
||||
when "black" then "915533"
|
||||
when "dead" then "c3e1dc"
|
||||
when "orc" then "6bd049"
|
||||
else user.preferences.skin
|
||||
|
||||
# --------- Gear ---------
|
||||
gear =
|
||||
owned: {}
|
||||
equipped: {}
|
||||
costume: {}
|
||||
|
||||
_.each {head: "showHelm", weapon: "showWeapon", shield: "showShield", armor: "showArmor"}, (show, type) ->
|
||||
user.items[type] = unless 0 < ~~user.items[type] < 9 then 0 else ~~user.items[type]
|
||||
_.times user.items[type]+1, (i) -> #+1 since 0 is significant
|
||||
item =
|
||||
if type is 'weapon'
|
||||
if i > 8 then 'weapon_warrior_6'
|
||||
else if i is 8 then "weapon_special_1"
|
||||
else if i is 7 then "weapon_special_0"
|
||||
else "weapon_warrior_#{i}"
|
||||
else
|
||||
if i > 7 then "#{type}_warrior_5"
|
||||
else if i is 7 then "#{type}_special_1"
|
||||
else if i is 6 then "#{type}_special_0"
|
||||
else if i is 0 then "#{type}_base_0"
|
||||
else "#{type}_warrior_#{i}"
|
||||
gear.owned[item] = true unless item is "#{type}_base_0"
|
||||
gear.equipped[type] = item
|
||||
|
||||
# # TODO how to handle combo of wearing / hiding?
|
||||
# if user.preferences[show] is false
|
||||
# gear.costume[type] = type + '_base_0';
|
||||
# user.preferences.costume = true;
|
||||
|
||||
delete user.preferences[show]
|
||||
delete user.items[type]
|
||||
user.items.gear = gear
|
||||
|
||||
# --------- Tasks ---------
|
||||
_.each user.habits.concat(user.dailys).concat(user.todos).concat(user.rewards), (task) ->
|
||||
# migrate task.priority from !, !!, !!! => 1, 1.5, 2
|
||||
task.priority = switch task.priority
|
||||
when "!!!" then 2
|
||||
when "!!" then 1.5
|
||||
else 1
|
||||
|
||||
# Add task attributes
|
||||
task.attribute = "str"
|
||||
|
||||
user.migration = '20131214_classes'
|
||||
users.update {_id: user._id}, user
|
||||
console.log("USERS DONE") if --count <= 0
|
||||
console.log(count) if count%1000 is 0
|
||||
console.log('lefnire processed') if user._id is '9'
|
||||
|
||||
###
|
||||
Need to also migrate challenges.tasks
|
||||
###
|
||||
challenges = db.collection('challenges')
|
||||
challenges.count (err, count) ->
|
||||
return console.error(err) if err
|
||||
challenges.findEach {}, {batchSize:500}, (err, challenge) ->
|
||||
unless challenge then err = 'Blank challenge';count--
|
||||
return console.log(err) if err
|
||||
_.each challenge.habits.concat(challenge.dailys).concat(challenge.todos).concat(challenge.rewards), (task) ->
|
||||
# migrate task.priority from !, !!, !!! => 1, 1.5, 2
|
||||
task.priority = switch task.priority
|
||||
when "!!!" then 2
|
||||
when "!!" then 1.5
|
||||
else 1
|
||||
# Add task attributes
|
||||
task.attribute = "str"
|
||||
challenges.update {_id: challenge._id}, challenge
|
||||
console.log("CHALLENGES DONE") if --count <= 0
|
||||
20
migrations/20131217_unearned_backer_gear.js
Normal file
20
migrations/20131217_unearned_backer_gear.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
var query = {
|
||||
'$or': [
|
||||
{'items.gear.owned.weapon_special_0': true},
|
||||
{'items.gear.owned.armor_special_0': true},
|
||||
{'items.gear.owned.head_special_0': true},
|
||||
{'items.gear.owned.shield_special_0': true}
|
||||
]
|
||||
};
|
||||
|
||||
db.users.find(query, {'items.gear.owned':1,backer:1}).forEach(function(user){
|
||||
var owned = user.items.gear.owned;
|
||||
var tier = (user.backer && user.backer.tier) || 0;
|
||||
if (tier < 70) delete owned.weapon_special_0;
|
||||
if (tier < 45) delete owned.armor_special_0;
|
||||
if (tier < 45) delete owned.head_special_0;
|
||||
if (tier < 45) delete owned.shield_special_0;
|
||||
|
||||
|
||||
db.users.update({_id:user._id}, {$set:{'items.gear.owned':owned}});
|
||||
});
|
||||
45
migrations/20131221_restore_NaN_history.js
Normal file
45
migrations/20131221_restore_NaN_history.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// node .migrations/20131221_restore_NaN_history.js
|
||||
|
||||
/**
|
||||
* After the classes migration, users lost some history entries
|
||||
*/
|
||||
var mongo = require('mongoskin');
|
||||
var _ = require('lodash');
|
||||
|
||||
var backupUsers = mongo.db('localhost:27017/habitrpg_old?auto_reconnect').collection('users');
|
||||
var liveUsers = mongo.db('localhost:27017/habitrpg?auto_reconnect').collection('users');
|
||||
|
||||
function filterNaNs(h) {
|
||||
return h && _.isNumber(+h.value) && !_.isNaN(+h.value);
|
||||
}
|
||||
|
||||
var fields = {history:1,habits:1,dailys:1,migration:1};
|
||||
var count = 0;
|
||||
liveUsers.findEach({migration: {$ne:'20131221_restore_NaN_history'}}, fields, {batchSize:500}, function(err, after){
|
||||
if (!after) err = '!after';
|
||||
if (err) {count++;return console.error(err);}
|
||||
|
||||
backupUsers.findById(after._id, fields, function(err, before){
|
||||
if (err) {count++;return console.error(err);}
|
||||
|
||||
_.each(['todos','exp'],function(type){
|
||||
if (!_.isEmpty(after.history[type]))
|
||||
after.history[type] = _.filter(after.history[type], filterNaNs);
|
||||
if (before && !_.isEmpty(before.history[type]))
|
||||
after.history[type] = before.history[type].concat(after.history[type]);
|
||||
})
|
||||
|
||||
_.each(['habits','dailys'], function(type){
|
||||
_.each(after[type], function(t){
|
||||
t.history = _.filter(t.history, filterNaNs);
|
||||
var found = before && _.find(before[type],{id:t.id});
|
||||
if (found && found.history) t.history = found.history.concat(t.history);
|
||||
})
|
||||
})
|
||||
|
||||
liveUsers.update({_id:after._id}, {$set:{history:after.history, dailys:after.dailys, habits:after.habits, migration:'20131221_restore_NaN_history'}, $inc:{_v:1}});
|
||||
//if (--count <= 0) console.log("DONE! " + after._id);
|
||||
if (++count%1000 == 0) console.log(count);
|
||||
if (after._id == '9') console.log('lefnire processed');
|
||||
})
|
||||
});
|
||||
32
migrations/20131225_restore_streaks.js
Normal file
32
migrations/20131225_restore_streaks.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// node .migrations/20131225_restore_streaks.js
|
||||
|
||||
/**
|
||||
* After the classes migration, users lost some history entries
|
||||
*/
|
||||
var mongo = require('mongoskin');
|
||||
var _ = require('lodash');
|
||||
|
||||
var backupUsers = mongo.db('localhost:27017/habitrpg_old?auto_reconnect').collection('users');
|
||||
var liveUsers = mongo.db('lefnire:mAdn3s5s@charlotte.mongohq.com:10015/habitrpg_large?auto_reconnect').collection('users');
|
||||
|
||||
var fields = {dailys:1,migration:1};
|
||||
var count = 0;
|
||||
liveUsers.findEach({migration: {$ne:'20131225_restore_streaks'}}, fields, {batchSize:250}, function(err, after){
|
||||
if (!after) err = '!after';
|
||||
if (err) {count++;return console.error(err);}
|
||||
|
||||
backupUsers.findById(after._id, fields, function(err, before){
|
||||
if (!before) err = '!before';
|
||||
if (err) {count++;return console.error(err);}
|
||||
|
||||
_.each(before.dailys,function(d){
|
||||
var found = _.find(after.dailys,{id: d.id});
|
||||
if (found && !found.streak) found.streak = d.streak;
|
||||
})
|
||||
|
||||
liveUsers.update({_id:after._id}, {$set:{dailys:after.dailys, migration:'20131225_restore_streaks'}, $inc:{_v:1}});
|
||||
//if (--count <= 0) console.log("DONE! " + after._id);
|
||||
if (++count%1000 == 0) console.log(count);
|
||||
if (after._id == '9') console.log('lefnire processed');
|
||||
})
|
||||
});
|
||||
8
migrations/20140119_task_creation_completion_dates.js
Normal file
8
migrations/20140119_task_creation_completion_dates.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
db.users.find({},{todos:1,dailys:1,rewards:1,habits:1}).forEach(function(user){
|
||||
_.each(user.habits.concat(user.dailys).concat(user.todos).concat(user.rewards), function(t){
|
||||
t.dateCreated = t.created || new Date;
|
||||
delete t.created;
|
||||
if (t.type == 'todo' && t.completed) t.dateCompleted = new Date;
|
||||
})
|
||||
db.users.update({_id:user._id}, {$set:{habits:user.habits,dailys:user.dailys,todos:user.todos,rewards:user.rewards}});
|
||||
});
|
||||
1
migrations/20140130_birthdayEnd.js
Normal file
1
migrations/20140130_birthdayEnd.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
db.users.update({},{$set:{'achievements.habitBirthday':true}},{multi:1})
|
||||
12
migrations/20140130_birthdayStart.js
Normal file
12
migrations/20140130_birthdayStart.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
db.users.update({},{$set:{
|
||||
'items.food.Cake_Skeleton':1,
|
||||
'items.food.Cake_Base':1,
|
||||
'items.food.Cake_CottonCandyBlue':1,
|
||||
'items.food.Cake_CottonCandyPink':1,
|
||||
'items.food.Cake_Shade':1,
|
||||
'items.food.Cake_White':1,
|
||||
'items.food.Cake_Golden':1,
|
||||
'items.food.Cake_Zombie':1,
|
||||
'items.food.Cake_Desert':1,
|
||||
'items.food.Cake_Red':1
|
||||
}},{multi:1})
|
||||
3
migrations/20140220_challenge_memberCount.js
Normal file
3
migrations/20140220_challenge_memberCount.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
db.challenges.find({},{members:1}).forEach(function(chal){
|
||||
db.challenges.update({_id:chal._id}, {$set:{memberCount:chal.members.length}});
|
||||
});
|
||||
14
migrations/20140301_missing_mysteries.js
Normal file
14
migrations/20140301_missing_mysteries.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
db.users.update(
|
||||
{
|
||||
'purchased.plan.dateCreated':{$gte:new Date('2014-02-22'),$lt:new Date('2014-02-29')},
|
||||
'items.gear.owned.armor_mystery_201402':null,
|
||||
'items.gear.owned.head_mystery_201402': null,
|
||||
'items.gear.owned.back_mystery_201402': null,
|
||||
'purchased.plan.mysteryItems':{$nin:['armor_mystery_201402','head_mystery_201402','back_mystery_201402']}
|
||||
},
|
||||
//{_id:1,'purchased.plan':1,'items.gear.owned':1}
|
||||
{$push: {'purchased.plan.mysteryItems':{$each:['armor_mystery_201402','head_mystery_201402','back_mystery_201402']}}},
|
||||
{multi:true}
|
||||
)/*.forEach(function(user){
|
||||
printjson(user);
|
||||
});*/
|
||||
1
migrations/20140610_missing_backer_mount.js
Normal file
1
migrations/20140610_missing_backer_mount.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
db.users.update({'backer.tier':{$gt:69}},{$set:{'items.mounts.LionCub-Ethereal':true}},{multi:1})
|
||||
11
migrations/20140712_wiped_quest_membership.js
Normal file
11
migrations/20140712_wiped_quest_membership.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
//mongo habitrpg node_modules/lodash/lodash.js ./migrations/20140712_wiped_quest_membership.js
|
||||
db.groups.find({type:'party','quest.key':{$ne:null},'quest.active':true},{quest:1}).forEach(function(group){
|
||||
var activeMembers = _.reduce(group.quest.members, function(m,v,k){
|
||||
if (v===true) m.push(k); return m;
|
||||
},[]);
|
||||
db.users.update(
|
||||
{_id:{$in: activeMembers}},
|
||||
{$set:{'party.quest.key':group.quest.key,'party.quest.completed':null}},
|
||||
{multi:true}
|
||||
);
|
||||
});
|
||||
13
migrations/20140803_remove_undefined_notifications.js
Normal file
13
migrations/20140803_remove_undefined_notifications.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
var _ = require('lodash');
|
||||
|
||||
db.users.find({}).forEach(function(user){
|
||||
var newNewMessages = {};
|
||||
|
||||
_.each(user.newMessages, function(val, key){
|
||||
if(key != "undefined"){
|
||||
newNewMessages[key] = val;
|
||||
};
|
||||
});
|
||||
|
||||
db.users.update({_id: user._id}, {$set: {'newMessages': newNewMessages}});
|
||||
});
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
db.users.find({}).forEach(function(user){
|
||||
var newNewMessages = {};
|
||||
|
||||
for(var key in user.newMessages) {
|
||||
var val = user.newMessages[key];
|
||||
// print("\n" + key + " " + val);
|
||||
if(key != "undefined" && val['value']){
|
||||
newNewMessages[key] = val;
|
||||
}
|
||||
}
|
||||
|
||||
db.users.update({_id: user._id}, {$set: {'newMessages': newNewMessages}});
|
||||
});
|
||||
75
migrations/20140829_change_headAccessory_to_eyewear.js
Normal file
75
migrations/20140829_change_headAccessory_to_eyewear.js
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
// node .migrations/20140829_change_headAccessory_to_eyewear.js
|
||||
|
||||
var migrationName = '20140829_change_headAccessory_to_eyewear';
|
||||
var authorName = 'Alys'; // in case script author needs to know when their ...
|
||||
var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
|
||||
|
||||
/**
|
||||
* https://github.com/HabitRPG/habitrpg/issues/3645
|
||||
*/
|
||||
var mongo = require('mongoskin');
|
||||
var _ = require('lodash');
|
||||
var liveUsers = mongo.db('localhost:27017/habitrpg2?auto_reconnect').collection('users');
|
||||
|
||||
var fields = {'migration':1,
|
||||
'items.gear.costume.headAccessory':1,
|
||||
'items.gear.equipped.headAccessory':1,
|
||||
'items.gear.owned.headAccessory_special_wondercon_black':1,
|
||||
'items.gear.owned.headAccessory_special_wondercon_red':1,
|
||||
'items.gear.owned.headAccessory_special_summerRogue':1,
|
||||
'items.gear.owned.headAccessory_special_summerWarrior':1
|
||||
};
|
||||
|
||||
var progressCount = 1000;
|
||||
var count = 0;
|
||||
liveUsers.findEach({ $and: [
|
||||
{ migration: {$ne:migrationName} },
|
||||
{ $or: [
|
||||
{'items.gear.owned.headAccessory_special_summerRogue': {'$exists':true}},
|
||||
{'items.gear.owned.headAccessory_special_summerWarrior':{'$exists':true}},
|
||||
{'items.gear.owned.headAccessory_special_wondercon_red':{'$exists':true}},
|
||||
{'items.gear.owned.headAccessory_special_wondercon_black':{'$exists':true}}
|
||||
]}
|
||||
]}, fields, {batchSize:250}, function(err, user){
|
||||
count++;
|
||||
if (!user) err = '!user';
|
||||
if (err) {return console.error(err);}
|
||||
|
||||
var set = {'migration': migrationName};
|
||||
var unset = {};
|
||||
|
||||
var oldToNew = {
|
||||
'headAccessory_special_summerRogue': 'eyewear_special_summerRogue',
|
||||
'headAccessory_special_summerWarrior': 'eyewear_special_summerWarrior',
|
||||
'headAccessory_special_wondercon_red': 'eyewear_special_wondercon_red',
|
||||
'headAccessory_special_wondercon_black':'eyewear_special_wondercon_black'
|
||||
};
|
||||
|
||||
// items.gear.costume, items.gear.equipped:
|
||||
_.each(['costume','equipped'],function(type){
|
||||
_.each(oldToNew,function(newName,oldName){
|
||||
if (user.items.gear[type].headAccessory === oldName) {
|
||||
unset['items.gear.'+type+'.headAccessory'] = "";
|
||||
set['items.gear.'+type+'.eyewear'] = newName;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// items.gear.owned:
|
||||
_.each(oldToNew,function(newName,oldName){
|
||||
if (oldName in user.items.gear.owned) {
|
||||
unset['items.gear.owned.'+oldName] = "";
|
||||
set['items.gear.owned.'+newName] = user.items.gear.owned[oldName];
|
||||
}
|
||||
});
|
||||
|
||||
//console.log(JSON.stringify(user, null, " "));
|
||||
//console.log("set: " + JSON.stringify(set, null, " "));
|
||||
//console.log("unset: " + JSON.stringify(unset, null, " "));
|
||||
|
||||
liveUsers.update({_id:user._id}, {$set:set, $unset:unset, $inc:{_v:1}});
|
||||
|
||||
if (count%progressCount == 0) console.log(count + ' ' + user._id);
|
||||
if (user._id == '9') console.log('lefnire processed');
|
||||
if (user._id == authorUuid) console.log(authorName + ' processed');
|
||||
});
|
||||
126
migrations/20140831_increase_gems_for_previous_contributions.js
Normal file
126
migrations/20140831_increase_gems_for_previous_contributions.js
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
// IMPORTANT:
|
||||
//
|
||||
// run like this to capture all output:
|
||||
//
|
||||
// node 20140831_increase_gems_for_previous_contributions.js > 20140831_increase_gems_for_previous_contributions_output.txt
|
||||
|
||||
|
||||
var migrationName = '20140831_increase_gems_for_previous_contributions';
|
||||
|
||||
/**
|
||||
* https://github.com/HabitRPG/habitrpg/issues/3933
|
||||
* Increase Number of Gems for Contributors
|
||||
* author: Alys (d904bd62-da08-416b-a816-ba797c9ee265)
|
||||
*
|
||||
* Increase everyone's gems per their contribution level.
|
||||
* Originally they were given 2 gems per tier.
|
||||
* Now they are given 3 gems per tier for tiers 1,2,3
|
||||
* and 4 gems per tier for tiers 4,5,6,7
|
||||
* So that means an EXTRA 1 for tier 1,
|
||||
* 2 for tier 2,
|
||||
* 3 for tier 3,
|
||||
* 5 for tier 4,
|
||||
* 7 for tier 5,
|
||||
* 9 for tier 6,
|
||||
* 11 for tier 7,
|
||||
* 11 for tier 8 (moderators = tier 7 + admin privileges),
|
||||
* none for tier 9 (staff)
|
||||
*/
|
||||
|
||||
var mongo = require('mongoskin');
|
||||
var _ = require('lodash');
|
||||
|
||||
|
||||
var dbUsers = mongo.db('localhost:27017/habitrpg?auto_reconnect').collection('users');
|
||||
|
||||
|
||||
var query = {
|
||||
'contributor.level': {$gt: 0, $lt: 9},
|
||||
'migration': {$ne: migrationName}
|
||||
};
|
||||
|
||||
var fields = {
|
||||
'migration':1,
|
||||
'contributor.level':1,
|
||||
'balance':1
|
||||
};
|
||||
|
||||
var userResults = {}; // each key is a UUID, each value is a string
|
||||
// describing what changed for that user
|
||||
|
||||
console.warn('Updating users...');
|
||||
var progressCount = 50;
|
||||
var count = 0;
|
||||
dbUsers.findEach(query, fields, function(err, user) {
|
||||
if (err) { return exiting(1, 'ERROR! ' + err); }
|
||||
if (!user) {
|
||||
console.warn('All users found. Fetching final balances...');
|
||||
return fetchFinalBalances();
|
||||
}
|
||||
count++;
|
||||
|
||||
var set = {'migration': migrationName};
|
||||
|
||||
var tier = user.contributor.level;
|
||||
var extraGems = tier; // tiers 1,2,3
|
||||
if (tier > 3) { extraGems = 3 + (tier - 3) * 2; }
|
||||
if (tier == 8) { extraGems = 11; }
|
||||
extraBalance = extraGems / 4;
|
||||
set['balance'] = user.balance + extraBalance;
|
||||
|
||||
// Capture current state of user:
|
||||
userResults[user._id] =
|
||||
user._id + ' ' + ':\n' +
|
||||
' contrib tier : ' + tier + '\n' +
|
||||
' balance before : ' + user.balance + '\n' +
|
||||
' balance (gems) added : ' + extraBalance + ' (' +
|
||||
extraGems + ')' + '\n' +
|
||||
' expected balance after: ' + (user.balance + extraBalance) + '\n';
|
||||
|
||||
// Update user:
|
||||
dbUsers.update({_id:user._id}, {$set:set, $inc:{_v:1}});
|
||||
if (count%progressCount == 0) console.warn(count + ' ' + user._id);
|
||||
});
|
||||
|
||||
|
||||
function fetchFinalBalances() {
|
||||
var query = {_id: {$in: Object.keys(userResults)}};
|
||||
var fields = {
|
||||
'balance':1,
|
||||
};
|
||||
|
||||
var count1 = 0;
|
||||
dbUsers.findEach(query, fields, function(err, user) {
|
||||
if (err) { return exiting(1, 'ERROR! ' + err); }
|
||||
if (!user) {
|
||||
console.warn('All final balances found.');
|
||||
return displayData();
|
||||
}
|
||||
count1++;
|
||||
userResults[user._id] = userResults[user._id] +
|
||||
user._id + ' ' + ':\n' +
|
||||
' actual balance after : ' + user.balance + '\n';
|
||||
if (count1%progressCount == 0) console.warn(count1 + ' ' + user._id);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function displayData() {
|
||||
_.each(userResults, function(text, uuid) {
|
||||
console.log(text); // text contains uuid
|
||||
});
|
||||
console.log('\n' + count +
|
||||
' users processed (should be roughly 335 according to the Hall)\n');
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
|
||||
function exiting(code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
if (code && !msg) { msg = 'ERROR!'; }
|
||||
if (msg) {
|
||||
if (code) { console.error(msg); }
|
||||
else { console.log( msg); }
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
73
migrations/20140914_upgrade_admin_contrib_tiers.js
Normal file
73
migrations/20140914_upgrade_admin_contrib_tiers.js
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
var migrationName = '20140914_upgrade_admin_contrib_tiers';
|
||||
var authorName = 'Alys'; // in case script author needs to know when their ...
|
||||
var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
|
||||
|
||||
/**
|
||||
* https://github.com/HabitRPG/habitrpg/issues/3801
|
||||
* Convert Tier 8 contributors to Tier 9 (staff) (all current Tier 8s are admins).
|
||||
* Convert Tier 7 contributors with admin flag to Tier 8 (moderators).
|
||||
*/
|
||||
|
||||
var mongo = require('mongoskin');
|
||||
var _ = require('lodash');
|
||||
|
||||
var dbUsers = mongo.db('localhost:27017/habitrpg?auto_reconnect').collection('users');
|
||||
|
||||
var query =
|
||||
{ 'contributor.level':{$gte:7}, 'contributor.admin':true, 'migration': {$ne: migrationName} };
|
||||
|
||||
var fields = {'migration':1,
|
||||
'contributor.admin':1,
|
||||
'contributor.level':1,
|
||||
'auth.local.username':1,
|
||||
'profile.name':1,
|
||||
};
|
||||
|
||||
var userResults = {}; // each key is a UUID, each value is a username;
|
||||
// contains only the users changed
|
||||
|
||||
console.warn('Updating users...');
|
||||
var progressCount = 1000;
|
||||
var count = 0;
|
||||
dbUsers.findEach(query, fields, {batchSize:250}, function(err, user) {
|
||||
if (err) { return exiting(1, 'ERROR! ' + err); }
|
||||
if (!user) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
return displayData();
|
||||
}
|
||||
count++;
|
||||
|
||||
var set = {'migration': migrationName};
|
||||
var inc = {'contributor.level':1, _v:1};
|
||||
|
||||
userResults[user._id] = user.profile.name;
|
||||
|
||||
dbUsers.update({_id:user._id}, {$set:set, $inc:inc});
|
||||
|
||||
if (count%progressCount == 0) console.warn(count + ' ' + user._id);
|
||||
if (user._id == authorUuid) console.warn(authorName + ' processed');
|
||||
if (user._id == '9' ) console.warn('lefnire' + ' processed');
|
||||
});
|
||||
|
||||
|
||||
function displayData() {
|
||||
console.log('users modified:');
|
||||
_.each(userResults, function(name, uuid) {
|
||||
console.log(name);
|
||||
});
|
||||
console.warn('\n' + count +
|
||||
' users processed (should be 11 according to the Hall)\n');
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
|
||||
function exiting(code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
if (code && !msg) { msg = 'ERROR!'; }
|
||||
if (msg) {
|
||||
if (code) { console.error(msg); }
|
||||
else { console.log( msg); }
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
18
migrations/20140922_free_candy.js
Normal file
18
migrations/20140922_free_candy.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
db.users.update(
|
||||
{},
|
||||
{
|
||||
$inc: {
|
||||
'items.food.Candy_Base':1,
|
||||
'items.food.Candy_CottonCandyBlue':1,
|
||||
'items.food.Candy_CottonCandyPink':1,
|
||||
'items.food.Candy_Desert':1,
|
||||
'items.food.Candy_Golden':1,
|
||||
'items.food.Candy_Red':1,
|
||||
'items.food.Candy_Shade':1,
|
||||
'items.food.Candy_Skeleton':1,
|
||||
'items.food.Candy_White':1,
|
||||
'items.food.Candy_Zombie':1
|
||||
}
|
||||
},
|
||||
{multi:1}
|
||||
);
|
||||
1
migrations/20141006_jackolantern_pet.js
Normal file
1
migrations/20141006_jackolantern_pet.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
db.users.update({_id:'9'},{$set:{'items.pets.JackOLantern-Base':5, 'flags.newStuff':true}}, {multi:true});
|
||||
14
migrations/20141117_consecutive_months.js
Normal file
14
migrations/20141117_consecutive_months.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// require moment, lodash
|
||||
db.users.find(
|
||||
{'purchased.plan.customerId':{$ne:null}},
|
||||
{_id:1, 'purchased.plan':1}
|
||||
).forEach(function(user){
|
||||
var p = user.purchased.plan
|
||||
, latestMonth = p.dateTerminated || new Date() // their last sub date, or on-going (now)
|
||||
, count = moment(latestMonth).diff(p.dateCreated, 'months');
|
||||
db.users.update({_id: user._id}, {$set: {
|
||||
'purchased.plan.consecutive.count': count,
|
||||
'purchased.plan.consecutive.gemCapExtra': _.min([ Math.floor(count/3)*5, 25 ]),
|
||||
'purchased.plan.consecutive.trinkets': Math.floor(count/3)
|
||||
}});
|
||||
});
|
||||
11
migrations/20141126_turkey_mounts.js
Normal file
11
migrations/20141126_turkey_mounts.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
db.users.update(
|
||||
{'items.pets.Turkey-Base':{$ne:null}},
|
||||
{$set:{'items.mounts.Turkey-Base':true}},
|
||||
{multi:1}
|
||||
)
|
||||
|
||||
db.users.update(
|
||||
{'items.pets.Turkey-Base':null},
|
||||
{$set:{'items.pets.Turkey-Base':5}},
|
||||
{multi:1}
|
||||
)
|
||||
4
migrations/20141211_NaN_consecutives.js
Normal file
4
migrations/20141211_NaN_consecutives.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
db.users.update({'purchased.plan.consecutive.count':NaN}, {$set:{'purchased.plan.consecutive.count':0}}, {multi:1});
|
||||
db.users.update({'purchased.plan.consecutive.offset':NaN}, {$set:{'purchased.plan.consecutive.offset':0}}, {multi:1});
|
||||
db.users.update({'purchased.plan.consecutive.gemCapExtra':NaN}, {$set:{'purchased.plan.consecutive.gemCapExtra':0}}, {multi:1});
|
||||
db.users.update({'purchased.plan.consecutive.trinkets':NaN}, {$set:{'purchased.plan.consecutive.trinkets':0}}, {multi:1});
|
||||
11
migrations/20141230_new_years_hats.js
Normal file
11
migrations/20141230_new_years_hats.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
db.users.update(
|
||||
{'items.gear.owned.head_special_nye':{$ne:null}},
|
||||
{$set:{'items.gear.owned.head_special_nye2014':false}},
|
||||
{multi:1}
|
||||
)
|
||||
|
||||
db.users.update(
|
||||
{'items.gear.owned.head_special_nye':null},
|
||||
{$set:{'items.gear.owned.head_special_nye':false}},
|
||||
{multi:1}
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue