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
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
"request-ip": "^3.3.0",
"resend": "^4.2.0",
"response-time": "^2.3.3",
"ts-node": "^10.9.2"
"ts-node": "^10.9.2",
"ws": "^8.18.0"
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify WebSocket library versions and security status

Please ensure these versions are the latest stable releases and free from known vulnerabilities.

Also applies to: 64-64


🏁 Script executed:

#!/bin/bash
# Description: Check for latest versions and security advisories for ws and @types/ws packages

# Check latest versions on npm
echo "Checking latest versions..."
curl -s https://registry.npmjs.org/ws | jq -r '.["dist-tags"].latest'
curl -s https://registry.npmjs.org/@types/ws | jq -r '.["dist-tags"].latest'

# Check for security advisories using GitHub Advisory Database
echo -e "\nChecking security advisories for ws..."
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: NPM, package: "ws") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
      vulnerableVersionRange
      firstPatchedVersion {
        identifier
      }
    }
  }
}'

Length of output: 1813


Update ws and @types/ws to the latest stable releases

The ws package at ^8.18.0 already includes the DoS fix patched in 8.17.1, so it’s no longer vulnerable, but the current latest is 8.18.3. Similarly, @types/ws is at 8.5.13 while the latest is 8.18.1. For best compatibility and to pick up any minor fixes:

• package.json (line 51): update

-  "ws": "^8.18.0"
+  "ws": "^8.18.3"

• package.json (line 64): update

-  "@types/ws": "^8.5.13"
+  "@types/ws": "^8.18.1"
📝 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
"ws": "^8.18.0"
"dependencies": {
- "ws": "^8.18.0"
+ "ws": "^8.18.3"
},
"devDependencies": {
- "@types/ws": "^8.5.13"
+ "@types/ws": "^8.18.1"
}
🤖 Prompt for AI Agents
In package.json at lines 51 and 64, update the versions of "ws" and "@types/ws"
dependencies to their latest stable releases, specifically change "ws" from
"^8.18.0" to "^8.18.3" and "@types/ws" from "8.5.13" to "8.18.1" to ensure best
compatibility and include minor fixes.

},
"devDependencies": {
"@types/react": "^19.0.12",
Expand All @@ -60,6 +61,7 @@
"@types/node-statsd": "^0.1.6",
"@types/request-ip": "^0.0.41",
"@types/response-time": "^2.3.8",
"@types/ws": "^8.5.13",
"nodemon": "^3.1.9",
"typescript": "^5.7.2"
},
Expand Down
2 changes: 2 additions & 0 deletions src/admin-routes/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { logRequest } from "src/middleware/logRequest";
import { db } from "src/utils/db";
import { authenticateToken, getUserInfo } from "src/utils/jwt";
import domainRouter from "./routes/domain";
import sinkingYachtsRouter from "./routes/sinking-yachts";
import userRouter from "./routes/user";
const router = express.Router();
router.use(express.json());
Expand Down Expand Up @@ -297,6 +298,7 @@ router.get("/metrics", logRequest, async (req, res) => {
});

router.use("/domain", logRequest, domainRouter);
router.use("/sinking-yachts", logRequest, sinkingYachtsRouter);
router.use("/user", userRouter);

export default router;
123 changes: 123 additions & 0 deletions src/admin-routes/routes/sinking-yachts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import express, { Request, Response } from "express";
import { sinkingYahtsService } from "src/services/_index";

const router = express.Router();

