mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-18 23:27:26 +01:00
Merge branch 'develop' into api-v3
This commit is contained in:
@@ -179,5 +179,8 @@
|
|||||||
"businessInquiries": "Business Inquiries",
|
"businessInquiries": "Business Inquiries",
|
||||||
"merchandiseInquiries": "Merchandise Inquiries",
|
"merchandiseInquiries": "Merchandise Inquiries",
|
||||||
"marketingInquiries": "Marketing/Social Media Inquiries",
|
"marketingInquiries": "Marketing/Social Media Inquiries",
|
||||||
"tweet": "Tweet"
|
"tweet": "Tweet",
|
||||||
|
"apps": "Apps",
|
||||||
|
"notifyAndroidApp": "Want us to notify you when the Android app is ready? Sign up for this mailing list!",
|
||||||
|
"checkOutIOSApp": "Check out our new iOS App!"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../helpers/api-integration.helper';
|
} from '../../../helpers/api-integration.helper';
|
||||||
import { v4 as generateRandomUserName } from 'uuid';
|
import { v4 as generateRandomUserName } from 'uuid';
|
||||||
|
import { each } from 'lodash';
|
||||||
|
|
||||||
describe('POST /register', () => {
|
describe('POST /register', () => {
|
||||||
|
|
||||||
@@ -137,4 +138,106 @@ describe('POST /register', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
context('successful login via api', () => {
|
||||||
|
let api, username, email, password;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
api = requester();
|
||||||
|
username = generateRandomUserName();
|
||||||
|
email = `${username}@example.com`;
|
||||||
|
password = 'password';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets all site tour values to -2 (already seen)', () => {
|
||||||
|
return api.post('/register', {
|
||||||
|
username: username,
|
||||||
|
email: email,
|
||||||
|
password: password,
|
||||||
|
confirmPassword: password,
|
||||||
|
}).then((user) => {
|
||||||
|
expect(user.flags.tour).to.not.be.empty;
|
||||||
|
|
||||||
|
each(user.flags.tour, (value, attribute) => {
|
||||||
|
expect(value).to.eql(-2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('populates user with default todos, not no other task types', () => {
|
||||||
|
return api.post('/register', {
|
||||||
|
username: username,
|
||||||
|
email: email,
|
||||||
|
password: password,
|
||||||
|
confirmPassword: password,
|
||||||
|
}).then((user) => {
|
||||||
|
expect(user.todos).to.not.be.empty;
|
||||||
|
expect(user.dailys).to.be.empty;
|
||||||
|
expect(user.habits).to.be.empty;
|
||||||
|
expect(user.rewards).to.be.empty;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('populates user with default tags', () => {
|
||||||
|
return api.post('/register', {
|
||||||
|
username: username,
|
||||||
|
email: email,
|
||||||
|
password: password,
|
||||||
|
confirmPassword: password,
|
||||||
|
}).then((user) => {
|
||||||
|
expect(user.tags).to.not.be.empty;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('successful login with habitica-web header', () => {
|
||||||
|
let api, username, email, password;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
api = requester({}, {'x-client': 'habitica-web'});
|
||||||
|
username = generateRandomUserName();
|
||||||
|
email = `${username}@example.com`;
|
||||||
|
password = 'password';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets all common tutorial flags to true', () => {
|
||||||
|
return api.post('/register', {
|
||||||
|
username: username,
|
||||||
|
email: email,
|
||||||
|
password: password,
|
||||||
|
confirmPassword: password,
|
||||||
|
}).then((user) => {
|
||||||
|
expect(user.flags.tour).to.not.be.empty;
|
||||||
|
|
||||||
|
each(user.flags.tutorial.common, (value, attribute) => {
|
||||||
|
expect(value).to.eql(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('populates user with default todos, habits, and rewards', () => {
|
||||||
|
return api.post('/register', {
|
||||||
|
username: username,
|
||||||
|
email: email,
|
||||||
|
password: password,
|
||||||
|
confirmPassword: password,
|
||||||
|
}).then((user) => {
|
||||||
|
expect(user.todos).to.not.be.empty;
|
||||||
|
expect(user.dailys).to.be.empty;
|
||||||
|
expect(user.habits).to.not.be.empty;
|
||||||
|
expect(user.rewards).to.not.be.empty;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('populates user with default tags', () => {
|
||||||
|
return api.post('/register', {
|
||||||
|
username: username,
|
||||||
|
email: email,
|
||||||
|
password: password,
|
||||||
|
confirmPassword: password,
|
||||||
|
}).then((user) => {
|
||||||
|
expect(user.tags).to.not.be.empty;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,7 +8,14 @@ describe('GET /user/tasks/', () => {
|
|||||||
let api, user;
|
let api, user;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
return generateUser().then((_user) => {
|
return generateUser({
|
||||||
|
dailys: [
|
||||||
|
{text: 'daily', type: 'daily'},
|
||||||
|
{text: 'daily', type: 'daily'},
|
||||||
|
{text: 'daily', type: 'daily'},
|
||||||
|
{text: 'daily', type: 'daily'},
|
||||||
|
],
|
||||||
|
}).then((_user) => {
|
||||||
user = _user;
|
user = _user;
|
||||||
api = requester(user);
|
api = requester(user);
|
||||||
});
|
});
|
||||||
@@ -17,7 +24,7 @@ describe('GET /user/tasks/', () => {
|
|||||||
it('gets all tasks', () => {
|
it('gets all tasks', () => {
|
||||||
return api.get(`/user/tasks/`).then((tasks) => {
|
return api.get(`/user/tasks/`).then((tasks) => {
|
||||||
expect(tasks).to.be.an('array');
|
expect(tasks).to.be.an('array');
|
||||||
expect(tasks.length).to.be.greaterThan(4);
|
expect(tasks.length).to.be.greaterThan(3);
|
||||||
|
|
||||||
let task = tasks[0];
|
let task = tasks[0];
|
||||||
expect(task.id).to.exist;
|
expect(task.id).to.exist;
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ const API_TEST_SERVER_PORT = 3003;
|
|||||||
// Sets up an abject that can make all REST requests
|
// Sets up an abject that can make all REST requests
|
||||||
// If a user is passed in, the uuid and api token of
|
// If a user is passed in, the uuid and api token of
|
||||||
// the user are used to make the requests
|
// the user are used to make the requests
|
||||||
export function requester(user={}) {
|
export function requester(user={}, additionalSets) {
|
||||||
return {
|
return {
|
||||||
get: _requestMaker(user, 'get'),
|
get: _requestMaker(user, 'get', additionalSets),
|
||||||
post: _requestMaker(user, 'post'),
|
post: _requestMaker(user, 'post', additionalSets),
|
||||||
put: _requestMaker(user, 'put'),
|
put: _requestMaker(user, 'put', additionalSets),
|
||||||
del: _requestMaker(user, 'del'),
|
del: _requestMaker(user, 'del', additionalSets),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -207,18 +207,22 @@ export function resetHabiticaDB() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function _requestMaker(user, method) {
|
function _requestMaker(user, method, additionalSets) {
|
||||||
return (route, send, query) => {
|
return (route, send, query) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let request = superagent[method](`http://localhost:${API_TEST_SERVER_PORT}/api/v2${route}`)
|
let request = superagent[method](`http://localhost:${API_TEST_SERVER_PORT}/api/v2${route}`)
|
||||||
.accept('application/json');
|
.accept('application/json');
|
||||||
|
|
||||||
if (user._id && user.apiToken) {
|
if (user && user._id && user.apiToken) {
|
||||||
request
|
request
|
||||||
.set('x-api-user', user._id)
|
.set('x-api-user', user._id)
|
||||||
.set('x-api-key', user.apiToken);
|
.set('x-api-key', user.apiToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (additionalSets) {
|
||||||
|
request.set(additionalSets);
|
||||||
|
}
|
||||||
|
|
||||||
request
|
request
|
||||||
.query(query)
|
.query(query)
|
||||||
.send(send)
|
.send(send)
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ angular.module('habitrpg')
|
|||||||
function($scope, $rootScope, User, $http, $location, $window, ApiUrl, $modal, Analytics) {
|
function($scope, $rootScope, User, $http, $location, $window, ApiUrl, $modal, Analytics) {
|
||||||
$scope.Analytics = Analytics;
|
$scope.Analytics = Analytics;
|
||||||
|
|
||||||
|
$http.defaults.headers.common['x-client'] = 'habitica-web';
|
||||||
|
|
||||||
$scope.logout = function() {
|
$scope.logout = function() {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
window.location.href = '/logout';
|
window.location.href = '/logout';
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
|
|||||||
$rootScope.toJson = angular.toJson;
|
$rootScope.toJson = angular.toJson;
|
||||||
$rootScope.Payments = Payments;
|
$rootScope.Payments = Payments;
|
||||||
|
|
||||||
|
$http.defaults.headers.common['x-client'] = 'habitica-web';
|
||||||
|
|
||||||
// Angular UI Router
|
// Angular UI Router
|
||||||
$rootScope.$state = $state;
|
$rootScope.$state = $state;
|
||||||
$rootScope.$stateParams = $stateParams;
|
$rootScope.$stateParams = $stateParams;
|
||||||
|
|||||||
@@ -123,6 +123,8 @@ api.registerUser = function(req, res, next) {
|
|||||||
};
|
};
|
||||||
analytics.track('register', analyticsData)
|
analytics.track('register', analyticsData)
|
||||||
|
|
||||||
|
user.registeredThrough = req.headers['x-client']
|
||||||
|
|
||||||
user.save(function(err, savedUser){
|
user.save(function(err, savedUser){
|
||||||
// Clean previous email preferences
|
// Clean previous email preferences
|
||||||
// TODO when emails added to EmailUnsubcription they should use lowercase version
|
// TODO when emails added to EmailUnsubcription they should use lowercase version
|
||||||
|
|||||||
@@ -481,34 +481,7 @@ UserSchema.pre('save', function(next) {
|
|||||||
|
|
||||||
// Populate new users with default content
|
// Populate new users with default content
|
||||||
if (this.isNew){
|
if (this.isNew){
|
||||||
//TODO for some reason this doesn't work here: `_.merge(this, shared.content.userDefaults);`
|
_populateDefaultsForNewUser(this);
|
||||||
var self = this;
|
|
||||||
_.each(['habits', 'dailys', 'todos', 'rewards', 'tags'], function(taskType){
|
|
||||||
self[taskType] = _.map(shared.content.userDefaults[taskType], function(task){
|
|
||||||
var newTask = _.cloneDeep(task);
|
|
||||||
|
|
||||||
// Render task's text and notes in user's language
|
|
||||||
if(taskType === 'tags'){
|
|
||||||
// tasks automatically get id=helpers.uuid() from TaskSchema id.default, but tags are Schema.Types.Mixed - so we need to manually invoke here
|
|
||||||
newTask.id = shared.uuid();
|
|
||||||
newTask.name = newTask.name(self.preferences.language);
|
|
||||||
}else{
|
|
||||||
newTask.text = newTask.text(self.preferences.language);
|
|
||||||
if(newTask.notes) {
|
|
||||||
newTask.notes = newTask.notes(self.preferences.language);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(newTask.checklist){
|
|
||||||
newTask.checklist = _.map(newTask.checklist, function(checklistItem){
|
|
||||||
checklistItem.text = checklistItem.text(self.preferences.language);
|
|
||||||
return checklistItem;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return newTask;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//this.markModified('tasks');
|
//this.markModified('tasks');
|
||||||
@@ -601,6 +574,57 @@ UserSchema.methods.unlink = function(options, cb) {
|
|||||||
self.save(cb);
|
self.save(cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _populateDefaultsForNewUser(user) {
|
||||||
|
var taskTypes;
|
||||||
|
|
||||||
|
if (user.registeredThrough === "habitica-web") {
|
||||||
|
taskTypes = ['habits', 'dailys', 'todos', 'rewards', 'tags'];
|
||||||
|
|
||||||
|
_.each(user.flags.tutorial.common, function(value, section) {
|
||||||
|
user.flags.tutorial.common[section] = true;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
taskTypes = ['todos', 'tags']
|
||||||
|
|
||||||
|
user.flags.showTour = false;
|
||||||
|
|
||||||
|
_.each(user.flags.tour, function(value, section) {
|
||||||
|
user.flags.tour[section] = -2;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_populateDefaultTasks(user, taskTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _populateDefaultTasks (user, taskTypes) {
|
||||||
|
_.each(taskTypes, function(taskType){
|
||||||
|
user[taskType] = _.map(shared.content.userDefaults[taskType], function(task){
|
||||||
|
var newTask = _.cloneDeep(task);
|
||||||
|
|
||||||
|
// Render task's text and notes in user's language
|
||||||
|
if(taskType === 'tags'){
|
||||||
|
// tasks automatically get id=helpers.uuid() from TaskSchema id.default, but tags are Schema.Types.Mixed - so we need to manually invoke here
|
||||||
|
newTask.id = shared.uuid();
|
||||||
|
newTask.name = newTask.name(user.preferences.language);
|
||||||
|
}else{
|
||||||
|
newTask.text = newTask.text(user.preferences.language);
|
||||||
|
if(newTask.notes) {
|
||||||
|
newTask.notes = newTask.notes(user.preferences.language);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(newTask.checklist){
|
||||||
|
newTask.checklist = _.map(newTask.checklist, function(checklistItem){
|
||||||
|
checklistItem.text = checklistItem.text(user.preferences.language);
|
||||||
|
return checklistItem;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newTask;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
module.exports.schema = UserSchema;
|
module.exports.schema = UserSchema;
|
||||||
module.exports.model = mongoose.model("User", UserSchema);
|
module.exports.model = mongoose.model("User", UserSchema);
|
||||||
// Initially export an empty object so external requires will get
|
// Initially export an empty object so external requires will get
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ router.get('/', i18n.getUserLanguage, locals, function(req, res) {
|
|||||||
|
|
||||||
// -------- Marketing --------
|
// -------- Marketing --------
|
||||||
|
|
||||||
var pages = ['front', 'privacy', 'terms', 'api', 'features', 'videos', 'contact', 'plans', 'new-stuff', 'community-guidelines', 'old-news', 'press-kit', 'faq'];
|
var pages = ['front', 'privacy', 'terms', 'api', 'features', 'videos', 'contact', 'plans', 'new-stuff', 'community-guidelines', 'old-news', 'press-kit', 'faq', 'apps'];
|
||||||
|
|
||||||
_.each(pages, function(name){
|
_.each(pages, function(name){
|
||||||
router.get('/static/' + name, i18n.getUserLanguage, locals, function(req, res) {
|
router.get('/static/' + name, i18n.getUserLanguage, locals, function(req, res) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ footer.footer(ng-controller='FooterCtrl')
|
|||||||
li
|
li
|
||||||
a(href='https://itunes.apple.com/us/app/habitica/id994882113?ls=1&mt=8', target='_blank')=env.t('mobileIOS')
|
a(href='https://itunes.apple.com/us/app/habitica/id994882113?ls=1&mt=8', target='_blank')=env.t('mobileIOS')
|
||||||
li
|
li
|
||||||
a(href='https://play.google.com/store/apps/details?id=com.ocdevel.habitrpg', target='_blank')=env.t('mobileAndroid')
|
a(href='/static/apps')=env.t('mobileAndroid')
|
||||||
if env.isStaticPage
|
if env.isStaticPage
|
||||||
h4=env.t('language')
|
h4=env.t('language')
|
||||||
select(ng-change='changeLang()', ng-model='selectedLanguage', ng-options='language.name for language in languages')
|
select(ng-change='changeLang()', ng-model='selectedLanguage', ng-options='language.name for language in languages')
|
||||||
|
|||||||
23
website/views/static/apps.jade
Normal file
23
website/views/static/apps.jade
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
extends ./layout
|
||||||
|
|
||||||
|
block vars
|
||||||
|
- var layoutEnv = env
|
||||||
|
|
||||||
|
block title
|
||||||
|
title Habitica |
|
||||||
|
=env.t('apps')
|
||||||
|
|
||||||
|
block content
|
||||||
|
.lead
|
||||||
|
=env.t('checkOutIOSApp')
|
||||||
|
.row.text-center
|
||||||
|
#ibb-widget-root-994882113
|
||||||
|
script.
|
||||||
|
(function(t,e,i,d){var o=t.getElementById(i),n=t.createElement(e);o.style.height=250;o.style.width=300;o.style.display='inline-block';n.id='ibb-widget',n.setAttribute('src',('https:'===t.location.protocol?'https://':'http://')+d),n.setAttribute('width','300'),n.setAttribute('height','250'),n.setAttribute('frameborder','0'),n.setAttribute('scrolling','no'),o.appendChild(n)})(document,'iframe','ibb-widget-root-994882113',"banners.itunes.apple.com/banner.html?partnerId=&aId=&bt=catalog&t=catalog_white&id=994882113&c=us&l=en-US&w=300&h=250&store=apps");
|
||||||
|
.lead
|
||||||
|
=env.t('notifyAndroidApp')
|
||||||
|
.row.text-center
|
||||||
|
.sendgrid-subscription-widget(data-token='WEc2mXZw9YqemYAJp3a%2FO%2FxyvSWuFCFS2x88bbdMa%2FJjEH%2FlDdpc%2Flbz4PPZZRZMnTmr8kNOzI6ypw%2Br3r55wQ%3D%3D')
|
||||||
|
script.
|
||||||
|
!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?"http":"https";if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+"://s3.amazonaws.com/subscription-cdn/0.2/widget.min.js";fjs.parentNode.insertBefore(js,fjs);}}(document, "script", "sendgrid-subscription-widget-js");
|
||||||
|
|
||||||
Reference in New Issue
Block a user