Skip to content

Commit

Permalink
Merge tag '1.5.6' into develop
Browse files Browse the repository at this point in the history
Bug Fixes
- Fixed the error that did not exclude all user resources when receiving the UserDeleteEvent event;
- Corrected the error that did not synchronize the physical activities and the sleep records of the current day;
- Fixed the error in mapping the heart rate zones;
- Fixed the error caused by the transformation of dates and time in UTC. Now, the information is not converted, however, the .Z is inserted at the end of the timestamp, so that the iot-tracking service does not convert to the local time zone, it is up to customers to convert when necessary;
- Fixed the error that caused the end_time to be less than the start_date;
- Fixed token revocation process to always return 204, except in cases of non-existent authentication data, invalid user ID or internal error.
  • Loading branch information
douglasrafael committed Jan 29, 2020
2 parents 2574e58 + ac9a7fd commit f59f803
Show file tree
Hide file tree
Showing 24 changed files with 1,210 additions and 766 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Application settings are defined by environment variables. To define the setting
| `RABBITMQ_CA_PATH` | RabbitMQ CA file location. Must always be provided when using `amqps` protocol. | `.certs/rabbitmqca.crt` |
| `FITBIT_CLIENT_ID` | Client Id for Fitbit Application resposible to manage user data. | `CIENT_ID_HERE` |
| `FITBIT_CLIENT_SECRET` | Client Secret for Fitbit Application resposible to manage user data. | `CIENT_SECRET_HERE` |
| `FITBIT_CLIENT_SUBSCRIBER` | Client Subscriber code for automatically get notification from new sync data. | `CLIENT_SUBSCRIBER_HERE` |
| `FITBIT_CLIENT_SUBSCRIBER` | Code used by Fitbit to verify the subscriber. | `CLIENT_SUBSCRIBER_HERE` |
| `FITBIT_SUBSCRIBER_ID` | Customer Subscriber ID, used to manage the subscriber who will receive notification of a user resource. | `FITBIT_SUBSCRIBER_ID` |
| `EXPRESSION_AUTO_SYNC` | Defines how often the application will automatically sync user data in the background according to the crontab expression. | `0 0 * * 0` |

Expand Down
1,301 changes: 761 additions & 540 deletions package-lock.json

Large diffs are not rendered by default.