/**
* POST /admin/sinking-yachts/start-feed
* @summary Starts the SinkingYachts realtime feed monitoring
* @tags SinkingYachts - Feed Management
* @security BearerAuth
* @param {object} request.body
* @param {boolean} request.body.skipBulkImport - Skip the initial bulk import (optional)
* @return {object} 200 - Success response
* @return {object} 500 - Error response
*/
router.post("/start-feed", async (req: Request, res: Response) => {
try {
const { skipBulkImport = false } = req.body;

await sinkingYahtsService.startFeedMonitoring(skipBulkImport);

return res.status(200).json({
success: true,
message: "SinkingYachts feed monitoring started successfully",
skipBulkImport
});
} catch (error: any) {
return res.status(500).json({
success: false,
message: "Failed to start SinkingYachts feed monitoring",
error: error.message
Comment on lines +27 to +31
Copy link
Preview

Copilot AI Jul 20, 2025

Choose a reason for hiding this comment

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

Using 'any' type for error handling reduces type safety. Consider using 'Error | unknown' or create a specific error type for better type checking.

Suggested change
} catch (error: any) {
return res.status(500).json({
success: false,
message: "Failed to start SinkingYachts feed monitoring",
error: error.message
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : "An unknown error occurred";
return res.status(500).json({
success: false,
message: "Failed to start SinkingYachts feed monitoring",
error: errorMessage

Copilot uses AI. Check for mistakes.

});
Comment on lines +27 to +32

Choose a reason for hiding this comment

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

medium

The error object in a catch block is of type unknown in modern TypeScript. Accessing error.message directly is unsafe as error might not be an Error instance. This can lead to runtime errors. It's safer to check if error is an instance of Error before accessing its message property.

    return res.status(500).json({
      success: false,
      message: "Failed to start SinkingYachts feed monitoring",
      error: error instanceof Error ? error.message : String(error)
    });

}
});

/**
* POST /admin/sinking-yachts/stop-feed
* @summary Stops the SinkingYachts realtime feed monitoring
* @tags SinkingYachts - Feed Management
* @security BearerAuth
* @return {object} 200 - Success response
*/
router.post("/stop-feed", async (req: Request, res: Response) => {

Choose a reason for hiding this comment

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

medium

The stopRealtimeFeed method is synchronous and does not return a promise. Using async for this route handler is unnecessary and can be slightly misleading. Removing it improves code clarity.

router.post("/stop-feed", (req: Request, res: Response) => {

try {
sinkingYahtsService.stopRealtimeFeed();

return res.status(200).json({
success: true,
message: "SinkingYachts feed monitoring stopped successfully"
});
} catch (error: any) {
return res.status(500).json({
success: false,
message: "Failed to stop SinkingYachts feed monitoring",
error: error.message
});
}
});

/**
* POST /admin/sinking-yachts/bulk-import
* @summary Manually triggers a bulk import of all domains from SinkingYachts
* @tags SinkingYachts - Feed Management
* @security BearerAuth
* @return {object} 200 - Success response
* @return {object} 500 - Error response
*/
router.post("/bulk-import", async (req: Request, res: Response) => {
try {
await sinkingYahtsService.initializeBulkImport();

return res.status(200).json({
success: true,
message: "SinkingYachts bulk import completed successfully"
});
} catch (error: any) {
return res.status(500).json({
success: false,
message: "Failed to complete SinkingYachts bulk import",
error: error.message
});
}
});

/**
* GET /admin/sinking-yachts/recent
* @summary Fetches recent domains from SinkingYachts API
* @tags SinkingYachts - Feed Management
* @security BearerAuth
* @param {string} since.query.required - ISO date string for filtering recent domains
* @return {object} 200 - Array of recent domains
* @return {object} 400 - Bad request (missing since parameter)
* @return {object} 500 - Error response
*/
router.get("/recent", async (req: Request, res: Response) => {
try {
const { since } = req.query;

if (!since || typeof since !== "string") {
return res.status(400).json({
success: false,
message: "Missing or invalid 'since' query parameter (ISO date string required)"
});
}

const recentDomains = await sinkingYahtsService.getRecentDomains(since);

return res.status(200).json({
success: true,
data: recentDomains,
count: recentDomains.length,
since
});
} catch (error: any) {
return res.status(500).json({
success: false,
message: "Failed to fetch recent domains",
error: error.message
});
}
});

export default router;
16 changes: 16 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { server } from "./server";
// import metrics from "./metrics";
import { swaggerOptions as adminSwagOptions } from "./admin-routes/swaggerOptions";
import { swaggerOptions as mainSwagOptions } from "./swaggerOptions";
import { sinkingYahtsService } from "./services/_index";
import * as logger from "./utils/logger";
dotenv.config();

Expand All @@ -26,6 +27,21 @@ try {
}
const db = drizzle(process.env.DATABASE_URL);
logger.database("Database connection initialized successfully");

// Initialize SinkingYachts realtime feed monitoring
if (process.env.ENABLE_SINKING_YACHTS_FEED !== "false") {
logger.info("Starting SinkingYachts realtime feed monitoring...");
sinkingYahtsService.startFeedMonitoring(process.env.SKIP_BULK_IMPORT === "true")
.then(() => {
logger.info("SinkingYachts feed monitoring started successfully");
})
.catch((error) => {
logger.error(`Failed to start SinkingYachts feed monitoring: ${error.message}`);

Choose a reason for hiding this comment

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

medium

The error object from a promise rejection can be of any type. Accessing error.message directly is unsafe and could lead to a runtime error if the rejected value is not an Error instance. It's best practice to check the type of the error before accessing properties on it.

Suggested change
logger.error(`Failed to start SinkingYachts feed monitoring: ${error.message}`);
logger.error(`Failed to start SinkingYachts feed monitoring: ${error instanceof Error ? error.message : String(error)}`);

// Don't exit the process, let the server continue without feed monitoring
});
} else {
logger.info("SinkingYachts feed monitoring disabled by ENABLE_SINKING_YACHTS_FEED=false");
}
} catch (error) {
logger.error(
`Failed to initialize database connection: ${error instanceof Error ? error.message : String(error)}`
Expand Down
Loading
Loading