-
Notifications
You must be signed in to change notification settings - Fork 263
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add: Feature flag service backend integration #2266
Changes from 4 commits
c83575b
435675d
7bf47cd
f669133
1fc0930
9e41600
bbdfe5d
7b4347a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import { CustomRequest, CustomResponse } from "../types/global"; | ||
import featureFlagService from "../services/featureFlagService"; | ||
import { FeatureFlag, UpdateFeatureFlagRequestBody } from "../types/featureFlags"; | ||
|
||
export const getAllFeatureFlags = async (req: CustomRequest, res: CustomResponse) => { | ||
try { | ||
const serviceResponse = await featureFlagService.getAllFeatureFlags(); | ||
|
||
if (!serviceResponse) { | ||
return res.status(500).json({ | ||
error: "Failed to fetch feature flags" | ||
}); | ||
} | ||
|
||
return res.status(200).json({ | ||
message: "Feature flags retrieved successfully", | ||
data: serviceResponse | ||
}); | ||
|
||
} catch (err) { | ||
logger.error(`Error in fetching feature flags: ${err}`); | ||
return res.boom.badImplementation('Internal server error'); | ||
} | ||
}; | ||
|
||
|
||
export const getFeatureFlagById = async (req: CustomRequest, res: CustomResponse) => { | ||
try { | ||
const { flagId } = req.params; | ||
const serviceResponse = await featureFlagService.getFeatureFlagById(flagId); | ||
|
||
if (!serviceResponse) { | ||
return res.status(404).json({ error: "Feature flag not found" }); | ||
} | ||
|
||
return res.status(200).json({ | ||
message: "Feature flag retrieved successfully", | ||
data: serviceResponse | ||
}); | ||
} catch (err) { | ||
logger.error(`Error in fetching feature flag: ${err}`); | ||
return res.boom.badImplementation('Internal server error'); | ||
} | ||
}; | ||
|
||
export const createFeatureFlag = async (req: CustomRequest, res: CustomResponse) => { | ||
try { | ||
const flagData: Partial<FeatureFlag> = req.body as Partial<FeatureFlag>; | ||
|
||
const serviceResponse = await featureFlagService.createFeatureFlag(flagData); | ||
|
||
if (serviceResponse.data) { | ||
return res.status(serviceResponse.status).json({ | ||
message: "Feature flag created successfully", | ||
data: serviceResponse.data, | ||
}); | ||
} else if (serviceResponse.error) { | ||
|
||
return res.status(serviceResponse.status).json({ | ||
error: serviceResponse.error.message || "Internal server error", | ||
}); | ||
} else { | ||
return res.status(500).json({ error: "Unknown error occurred" }); | ||
} | ||
} catch (err) { | ||
logger.error(`Unexpected error in creating feature flag: ${err}`); | ||
return res.boom.badImplementation("Internal server error."); | ||
} | ||
}; | ||
|
||
|
||
export const updateFeatureFlag = async (req: CustomRequest, res: CustomResponse) => { | ||
try { | ||
const { flagId } = req.params; | ||
const updateData: UpdateFeatureFlagRequestBody = { | ||
Status: req.body.Status, | ||
UserId: req.body.UserId, | ||
}; | ||
|
||
const serviceResponse = await featureFlagService.updateFeatureFlag(flagId, updateData); | ||
|
||
if (serviceResponse.status >= 400) { | ||
return res.status(serviceResponse.status).json({ | ||
error: serviceResponse.error?.message || "An error occurred while updating the feature flag", | ||
}); | ||
} | ||
|
||
return res.status(serviceResponse.status).json({ | ||
message: "Feature flag updated successfully", | ||
data: serviceResponse.data, | ||
}); | ||
} catch (err) { | ||
logger.error(`Error in updating feature flag: ${err}`); | ||
return res.boom.badImplementation('Internal server error'); | ||
} | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import Joi from 'joi'; | ||
import { Request, Response, NextFunction } from 'express'; | ||
import { CustomResponse } from '../../types/global'; | ||
|
||
const updateFeatureFlagSchema = Joi.object({ | ||
Status: Joi.string() | ||
.valid('ENABLED', 'DISABLED') | ||
.required() | ||
.messages({ | ||
'string.valid': 'Allowed values of Status are ENABLED, DISABLED', | ||
'any.required': 'Status is required' | ||
}), | ||
UserId: Joi.string() | ||
.required() | ||
.messages({ | ||
'any.required': 'UserId is required' | ||
}) | ||
}); | ||
|
||
export const validateUpdateFeatureFlag = async (req: Request, res: CustomResponse, next: NextFunction) => { | ||
try { | ||
await updateFeatureFlagSchema.validateAsync(req.body); | ||
next(); | ||
} catch (error) { | ||
logger.error(`Error validating update feature flag payload: ${error.message}`); | ||
res.boom.badRequest(error.message); | ||
} | ||
}; | ||
|
||
const createFeatureFlagSchema = Joi.object({ | ||
Name: Joi.string() | ||
.required() | ||
.messages({ | ||
'any.required': 'Name is required' | ||
}), | ||
Description: Joi.string() | ||
.required() | ||
.messages({ | ||
'any.required': 'Description is required' | ||
}), | ||
UserId: Joi.string() | ||
.required() | ||
.messages({ | ||
'any.required': 'UserId is required' | ||
}) | ||
}); | ||
|
||
export const validateCreateFeatureFlag = async (req: Request, res: CustomResponse, next: NextFunction) => { | ||
try { | ||
await createFeatureFlagSchema.validateAsync(req.body); | ||
next(); | ||
} catch (error) { | ||
logger.error(`Error validating create feature flag payload: ${error.message}`); | ||
res.boom.badRequest(error.message); | ||
} | ||
}; |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,14 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import express from "express"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const router = express.Router(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import authenticate from "../middlewares/authenticate"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const authorizeRoles = require("../middlewares/authorizeRoles"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { createFeatureFlag, getAllFeatureFlags, getFeatureFlagById, updateFeatureFlag } from "../controllers/featureFlags"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const { SUPERUSER } = require("../constants/roles"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { validateUpdateFeatureFlag, validateCreateFeatureFlag } from '../middlewares/validators/featureFlag'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
router.get("/getAllFeatureFlags", authenticate, getAllFeatureFlags); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
router.get("/getFeatureFlag/:flagId", authenticate, getFeatureFlagById); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Check failure Code scanning / CodeQL Missing rate limiting High
This route handler performs
authorization Error loading related location Loading This route handler performs authorization Error loading related location Loading This route handler performs authorization Error loading related location Loading
Copilot Autofix AI about 1 month ago To fix the problem, we need to introduce rate limiting to the route handler in question. The best way to do this is by using the
Suggested changeset
2
routes/featureFlag.ts
package.json
Outside changed files
This fix introduces these dependencies
Copilot is powered by AI and may make mistakes. Always verify output.
Refresh and try again.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
router.post('/createFeatureFlag', authenticate, authorizeRoles([SUPERUSER]), validateCreateFeatureFlag, createFeatureFlag); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Check failure Code scanning / CodeQL Missing rate limiting High
This route handler performs
authorization Error loading related location Loading This route handler performs authorization Error loading related location Loading This route handler performs authorization Error loading related location Loading
Copilot Autofix AI about 1 month ago To fix the problem, we need to introduce rate limiting to the route handler to prevent potential denial-of-service attacks. The best way to do this is by using the We will:
Suggested changeset
2
routes/featureFlag.ts
package.json
Outside changed files
This fix introduces these dependencies
Copilot is powered by AI and may make mistakes. Always verify output.
Refresh and try again.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
router.patch('/updateFeatureFlag/:flagId', authenticate, authorizeRoles([SUPERUSER]), validateUpdateFeatureFlag, updateFeatureFlag); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
module.exports = router; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import config from "config"; | ||
import { FeatureFlag, FeatureFlagResponse, FeatureFlagService, UpdateFeatureFlagRequestBody } from "../types/featureFlags"; | ||
|
||
const FEATURE_FLAG_BASE_URL = config.get<string>("services.featureFlag.baseUrl"); | ||
const FEATURE_FLAG_API_KEY = config.get<string>("services.featureFlag.apiKey"); | ||
|
||
const generateHeaders = (): HeadersInit => { | ||
return { | ||
"Content-Type": "application/json", | ||
"x-api-key": `${FEATURE_FLAG_API_KEY}`, | ||
}; | ||
}; | ||
|
||
const getAllFeatureFlags = async (): Promise<FeatureFlagResponse> => { | ||
try { | ||
const response = await fetch(`${FEATURE_FLAG_BASE_URL}/feature-flags`, { | ||
method: "GET", | ||
headers: generateHeaders(), | ||
}); | ||
|
||
if (!response.ok) { | ||
logger.error(`Failed to fetch feature flags. Status: ${response.status}`); | ||
throw new Error(`HTTP error! status: ${response.status}`); | ||
} | ||
|
||
const data = await response.json(); | ||
return data; | ||
} catch (err) { | ||
logger.error("Error in fetching feature flags", err); | ||
return { status: 500, error: { message: "Internal error while connecting to the feature flag service" } }; | ||
} | ||
}; | ||
|
||
const createFeatureFlag = async (flagData: any): Promise<{ status: number; data?: any; error?: any }> => { | ||
try { | ||
const response = await fetch(`${FEATURE_FLAG_BASE_URL}/feature-flags`, { | ||
method: "POST", | ||
headers: generateHeaders(), | ||
body: JSON.stringify(flagData), | ||
}); | ||
const status = response.status; | ||
|
||
if (response.ok) { | ||
const data = await response.json().catch(() => ({ | ||
message: "Feature flag created successfully", | ||
})); | ||
return { status, data }; | ||
} else { | ||
const error = await response.json().catch(() => ({ | ||
message: "An error occurred while creating the feature flag", | ||
})); | ||
return { status, error }; | ||
} | ||
} catch (err) { | ||
logger.error("Error in createFeatureFlag service:", err); | ||
return { status: 500, error: { message: "Internal error while connecting to the feature flag service" } }; | ||
} | ||
}; | ||
|
||
const updateFeatureFlag = async ( | ||
flagId: string, | ||
updateData: UpdateFeatureFlagRequestBody | ||
): Promise<FeatureFlagResponse> => { | ||
try { | ||
const response = await fetch(`${FEATURE_FLAG_BASE_URL}/feature-flags/${flagId}`, { | ||
method: "PATCH", | ||
headers: generateHeaders(), | ||
body: JSON.stringify(updateData), | ||
}); | ||
|
||
|
||
if (!response.ok) { | ||
const error = await response.json(); | ||
return { | ||
status: response.status, | ||
error: error || { message: `HTTP error! status: ${response.status}` } | ||
}; | ||
} | ||
|
||
const data = await response.json(); | ||
return { status: response.status, data }; | ||
} catch (err) { | ||
logger.error("Error in updating feature flag", err); | ||
return { | ||
status: 500, | ||
error: { message: "Internal error while connecting to the feature flag service" } | ||
}; | ||
} | ||
}; | ||
|
||
const getFeatureFlagById = async (flagId: string): Promise<FeatureFlagResponse> => { | ||
try { | ||
const response = await fetch(`${FEATURE_FLAG_BASE_URL}/feature-flags/${flagId}`, { | ||
method: "GET", | ||
headers: generateHeaders(), | ||
}); | ||
|
||
|
||
if (!response.ok) { | ||
logger.error(`Failed to fetch feature flag. Status: ${response.status}`); | ||
return { | ||
status: response.status, | ||
error: { message: `HTTP error! status: ${response.status}` } | ||
}; | ||
} | ||
|
||
const data = await response.json(); | ||
return { status: response.status, data }; | ||
} catch (err) { | ||
logger.error("Error in fetching feature flag by ID", err); | ||
return { | ||
status: 500, | ||
error: { message: "Internal error while connecting to the feature flag service" } | ||
}; | ||
} | ||
}; | ||
|
||
const featureFlagService: FeatureFlagService = { | ||
getAllFeatureFlags, | ||
createFeatureFlag, | ||
updateFeatureFlag, | ||
getFeatureFlagById, | ||
}; | ||
|
||
export default featureFlagService; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
export interface FeatureFlag { | ||
id?: string; | ||
name: string; | ||
description?: string; | ||
status: string; | ||
createdAt?: number; | ||
updatedAt?: number; | ||
createdBy: string | ||
updatedBy: string | ||
} | ||
|
||
export interface FeatureFlagResponse { | ||
status: number; | ||
data?: FeatureFlag | FeatureFlag[]; | ||
error?: { | ||
message: string; | ||
}; | ||
} | ||
|
||
export interface FeatureFlagService { | ||
getAllFeatureFlags(): Promise<FeatureFlagResponse>; | ||
createFeatureFlag(flagData: Partial<FeatureFlag>): Promise<FeatureFlagResponse>; | ||
updateFeatureFlag(flagId: string, updateData: UpdateFeatureFlagRequestBody): Promise<FeatureFlagResponse>; | ||
getFeatureFlagById: (flagId: string) => Promise<FeatureFlagResponse>; | ||
} | ||
|
||
export interface UpdateFeatureFlagRequestBody { | ||
Status: string; | ||
UserId: string; | ||
} |
Check failure
Code scanning / CodeQL
Missing rate limiting High
Copilot Autofix AI about 1 month ago
To fix the problem, we should introduce rate limiting to the routes in the
routes/featureFlag.ts
file. The best way to do this is by using theexpress-rate-limit
package, which allows us to easily set up rate limiting for our Express routes.express-rate-limit
package.express-rate-limit
package in theroutes/featureFlag.ts
file.