-
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 all 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,73 @@ | ||
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.error) { | ||
return res.status(serviceResponse.status).json({ | ||
error: serviceResponse.error.message | ||
}); | ||
} | ||
|
||
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.data) { | ||
return res.status(serviceResponse.status).json({ | ||
message: "Feature flag retrieved 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 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."); | ||
} | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import Joi from 'joi'; | ||
import { Request, Response, NextFunction } from 'express'; | ||
import { CustomResponse } from '../../types/global'; | ||
|
||
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,13 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import express from "express"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const router = express.Router(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import authenticate from "../middlewares/authenticate"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const authorizeRoles = require("../middlewares/authorizeRoles"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { createFeatureFlag, getAllFeatureFlags, getFeatureFlagById } from "../controllers/featureFlags"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const { SUPERUSER } = require("../constants/roles"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { 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.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
module.exports = router; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import config from "config"; | ||
import { FeatureFlagResponse, FeatureFlagService } 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 defaultHeaders: HeadersInit = { | ||
"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: defaultHeaders, | ||
}); | ||
|
||
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: defaultHeaders, | ||
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 getFeatureFlagById = async (flagId: string): Promise<{ status: number; data?: any; error?: any }> => { | ||
try { | ||
const response = await fetch(`${FEATURE_FLAG_BASE_URL}/feature-flags/${flagId}`, { | ||
method: "GET", | ||
headers: defaultHeaders, | ||
}); | ||
Comment on lines
+60
to
+63
Check failure Code scanning / CodeQL Server-side request forgery Critical
The
URL Error loading related location Loading user-provided value Error loading related location Loading |
||
const status = response.status; | ||
const responseText = await response.text(); | ||
|
||
if (response.ok) { | ||
try { | ||
const parsedData = JSON.parse(responseText); | ||
return { | ||
status, | ||
data: parsedData | ||
}; | ||
} catch (parseError) { | ||
logger.error("Error parsing success response:", parseError); | ||
return { | ||
status: 500, | ||
error: { message: "Error parsing service response" } | ||
}; | ||
} | ||
} | ||
return { | ||
status, | ||
error: { message: responseText } | ||
}; | ||
} catch (err) { | ||
logger.error("Error in getFeatureFlagById service:", err); | ||
return { | ||
status: 500, | ||
error: { | ||
message: err instanceof Error ? err.message : "Internal error while connecting to the feature flag service" | ||
} | ||
}; | ||
} | ||
}; | ||
|
||
const featureFlagService: FeatureFlagService = { | ||
getAllFeatureFlags, | ||
createFeatureFlag, | ||
getFeatureFlagById, | ||
}; | ||
|
||
export default featureFlagService; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
interface FeatureFlag { | ||
id: string; | ||
name: string; | ||
description: string; | ||
status: string; | ||
createdAt: number; | ||
createdBy: string; | ||
updatedAt: number; | ||
updatedBy: string; | ||
} | ||
|
||
const featureFlagData = [ | ||
{ | ||
id: "60b00c3a-3928-4f0c-8581-dfce71aa8605", | ||
name: "feature-flag", | ||
description: "It is a demo project", | ||
status: "ENABLED", | ||
createdAt: 1718139019, | ||
createdBy: "sduhasdjasdas", | ||
updatedAt: 1718139019, | ||
updatedBy: "sduhasdjasdas" | ||
}, | ||
{ | ||
id: "flag-1", | ||
name: "feature-flag-1", | ||
description: "First demo flag", | ||
status: "ENABLED", | ||
createdAt: 1718139019, | ||
createdBy: "user1", | ||
updatedAt: 1718139019, | ||
updatedBy: "user1" | ||
}, | ||
{ | ||
id: "flag-2", | ||
name: "feature-flag-2", | ||
description: "Second demo flag", | ||
status: "DISABLED", | ||
createdAt: 1718139019, | ||
createdBy: "user2", | ||
updatedAt: 1718139019, | ||
updatedBy: "user2" | ||
} | ||
]; | ||
|
||
const newFeatureFlag = { | ||
Name: "Demo-feature", | ||
Description: "Description for demo feature", | ||
UserId: "superUserId" | ||
}; | ||
|
||
const invalidFeatureFlag = { | ||
Description: "Missing required name", | ||
UserId: "superUserId" | ||
}; | ||
|
||
module.exports = { | ||
featureFlagData, | ||
newFeatureFlag, | ||
invalidFeatureFlag | ||
}; |
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.