Skip to content

Commit aabfc19

Browse files
author
jiangliwu- macbook
committed
facebook login
0 parents  commit aabfc19

File tree

94 files changed

+17804
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

94 files changed

+17804
-0
lines changed

.eslintrc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"extends": "airbnb-base",
3+
"rules": {
4+
//close error for co( function * (){});
5+
"func-names": 0,
6+
//sometimes don't need yield in function *
7+
"require-yield": 0,
8+
// need update entity that from request.body
9+
"no-param-reassign": 0,
10+
"no-use-before-define": 0,
11+
"comma-dangle": 0,
12+
"max-len": [
13+
2,
14+
130
15+
]
16+
}
17+
}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
*.log
3+
.DS_Store

README.md

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# TI Mobile Nodejs Backend
2+
3+
4+
## Install software
5+
6+
- node 8.2.0 (v6+ also work)
7+
- npm 5.x
8+
- mysql 5.7
9+
- a mock SMTP server, e.g. fakeSMTP (http://nilhcem.com/FakeSMTP/)
10+
11+
12+
13+
## Configuration
14+
15+
Config is provided at config/default.js and production.js.
16+
17+
18+
| key | system Environment name | description |
19+
| ----------------------------- | ----------------------------- | ---------------------------------------- |
20+
| LOG_LEVEL | LOG_LEVEL | the log level |
21+
| PORT | PORT | the server port |
22+
| PASSWORD_HASH_SALT_LENGTH | PASSWORD_HASH_SALT_LENGTH | the password hash salt length |
23+
| ACCESS_TOKEN_EXPIRES | ACCESS_TOKEN_EXPIRES | the access token expiration in seconds |
24+
| VERIFY_TOKEN_EXPIRES | VERIFY_TOKEN_EXPIRES | the verify email token expiration in seconds |
25+
| FORGOT_PASSWORD_TOKEN_EXPIRES | FORGOT_PASSWORD_TOKEN_EXPIRES | the forgot password token expiration in seconds |
26+
| QUERY_DEFAULT_LIMIT | QUERY_DEFAULT_LIMIT | the default query limit |
27+
| API_VERSION | API_VERSION | the API version |
28+
| VERIFY_EMAIL_SUBJECT | VERIFY_EMAIL_SUBJECT | the verify email subject |
29+
| VERIFY_EMAIL_CONTENT | VERIFY_EMAIL_CONTENT | the verify email content |
30+
| FORGOT_PASSWORD_EMAIL_SUBJECT | FORGOT_PASSWORD_EMAIL_SUBJECT | the forgot password email subject |
31+
| FORGOT_PASSWORD_EMAIL_CONTENT | FORGOT_PASSWORD_EMAIL_CONTENT | the forgot password email content |
32+
| FROM_EMAIL | FROM_EMAIL | the from email used to send email |
33+
| email | | the email config used for nodermail |
34+
| email.host | SMTP_HOST | the SMTP host |
35+
| email.port | SMTP_PORT | the SMTP port |
36+
| email.auth.user | SMTP_USER | the SMTP user name |
37+
| email.auth.pass | SMTP_PASSWORD | the SMTP password |
38+
| db | | the MySQL db connection object |
39+
| db.uri | MYSQL_URI | the mysql connect uri , for example "mysql://root:123456@localhost:3306/ti" |
40+
| db.options | | the mysql connection options |
41+
42+
43+
44+
## SMTP server setup
45+
46+
We may use a mock SMTP server to simplify tests. And fakeSMTP (http://nilhcem.com/FakeSMTP/) can be used.
47+
Download the `fakeSMTP-2.0.jar` from the website, then run `java -jar fakeSMTP-2.0.jar -m` to start mock SMTP server.
48+
When the GUI is shown, cilck the `Start Server` to start the mock SMTP server.
49+
50+
Note that when you view the email content in fakeSMTP, it is raw email content, which is encoded using quoted-printable
51+
encoding. It is a little different than that is really displayed. At the end of some lines, you may see '=' character,
52+
this '=' is used to link lines, it is not part of final decoded content.
53+
54+
For example, if you see some token like below:
55+
```
56+
99a12ba0-97a4-43dc-beb2-e4a9886b73=
57+
b1-1511720563554
58+
```
59+
60+
the actual token should be:
61+
```
62+
99a12ba0-97a4-43dc-beb2-e4a9886b73b1-1511720563554
63+
```
64+
65+
66+
Note that the fakeSMTP can accept any username/password.
67+
68+
You may also consider using other SMTP server.
69+
70+
##Heroku deployment
71+
72+
- heroku create
73+
- git init
74+
- git add *
75+
- git commit -m "upload project"
76+
- heroku addons:create jawsdb
77+
- heroku config:set EMAIL_USER=[email protected] EMAIL_PASS=123456 SMTP_HOST=smtp.gmail.com SMTP_PORT=465
78+
- git push heroku master
79+
80+
## Local deployment
81+
82+
- start MySQL server, create an emtpy database, update config/default.js db.uri to point to the database
83+
- npm i - install dependencies
84+
- npm run lint - run code lint check
85+
- npm run testdata - it will clear all data and insert test data,
86+
it creates an admin ('[email protected]' / 'password') and normal user ('[email protected]' / 'password')
87+
- npm start - it will create tables if not present, but if tables are present, it won't change data
88+
89+
90+
91+
## Verification
92+
93+
- start mock SMTP, start MySQL db, start app as above
94+
- local Postman collection and environment in the docs folder into Postman
95+
- do login as admin or user via the `security api` / `login - admin` or `login - user`, after login, the access token will be automatically set
96+
to environment variable accessToken, and can be used by other API calls;
97+
please note that some APIs need user role, others need admin role, you may check the routes definitions about which roles are needed for different routes
98+
- some API depends on other API's data;
99+
for example, puchase card API needs a card, so you will need to first create a card in Postman, then login as user (purchase API
100+
needs user role), then purchase it; after purchse, you may get current user data to see that some pointsAmount is cut;
101+
you may also call the get user cards API to see user's cards
102+
- you may create a track story, then use progress API to complete its progress, then use progress API to receive rewards,
103+
then get current user's cards and badges to verify that cards and badge are assigned to user
104+
- flow to signup user:
105+
call the signup API;
106+
in the fakeSMTP, view the last email content, you can see token like below:
107+
```
108+
a0a13351-7192-4151-b4ed-168c25507a=
109+
ba-1511721439823
110+
```
111+
the ending '=' should be removed, and the two lines should be linked together, the token is:
112+
```
113+
a0a13351-7192-4151-b4ed-168c25507aba-1511721439823
114+
```
115+
copy the token to verifyEmail API verificationToken parameter, and call the verifyEmail API
116+
117+
- flow to reset forgot password:
118+
call the initiateForgotPassword API;
119+
in the fakeSMTP, view the last email content, you can see token like below:
120+
```
121+
a8181534-05c2-4e51-9da2-27b46f95c98e-1511721790345
122+
```
123+
copy the token to changeForgotPassword API body forgotPasswordToken parameter, and call the changeForgotPassword API
124+
125+
- other API tests should be intuitive
126+
127+
128+
129+
## Notes
130+
131+
- see discussion in forum, the app needs to support transaction, so RDBMS (MySQL) should be used instead of MongoDB, MongoDB doesn't support transation.
132+
Transaction is used when an operation involves multiple db updates.
133+
For example, when creating/updating TrackStory, multiple child entities are created/updated,
134+
it is hard to support atomic action if using MongoDB.
135+
There are other cases when transaction is needed, e.g. purchase card, persisting TrackStoryUserProgress, persisting Achievement,
136+
receive rewards etc.
137+
For sequelize transaction management, see:
138+
http://docs.sequelizejs.com/manual/tutorial/transactions.html#managed-transaction-auto-callback-
139+
- please take TCUML in precedence over swagger file, the original swagger file has quite some flaws, it is now fixed to sync with the latest APIs;
140+
but still the TCUML entities/services are more accurate and more thorough, e.g. swagger API request/response may be just part of entities fields.
141+
I exported two major diagrams (the two PNG files) from TCUML for your reference.
142+
- for the RacetrackService.search, the given SQL seems not right, I follow the below links to implement it:
143+
https://gis.stackexchange.com/questions/31628/find-points-within-a-distance-using-mysql
144+
https://stackoverflow.com/questions/1006654/fastest-way-to-find-distance-between-two-lat-long-points
145+
- for the RacetrackService.search, raw SQL query is used, so it is important to avoid SQL injection attack,
146+
helper.sanitize is used to sanitize name filter parameter to avoid SQL injection attack
147+
- for TrackStory, its child entities are also managed, when creating/updating TrackStory, if a child entity has no id, then the child entity is created,
148+
otherwise the child entity is updated;
149+
it is similar for TrackStoryUserProgress and Achievement, their child entities are also managed.
150+
151+
152+
153+
## Video
154+
155+
https://youtu.be/F1Wiv2c2niY
156+

app.js

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/**
2+
* Copyright (C) 2017 TopCoder Inc., All Rights Reserved.
3+
*/
4+
5+
/**
6+
* The application entry point
7+
*
8+
* @author TCSCODER
9+
* @version 1.0
10+
*/
11+
12+
require('./src/bootstrap');
13+
14+
const config = require('config');
15+
const express = require('express');
16+
const cors = require('cors');
17+
const bodyParser = require('body-parser');
18+
const _ = require('lodash');
19+
const winston = require('winston');
20+
const httpStatus = require('http-status');
21+
const helper = require('./src/common/helper');
22+
const errorMiddleware = require('./src/common/ErrorMiddleware');
23+
const routes = require('./src/routes');
24+
const errors = require('common-errors');
25+
const models = require('./src/models');
26+
const passport = require('passport');
27+
const BearerStrategy = require('passport-http-bearer');
28+
const UserService = require('./src/services/UserService');
29+
const co = require('co');
30+
const passportLogic = require('./src/passport');
31+
32+
const app = express();
33+
const http = require('http').Server(app);
34+
35+
app.use(passport.initialize());
36+
app.use(passport.session());
37+
app.set('port', config.PORT);
38+
app.use(bodyParser.json());
39+
app.use(bodyParser.urlencoded({ extended: true }));
40+
app.use(cors());
41+
42+
passportLogic.init(app);
43+
models.init(false);
44+
45+
passport.use(new BearerStrategy((token, done) =>
46+
co(function* () {
47+
return yield UserService.getByAccessToken(token);
48+
}).then(user => (user ? done(null, user) : done(null, false))).catch(err => done(err))
49+
));
50+
51+
const apiRouter = express.Router();
52+
53+
// load all routes
54+
_.each(routes, (verbs, url) => {
55+
_.each(verbs, (def, verb) => {
56+
const actions = [
57+
function (req, res, next) {
58+
req.signature = `${def.controller}#${def.method}`;
59+
next();
60+
},
61+
];
62+
const method = require(`./src/controllers/${def.controller}`)[ def.method ]; // eslint-disable-line
63+
64+
if (!method) {
65+
throw new Error(`${def.method} is undefined, for controller ${def.controller}`);
66+
}
67+
if (!def.public) {
68+
// authentication
69+
actions.push(passport.authenticate('bearer', { session: false }));
70+
// authorization
71+
if (def.roles && def.roles.length > 0) {
72+
actions.push((req, res, next) => {
73+
if (_.indexOf(def.roles, req.user.role) < 0) {
74+
next(new errors.NotPermittedError('invalid role'));
75+
} else {
76+
next();
77+
}
78+
});
79+
}
80+
}
81+
82+
actions.push(method);
83+
winston.info(`API : ${verb.toLocaleUpperCase()} /${config.API_VERSION}${url}`);
84+
apiRouter[verb](`/${config.API_VERSION}${url}`, helper.autoWrapExpress(actions));
85+
});
86+
});
87+
88+
app.use('/', apiRouter);
89+
app.use(errorMiddleware());
90+
app.use('*', (req, res) => {
91+
const pathKey = req.baseUrl.substring(config.API_VERSION.length + 1);
92+
const route = routes[pathKey];
93+
if (route) {
94+
res.status(httpStatus.METHOD_NOT_ALLOWED).json({ message: 'The requested method is not supported.' });
95+
} else {
96+
res.status(httpStatus.NOT_FOUND).json({ message: 'The requested resouce cannot found.' });
97+
}
98+
});
99+
100+
http.listen(app.get('port'), () => {
101+
winston.info(`Express server listening on port ${app.get('port')}`);
102+
});
103+
104+
module.exports = app;

config/default.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
* Copyright (C) 2017 TopCoder Inc., All Rights Reserved.
3+
*/
4+
5+
/**
6+
* the default config
7+
*
8+
* @author TCSCODER
9+
* @version 1.0
10+
*/
11+
12+
module.exports = {
13+
LOG_LEVEL: process.env.LOG_LEVEL || 'debug',
14+
PORT: process.env.PORT || 3000,
15+
16+
PASSWORD_HASH_SALT_LENGTH: (process.env.PASSWORD_HASH_SALT_LENGTH && Number(process.env.PASSWORD_HASH_SALT_LENGTH)) || 10,
17+
18+
ACCESS_TOKEN_EXPIRES: (process.env.ACCESS_TOKEN_EXPIRES && Number(process.env.ACCESS_TOKEN_EXPIRES))
19+
|| 12 * 60 * 60, // 12 hours
20+
VERIFY_TOKEN_EXPIRES: (process.env.VERIFY_TOKEN_EXPIRES && Number(process.env.VERIFY_TOKEN_EXPIRES))
21+
|| 2 * 60 * 60, // 2 hours
22+
FORGOT_PASSWORD_TOKEN_EXPIRES: (process.env.FORGOT_PASSWORD_TOKEN_EXPIRES && Number(process.env.FORGOT_PASSWORD_TOKEN_EXPIRES))
23+
|| 2 * 60 * 60, // 2 hours
24+
QUERY_DEFAULT_LIMIT: (process.env.QUERY_DEFAULT_LIMIT && Number(process.env.QUERY_DEFAULT_LIMIT)) || 20,
25+
API_VERSION: process.env.API_VERSION || 'api/v1',
26+
27+
VERIFY_EMAIL_SUBJECT: process.env.VERIFY_EMAIL_SUBJECT || 'Verify Your Email',
28+
VERIFY_EMAIL_CONTENT: process.env.VERIFY_EMAIL_CONTENT ||
29+
`Hello, Your verification token is:
30+
%s
31+
32+
You can click following url to verify email:
33+
%s`,
34+
35+
FORGOT_PASSWORD_EMAIL_SUBJECT: process.env.FORGOT_PASSWORD_EMAIL_SUBJECT || 'Reset Forgot Password',
36+
FORGOT_PASSWORD_EMAIL_CONTENT: process.env.FORGOT_PASSWORD_EMAIL_CONTENT ||
37+
`Hello, Your reset password token is:
38+
%s`,
39+
SESSION_SECRET: 'secret',
40+
FROM_EMAIL: process.env.FROM_EMAIL || '[email protected]',
41+
email: {
42+
host: process.env.SMTP_HOST || 'localhost',
43+
port: (process.env.SMTP_PORT && Number(process.env.SMTP_PORT)) || 25,
44+
auth: {
45+
user: process.env.SMTP_USER || 'username',
46+
pass: process.env.SMTP_PASSWORD || 'password',
47+
},
48+
},
49+
facebook: {
50+
clientId: process.env.FACEBOOK_APP_ID || '135794683759765',
51+
clientSecret: process.env.FACEBOOK_APP_SECRET || '7802759f88fbfa9c7ada1cdada38be5e',
52+
},
53+
socialRedirectUrl: 'tiMobile://social',
54+
db: {
55+
// the uri format is mysql://username:password@host:port/dbname
56+
uri: process.env.JAWSDB_URL || 'mysql://root:@localhost:3306/ti_mobile_db',
57+
options: {
58+
dialect: 'mysql',
59+
pool: {
60+
max: 5,
61+
min: 0,
62+
idle: 10000,
63+
},
64+
},
65+
},
66+
};

config/production.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* Copyright (c) 2017 Zero Inc, All rights reserved.
3+
*/
4+
5+
6+
/**
7+
* The production configuration file.
8+
*
9+
* @author JiangLiwu
10+
* @version 1.0
11+
*/
12+
13+
module.exports = {
14+
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
15+
};

docs/Backend_Class_Diagram.png

102 KB
Loading

docs/Models_Class_Diagram.png

32.7 KB
Loading
237 KB
Binary file not shown.

0 commit comments

Comments
 (0)