Skip to content

Commit

Permalink
Merge pull request #621 from hngprojects/feat/api-status
Browse files Browse the repository at this point in the history
feat: api status updates
  • Loading branch information
PreciousIfeaka authored Aug 24, 2024
2 parents 290c1b2 + 28f0f86 commit 392fe97
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 2 deletions.
13 changes: 13 additions & 0 deletions src/controllers/api-status.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Request, Response } from "express";
import { asyncHandler } from "../middleware/asyncHandler";
import { parseJsonResponse } from "../services/api-status.services";
import { sendJsonResponse } from "../utils/sendJsonResponse";

export const createApiStatus = asyncHandler(
async (req: Request, res: Response) => {
const resultJson = req.body;

await parseJsonResponse(resultJson);
sendJsonResponse(res, 201, "API status updated successfully");
},
);
1 change: 1 addition & 0 deletions src/controllers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ export * from "./billingController";
export * from "./SqueezeController";
export * from "./NotificationController";
export * from "./billingplanController";
export * from "./api-status.controller";
6 changes: 4 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import AppDataSource from "./data-source";
import { errorHandler, routeNotFound } from "./middleware";
import {
adminRouter,
apiStatusRouter,
authRoute,
billingPlanRouter,
billingRouter,
Expand Down Expand Up @@ -59,8 +60,8 @@ server.use(
);

server.use(Limiter);
server.use(express.json());
server.use(express.urlencoded({ extended: true }));
server.use(express.json({ limit: "10mb" }));
server.use(express.urlencoded({ limit: "10mb", extended: true }));
server.use(passport.initialize());

server.get("/", (req: Request, res: Response) => {
Expand Down Expand Up @@ -101,6 +102,7 @@ server.use("/api/v1", billingPlanRouter);
server.use("/api/v1", newsLetterSubscriptionRoute);
server.use("/api/v1", squeezeRoute);
server.use("/api/v1", planRouter);
server.use("/api/v1", apiStatusRouter);

server.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec));
server.use("/openapi.json", (_req: Request, res: Response) => {
Expand Down
14 changes: 14 additions & 0 deletions src/middleware/asyncHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { NextFunction, Request, Response } from "express";

/**
* Async handler to wrap the API routes, this allows for async error handling.
* @param fn Function to call for the API endpoint
* @returns Promise with a catch statement
*/
const asyncHandler =
(fn: (req: Request, res: Response, next: NextFunction) => void) =>
(req: Request, res: Response, next: NextFunction) => {
return Promise.resolve(fn(req, res, next)).catch(next);
};

export { asyncHandler };
44 changes: 44 additions & 0 deletions src/models/api-model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {
Column,
CreateDateColumn,
DeleteDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from "typeorm";

export enum API_STATUS {
OPERATIONAL = "operational",
DEGRADED = "degraded",
DOWN = "down",
}

@Entity({ name: "api_status" })
export class ApiStatus {
@PrimaryGeneratedColumn("uuid")
id: string;

@Column({ name: "api_group" })
api_group: string;

@Column({ name: "api_name" })
api_name: string;

@Column({ name: "status", type: "enum", enum: API_STATUS })
status: API_STATUS;

@Column("text", { nullable: true })
details: string;

@Column({ name: "response_time", type: "int", nullable: true })
response_time: string;

@CreateDateColumn({ name: "created_at" })
created_at: Date;

@UpdateDateColumn({ name: "updated_at" })
updated_at: Date;

@DeleteDateColumn({ nullable: true })
deleted_at: Date;
}
8 changes: 8 additions & 0 deletions src/routes/api-status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Router } from "express";
import { createApiStatus } from "../controllers/api-status.controller";

const apiStatusRouter = Router();

apiStatusRouter.post("/api-status", createApiStatus);

export { apiStatusRouter };
1 change: 1 addition & 0 deletions src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ export * from "./squeeze";
export * from "./newsLetterSubscription";
export * from "./notification";
export * from "./billingplan";
export * from "./api-status";
50 changes: 50 additions & 0 deletions src/services/api-status.services.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import AppDataSource from "../data-source";
import { API_STATUS, ApiStatus } from "../models/api-model";

const apiStatusRepository = AppDataSource.getRepository(ApiStatus);
const MAX_ALLOWED_RESPONSE_TIME = 2000;

const determineStatus = (
statusCode: number,
responseTime: number,
): API_STATUS => {
if (statusCode >= 200 && statusCode < 300) {
if (responseTime && responseTime > MAX_ALLOWED_RESPONSE_TIME) {
return API_STATUS.DEGRADED;
}
return API_STATUS.OPERATIONAL;
} else if (statusCode >= 500) {
return API_STATUS.DOWN;
}
return API_STATUS.DEGRADED;
};

const parseJsonResponse = async (resultJson: any): Promise<void> => {
const apiGroups = resultJson.collection.item;

for (const apiGroup of apiGroups) {
for (const api of apiGroup.item) {
let status = API_STATUS.DEGRADED;
let responseTime = null;

if (api.response && api.response.length > 0) {
const response = api.response[0];
responseTime = response.responseTime || null;
status = determineStatus(response.code, responseTime);
}

const apiStatus = apiStatusRepository.create({
api_group: apiGroup.name,
api_name: api.name,
status,
response_time: responseTime,
details: responseTime ? `Response time: ${responseTime}ms` : null,
});

await apiStatusRepository.save(apiStatus);
}
}
};

export { parseJsonResponse };
1 change: 1 addition & 0 deletions src/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ export * from "./billing-plans.services";
export * from "./squeezeService";
export * from "./blogComment.services";
export * from "./notification.services";
export * from "./api-status.services";
34 changes: 34 additions & 0 deletions src/utils/sendJsonResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Response } from "express";

/**
* Sends a JSON response with a standard structure.
*
* @param res - The Express response object.
* @param statusCode - The HTTP status code to send.
* @param message - The message to include in the response.
* @param data - The data to include in the response. Can be any type.
* @param accessToken - Optional access token to include in the response.
*/
const sendJsonResponse = (
res: Response,
statusCode: number,
message: string,
data?: any,
accessToken?: string,
) => {
const responsePayload: any = {
status: "success",
message,
status_code: statusCode,
data,
};

if (accessToken) {
responsePayload.access_token = accessToken;
}

res.status(statusCode).json(responsePayload);
};

export { sendJsonResponse };

0 comments on commit 392fe97

Please sign in to comment.