Added /stats
endpoint
This commit is contained in:
parent
02d7d885ba
commit
f050397509
8 changed files with 136 additions and 0 deletions
src
repos
requests
responses
routes/api/v1
43
src/repos/Stats.ts
Normal file
43
src/repos/Stats.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import {LocationStats} from '../responses';
|
||||
import {Sequelize} from 'sequelize';
|
||||
import {StatsRequest} from '../requests';
|
||||
|
||||
class Stats {
|
||||
public async get(req: StatsRequest): Promise<LocationStats[]> {
|
||||
const dbColumnsToProps = req.groupBy.reduce((acc, g) => {
|
||||
acc[$db.locationTableColumns[g]] = g;
|
||||
return acc;
|
||||
}, {} as Record<string, string>);
|
||||
|
||||
const groupBy = Object.keys(dbColumnsToProps);
|
||||
|
||||
return (
|
||||
await $db.GPSData().findAll({
|
||||
attributes: [
|
||||
...groupBy,
|
||||
[Sequelize.fn('COUNT', Sequelize.col($db.locationTableColumns.id)), 'count'],
|
||||
[Sequelize.fn('MIN', Sequelize.col($db.locationTableColumns.timestamp)), 'startDate'],
|
||||
[Sequelize.fn('MAX', Sequelize.col($db.locationTableColumns.timestamp)), 'endDate'],
|
||||
],
|
||||
where: {
|
||||
deviceId: (await $db.UserDevice() .findAll({where: {userId: req.userId}}))
|
||||
.map((d) => d.dataValues.id)
|
||||
},
|
||||
group: groupBy,
|
||||
order: [[Sequelize.fn('COUNT', Sequelize.col($db.locationTableColumns.id)), req.order]],
|
||||
})
|
||||
).map(({dataValues: data}: any) =>
|
||||
new LocationStats({
|
||||
key: Object.keys(data).reduce((acc, k) => {
|
||||
acc[dbColumnsToProps[k] || k] = data[k];
|
||||
return acc;
|
||||
}, {} as Record<string, any>),
|
||||
count: data.count,
|
||||
startDate: data.startDate,
|
||||
endDate: data.endDate,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Stats;
|
|
@ -1,4 +1,5 @@
|
|||
import Location from './Location';
|
||||
import Stats from './Stats';
|
||||
import Users from './Users';
|
||||
import UserDevices from './UserDevices';
|
||||
import UserRoles from './UserRoles';
|
||||
|
@ -6,6 +7,7 @@ import UserSessions from './UserSessions';
|
|||
|
||||
class Repositories {
|
||||
public location: Location;
|
||||
public stats: Stats;
|
||||
public users: Users;
|
||||
public userDevices: UserDevices;
|
||||
public userRoles: UserRoles;
|
||||
|
@ -13,6 +15,7 @@ class Repositories {
|
|||
|
||||
constructor() {
|
||||
this.location = new Location();
|
||||
this.stats = new Stats();
|
||||
this.users = new Users();
|
||||
this.userDevices = new UserDevices();
|
||||
this.userRoles = new UserRoles();
|
||||
|
|
25
src/requests/StatsRequest.ts
Normal file
25
src/requests/StatsRequest.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
type Order = 'ASC' | 'DESC';
|
||||
type GroupBy = 'device' | 'country' | 'locality' | 'postalCode' | 'description';
|
||||
|
||||
class StatsRequest {
|
||||
userId: number;
|
||||
groupBy: GroupBy[];
|
||||
order: Order = 'DESC';
|
||||
|
||||
constructor(req: {
|
||||
userId: number;
|
||||
groupBy: string[] | string;
|
||||
order?: string;
|
||||
}) {
|
||||
this.userId = req.userId;
|
||||
this.groupBy = (
|
||||
typeof req.groupBy === 'string' ?
|
||||
req.groupBy.split(/\s*,\s*/) :
|
||||
req.groupBy
|
||||
) as GroupBy[];
|
||||
|
||||
this.order = (req.order || this.order).toUpperCase() as Order;
|
||||
}
|
||||
}
|
||||
|
||||
export default StatsRequest;
|
|
@ -1,5 +1,7 @@
|
|||
import LocationRequest from "./LocationRequest";
|
||||
import StatsRequest from "./StatsRequest";
|
||||
|
||||
export {
|
||||
LocationRequest,
|
||||
StatsRequest,
|
||||
}
|
||||
|
|
22
src/responses/LocationStats.ts
Normal file
22
src/responses/LocationStats.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import {Optional} from "~/types";
|
||||
|
||||
class LocationStats {
|
||||
public key: Record<string, any>;
|
||||
public count: number;
|
||||
public startDate: Optional<Date>;
|
||||
public endDate: Optional<Date>;
|
||||
|
||||
constructor(data: {
|
||||
key: Record<string, any>;
|
||||
count: number;
|
||||
startDate: Optional<Date>;
|
||||
endDate: Optional<Date>;
|
||||
}) {
|
||||
this.key = data.key;
|
||||
this.count = data.count;
|
||||
this.startDate = data.startDate;
|
||||
this.endDate = data.endDate;
|
||||
}
|
||||
}
|
||||
|
||||
export default LocationStats;
|
5
src/responses/index.ts
Normal file
5
src/responses/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import LocationStats from "./LocationStats"
|
||||
|
||||
export {
|
||||
LocationStats,
|
||||
}
|
34
src/routes/api/v1/Stats.ts
Normal file
34
src/routes/api/v1/Stats.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { Request, Response } from 'express';
|
||||
|
||||
import { authenticate } from '../../../auth';
|
||||
import { AuthInfo } from '../../../auth';
|
||||
import { Optional } from '../../../types';
|
||||
import { StatsRequest } from '../../../requests';
|
||||
import ApiV1Route from './Route';
|
||||
|
||||
class Stats extends ApiV1Route {
|
||||
constructor() {
|
||||
super('/stats');
|
||||
}
|
||||
|
||||
@authenticate()
|
||||
get = async (req: Request, res: Response, auth: Optional<AuthInfo>) => {
|
||||
let query: StatsRequest;
|
||||
|
||||
try {
|
||||
query = new StatsRequest({
|
||||
...req.query as any,
|
||||
userId: auth!.user.id,
|
||||
});
|
||||
} catch (error) {
|
||||
const e = `Error parsing query: ${error}`;
|
||||
console.warn(e);
|
||||
res.status(400).send(e);
|
||||
return;
|
||||
}
|
||||
|
||||
res.json(await $repos.stats.get(query));
|
||||
}
|
||||
}
|
||||
|
||||
export default Stats;
|
|
@ -3,6 +3,7 @@ import Devices from "./Devices";
|
|||
import DevicesById from "./DevicesById";
|
||||
import GPSData from "./GPSData";
|
||||
import Routes from "../../Routes";
|
||||
import Stats from "./Stats";
|
||||
import Tokens from "./Tokens";
|
||||
import TokensById from "./TokensById";
|
||||
import UserSelf from "./UserSelf";
|
||||
|
@ -13,6 +14,7 @@ class ApiV1Routes extends Routes {
|
|||
new Devices(),
|
||||
new DevicesById(),
|
||||
new GPSData(),
|
||||
new Stats(),
|
||||
new Tokens(),
|
||||
new TokensById(),
|
||||
new UserSelf(),
|
||||
|
|
Loading…
Add table
Reference in a new issue