Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM --platform=linux/amd64 node:20-alpine
FROM --platform=linux/amd64 public.ecr.aws/docker/library/node:20-alpine
RUN apk add libcrypto3=3.3.0-r2 libssl3=3.3.0-r2
RUN apk upgrade
RUN mkdir -p /opt/obsrv-api-service
Expand Down
55 changes: 48 additions & 7 deletions api-service/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,50 @@
FROM --platform=linux/amd64 node:24.3.0-alpine
RUN mkdir -p /opt/api-service
COPY ./api-service ./opt/api-service
# Build stage
FROM --platform=linux/amd64 public.ecr.aws/docker/library/node:24.13.1-slim AS builder
RUN npm install -g npm@11.10.1 \
&& npm pack tar@7.5.11 \
&& tar -xzf tar-7.5.11.tgz -C /usr/local/lib/node_modules/npm/node_modules/tar --strip-components=1 \
&& rm tar-7.5.11.tgz \
&& npm install -g minimatch@10.2.2 \
&& npm cache clean --force \
&& rm -rf /usr/local/lib/node_modules/npm/node_modules/minimatch \
&& cp -r /usr/local/lib/node_modules/minimatch /usr/local/lib/node_modules/npm/node_modules/
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates openssl \
&& apt-get upgrade -y \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /opt/api-service
RUN rm -rf postman-collection
RUN npm install
COPY . .
# Install all dependencies for building
COPY api-service/package.json api-service/package-lock.json ./
RUN npm ci --ignore-scripts
# Copy source code and build
COPY api-service/tsconfig.json ./
COPY api-service/src/ ./src/
COPY api-service/@types/ ./@types/
RUN npm run build
# Remove declaration files to avoid module resolution conflicts
RUN find dist -name "*.d.ts" -delete
Comment on lines +20 to +25
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "JSON imports under api-service/src:"
rg -n "\\.json[\"']" api-service/src
echo
echo "api-service/package.json build script:"
python - <<'PY'
import json
with open("api-service/package.json") as f:
    print(json.load(f)["scripts"]["build"])
PY
echo
echo "Relevant Dockerfile steps:"
sed -n '17,26p;43,48p' api-service/Dockerfile

Repository: Sanketika-Obsrv/obsrv-api-service

Length of output: 12569


🏁 Script executed:

cat api-service/tsconfig.json

Repository: Sanketika-Obsrv/obsrv-api-service

Length of output: 11458


Copy JSON schema files into dist during the build stage.

api-service/src/controllers/DataMetrics/DataMetricsController.ts and 39+ other files import JSON schemas (e.g., DataMetricsValidationSchema.json, Constants.json, SchemaMapping.json). The build script at line 23 runs npm run build which compiles TypeScript with resolveJsonModule: true, but only copies package.json to dist/. The build does not copy schema/config JSON files from src/ into dist/, so those files will be missing at runtime when the compiled application tries to load them.

📦 Suggested fix
 COPY api-service/tsconfig.json ./
 COPY api-service/src/ ./src/
 COPY api-service/@types/ ./@types/
 RUN npm run build
+RUN find src -name "*.json" -not -path "*/node_modules/*" -exec sh -c 'for f do \
+  target="dist/${f#src/}"; \
+  mkdir -p "$(dirname "$target")"; \
+  cp "$f" "$target"; \
+done' sh {} +
 # Remove declaration files to avoid module resolution conflicts
 RUN find dist -name "*.d.ts" -delete

