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 Location from './Location';
|
||||||
|
import Stats from './Stats';
|
||||||
import Users from './Users';
|
import Users from './Users';
|
||||||
import UserDevices from './UserDevices';
|
import UserDevices from './UserDevices';
|
||||||
import UserRoles from './UserRoles';
|
import UserRoles from './UserRoles';
|
||||||
|
@ -6,6 +7,7 @@ import UserSessions from './UserSessions';
|
||||||
|
|
||||||
class Repositories {
|
class Repositories {
|
||||||
public location: Location;
|
public location: Location;
|
||||||
|
public stats: Stats;
|
||||||
public users: Users;
|
public users: Users;
|
||||||
public userDevices: UserDevices;
|
public userDevices: UserDevices;
|
||||||
public userRoles: UserRoles;
|
public userRoles: UserRoles;
|
||||||
|
@ -13,6 +15,7 @@ class Repositories {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.location = new Location();
|
this.location = new Location();
|
||||||
|
this.stats = new Stats();
|
||||||
this.users = new Users();
|
this.users = new Users();
|
||||||
this.userDevices = new UserDevices();
|
this.userDevices = new UserDevices();
|
||||||
this.userRoles = new UserRoles();
|
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 LocationRequest from "./LocationRequest";
|
||||||
|
import StatsRequest from "./StatsRequest";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
LocationRequest,
|
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 DevicesById from "./DevicesById";
|
||||||
import GPSData from "./GPSData";
|
import GPSData from "./GPSData";
|
||||||
import Routes from "../../Routes";
|
import Routes from "../../Routes";
|
||||||
|
import Stats from "./Stats";
|
||||||
import Tokens from "./Tokens";
|
import Tokens from "./Tokens";
|
||||||
import TokensById from "./TokensById";
|
import TokensById from "./TokensById";
|
||||||
import UserSelf from "./UserSelf";
|
import UserSelf from "./UserSelf";
|
||||||
|
@ -13,6 +14,7 @@ class ApiV1Routes extends Routes {
|
||||||
new Devices(),
|
new Devices(),
|
||||||
new DevicesById(),
|
new DevicesById(),
|
||||||
new GPSData(),
|
new GPSData(),
|
||||||
|
new Stats(),
|
||||||
new Tokens(),
|
new Tokens(),
|
||||||
new TokensById(),
|
new TokensById(),
|
||||||
new UserSelf(),
|
new UserSelf(),
|
||||||
|
|
Loading…
Add table
Reference in a new issue