-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* ✨ feat: added metrics service * ✨ feat: added metrics API controller * ✨ feat: added metrics API middleware * ✨ feat: added metrics API routes * 🏷️ chore: added metrics API related types * 🎨 chore: added events table to MatrixDB collections * 🎨 chore: export metrics API * 🎨 feat: integrate matrics API routes to main app * 🚨 chore: lint * 🧪 chore: added controller tests * 🧪 chore: added middleware test * 🧪 chore: added route test * 🧪 chore: added metrics service test * 🐛 fix: swagger openapi docs * 📝 chore: generate swagger docs
- Loading branch information
Showing
13 changed files
with
1,196 additions
and
1 deletion.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { type TwakeLogger } from '@twake/logger' | ||
import { type MatrixDBBackend } from '@twake/matrix-identity-server' | ||
import { type NextFunction, type Request, type Response } from 'express' | ||
import { type IMetricsService } from '../types' | ||
import MetricsService from '../services' | ||
|
||
export default class MetricsApiController { | ||
private readonly metricsService: IMetricsService | ||
|
||
constructor( | ||
private readonly db: MatrixDBBackend, | ||
private readonly logger: TwakeLogger | ||
) { | ||
this.metricsService = new MetricsService(this.db, this.logger) | ||
} | ||
|
||
/** | ||
* Fetches the users activity stats | ||
* | ||
* @param {Request} _req - the request object. | ||
* @param {Response} res - the response object. | ||
* @param {NextFunction} next - the next hundler | ||
*/ | ||
getActivityStats = async ( | ||
_req: Request, | ||
res: Response, | ||
next: NextFunction | ||
): Promise<void> => { | ||
try { | ||
const stats = await this.metricsService.getUserActivityStats() | ||
|
||
res.status(200).json(stats) | ||
} catch (err) { | ||
next(err) | ||
} | ||
} | ||
|
||
/** | ||
* Fetches the users message stats | ||
* | ||
* @param {Request} _req - the request object. | ||
* @param {Response} res - the response object. | ||
* @param {NextFunction} next - the next hundler | ||
*/ | ||
getMessageStats = async ( | ||
_req: Request, | ||
res: Response, | ||
next: NextFunction | ||
): Promise<void> => { | ||
try { | ||
const stats = await this.metricsService.getUserMessageStats() | ||
|
||
res.status(200).json(stats) | ||
} catch (err) { | ||
next(err) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default } from './routes' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { type MatrixDBBackend } from '@twake/matrix-identity-server' | ||
import { type AuthRequest } from '../../types' | ||
import { type NextFunction, type Response } from 'express' | ||
import { type TwakeLogger } from '@twake/logger' | ||
|
||
export default class MetricsApiMiddleware { | ||
constructor( | ||
private readonly matrixDb: MatrixDBBackend, | ||
private readonly logger: TwakeLogger | ||
) {} | ||
|
||
/** | ||
* Checks if the user is an admin | ||
* | ||
* @param {AuthRequest} req - request object | ||
* @param {Response} res - response object | ||
* @param {NextFunction} next - next function | ||
*/ | ||
checkPermissions = async ( | ||
req: AuthRequest, | ||
res: Response, | ||
next: NextFunction | ||
): Promise<void> => { | ||
try { | ||
const { userId } = req | ||
|
||
if (userId === undefined) { | ||
throw new Error('Unauthenticated', { | ||
cause: 'userId is missing' | ||
}) | ||
} | ||
|
||
const isAdmin = await this._checkAdmin(userId) | ||
|
||
if (!isAdmin) { | ||
this.logger.warn('User is not an admin', { userId }) | ||
res.status(403).json({ message: 'Forbidden' }) | ||
|
||
return | ||
} | ||
|
||
next() | ||
} catch (err) { | ||
res.status(400).json({ message: 'Bad Request' }) | ||
} | ||
} | ||
|
||
/** | ||
* checks if the user is an admin | ||
* | ||
* @param {string} userId - the user id to check | ||
* @returns {Promise<boolean>} - true if the user is an admin, false otherwise | ||
*/ | ||
private readonly _checkAdmin = async (userId: string): Promise<boolean> => { | ||
try { | ||
const user = await this.matrixDb.get('users', ['name'], { | ||
name: userId, | ||
admin: 1 | ||
}) | ||
|
||
if (user.length === 0) { | ||
this.logger.warn('User is not an admin', { userId }) | ||
|
||
return false | ||
} | ||
|
||
return true | ||
} catch (error) { | ||
this.logger.error('Failed to check if user is admin', { error }) | ||
|
||
return false | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
/* eslint-disable @typescript-eslint/no-misused-promises */ | ||
import { | ||
getLogger, | ||
type Config as LoggerConfig, | ||
type TwakeLogger | ||
} from '@twake/logger' | ||
import { type AuthenticationFunction, type Config } from '../../types' | ||
import { type MatrixDBBackend } from '@twake/matrix-identity-server' | ||
import { Router } from 'express' | ||
import authMiddleware from '../../utils/middlewares/auth.middleware' | ||
import MetricsApiController from '../controllers' | ||
import MetricsApiMiddleware from '../middlewares' | ||
|
||
export const PATH = '/_twake/v1/metrics' | ||
|
||
export default ( | ||
config: Config, | ||
matrixDb: MatrixDBBackend, | ||
authenticator: AuthenticationFunction, | ||
defaultLogger?: TwakeLogger | ||
): Router => { | ||
const logger = defaultLogger ?? getLogger(config as unknown as LoggerConfig) | ||
const router = Router() | ||
const authenticate = authMiddleware(authenticator, logger) | ||
const controller = new MetricsApiController(matrixDb, logger) | ||
const middleware = new MetricsApiMiddleware(matrixDb, logger) | ||
|
||
/** | ||
* @openapi | ||
* components: | ||
* schemas: | ||
* ActivityMetric: | ||
* type: object | ||
* properties: | ||
* dailyActiveUsers: | ||
* type: number | ||
* weeklyActiveUsers: | ||
* type: number | ||
* monthlyActiveUsers: | ||
* type: number | ||
* weeklyNewUsers: | ||
* type: number | ||
* monthlyNewUsers: | ||
* type: number | ||
* MessageMetric: | ||
* type: array | ||
* items: | ||
* type: object | ||
* properties: | ||
* user_id: | ||
* type: string | ||
* message_count: | ||
* type: number | ||
*/ | ||
|
||
/** | ||
* @openapi | ||
* /_twake/v1/metrics/activity: | ||
* get: | ||
* tags: | ||
* - Metrics | ||
* description: Get user activity metrics | ||
* responses: | ||
* 200: | ||
* description: Activity metrics found | ||
* content: | ||
* application/json: | ||
* schema: | ||
* type: object | ||
* $ref: '#/components/schemas/ActivityMetric' | ||
* 500: | ||
* description: Internal error | ||
* 400: | ||
* description: Bad request | ||
* 403: | ||
* description: Forbidden | ||
*/ | ||
router.get( | ||
`${PATH}/activity`, | ||
authenticate, | ||
middleware.checkPermissions, | ||
controller.getActivityStats | ||
) | ||
|
||
/** | ||
* @openapi | ||
* /_twake/v1/metrics/messages: | ||
* get: | ||
* tags: | ||
* - Metrics | ||
* description: Get user messages metrics | ||
* responses: | ||
* 200: | ||
* description: Messages metrics found | ||
* content: | ||
* application/json: | ||
* schema: | ||
* type: object | ||
* $ref: '#/components/schemas/MessageMetric' | ||
* 500: | ||
* description: Internal error | ||
* 400: | ||
* description: Bad request | ||
* 403: | ||
* description: Forbidden | ||
*/ | ||
router.get( | ||
`${PATH}/messages`, | ||
authenticate, | ||
middleware.checkPermissions, | ||
controller.getMessageStats | ||
) | ||
|
||
return router | ||
} |
Oops, something went wrong.