Skip to content

Commit f85562d

Browse files
committed
Add Sessions list resource
1 parent 6070df1 commit f85562d

5 files changed

Lines changed: 181 additions & 13 deletions

File tree

src/controllers/Sessions.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,28 @@
11
import * as Sessions from '../lib/Sessions'
22

33
export default {
4+
list(req, res) {
5+
Promise
6+
.all([
7+
Sessions.list({
8+
res,
9+
query: req.query,
10+
returnData: true,
11+
jsonData: true
12+
}),
13+
Sessions.pages({ query: req.query })
14+
])
15+
.then(promises => {
16+
res.status(200).send({
17+
rows: promises[0],
18+
pages: promises[1]
19+
})
20+
})
21+
.catch(error => {
22+
res.status(400).send(error)
23+
})
24+
},
25+
426
authenticate(req, res) {
527
Sessions
628
.auth(req, res)

src/helpers/ActiveRecord.js

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,52 @@
11
import _ from 'lodash'
22
import Sequelize from 'sequelize'
33

4+
const Op = Sequelize.Op
45
const DEFAULT_LIMIT = 20
56

67
export function filters(filtered, options={}) {
78
if (!filtered) return {}
89

910
const filterArray = []
1011

11-
decodeURIComponent(filtered).split(',').map(filter => {
12+
_.map(decodeURIComponent(filtered).split(','), filter => {
1213
const optionKeys = Object.keys(options)
1314
const array = filter.split(':')
1415
const column = array[0]
1516
const value = array[1]
1617

1718
if (_.indexOf(optionKeys, 'regexp') > -1 && _.indexOf(options.regexp, column) > -1)
18-
decodeURIComponent(value).split(' ').map(decodedValue => {
19+
_.map(decodeURIComponent(value).split(' '), decodedValue => {
1920
filterArray.push(whereSQLFn(column, decodedValue, 'regexp'))
2021
})
2122
else
2223
filterArray.push(filterType(column, decodeURIComponent(value), options))
2324
})
2425

25-
return { $and: filterArray }
26+
return { [Op.and]: filterArray }
2627
}
2728

28-
export function orderBy(sorted, defaultOrder=[]) {
29+
export function orderBy(sorted, defaultOrder=[], options) {
2930
if (!sorted) return [defaultOrder]
3031

31-
return sorted.split(',').map(sort => {
32-
return sort.split(':')
33-
})
32+
return parseEncodedQuery(sorted, options)
3433
}
3534

3635
export function pageCount({ limit }, count) {
3736
return Math.ceil(count / (limit ? limit : DEFAULT_LIMIT))
3837
}
3938

39+
export function parseEncodedQuery(query, options) {
40+
return _.map(query.split(','), q => {
41+
let kv = q.split(':')
42+
43+
if (options && options.replace && options.replace[kv[0]])
44+
return [options.replace[kv[0]], kv[1]]
45+
46+
return kv
47+
})
48+
}
49+
4050
function filterType(key, value, options) {
4151
const optionKeys = Object.keys(options)
4252

@@ -46,13 +56,13 @@ function filterType(key, value, options) {
4656
const col = options[key].col
4757

4858
if (type === 'integer')
49-
filterObject[key] = parseInt(value)
59+
filterObject[key] = isNaN(parseInt(value)) ? 0 : parseInt(value)
5060

5161
if (type === 'minDate')
52-
filterObject[col] = { $gte: new Date(value) }
62+
filterObject[col] = { [Op.gte]: new Date(value) }
5363

5464
if (type === 'maxDate')
55-
filterObject[col] = { $lte: new Date(value) }
65+
filterObject[col] = { [Op.lte]: new Date(value) }
5666

5767
return filterObject
5868
}
@@ -63,8 +73,10 @@ function filterType(key, value, options) {
6373
function whereSQLFn(key, value, type='equal') {
6474
let op = {}
6575

66-
if (type === 'equal') op = { $eq: value.toLowerCase() }
67-
if (type === 'regexp') op = { $regexp: ['\\y', value, '\\y'].join('') }
76+
value = value.toLowerCase()
77+
78+
if (type === 'equal') op = { [Op.eq]: value }
79+
if (type === 'regexp') op = { [Op.regexp]: ['\\y', value, '\\y'].join('') }
6880

6981
return Sequelize.where(
7082
Sequelize.fn(

src/lib/Sessions.js

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
1+
import _ from 'lodash'
2+
import Sequelize from 'sequelize'
13
import JWT from 'jsonwebtoken'
24
import Passport from 'passport'
35
import HttpBearerStrategy from 'passport-http-bearer'
6+
import { filters, pageCount, orderBy, parseEncodedQuery } from '../helpers/ActiveRecord'
47
import DB from '../models'
58
import * as Users from './Users'
69

10+
const Op = Sequelize.Op
11+
const FILTER_OPTIONS = {
12+
dateFrom: { col: 'createdAt', type: 'minDate' },
13+
dateTo: { col: 'createdAt', type: 'maxDate' },
14+
regexp: ['userAgent']
15+
}
16+
717
Passport.use(new HttpBearerStrategy(
818
function(token, done) {
919
const { userId } = verifyToken(null, { token, returnData: true })
@@ -22,6 +32,58 @@ Passport.use(new HttpBearerStrategy(
2232
}
2333
))
2434

35+
export function list(options) {
36+
const { res, query, returnData, jsonData } = options
37+
const { filtered, sorted, limit, page } = query
38+
const orderOptions = {
39+
sorted: true,
40+
replace: {
41+
user: Sequelize.literal('\"User\".\"firstName\"'),
42+
updatedAt: Sequelize.literal([
43+
'\"Session\".\"signedOut\" DESC',
44+
'\"Session\".\"updatedAt\"'
45+
].join(',')),
46+
}
47+
}
48+
49+
return DB.Session
50+
.findAll({
51+
where: filters(setQuery(filtered), FILTER_OPTIONS),
52+
include: [setIncludeUser(filtered)],
53+
offset: (page - 1) * limit,
54+
order: orderBy(
55+
setQuery(sorted, true),
56+
['sessionId', 'desc'],
57+
orderOptions
58+
),
59+
limit
60+
})
61+
.then(Sessions => {
62+
const data = jsonData ? jsonSessions(Sessions) : Sessions
63+
64+
if (returnData) return data
65+
66+
return res.status(data ? 200 : 404).send(data)
67+
})
68+
.catch(error => {
69+
console.log(error)
70+
71+
return returnData ? error : res.status(400).send(error)
72+
})
73+
}
74+
75+
export function pages({ query }) {
76+
return DB.Session
77+
.count({
78+
col: 'sessionId',
79+
where: filters(setQuery(query.filtered), FILTER_OPTIONS),
80+
include: [setIncludeUser(query.filtered)]
81+
})
82+
.then(count => {
83+
return pageCount(query, count)
84+
})
85+
}
86+
2587
export function find(res, options) {
2688
const { where, returnData } = options
2789

@@ -31,7 +93,7 @@ export function find(res, options) {
3193
include: [{
3294
model: DB.User,
3395
as: 'User',
34-
attributes: ['userId', 'firstName', 'lastName', 'email', 'role', 'status', 'redirect']
96+
attributes: ['userId']
3597
}]
3698
})
3799
.then(Session => {
@@ -122,3 +184,70 @@ function getIpAddress(req) {
122184
req.connection.socket.remoteAddress
123185
).split(',')[0]
124186
}
187+
188+
function jsonSessions(Sessions) {
189+
return _.map(Sessions, Session => {
190+
return {
191+
sessionId: Session.sessionId,
192+
user: {
193+
userId: Session.User.userId,
194+
name: Session.User.fullName()
195+
},
196+
userAgent: Session.userAgent,
197+
ipAddress: Session.ipAddress,
198+
createdAt: Session.createdAt,
199+
signedOutAt: Session.signedOutAt()
200+
}
201+
})
202+
}
203+
204+
function setQuery(query, sorted=false) {
205+
const parsed = query ? parseEncodedQuery(query) : []
206+
const newQuery = _.compact(
207+
_.map(parsed, p => {
208+
if (p[0] === 'signedOutAt') return ['updatedAt', p[1]].join(':')
209+
if (p[0] !== 'user' || (p[0] === 'user' && sorted)) return p.join(':')
210+
})
211+
).join(',')
212+
213+
return newQuery
214+
}
215+
216+
function setIncludeUser(filtered) {
217+
const parsed = filtered ? parseEncodedQuery(filtered) : []
218+
219+
let includeUser = {
220+
model: DB.User,
221+
as: 'User',
222+
attributes: ['userId', 'firstName', 'lastName']
223+
}
224+
225+
_.map(parsed, p => {
226+
if (p[0] === 'user')
227+
return includeUser['where'] = whereUser(p[1])
228+
})
229+
230+
return includeUser
231+
}
232+
233+
function whereUser(values) {
234+
const decodedValues = decodeURIComponent(decodeURIComponent(values))
235+
const userSQLFnArray = _.map(decodedValues.split(' '), value => {
236+
return { [Op.or]: [
237+
userSQLFn('firstName', value),
238+
userSQLFn('lastName', value)
239+
]}
240+
})
241+
242+
return { [Op.and]: userSQLFnArray }
243+
}
244+
245+
function userSQLFn(column, value) {
246+
return Sequelize.where(
247+
Sequelize.fn(
248+
'lower',
249+
Sequelize.col(column)
250+
),
251+
{ [Op.regexp]: ['\\y', value.toLowerCase(), '\\y'].join('') }
252+
)
253+
}

src/models/Session.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ export default (sequelize, DataTypes) => {
1414
signedOut: DataTypes.BOOLEAN
1515
})
1616

17+
Session.prototype.signedOutAt = function() {
18+
return this.signedOut ? this.updatedAt : null
19+
}
20+
1721
Session.associate = (models) => {
1822
Session.belongsTo(models.User, {
1923
as: 'User',

src/routes/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export default (app) => {
1010
app.post('/sign-in', C.Sessions.authenticate)
1111
app.post('/sign-out', authBearer(), C.Sessions.signOut)
1212
app.get('/verify-token', authBearer(), C.Sessions.verifyToken)
13+
app.get('/sessions', authBearer(), C.Sessions.list)
1314

1415
/* Users */
1516
app.get('/users', authBearer(), C.Users.list)

0 commit comments

Comments
 (0)