Also applies to: lines 46-48

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api-service/Dockerfile` around lines 20 - 25, The Docker build runs npm run
build but never copies JSON schema/config files into dist, so imports in
DataMetricsController.ts (and others) like DataMetricsValidationSchema.json,
Constants.json, SchemaMapping.json will be missing at runtime; update the
Dockerfile to copy source JSON files into the built output (e.g., after RUN npm
run build add a COPY or RUN command to copy src/**/*.json into dist/ or modify
the earlier COPY to include *.json from api-service/src into dist) and ensure
the find ... -delete step only removes .d.ts files and does not remove any JSON
assets.


# Production stage
FROM --platform=linux/amd64 public.ecr.aws/docker/library/node:24.13.1-slim AS production
RUN npm install -g npm@11.10.1 \
&& npm pack tar@7.5.11 \
&& tar -xzf tar-7.5.11.tgz -C /usr/local/lib/node_modules/npm/node_modules/tar --strip-components=1 \
&& rm tar-7.5.11.tgz \
&& npm install -g minimatch@10.2.2 \
&& npm cache clean --force \
&& rm -rf /usr/local/lib/node_modules/npm/node_modules/minimatch \
&& cp -r /usr/local/lib/node_modules/minimatch /usr/local/lib/node_modules/npm/node_modules/
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates openssl \
&& apt-get upgrade -y \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /opt/api-service
# Copy package.json and install only production dependencies
COPY api-service/package.json api-service/package-lock.json ./
RUN npm ci --omit=dev --ignore-scripts
# Copy built application from build stage
COPY --from=builder /opt/api-service/dist ./
# Verify the structure and ensure no declaration files
RUN ls -la && echo "=== Checking for .d.ts files ===" && find . -name "*.d.ts" | head -5 || echo "No .d.ts files found"
EXPOSE 3000
CMD ["npm", "run", "start"]
CMD ["node", "app.js"]
45 changes: 45 additions & 0 deletions api-service/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import js from '@eslint/js';
import typescript from '@typescript-eslint/eslint-plugin';
import typescriptParser from '@typescript-eslint/parser';

export default [
Comment on lines +1 to +5
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Module type mismatch: ESM syntax with CommonJS package type.

The package.json declares "type": "commonjs", but this config uses ESM syntax (import/export). ESLint will fail to load this configuration.

Either rename the file to eslint.config.mjs to force ESM parsing, or convert to CommonJS syntax.

🔧 Option 1: Rename to `eslint.config.mjs` (preferred for ESLint 9)

Rename the file from eslint.config.js to eslint.config.mjs.

🔧 Option 2: Convert to CommonJS syntax
-import js from '@eslint/js';
-import typescript from '@typescript-eslint/eslint-plugin';
-import typescriptParser from '@typescript-eslint/parser';
+const js = require('@eslint/js');
+const typescript = require('@typescript-eslint/eslint-plugin');
+const typescriptParser = require('@typescript-eslint/parser');

-export default [
+module.exports = [
   {
     files: ['**/*.ts', '**/*.tsx'],
     // ... rest unchanged
   }
 ];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import js from '@eslint/js';
import typescript from '@typescript-eslint/eslint-plugin';
import typescriptParser from '@typescript-eslint/parser';
export default [
const js = require('@eslint/js');
const typescript = require('@typescript-eslint/eslint-plugin');
const typescriptParser = require('@typescript-eslint/parser');
module.exports = [
🧰 Tools
🪛 Biome (2.4.9)

[error] 1-1: Illegal use of an import declaration outside of a module

(parse)


[error] 2-2: Illegal use of an import declaration outside of a module

(parse)


[error] 3-3: Illegal use of an import declaration outside of a module

(parse)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api-service/eslint.config.js` around lines 1 - 5, The ESLint config is using
ESM imports/exports (imports: js, typescript, typescriptParser; export default)
while the repo is CommonJS; fix by either renaming the file to eslint.config.mjs
to force ESM parsing, or convert the file to CommonJS: replace the import lines
for js, typescript, typescriptParser with const js = require('@eslint/js'),
const typescript = require('@typescript-eslint/eslint-plugin'), const
typescriptParser = require('@typescript-eslint/parser'), and replace export
default [...] with module.exports = [...]; choose one of the two approaches and
update the file accordingly.

