-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#167313420 A user should be able to see their reading statistics #49
Changes from 4 commits
4b13269
71e5ce1
27c36d4
aef2e18
51b1bea
8dc668a
b01d67d
b2f93ac
008f5c0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
/* eslint-disable require-jsdoc */ | ||
import StatsService from '../services/db.service'; | ||
import { success } from '../helpers/responses'; | ||
|
||
const { getStat } = StatsService; | ||
|
||
class statsController { | ||
static async getStats(req, res) { | ||
const readerId = req.auth.id; | ||
const stats = await getStat({ readerId }, 'Stats'); | ||
return success('your reading stats', stats).send(res); | ||
} | ||
} | ||
|
||
export default statsController; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import Util from './util'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Parsing error: 'import' and 'export' may appear only with 'sourceType: module' |
||
|
||
const util = new Util(); | ||
|
||
export const notFound = (msg) => { | ||
util.setError(404, `${msg} not found`); | ||
return util; | ||
}; | ||
|
||
export const error = (msg, code = 400) => { | ||
util.setError(code, msg); | ||
return util; | ||
}; | ||
|
||
export const success = (msg, data = null) => { | ||
util.setSuccess(200, msg, data); | ||
return util; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/* eslint-disable require-jsdoc */ | ||
|
||
import jwt from 'jsonwebtoken'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Parsing error: 'import' and 'export' may appear only with 'sourceType: module' |
||
|
||
import StatsService from '../services/db.service'; | ||
import { notFound, error } from '../helpers/responses'; | ||
|
||
const { getStat } = StatsService; | ||
|
||
class statsWare { | ||
static async checkStats(req, res, next) { | ||
Anguandia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (!req.auth) { | ||
return error('you are not logged in').send(res); | ||
} | ||
const readerId = req.auth.id; | ||
const stats = await getStat({ readerId }, 'Stats'); | ||
if (!stats.length) { | ||
return notFound('reading stats').send(res); | ||
} | ||
next(); | ||
} | ||
|
||
static async saveStat(req, res, next) { | ||
try { | ||
let token = req.headers['x-access-token'] || req.headers.authorization; | ||
token = token.slice(7, token.length); | ||
jwt.verify(token, process.env.SECRET_KEY, (err, decode) => { | ||
req.auth = decode; | ||
next(); | ||
}); | ||
} catch (err) { | ||
next(); | ||
} | ||
} | ||
} | ||
|
||
export default statsWare; | ||
Anguandia marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
'use strict'; | ||
module.exports = { | ||
up: (queryInterface, Sequelize) => { | ||
return queryInterface.createTable('Stats', { | ||
id: { | ||
allowNull: false, | ||
autoIncrement: true, | ||
primaryKey: true, | ||
type: Sequelize.INTEGER | ||
}, | ||
slug: { | ||
type: Sequelize.STRING | ||
}, | ||
item: { | ||
type: Sequelize.STRING | ||
}, | ||
readerId: { | ||
type: Sequelize.INTEGER | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens if the user is deleted? |
||
}, | ||
createdAt: { | ||
allowNull: false, | ||
type: Sequelize.DATE | ||
}, | ||
updatedAt: { | ||
allowNull: false, | ||
type: Sequelize.DATE | ||
} | ||
}); | ||
}, | ||
down: (queryInterface, Sequelize) => { | ||
return queryInterface.dropTable('Stats'); | ||
} | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
'use strict'; | ||
module.exports = (sequelize, DataTypes) => { | ||
const Stats = sequelize.define('Stats', { | ||
slug: DataTypes.STRING, | ||
item: DataTypes.STRING, | ||
readerId: DataTypes.INTEGER | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In my opinion I think we can have a relationship between readerId and user table. What do you think @Anguandia There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, readerId actually translates to the id in the user table |
||
}, {}); | ||
Stats.associate = function (models) { | ||
// associations can be defined here | ||
} | ||
return Stats; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ import tagController from '../../../controllers/tag'; | |
import TagWare from '../../../middlewares/tag.middleware'; | ||
import highlight from '../../../controllers/highlight.controller'; | ||
import share from '../../../middlewares/shareHighlight.middleware'; | ||
import StatsWare from '../../../middlewares/stats'; | ||
|
||
const router = express.Router(); | ||
const { | ||
|
@@ -24,7 +25,7 @@ const { | |
router.post('/:articleId/favorite', [auth, confirmEmailAuth, validateId], FavoritesController.createOrRemoveFavorite); | ||
router.post('/', [auth, confirmEmailAuth], imageUpload.array('images', 10), validate(schema.articleSchema), articleController.createArticles); | ||
router.get('/', checkQuery, articleController.getAllArticles); | ||
router.get('/:slug', articleController.getOneArticle); | ||
router.get('/:slug', StatsWare.saveStat, articleController.getOneArticle); | ||
Anguandia marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can an Unauthenticated user get his reading statistics? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No Patric, it is the user stats, and the only way of identifying the user is through his auth token/userid or email extracted from here |
||
router.delete('/:slug', [auth, confirmEmailAuth], articleController.deleteArticle); | ||
router.patch('/:slug', [auth, confirmEmailAuth], imageUpload.array('images', 10), articleController.UpdateArticle); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/* eslint-disable require-jsdoc */ | ||
import models from '../models'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Parsing error: 'import' and 'export' may appear only with 'sourceType: module' |
||
|
||
const { Stats, Article } = models; | ||
const Models = { Stats, Article }; | ||
const conditon = where => ({ where }); | ||
|
||
class StatsService { | ||
static async createStat(where, model) { | ||
return Models[model].create(where); | ||
} | ||
|
||
static async getStat(where, model) { | ||
return Models[model].findAll(conditon(where)); | ||
} | ||
} | ||
|
||
export default StatsService; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { chai, expect, server } from './test-setup'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Parsing error: 'import' and 'export' may appear only with 'sourceType: module' |
||
|
||
let token; | ||
describe('Test user stats', () => { | ||
before((done) => { | ||
chai.request(server) | ||
.post('/api/v1/users/login') | ||
.send({ email: 'admin@gmail.com', password: 'ASqw12345' }) | ||
.end((error, res) => { | ||
token = `Bearer ${res.body.token}`; | ||
done(); | ||
}); | ||
}); | ||
describe('test getting stats', () => { | ||
it('should return correct response if none', (done) => { | ||
chai.request(server) | ||
.get('/api/v1/users/stats') | ||
.set('Authorization', token) | ||
.end((err, res) => { | ||
expect(res.status).to.be.equal(404); | ||
expect(res.body.message).to.include('not found'); | ||
done(); | ||
}); | ||
}); | ||
it('should return correct response if user not logged in', (done) => { | ||
chai.request(server) | ||
.get('/api/v1/users/stats') | ||
.end((err, res) => { | ||
expect(res.status).to.be.equal(400); | ||
expect(res.body.message).to.include('not logged in'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('test stats gathering', () => { | ||
it('should not populate stats if user unauthenticated', (done) => { | ||
chai.request(server) | ||
.get('/api/v1/articles/fakeslug2') | ||
.end(() => { | ||
chai.request(server) | ||
.get('/api/v1/users/stats') | ||
.set('Authorization', token) | ||
.end((err, res) => { | ||
expect(res.status).to.be.equal(404); | ||
expect(res.body.message).to.include('not found'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('should log every read', (done) => { | ||
chai.request(server) | ||
.get('/api/v1/articles/fakeslug2') | ||
.set('Authorization', token) | ||
.end(() => { | ||
chai.request(server) | ||
.get('/api/v1/users/stats') | ||
.set('Authorization', token) | ||
.end((err, res) => { | ||
expect(res.status).to.be.equal(200); | ||
expect(res.body).to.have.deep.property('message', 'your reading stats'); | ||
expect(res.body.data).to.be.a('Array'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Parsing error: 'import' and 'export' may appear only with 'sourceType: module'