28 changes: 14 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "data-sync-agent",
"version": "1.5.5",
"version": "1.5.6",
"description": "Service responsible for data synchronization of FitBit and CVE platform with OCARIoT platform.",
"main": "dist/server.js",
"scripts": {
Expand Down Expand Up @@ -53,8 +53,8 @@
"all": true
},
"dependencies": {
"@ocariot/rabbitmq-client-node": "^1.4.0",
"axios": "^0.19.0",
"@ocariot/rabbitmq-client-node": "1.4.0",
"axios": "^0.19.2",
"body-parser": "^1.19.0",
"bull": "^3.12.1",
"dotenv": "^8.2.0",
Expand All @@ -65,38 +65,38 @@
"inversify-express-utils": "^6.3.2",
"jsonwebtoken": "^8.5.1",
"moment": "^2.24.0",
"mongoose": "^5.7.13",
"mongoose": "5.8.2",
"morgan": "^1.9.1",
"node-cron": "^2.0.3",
"query-strings-parser": "^2.1.3",
"reflect-metadata": "^0.1.13",
"swagger-ui-express": "^4.1.2",
"swagger-ui-express": "^4.1.3",
"winston": "^3.2.1",
"winston-daily-rotate-file": "^4.3.0"
"winston-daily-rotate-file": "^4.4.2"
},
"devDependencies": {
"@types/body-parser": "^1.17.1",
"@types/bull": "^3.10.6",
"@types/chai": "^4.2.5",
"@types/chai": "^4.2.7",
"@types/express": "^4.17.2",
"@types/helmet": "^0.0.45",
"@types/mocha": "^5.2.7",
"@types/mongoose": "^5.5.32",
"@types/mongoose": "^5.5.43",
"@types/morgan": "^1.7.37",
"@types/swagger-ui-express": "^4.1.0",
"@types/swagger-ui-express": "^4.1.1",
"chai": "^4.2.0",
"gulp": "^4.0.2",
"gulp-nodemon": "^2.4.2",
"gulp-tslint": "^8.1.4",
"gulp-typescript": "^5.0.1",
"mocha": "^6.2.2",
"nyc": "^14.1.1",
"nyc": "^15.0.0",
"sinon": "^7.5.0",
"sinon-mongoose": "^2.3.0",
"supertest": "^4.0.2",
"ts-node": "^8.5.2",
"tslint": "^5.20.1",
"typedoc": "^0.15.3",
"typescript": "^3.7.2"
"ts-node": "^8.6.2",
"tslint": "^6.0.0",
"typedoc": "^0.16.9",
"typescript": "^3.7.5"
}
}
35 changes: 17 additions & 18 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ export class App {
* @private
* @return void
*/
private async initMiddleware(): Promise<void> {
private initMiddleware(): void {
try {
await this.setupInversifyExpress()
this.setupInversifyExpress()
this.setupSwaggerUI()
this.setupErrorsHandler()
} catch (err) {
Expand All @@ -75,7 +75,7 @@ export class App {
* @private
* @return Promise<void>
*/
private async setupInversifyExpress(): Promise<void> {
private setupInversifyExpress(): void {
const inversifyExpress: InversifyExpressServer = new InversifyExpressServer(
DIContainer, null, { rootPath: '/' })

Expand Down Expand Up @@ -117,15 +117,13 @@ export class App {
*/
private setupSwaggerUI(): void {
// Middleware swagger. It should not run in the test environment.
if ((process.env.NODE_ENV || Default.NODE_ENV) !== 'test') {
const options = {
swaggerUrl: Default.SWAGGER_URI,
customCss: '.swagger-ui .topbar { display: none }',
customfavIcon: Default.LOGO_URI,
customSiteTitle: `API Reference | ${Strings.APP.TITLE}`
}
this.express.use('/v1/reference', swaggerUi.serve, swaggerUi.setup({}, options))
const options = {
swaggerUrl: Default.SWAGGER_URI,
customCss: '.swagger-ui .topbar { display: none }',
customfavIcon: Default.LOGO_URI,
customSiteTitle: `API Reference | ${Strings.APP.TITLE}`
}
this.express.use('/v1/reference', swaggerUi.serve, swaggerUi.setup({}, options))
}

/**
Expand All @@ -137,9 +135,11 @@ export class App {
private setupErrorsHandler(): void {
// Handle 404
this.express.use((req, res) => {
const errorMessage: ApiException = new ApiException(404, `${req.url} not found.`,
`Specified resource: ${req.url} was not found or does not exist.`)
res.status(HttpStatus.NOT_FOUND).send(errorMessage.toJson())
const errorMessage: ApiException = new ApiException(
404,
Strings.ERROR_MESSAGE.ENDPOINT_NOT_FOUND.replace('{0}', req.url)
)
res.status(HttpStatus.NOT_FOUND).send(errorMessage.toJSON())
})

// Handle 400, 500
Expand All @@ -149,11 +149,10 @@ export class App {
if (err && err.statusCode === HttpStatus.BAD_REQUEST) {
statusCode = HttpStatus.BAD_REQUEST
errorMessage.code = statusCode
errorMessage.message = 'Unable to process request body.'
errorMessage.description = 'Please verify that the JSON provided in'
.concat(' the request body has a valid format and try again.')
errorMessage.message = Strings.ERROR_MESSAGE.REQUEST_BODY_INVALID
errorMessage.description = Strings.ERROR_MESSAGE.REQUEST_BODY_INVALID_DESC
}
res.status(statusCode).send(errorMessage.toJson())
res.status(statusCode).send(errorMessage.toJSON())
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { IUserAuthDataRepository } from '../../port/user.auth.data.repository.in
import { Query } from '../../../infrastructure/repository/query/query'
import { IFitbitDataRepository } from '../../port/fitbit.auth.data.repository.interface'
import { UserAuthData } from '../../domain/model/user.auth.data'
import { IResourceRepository } from '../../port/resource.repository.interface'

/**
* Handler for UserDeleteEvent operation.
Expand All @@ -20,6 +21,7 @@ export const userDeleteEventHandler = async (event: any) => {
DIContainer.get<IUserAuthDataRepository>(Identifier.USER_AUTH_DATA_REPOSITORY)
const fitbitAuthDataRepo: IFitbitDataRepository =
DIContainer.get<IFitbitDataRepository>(Identifier.FITBIT_DATA_REPOSITORY)
const resourceRepo: IResourceRepository = DIContainer.get<IResourceRepository>(Identifier.RESOURCE_REPOSITORY)

try {
if (typeof event === 'string') event = JSON.parse(event)
Expand All @@ -35,21 +37,23 @@ export const userDeleteEventHandler = async (event: any) => {
const query: Query = new Query().fromJSON({ filters: { user_id: childId } })
const userAuthData: UserAuthData = await userAuthDataRepo.findOne(query)
if (userAuthData) {
const payload: any = await fitbitAuthDataRepo.getTokenPayload(userAuthData.fitbit!.access_token!)
if (payload || payload.scopes) {
const scopes: Array<string> = payload.scopes.split(' ')
if (scopes.includes('rwei')) { // Scope reference from fitbit to weight data is rwei
await fitbitAuthDataRepo.unsubscribeUserEvent(userAuthData.fitbit!, 'body', 'BODY')
}
if (scopes.includes('ract')) { // Scope reference from fitbit to activity data is ract
await fitbitAuthDataRepo.unsubscribeUserEvent(userAuthData.fitbit!, 'activities', 'ACTIVITIES')
}
if (scopes.includes('rsle')) { // Scope reference from fitbit to sleep data is rsle
await fitbitAuthDataRepo.unsubscribeUserEvent(userAuthData.fitbit!, 'sleep', 'SLEEP')
if (userAuthData.fitbit!.scope!) {
const payload: any = await fitbitAuthDataRepo.getTokenPayload(userAuthData.fitbit!.access_token!)
if (payload || payload.scopes) {
const scopes: Array<string> = payload.scopes.split(' ')
if (scopes.includes('rwei')) { // Scope reference from fitbit to weight data is rwei
await fitbitAuthDataRepo.unsubscribeUserEvent(userAuthData.fitbit!, 'body', 'BODY')
}
if (scopes.includes('ract')) { // Scope reference from fitbit to activity data is ract
await fitbitAuthDataRepo.unsubscribeUserEvent(userAuthData.fitbit!, 'activities', 'ACTIVITIES')
}
if (scopes.includes('rsle')) { // Scope reference from fitbit to sleep data is rsle
await fitbitAuthDataRepo.unsubscribeUserEvent(userAuthData.fitbit!, 'sleep', 'SLEEP')
}
}
}
await userAuthDataRepo.deleteByQuery(query)

await resourceRepo.deleteByQuery(query)
// 3. If got here, it's because the action was successful.
logger.info(`Action for event ${event.event_name} successfully held!`)
}
Expand Down
4 changes: 3 additions & 1 deletion src/application/port/resource.repository.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { IQuery } from './query.interface'
import { IRepository } from './repository.interface'
import { Resource } from '../domain/model/resource'

export interface IResourceRepository extends IRepository<Resource>{
export interface IResourceRepository extends IRepository<Resource> {
checkExists(query: IQuery): Promise<boolean>

deleteByQuery(query: IQuery): Promise<boolean>
}
2 changes: 1 addition & 1 deletion src/application/port/user.auth.data.service.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { DataSync } from '../domain/model/data.sync'
export interface IUserAuthDataService extends IService<UserAuthData> {
getByUserId(userId: string): Promise<UserAuthData>

revokeFitbitAccessToken(userId: string): Promise<boolean>
revokeFitbitAccessToken(userId: string): Promise<void>

syncFitbitDataFromUser(userId: string): Promise<DataSync>

Expand Down
35 changes: 25 additions & 10 deletions src/application/service/user.auth.data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,36 +81,51 @@ export class UserAuthDataService implements IUserAuthDataService {
return this._userAuthDataRepo.findOne(new Query().fromJSON({ filters: { user_id: userId } }))
}

public revokeFitbitAccessToken(userId: string): Promise<boolean> {
public revokeFitbitAccessToken(userId: string): Promise<void> {
return new Promise(async (resolve, reject) => {
let authData: UserAuthData
try {
ObjectIdValidator.validate(userId)

// 1. Check if user has authorization data saved.
const authData: UserAuthData = await this._userAuthDataRepo
authData = await this._userAuthDataRepo
.findOne(new Query().fromJSON({ filters: { user_id: userId } }))
if (!authData || !authData.fitbit || !authData.fitbit.access_token) {
return resolve(false)
return resolve()
}

// 2. Unsubscribe from Fitbit events.
await this.unsubscribeFitbitEvents(authData)

// 3. Revokes Fitbit access token.

const isRevoked: boolean = await this._fitbitAuthDataRepo.revokeToken(authData.fitbit.access_token)
// 4. Remove Fitbit authorization data from local database.
if (await this._fitbitAuthDataRepo.revokeToken(authData.fitbit.access_token) &&
await this._fitbitAuthDataRepo.removeFitbitAuthData(userId)) {
// 5. Publish the Fitbit revoke event on the bus.
const isRemoved: boolean = await this._fitbitAuthDataRepo.removeFitbitAuthData(userId)

// 5. Publish the Fitbit revoke event on the bus.
if (isRevoked && isRemoved) {
this._eventBus.bus
.pubFitbitRevoke({ child_id: userId })
.then(() => this._logger.info(`Fitbit revoke event for child ${userId} successfully published!`))
.catch((err) => this._logger.error(`There was an error publishing Fitbit revoke event for child ${userId}. ${err.message}`))
return resolve(true)
} else {
return resolve(false)
}
return resolve()
} catch (err) {
return reject(err)
if (err.type) {
if (err.type === 'expired_token') {
this._fitbitAuthDataRepo
.refreshToken(userId, authData!.fitbit!.access_token!, authData!.fitbit!.refresh_token!)
.then(async newToken => {
await this.revokeFitbitAccessToken(userId)
return resolve()
}).catch(err => {
if (err.type !== 'system') this.updateTokenStatus(userId, err.type)
})
}
this.publishFitbitAuthError(err, userId)
}
return resolve()
}
})
}
Expand Down
Loading

0 comments on commit f59f803

Please sign in to comment.