{
files: ['**/*.ts', '**/*.tsx'],
languageOptions: {
parser: typescriptParser,
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
}
},
plugins: {
'@typescript-eslint': typescript
},
rules: {
...js.configs.recommended.rules,
...typescript.configs.recommended.rules,
'@typescript-eslint/no-unused-vars': ['error', { 'argsIgnorePattern': '^_' }],
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/quotes': [
'error',
'double',
{
'allowTemplateLiterals': true
}
]
}
},
{
files: ['**/*.js'],
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
},
rules: {
...js.configs.recommended.rules
}
},
{
ignores: ['dist/', 'node_modules/', '**/*.d.ts']
}
];
33 changes: 24 additions & 9 deletions api-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"name": "obsrv-api-service",
"version": "1.0.0",
"description": "API service for the obsrv api's",
"main": "dist/app.js",
"type": "commonjs",
"main": "app.js",
"scripts": {
"start": "ts-node ./src/app.ts",
"test": "source .env.test && nyc mocha ./src/tests/**/*.spec.ts --exit",
Expand Down Expand Up @@ -34,10 +35,10 @@
"@opentelemetry/sdk-trace-node": "^1.28.0",
"@opentelemetry/semantic-conventions": "^1.28.0",
"@project-sunbird/logger": "^0.0.9",
"ajv": "^8.11.2",
"ajv": "^8.18.0",
"ajv-formats": "^2.1.1",
"aws-sdk": "^2.1348.0",
"axios": "^1.6.0",
"axios": "^1.13.5",
"body-parser": "^1.20.2",
"busboy": "^1.6.0",
"compression": "^1.7.4",
Expand All @@ -61,7 +62,7 @@
"pg-hstore": "^2.3.4",
"prom-client": "^14.2.0",
"redis": "^4.6.15",
"sequelize": "^6.37.1",
"sequelize": "^6.37.8",
"slug": "^9.0.0",
"trino-client": "^0.2.2",
"uuid": "3.1.0",
Expand All @@ -70,7 +71,20 @@
},
"overrides": {
"semver": "^7.5.3",
"@babel/traverse": "7.23.2"
"@babel/traverse": "7.23.2",
"diff": "^8.0.3",
"lodash": "^4.17.21",
"minimatch": "^10.2.3",
"axios": "^1.13.5",
"tar": "^7.5.11",
"formidable": "^3.5.3",
"eslint": "^9.26.0",
"fast-xml-parser": "^5.5.6",
"qs": "^6.15.0",
"dottie": "^2.0.7",
"underscore": "^1.13.8",
"flatted": "^3.4.0",
"serialize-javascript": "^7.0.3"
},
"devDependencies": {
"@types/busboy": "^1.5.4",
Expand All @@ -92,15 +106,16 @@
"@types/sinon": "^17.0.3",
"@types/slug": "^5.0.8",
"@types/uuid": "^9.0.1",
"@typescript-eslint/eslint-plugin": "^7.1.1",
"@typescript-eslint/parser": "^7.1.1",
"@eslint/js": "^9.26.0",
"@typescript-eslint/eslint-plugin": "^8.19.0",
"@typescript-eslint/parser": "^8.19.0",
"chai": "^4.3.6",
"chai-as-promised": "^7.1.1",
"chai-http": "^4.3.0",
"chai-spies": "^1.0.0",
"diff-json": "^2.0.0",
"eslint": "^8.57.0",
"mocha": "^10.1.0",
"eslint": "^9.26.0",
"mocha": "^11.3.0",
"nock": "^13.2.9",
"nodemon": "^3.0.1",
"nyc": "^15.1.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7483,7 +7483,7 @@
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"manager\": \"grafana\",\n \"name\": \"functional-metrics-slack\",\n \"type\": \"slack\",\n \"config\": {\n \"channel\": \"grafana-alerts\",\n \"webhookUrl\": \"https://hooks.slack.com/services/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\"\n }\n}",
"raw": "{\n \"manager\": \"grafana\",\n \"name\": \"functional-metrics-slack\",\n \"type\": \"slack\",\n \"config\": {\n \"channel\": \"grafana-alerts\",\n \"webhookUrl\": \"https://hooks.slack.com/services/****/****/*****\"\n }\n}",
"options": {
"raw": {
"language": "json"
Expand Down
13 changes: 6 additions & 7 deletions api-service/src/configs/ConnectionsConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
const env = process.env;

export const connectionConfig = {
postgres: {
host: env.postgres_host || "localhost",
port: env.postgres_port || 5432,
database: env.postgres_database || "obsrv",
username: env.postgres_username || "postgres",
password: env.postgres_password || "postgres",
},
postgres: Object.freeze({
host: env['postgres_host'] || "localhost",
port: env['postgres_port'] || 5432,
database: env['postgres_database'] || "obsrv",
credentials: `${env['postgres_username'] || "postgres"}::${env['postgres_password'] || "postgres"}`
}),
kafka: {
"config": {
"brokers": [`${env.kafka_host || "localhost"}:${env.kafka_port || 9092}`],
Expand Down
29 changes: 20 additions & 9 deletions api-service/src/connections/databaseConnection.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
import { Sequelize } from "sequelize";
import { connectionConfig } from "../configs/ConnectionsConfig"

const { database, host, password, port, username } = connectionConfig.postgres
const { database, host, port } = connectionConfig.postgres;
const credentials = connectionConfig.postgres.credentials.split("::");

export const sequelize = new Sequelize({
database, password, username: username, dialect: "postgres", host, port: +port, pool: {
max: 2,
min: 1,
acquire: 30000,
idle: 10000
}
})
const decodedCredentials = Buffer.from(credentials[1], 'base64').toString('utf-8');

export const sequelize = new Sequelize(
database,
credentials[0],
decodedCredentials,
Comment on lines +5 to +12
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "Producer:"
sed -n '4,10p' api-service/src/configs/ConnectionsConfig.ts
echo
echo "Consumer:"
sed -n '4,12p' api-service/src/connections/databaseConnection.ts
echo
python - <<'PY'
import base64
raw = "postgres"
print("raw password:", raw)
print("base64-decoded bytes:", base64.b64decode(raw))
PY

Repository: Sanketika-Obsrv/obsrv-api-service

Length of output: 813


🏁 Script executed:

cat -n api-service/src/configs/ConnectionsConfig.ts

Repository: Sanketika-Obsrv/obsrv-api-service

Length of output: 1332


🏁 Script executed:

cat -n api-service/src/connections/databaseConnection.ts

Repository: Sanketika-Obsrv/obsrv-api-service

Length of output: 1172


🏁 Script executed:

rg "credentials" --type ts -B 2 -A 2 -i

Repository: Sanketika-Obsrv/obsrv-api-service

Length of output: 4936


Password must be base64-encoded when building credentials, or decoding must be removed from the consumer.

ConnectionsConfig.ts line 9 builds credentials from raw postgres_password env var with no encoding:

credentials: `${env['postgres_username'] || "postgres"}::${env['postgres_password'] || "postgres"}`

But databaseConnection.ts line 7 treats the second segment as base64 and decodes it:

const decodedCredentials = Buffer.from(credentials[1], 'base64').toString('utf-8');

Plain-text or default passwords will fail authentication. For example, "postgres" when decoded as base64 becomes corrupt binary data instead of the original password string.

Either base64-encode the password in ConnectionsConfig.ts, or use credentials[1] directly in databaseConnection.ts without decoding.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api-service/src/connections/databaseConnection.ts` around lines 5 - 12, The
code decodes the second credentials segment as base64
(Buffer.from(credentials[1], 'base64').toString('utf-8')) but
ConnectionsConfig.ts constructs credentials from the plain postgres_password,
causing wrong passwords; fix by stopping the incorrect base64 decode in
databaseConnection.ts and pass the raw password segment into Sequelize (use
credentials[1] instead of decodedCredentials in the new Sequelize call), or
alternatively make ConnectionsConfig.ts encode the password to base64 before
joining (update where credentials is built) so the existing Buffer.from call
becomes valid—choose one approach and update either the decodedCredentials usage
in databaseConnection.ts or the credentials construction in ConnectionsConfig.ts
accordingly.

{
host,
port: +port,
dialect: "postgres",
pool: {
max: 2,
min: 1,
acquire: 30000,
idle: 10000
}
}
)

export const health = async () => {
return sequelize.query("select 1")
Expand Down
14 changes: 7 additions & 7 deletions api-service/src/controllers/Alerts/Alerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const telemetryObject = { type: "alert", ver: "1.0.0" };

const createAlertHandler = async (req: Request, res: Response, next: NextFunction) => {
try {
const alertPayload = getAlertPayload(req.body);
const alertPayload = getAlertPayload(_.get(req, "body"));
const userID = (req as any)?.userID;
_.set(alertPayload, "created_by", userID);
_.set(alertPayload, "updated_by", userID);
Expand All @@ -30,7 +30,7 @@ const createAlertHandler = async (req: Request, res: Response, next: NextFunctio

const publishAlertHandler = async (req: Request, res: Response, next: NextFunction) => {
try {
const { alertId } = req.params;
const alertId = _.get(req, 'params.alertId');
const ruleModel: Record<string, any> | null = await getAlertRule(alertId);
if (!ruleModel) return next({ message: httpStatus[httpStatus.NOT_FOUND], statusCode: httpStatus.NOT_FOUND });
const rulePayload = ruleModel.toJSON();
Expand All @@ -57,7 +57,7 @@ const transformAlerts = async (alertModel: any) => {

const searchAlertHandler = async (req: Request, res: Response, next: NextFunction) => {
try {
const { limit, filters, offset, options = {} } = req.body?.request || {};
const { limit, filters, offset, options = {} } = _.get(req, 'body.request', {});
const alerts = await Alert.findAll({ limit: limit, offset: offset, ...(filters && { where: filters }), ...options });
const alertRulesWithStatus = await Promise.all(_.map(alerts, transformAlerts));
ResponseHandler.successResponse(req, res, { status: httpStatus.OK, data: { alerts: alertRulesWithStatus, count: alerts.length } });
Expand Down Expand Up @@ -105,8 +105,8 @@ const deleteAlertHandler = async (req: Request, res: Response, next: NextFunctio

const updateAlertHandler = async (req: Request, res: Response, next: NextFunction) => {
try {
const { alertId } = req.params;
const isEmpty = _.isEmpty(req.body);
const alertId = _.get(req, 'params.alertId');
const isEmpty = _.isEmpty(_.get(req, "body"));
if (isEmpty) throw new Error("Failed to update record");
const ruleModel = await getAlertRule(alertId);
if (!ruleModel) { return next({ message: httpStatus[httpStatus.NOT_FOUND], statusCode: httpStatus.NOT_FOUND }) }
Expand All @@ -117,7 +117,7 @@ const updateAlertHandler = async (req: Request, res: Response, next: NextFunctio
await deleteAlertRule(rulePayload, false);
await retireAlertSilence(alertId);
}
const updatedPayload = getAlertPayload({ ...req.body, manager: rulePayload?.manager });
const updatedPayload = getAlertPayload({ ..._.get(req, "body"), manager: rulePayload?.manager });
await Alert.update({ ...updatedPayload, status: "draft", updated_by: userID }, { where: { id: alertId } });
updateTelemetryAuditEvent({ request: req, currentRecord: rulePayload, object: { id: alertId, ...telemetryObject } });
ResponseHandler.successResponse(req, res, { status: httpStatus.OK, data: { id: alertId } });
Expand All @@ -132,7 +132,7 @@ const updateAlertHandler = async (req: Request, res: Response, next: NextFunctio

const deleteSystemAlertsHandler = async (req: Request, res: Response, next: NextFunction) => {
try {
const body = req.body;
const body = _.get(req, 'body', {});
const { filters } = body;
if (!filters) throw new Error("Failed to update record");
await deleteSystemRules({ filters, manager: "grafana" });
Expand Down
12 changes: 6 additions & 6 deletions api-service/src/controllers/Alerts/Metric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ const telemetryObject = { type: "metric", ver: "1.0.0" };

const createMetricHandler = async (req: Request, res: Response, next: NextFunction) => {
try {
const { component } = req.body;
const metricsBody = await Metrics.create({ ...(req.body), component: component });
const component = _.get(req, 'body.component');
const metricsBody = await Metrics.create({ ...(_.get(req, 'body')), component: component });
Comment on lines +13 to +14
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing validation for undefined request body.

If _.get(req, 'body') returns undefined, spreading it in the object literal produces {}, so Metrics.create would receive { component: undefined }. Unlike updateMetricHandler which validates with _.isEmpty (line 42-43), this handler has no guard against a missing body.

Consider adding a default or validation:

Proposed fix
         const component  = _.get(req, 'body.component');
-        const metricsBody = await Metrics.create({ ...(_.get(req, 'body')), component: component });
+        const requestBody = _.get(req, 'body', {});
+        const metricsBody = await Metrics.create({ ...requestBody, component });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const component = _.get(req, 'body.component');
const metricsBody = await Metrics.create({ ...(_.get(req, 'body')), component: component });
const component = _.get(req, 'body.component');
const requestBody = _.get(req, 'body', {});
const metricsBody = await Metrics.create({ ...requestBody, component });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api-service/src/controllers/Alerts/Metric.ts` around lines 13 - 14, The
create handler currently spreads _.get(req, 'body') into Metrics.create which
allows an undefined body and results in { component: undefined }; add an
explicit guard in the create metric handler to validate the request body (e.g.,
use _.get(req, 'body') and _.isEmpty or a null/undefined check) and return a
400/validation error when the body is missing or empty, or alternatively default
to an empty object before spreading; update the call to Metrics.create in this
handler so it receives a validated payload (and ensure component is present or
validated) similar to the validation used in updateMetricHandler and reference
the Metrics.create invocation to locate the change.

updateTelemetryAuditEvent({ request: req, object: { id: metricsBody?.dataValues?.id, ...telemetryObject } });
ResponseHandler.successResponse(req, res, { status: httpStatus.OK, data: { id: metricsBody.dataValues.id } });
} catch (error: any) {
Expand All @@ -36,8 +36,8 @@ const listMetricsHandler = async (req: Request, res: Response, next: NextFunctio

const updateMetricHandler = async (req: Request, res: Response, next: NextFunction) => {
try {
const { id } = req.params;
const toUpdatePayload = req.body;
const id = _.get(req, 'params.id');
const toUpdatePayload = _.get(req, 'body');
const { component } = toUpdatePayload;
const isEmpty = _.isEmpty(toUpdatePayload);
if (isEmpty) throw new Error("Failed to update record");
Expand All @@ -59,7 +59,7 @@ const updateMetricHandler = async (req: Request, res: Response, next: NextFuncti

const deleteMetricHandler = async (req: Request, res: Response, next: NextFunction) => {
try {
const { id } = req.params;
const id = _.get(req, 'params.id');
const record = await Metrics.findOne({ where: { id } });
if (!record) throw new Error(httpStatus[httpStatus.NOT_FOUND]);
await record.destroy();
Expand All @@ -72,7 +72,7 @@ const deleteMetricHandler = async (req: Request, res: Response, next: NextFuncti

const deleteMultipleMetricHandler = async (req: Request, res: Response, next: NextFunction) => {
try {
const { filters } = req.body;
const filters = _.get(req, 'body.filters');
if (!filters) throw new Error("Failed to update record");
await Metrics.destroy({ where: filters });
ResponseHandler.successResponse(req, res, { status: httpStatus.OK, data: {} });
Expand Down
10 changes: 5 additions & 5 deletions api-service/src/controllers/Alerts/Silence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const telemetryObject = { type: "alert-silence", ver: "1.0.0" };

const createHandler = async (request: Request, response: Response, next: NextFunction) => {
try {
const payload = request.body;
const payload = _.get(request,'body');
const { startDate, endDate, alertId } = payload;
Comment on lines +14 to 15
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "_\.get\(request,\s*['\"]body['\"]\)" api-service/src/controllers/Alerts/Silence.ts
echo
sed -n '12,18p;73,84p' api-service/src/controllers/Alerts/Silence.ts

Repository: Sanketika-Obsrv/obsrv-api-service

Length of output: 1370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Get the full createHandler function (lines 12-30 should cover the try-catch)
echo "=== createHandler ===" 
sed -n '12,35p' api-service/src/controllers/Alerts/Silence.ts

echo
echo "=== updateHandler ===" 
# Get the full updateHandler function
sed -n '73,95p' api-service/src/controllers/Alerts/Silence.ts

Repository: Sanketika-Obsrv/obsrv-api-service

Length of output: 2776


🏁 Script executed:

#!/bin/bash
set -euoelpipefail

# Get more lines to see the complete catch blocks
sed -n '12,45p' api-service/src/controllers/Alerts/Silence.ts

Repository: Sanketika-Obsrv/obsrv-api-service

Length of output: 751


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Get more lines to see the complete catch blocks
sed -n '12,45p' api-service/src/controllers/Alerts/Silence.ts

Repository: Sanketika-Obsrv/obsrv-api-service

Length of output: 1718


Return 400 for missing request bodies; _.get without defaults still returns undefined.

_.get(request, "body") returns undefined when the body is absent. Line 15 immediately destructures it, and lines 82–83 dereference it, so a missing client request body throws a TypeError caught by the try-catch block and returns a 500 error instead of the correct 400.

🛠️ Suggested fix
-        const payload = _.get(request,'body');
+        const payload = _.get(request, "body", {});
+        if (_.isEmpty(payload)) {
+            return next({ message: httpStatus[httpStatus.BAD_REQUEST], statusCode: httpStatus.BAD_REQUEST });
+        }
         const { startDate, endDate, alertId } = payload;
@@
-        const payload = _.get(request, 'body');
+        const payload = _.get(request, "body", {});
+        if (_.isEmpty(payload)) {
+            return next({ message: httpStatus[httpStatus.BAD_REQUEST], statusCode: httpStatus.BAD_REQUEST });
+        }

Also applies to: line 76 in updateHandler.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api-service/src/controllers/Alerts/Silence.ts` around lines 14 - 15, The
handler currently does _.get(request, 'body') then immediately destructures
payload into startDate, endDate, alertId which yields a TypeError when body is
missing; change the code to guard for a missing/empty payload (e.g. use
_.get(request, 'body', {}) or explicitly check if payload is truthy) and return
an HTTP 400 with a clear message when the request body is absent or required
fields are missing; apply the same defensive check/failure response in
updateHandler where the payload is read (referencing payload, startDate,
endDate, alertId and updateHandler).

const existingSilence = await Silence.findOne({ where: { alert_id: alertId } });
if (existingSilence) existingSilence.destroy();
Expand Down Expand Up @@ -59,7 +59,7 @@ const listHandler = async (request: Request, response: Response, next: NextFunct

const fetchHandler = async (request: Request, response: Response, next: NextFunction) => {
try {
const id = request.params.id;
const id = _.get(request,'params.id');
const silenceModel = await Silence.findOne({ where: { id } });
const transformedSilence = await transformSilences(silenceModel);
if (!silenceModel) return next({ message: httpStatus[httpStatus.NOT_FOUND], statusCode: httpStatus.NOT_FOUND });
Expand All @@ -72,8 +72,8 @@ const fetchHandler = async (request: Request, response: Response, next: NextFunc

const updateHandler = async (request: Request, response: Response, next: NextFunction) => {
try {
const id = request.params.id;
const payload = request.body;
const id = _.get(request,'params.id');
const payload = _.get(request, 'body');
const silenceModel = await Silence.findOne({ where: { id } });
if (!silenceModel) return next({ message: httpStatus[httpStatus.NOT_FOUND], statusCode: httpStatus.NOT_FOUND });
const silenceObject = silenceModel?.toJSON();
Expand All @@ -98,7 +98,7 @@ const updateHandler = async (request: Request, response: Response, next: NextFun

const deleteHandler = async (request: Request, response: Response, next: NextFunction) => {
try {
const id = request.params.id;
const id = _.get(request,'params.id');
const silenceModel = await Silence.findOne({ where: { id } });
if (!silenceModel) return next({ message: httpStatus[httpStatus.NOT_FOUND], statusCode: httpStatus.NOT_FOUND });
const silenceObject = silenceModel?.toJSON();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const dataMetrics = async (req: Request, res: Response) => {
const { url, method, headers = {}, body = {}, params = {}, ...rest } = query;
const apiResponse = await axios.request({ url, method, headers, params, data: body, ...rest })
const data = _.get(apiResponse, "data");
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
return res.json(data);
}
else {
Expand Down
Loading
Loading