Skip to content

Update/folder structure#2

Merged
sameerkrdev merged 7 commits intomainfrom
update/folder-structure
Oct 4, 2025
Merged

Update/folder structure#2
sameerkrdev merged 7 commits intomainfrom
update/folder-structure

Conversation

@sameerkrdev
Copy link
Copy Markdown
Owner

@sameerkrdev sameerkrdev commented Oct 4, 2025

Summary by CodeRabbit

  • New Features

    • Introduced API Gateway with trade creation endpoint and centralized error responses.
    • Added ClickHouse client and trade repository.
    • Provisioned local infrastructure via Docker (ClickHouse, Redis, Postgres, Kafka, Zookeeper) with trade schema and OHLCV views.
  • Documentation

    • Added architecture docs; reorganized README into a PNPM-based startup guide with infra steps.
  • Chores

    • Added environment examples; root scripts for infra control, DB health checks, and Kafka topic management.
    • Updated ignore rules and cleaning/type-check scripts; removed deprecated API server bootstrap.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Oct 4, 2025

Walkthrough

Introduces a new Express-based API Gateway with trade creation, environment-configured ClickHouse initialization, and graceful shutdown. Adds a ClickHouse client package with a repository for trades. Establishes infrastructure via Docker (ClickHouse, Kafka, Redis, Postgres, Zookeeper) and SQL schema for trades and OHLCV views. Updates docs/tooling, adds types, and removes legacy API remnants.

Changes

Cohort / File(s) Summary
Repo config & tooling
package.json, turbo.json, lint-staged.config.js, .gitignore
Adds infra, DB, and Kafka scripts; introduces new Turbo tasks; excludes types from lint-staged package parsing; ignores tsbuildinfo and logs.
Documentation
README.md, docs/index.md
Rewrites README to PNPM/infrastructure workflow and adds platform architecture docs.
Infrastructure (Docker)
infra/docker/.env.example, infra/docker/docker-compose.yml, infra/docker/clickhouse/init-scripts/01-init.sql
Adds env template and docker-compose for ClickHouse/Redis/Postgres/Zookeeper/Kafka; seeds ClickHouse schema with trade table, MV/state tables, and OHLCV views.
Data seed
clickhouseInitialData.txt
Adds HTTP insert scripts with sample trade rows for ClickHouse.
API Gateway (new app)
apps/api-gateway/.env.example, apps/api-gateway/package.json, apps/api-gateway/tsconfig.json, apps/api-gateway/src/...
Sets up Express app, routes, controller, service, types; integrates logger; bootstraps ClickHouse client from env; adds error handling and graceful shutdown; updates package name/scripts/deps.
Legacy API cleanup
apps/api/.env.example, apps/api/src/server.ts, apps/api/logs/*-audit.json
Removes env placeholders, deletes Express server bootstrap, and removes log audit artifacts.
ClickHouse client package (new)
packages/clickhouse/package.json, packages/clickhouse/eslint.config.mjs, packages/clickhouse/tsconfig.json, packages/clickhouse/src/index.ts, packages/clickhouse/src/client.ts, packages/clickhouse/src/repositories/trade.ts
Adds singleton ClickHouse manager, public exports, and TradeRepository with create/findById. Configures package, ESLint, and TS.
Shared types (new)
packages/types/package.json, packages/types/tsconfig.json, packages/types/index.d.ts, packages/types/trade.d.ts
Introduces shared Trade types and package setup with re-exports.
Logger adjustments
packages/logger/package.json, packages/logger/tsconfig.json
Switches build to tsc, updates scripts, and aligns TS config.
UI tweaks
packages/ui/package.json, packages/ui/src/button.tsx
Adds clean script, refines type-check command, and reformats Button JSX.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant API as API Gateway (Express)
  participant Ctrl as TradeController
  participant Svc as TradeService
  participant Repo as TradeRepository
  participant CH as ClickHouse

  Client->>API: POST /api/v1/trade { Trade }
  API->>Ctrl: createTrade(req)
  Ctrl->>Svc: createTrade(trade)
  Svc->>Repo: create(trade)
  Repo->>CH: INSERT INTO nerve.trade_data
  CH-->>Repo: id
  Repo-->>Svc: id
  Svc-->>Ctrl: id
  Ctrl-->>API: 201 { message }
  API-->>Client: 201 Created

  alt Insert error
    Repo--x CH: INSERT
    CH-->>Repo: error
    Repo-->>Svc: throw
    Svc-->>Ctrl: throw
    Ctrl-->>API: next(error)
    API-->>Client: 5xx { error payload }
  end
Loading
sequenceDiagram
  autonumber
  participant SRV as Server bootstrap
  participant Env as Env (dotenv/envalid)
  participant Mgr as ClickHouseManager
  participant CH as ClickHouse

  SRV->>Env: Read CLICKHOUSE_* and PORT
  SRV->>Mgr: initialize(config)
  Mgr->>CH: createClient + ping
  CH-->>Mgr: pong
  Mgr-->>SRV: client ready
  SRV->>SRV: start Express listen(PORT)

  alt Shutdown (SIGINT/SIGTERM)
    SRV->>Mgr: close()
    Mgr->>CH: close connection
    CH-->>Mgr: closed
    Mgr-->>SRV: done
    SRV->>SRV: process.exit(0)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Poem

In burrows of code, I tap and I write,
Spinning up ClickHouse in docker-lit night.
A gateway now squeaks, “Trade posted, all clear!”
Kafka and friends hum softly in ear.
With types tucked tight and logs swept away,
I thump my review—ship it today! 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The title “Update/folder structure” is overly generic and uses a slash that does not clearly communicate the primary changes in the pull request, such as reorganizing directories, adding an API gateway, infrastructure services, and new packages. It fails to provide a concise, descriptive summary of the main change, making it difficult for teammates scanning the history to understand the scope and intent at a glance. Therefore it does not meet the criteria for a clear and specific PR title. Please rename the PR to a concise sentence that highlights the key changes, for example “Reorganize project folder structure and add API gateway, ClickHouse infra, and documentation updates,” avoiding slashes and generic terms to make the intent immediately clear.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch update/folder-structure

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 13

🧹 Nitpick comments (24)
lint-staged.config.js (1)

37-38: Use consistent equality operators.

The condition mixes == (loose equality) and === (strict equality) operators. For consistency and to avoid potential type coercion issues, use === throughout.

Apply this diff:

-      if (match[1] == "eslint-config" || match[1] == "typescript-config" || match[1] === "types")
+      if (match[1] === "eslint-config" || match[1] === "typescript-config" || match[1] === "types")
         return acc;
infra/docker/.env.example (1)

1-26: Add a trailing newline.

The file is missing a trailing newline, which can cause issues with some Unix tools and Git diffs.

Add a blank line at the end of the file:

 # Kafka
 KAFKA_PORT=9092
 KAFKA_EXTERNAL_PORT=9093
+

Note: The dotenv-linter warnings about key ordering are stylistic preferences and don't affect functionality. The current grouping by service is more readable.

README.md (1)

167-167: Use a proper heading instead of emphasis.

The "End of Startup Guide" line uses bold emphasis instead of a heading, which affects document structure and navigation.

Apply this diff:

-**End of Startup Guide**
+## End of Startup Guide
packages/types/package.json (1)

13-15: Fill in license and author metadata.

The license and author fields are empty. For proper attribution and compliance, please populate these fields appropriately. If this is an internal package, consider using "UNLICENSED" for the license field.

docs/index.md (2)

35-59: Add language identifier to fenced code block.

The fenced code block lacks a language identifier. Adding one improves rendering and syntax highlighting.

Apply this diff:

-```
+```text
 exchange-platform/

169-169: Use heading syntax instead of emphasis.

The "End of Documentation" marker uses emphasis (**bold**) instead of proper heading syntax, which affects document structure and navigation.

Apply this diff:

-**End of Documentation**
+## End of Documentation
apps/api-gateway/src/config/dotenv.ts (1)

12-15: LGTM! Consider secrets management for production.

The ClickHouse configuration is correctly implemented with required environment variables and no defaults for sensitive credentials. For production deployments, consider using a secrets management solution (e.g., AWS Secrets Manager, HashiCorp Vault) instead of plain environment variables for CLICKHOUSE_PASSWORD.

package.json (2)

36-38: Document required arguments for Kafka scripts.

The kafka:topics:create, kafka:consumer, and kafka:producer scripts require topic name arguments but this isn't documented. Consider adding usage examples to the README or inline comments.

Example usage should be documented:

# Create topic
pnpm kafka:topics:create my-topic

# Consume from topic
pnpm kafka:consumer my-topic

# Produce to topic
pnpm kafka:producer my-topic

29-38: Verify cross-platform compatibility of shell scripts.

These scripts use bash-specific syntax (docker ps -qf, command substitution, pipes) that may not work on Windows without WSL. Additionally, the docker ps -qf name=<service> pattern assumes unique container naming and could fail if multiple matching containers exist.

Consider:

  1. Adding platform-specific scripts or documenting the requirement for bash/WSL on Windows
  2. Using more robust container identification (e.g., docker compose ps -q )

Would you like me to generate cross-platform compatible versions of these scripts?

packages/clickhouse/src/client.ts (1)

33-37: Review connection pool and keep-alive settings.

The current settings may need adjustment for production:

  • max_open_connections: 10 might be low for high-load scenarios
  • keep_alive.idle_socket_ttl: 2500 (2.5s) is quite short and may cause frequent reconnections

Consider making these configurable via the IClickHouseConfig interface or environment variables to allow tuning per deployment environment.

Based on learnings from @clickhouse/client 1.12.1 documentation.

packages/logger/package.json (1)

23-23: Remove unused tsup dependency.

tsup is listed in devDependencies but is no longer used in the build script. This can be removed to keep dependencies clean.

Apply this diff:

     "@repo/eslint-config": "workspace:*",
     "@repo/typescript-config": "workspace:*",
-    "tsup": "^8.5.0",
     "typescript": "^5.9.2"
apps/api-gateway/.env.example (1)

7-9: Document Docker Compose environment variables.
COMPOSE_PROJECT_NAME, CLICKHOUSE_HTTP_PORT, and CLICKHOUSE_NATIVE_PORT are used in infra/docker/docker-compose.yml for container names and port mappings—add comments or group them under a “Docker Compose” section in apps/api-gateway/.env.example.

packages/types/trade.d.ts (1)

4-5: Consider documenting or strengthening timestamp formats.

The clientTimestamp and engineTimestamp fields are typed as string. If these follow a specific format (e.g., ISO 8601), consider documenting this in JSDoc comments or using a branded type to ensure format consistency across the codebase.

Example with JSDoc:

 export interface Trade {
+  /** ISO 8601 timestamp string (e.g., "2025-10-04T13:12:49Z") */
   clientTimestamp: string;
+  /** ISO 8601 timestamp string (e.g., "2025-10-04T13:12:49Z") */
   engineTimestamp: string;
   symbol: string;
apps/api-gateway/src/routers/trade.route.ts (1)

16-18: Avoid the double type cast with proper handler typing.

The as unknown as CreateTradeRequest cast indicates a type mismatch. Since CreateTradeRequest extends Request, you can directly type the route handler parameters.

Apply this diff to remove the cast:

-router.route("/").post((req: Request, res: Response, next: NextFunction) => {
-  tradeController.createTrade(req as unknown as CreateTradeRequest, res, next);
-});
+router.route("/").post((req: CreateTradeRequest, res: Response, next: NextFunction) => {
+  tradeController.createTrade(req, res, next);
+});
apps/api-gateway/src/app.ts (1)

22-29: Consider populating or removing empty error fields.

The error response includes path and location fields that are always empty strings. Either populate these with meaningful values (e.g., req.path, error stack location) or remove them to avoid misleading API consumers.

Option 1 (populate with request path):

         {
           type: err.name,
           msg: err.message,
-          path: "",
+          path: _req.path,
           location: "",
         },

Option 2 (remove empty fields):

         {
           type: err.name,
           msg: err.message,
-          path: "",
-          location: "",
         },
cickhouseInitialData.txt (1)

1-2: Consider making this an executable shell script.

These curl commands would be more convenient as an executable .sh script with appropriate shebang and error handling. This would allow:

  • Direct execution: ./seed-clickhouse-data.sh
  • Better error handling and feedback
  • Easy integration into setup/CI workflows

Example structure:

#!/bin/bash
set -e

CLICKHOUSE_HOST="${CLICKHOUSE_HOST:-http://localhost:8123}"

echo "Seeding ClickHouse with initial trade data..."

curl -X POST "$CLICKHOUSE_HOST/" \
  --data-binary @- << 'EOF'
...
EOF

echo "Seeding complete!"
infra/docker/docker-compose.yml (2)

1-1: Consider upgrading to Compose v3.9 or newer.

Docker Compose v3.8 is older. Consider using v3.9 (released 2021) or the newer Compose Specification format (no version key) for better feature support and forward compatibility.


58-58: Remove commented-out code or document why it's preserved.

The postgres init-scripts volume mount is commented out. Either:

  • Remove it if not needed
  • Uncomment it if needed
  • Document why it's kept as a placeholder (e.g., "TODO: add postgres init scripts")
apps/api-gateway/src/services/trade.service.ts (1)

9-27: Consider simplifying by passing the entire Trade object.

You're destructuring all properties from the Trade parameter and then immediately reconstructing the same object to pass to tradeRepo.create(). This is redundant.

Apply this diff:

-  async createTrade({
-    engineTimestamp,
-    clientTimestamp,
-    symbol,
-    price,
-    volume,
-    side,
-    userId,
-  }: Trade) {
-    await this.tradeRepo.create({
-      engineTimestamp,
-      clientTimestamp,
-      symbol,
-      price,
-      volume,
-      side,
-      userId,
-    });
+  async createTrade(trade: Trade) {
+    await this.tradeRepo.create(trade);
   }
packages/clickhouse/src/repositories/trade.ts (2)

40-44: Validate the parsed JSON structure before casting.

The JSON.parse(row.text) as Trade cast is unsafe. If the database schema changes or returns unexpected data, this will silently create invalid Trade objects.

Consider adding runtime validation:

for await (const rows of resultSet.stream()) {
  for (const row of rows) {
    const data = JSON.parse(row.text);
    // Add validation here - e.g., using zod or a custom validator
    if (isValidTrade(data)) {
      return data as Trade;
    }
    throw new Error("Invalid trade data from database");
  }
}

Alternatively, use a schema validation library like zod to ensure type safety at runtime.


33-47: Add error handling for database query failures.

The findById method doesn't handle potential query failures. If the ClickHouse connection is down or the query fails, the error will propagate unhandled.

Consider wrapping the query in a try-catch with appropriate error handling:

async findById(id: string): Promise<Trade | null> {
  try {
    const resultSet = await this.client.query({
      query: "SELECT * FROM nerve.trade_data WHERE id = {id:UUID} LIMIT 1",
      query_params: { id },
      format: "JSONEachRow",
    });

    for await (const rows of resultSet.stream()) {
      for (const row of rows) {
        return JSON.parse(row.text) as Trade;
      }
    }

    return null;
  } catch (error) {
    // Log error and rethrow with context
    throw new Error(`Failed to find trade by id ${id}: ${error}`);
  }
}
apps/api-gateway/src/controllers/trade.controller.ts (2)

12-39: Simplify by passing req.body directly to the service.

You're destructuring all Trade properties from req.body (line 14), reconstructing them into an object (lines 16-24), and then logging all of them again (lines 26-34). This is redundant and verbose.

Apply this diff:

   async createTrade(req: CreateTradeRequest, res: Response, next: NextFunction) {
     try {
-      const { engineTimestamp, clientTimestamp, symbol, price, volume, side, userId } = req.body;
-
-      await this.TradeService.createTrade({
-        engineTimestamp,
-        clientTimestamp,
-        symbol,
-        price,
-        volume,
-        side,
-        userId,
-      });
+      await this.TradeService.createTrade(req.body);

-      this.logger.info("New trade is created", {
-        engineTimestamp,
-        clientTimestamp,
-        symbol,
-        price,
-        volume,
-        side,
-        userId,
-      });
+      this.logger.info("New trade is created", req.body);
       res.status(201).json({ message: "New trade is created" });
     } catch (error) {
       return next(error);
     }
   }

35-35: Consider returning the created trade ID in the response.

The response only returns a generic success message. It would be more useful to return the generated trade ID so clients can reference it.

Modify the service to return the trade ID, then include it in the response:

const tradeId = await this.TradeService.createTrade(req.body);
this.logger.info("New trade is created", { tradeId, ...req.body });
res.status(201).json({ message: "New trade is created", tradeId });

Note: This requires updating TradeService.createTrade() to return the ID from tradeRepo.create().

infra/docker/clickhouse/init-scripts/01-init.sql (1)

247-266: Consider uncommenting the testing queries as a separate verification script.

The commented testing queries are valuable for validation. Rather than keeping them commented in the init script, consider creating a separate 02-verify.sql script that can be run independently to verify the setup.

Create infra/docker/clickhouse/init-scripts/02-verify.sql:

-- Verify no duplicates in candles
SELECT 'Checking for duplicate 1m candles' as check_name, count(*) as duplicates
FROM (
  SELECT symbol, candle_time, count(*) as cnt 
  FROM nerve.candles_1m 
  GROUP BY symbol, candle_time 
  HAVING cnt > 1
);

-- Check record counts across all tables
SELECT 'Raw trades' as table_name, count() as record_count FROM nerve.trade_data
UNION ALL
SELECT '1m candles', count() FROM nerve.candles_1m
UNION ALL  
SELECT '5m candles', count() FROM nerve.candles_5m
UNION ALL
SELECT '4h candles', count() FROM nerve.candles_4h
UNION ALL
SELECT '1d candles', count() FROM nerve.candles_1d;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4b3276f and fcc66be.

⛔ Files ignored due to path filters (7)
  • apps/api/logs/combined-2025-09-13.log is excluded by !**/*.log
  • apps/api/logs/error-2025-09-13.log is excluded by !**/*.log
  • apps/api/logs/exceptions-2025-09-13.log is excluded by !**/*.log
  • apps/api/logs/rejections-2025-09-13.log is excluded by !**/*.log
  • apps/web/public/vite.svg is excluded by !**/*.svg
  • apps/web/src/assets/react.svg is excluded by !**/*.svg
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (40)
  • .gitignore (1 hunks)
  • README.md (1 hunks)
  • apps/api-gateway/.env.example (1 hunks)
  • apps/api-gateway/package.json (1 hunks)
  • apps/api-gateway/src/app.ts (1 hunks)
  • apps/api-gateway/src/config/dotenv.ts (1 hunks)
  • apps/api-gateway/src/controllers/trade.controller.ts (1 hunks)
  • apps/api-gateway/src/routers/trade.route.ts (1 hunks)
  • apps/api-gateway/src/server.ts (1 hunks)
  • apps/api-gateway/src/services/trade.service.ts (1 hunks)
  • apps/api-gateway/src/types/index.ts (1 hunks)
  • apps/api-gateway/tsconfig.json (1 hunks)
  • apps/api/.env.example (0 hunks)
  • apps/api/logs/.2abc85c82cdda96487f9b2edd172659f093a115b-audit.json (0 hunks)
  • apps/api/logs/.52496fbede3b4e1e307973b1f7482eea6a9e1553-audit.json (0 hunks)
  • apps/api/logs/.6a8385029aaed3383ca37d3aa171a144e160f8bf-audit.json (0 hunks)
  • apps/api/logs/.e5210eccd550f9edac74ec60024b73718af4e4e8-audit.json (0 hunks)
  • apps/api/src/server.ts (0 hunks)
  • cickhouseInitialData.txt (1 hunks)
  • docs/index.md (1 hunks)
  • infra/docker/.env.example (1 hunks)
  • infra/docker/clickhouse/init-scripts/01-init.sql (1 hunks)
  • infra/docker/docker-compose.yml (1 hunks)
  • lint-staged.config.js (1 hunks)
  • package.json (1 hunks)
  • packages/clickhouse/eslint.config.mjs (1 hunks)
  • packages/clickhouse/package.json (1 hunks)
  • packages/clickhouse/src/client.ts (1 hunks)
  • packages/clickhouse/src/index.ts (1 hunks)
  • packages/clickhouse/src/repositories/trade.ts (1 hunks)
  • packages/clickhouse/tsconfig.json (1 hunks)
  • packages/logger/package.json (1 hunks)
  • packages/logger/tsconfig.json (1 hunks)
  • packages/types/index.d.ts (1 hunks)
  • packages/types/package.json (1 hunks)
  • packages/types/trade.d.ts (1 hunks)
  • packages/types/tsconfig.json (1 hunks)
  • packages/typescript-config/node.json (1 hunks)
  • packages/ui/src/button.tsx (1 hunks)
  • turbo.json (2 hunks)
💤 Files with no reviewable changes (6)
  • apps/api/src/server.ts
  • apps/api/logs/.6a8385029aaed3383ca37d3aa171a144e160f8bf-audit.json
  • apps/api/logs/.e5210eccd550f9edac74ec60024b73718af4e4e8-audit.json
  • apps/api/logs/.52496fbede3b4e1e307973b1f7482eea6a9e1553-audit.json
  • apps/api/logs/.2abc85c82cdda96487f9b2edd172659f093a115b-audit.json
  • apps/api/.env.example
🧰 Additional context used
🧬 Code graph analysis (8)
apps/api-gateway/src/routers/trade.route.ts (5)
packages/clickhouse/src/repositories/trade.ts (1)
  • TradeRepository (5-48)
apps/api-gateway/src/services/trade.service.ts (1)
  • TradeService (6-32)
apps/api-gateway/src/controllers/trade.controller.ts (1)
  • TradeController (6-40)
packages/logger/src/index.ts (1)
  • logger (109-109)
apps/api-gateway/src/types/index.ts (1)
  • CreateTradeRequest (4-6)
packages/clickhouse/src/repositories/trade.ts (3)
packages/clickhouse/src/index.ts (2)
  • TradeRepository (2-2)
  • clickHouseManager (1-1)
packages/clickhouse/src/client.ts (1)
  • clickHouseManager (58-58)
packages/types/trade.d.ts (1)
  • Trade (3-11)
apps/api-gateway/src/app.ts (1)
packages/logger/src/index.ts (1)
  • logger (109-109)
apps/api-gateway/src/services/trade.service.ts (2)
packages/clickhouse/src/repositories/trade.ts (1)
  • TradeRepository (5-48)
packages/types/trade.d.ts (1)
  • Trade (3-11)
packages/clickhouse/src/client.ts (1)
packages/clickhouse/src/index.ts (2)
  • IClickHouseConfig (1-1)
  • clickHouseManager (1-1)
apps/api-gateway/src/types/index.ts (1)
packages/types/trade.d.ts (1)
  • Trade (3-11)
apps/api-gateway/src/controllers/trade.controller.ts (3)
apps/api-gateway/src/services/trade.service.ts (1)
  • TradeService (6-32)
packages/logger/src/index.ts (2)
  • logger (109-109)
  • Logger (115-115)
apps/api-gateway/src/types/index.ts (1)
  • CreateTradeRequest (4-6)
apps/api-gateway/src/server.ts (3)
packages/clickhouse/src/client.ts (1)
  • clickHouseManager (58-58)
apps/api-gateway/src/config/dotenv.ts (1)
  • env (9-16)
packages/logger/src/index.ts (1)
  • logger (109-109)
🪛 dotenv-linter (3.3.0)
infra/docker/.env.example

[warning] 6-6: [UnorderedKey] The CLICKHOUSE_HTTP_PORT key should go before the CLICKHOUSE_URL key

(UnorderedKey)


[warning] 7-7: [UnorderedKey] The CLICKHOUSE_NATIVE_PORT key should go before the CLICKHOUSE_URL key

(UnorderedKey)


[warning] 8-8: [UnorderedKey] The CLICKHOUSE_DB key should go before the CLICKHOUSE_HTTP_PORT key

(UnorderedKey)


[warning] 10-10: [UnorderedKey] The CLICKHOUSE_PASSWORD key should go before the CLICKHOUSE_URL key

(UnorderedKey)


[warning] 17-17: [UnorderedKey] The POSTGRES_DB key should go before the POSTGRES_PORT key

(UnorderedKey)


[warning] 19-19: [UnorderedKey] The POSTGRES_PASSWORD key should go before the POSTGRES_PORT key

(UnorderedKey)


[warning] 26-26: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)


[warning] 26-26: [UnorderedKey] The KAFKA_EXTERNAL_PORT key should go before the KAFKA_PORT key

(UnorderedKey)

apps/api-gateway/.env.example

[warning] 2-2: [UnorderedKey] The NODE_ENV key should go before the PORT key

(UnorderedKey)


[warning] 3-3: [UnorderedKey] The CLICKHOUSE_DB key should go before the NODE_ENV key

(UnorderedKey)


[warning] 4-4: [UnorderedKey] The CLICKHOUSE_USER key should go before the NODE_ENV key

(UnorderedKey)


[warning] 5-5: [UnorderedKey] The CLICKHOUSE_PASSWORD key should go before the CLICKHOUSE_USER key

(UnorderedKey)


[warning] 6-6: [UnorderedKey] The CLICKHOUSE_URL key should go before the CLICKHOUSE_USER key

(UnorderedKey)


[warning] 7-7: [UnorderedKey] The COMPOSE_PROJECT_NAME key should go before the NODE_ENV key

(UnorderedKey)


[warning] 8-8: [UnorderedKey] The CLICKHOUSE_HTTP_PORT key should go before the CLICKHOUSE_PASSWORD key

(UnorderedKey)


[warning] 9-9: [UnorderedKey] The CLICKHOUSE_NATIVE_PORT key should go before the CLICKHOUSE_PASSWORD key

(UnorderedKey)

🪛 markdownlint-cli2 (0.18.1)
README.md

167-167: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

docs/index.md

35-35: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


169-169: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

🔇 Additional comments (27)
.gitignore (1)

42-43: Good call on ignoring build artifacts.

Adding tsconfig.tsbuildinfo and general logs keeps transient build output and runtime logs out of version control. 👍

packages/ui/src/button.tsx (1)

13-13: LGTM!

The formatting change consolidates the button tag to a single line with no behavioral impact.

apps/api-gateway/tsconfig.json (1)

5-5: LGTM!

The addition of rootDir: "src" aligns the API gateway configuration with other packages in the monorepo and ensures consistent build behavior.

packages/clickhouse/eslint.config.mjs (1)

1-3: LGTM!

The ESLint configuration correctly re-exports the shared Node.js config, maintaining consistency across the monorepo.

turbo.json (4)

11-12: LGTM!

Adding dependsOn: ["^build"] ensures that dependencies are built before starting development servers, preventing runtime errors from missing artifacts. The persistent: true setting is appropriate for long-running dev servers.


20-23: LGTM!

Adding dependsOn: ["^build"] ensures type checking runs against built dependencies, and outputs: [] correctly indicates that type checking doesn't produce cacheable artifacts.


24-30: LGTM!

The new clean and infra:up tasks are correctly configured:

  • clean with cache: false prevents caching of deletion operations
  • infra:up with empty outputs indicates infrastructure startup doesn't produce build artifacts

5-7: Consider retaining .env* in Turbo build inputs
Our search of package.json scripts and common build configs (Vite/Next/Webpack) found no direct process.env or dotenv usage, but absence of matches isn’t proof. Manually confirm your build doesn’t bake in environment variables; if it does, restore .env* to turbo.json inputs or document that env-changes require a manual rebuild.

infra/docker/.env.example (1)

10-19: LGTM! Verify passwords are for development only.

The default passwords ("nerve") are appropriate for local development. Ensure these are never used in production environments and that the documentation clearly indicates this is a development-only configuration.

Consider adding a comment to clarify this is for development:

 # ClickHouse
 CLICKHOUSE_URL=http://localhost:8123
 CLICKHOUSE_HTTP_PORT=8123
 CLICKHOUSE_NATIVE_PORT=9000
 CLICKHOUSE_DB=default
 CLICKHOUSE_USER=default
+# Default password for local development only - DO NOT use in production
 CLICKHOUSE_PASSWORD=nerve
README.md (1)

1-166: LGTM! Comprehensive startup documentation.

The updated README provides clear, step-by-step instructions for:

  • Installing dependencies
  • Starting infrastructure services
  • Health checking
  • Running applications
  • Managing Kafka topics

The PNPM-based workflow is well-documented and aligns with the monorepo structure changes in this PR.

apps/api-gateway/package.json (1)

24-24: Verify Express v5 compatibility.

The package uses Express ^5.1.0 (latest). Express v5 introduced breaking changes from v4, including route matching behavior and middleware compatibility. Ensure that:

  1. All route definitions have been updated per the v5 migration guide
  2. Third-party middleware is compatible with Express v5
  3. The official codemod has been run if migrating from v4

Based on learnings about Express v5.1.0.

packages/logger/package.json (1)

15-15: Ensure build outputs match exports & remove unused tsup.

The tsc -p tsconfig.json build must generate dist/index.js, dist/index.mjs, and dist/index.d.ts as declared in exports. Verify locally that all three files exist; if any are missing, remove tsup from devDependencies, update exports to the actual outputs, or adjust your tsc configuration to emit both CJS and ESM.

packages/types/index.d.ts (1)

1-1: LGTM!

The re-export pattern is standard and appropriate for a types package index.

packages/clickhouse/tsconfig.json (1)

1-6: LGTM!

The TypeScript configuration follows standard conventions and properly configures the build output structure.

packages/clickhouse/src/index.ts (1)

1-2: LGTM!

The public API surface is well-defined with appropriate re-exports for the ClickHouse package.

apps/api-gateway/src/types/index.ts (1)

1-6: LGTM!

The CreateTradeRequest type properly extends Express Request with Trade-specific body typing.

apps/api-gateway/src/server.ts (2)

6-19: LGTM!

The database initialization logic properly handles errors and exits on failure, preventing the server from running without a database connection.


22-32: LGTM!

The graceful shutdown handlers for both SIGINT and SIGTERM properly close the ClickHouse connection before exiting.

apps/api-gateway/src/app.ts (1)

1-13: LGTM!

The Express app setup with JSON parsing and route mounting follows best practices.

infra/docker/docker-compose.yml (2)

24-27: Document the rationale for the high file descriptor limit.

The ulimit of 262,144 is exceptionally high (default is typically 1,024). While ClickHouse can benefit from higher limits for concurrent queries and large datasets, this specific value should be documented to explain why it's necessary for your workload.

Consider adding a comment in the docker-compose file or in documentation explaining:

  • Expected query concurrency
  • Dataset size projections
  • Performance benchmarks that led to this configuration

89-91: LGTM! Proper healthcheck dependency.

The use of condition: service_healthy ensures Kafka only starts after Zookeeper is ready, preventing connection failures during startup.

packages/typescript-config/node.json (1)

4-7: LGTM! Modern ES2022 with Node16 module resolution.

The configuration correctly targets ES2022 with Node16 module resolution, which provides proper ESM/CJS interop for Node.js projects. This is a good modernization step.

Based on learnings: Node16 module resolution is the recommended approach for Node.js TypeScript projects targeting modern Node versions.

packages/clickhouse/package.json (1)

1-33: LGTM! Package structure is well-configured.

The package manifest correctly defines:

  • Dual export paths (types and default)
  • Standard build/lint/clean scripts
  • Appropriate dependencies including the latest stable @clickhouse/client@^1.12.1

Based on learnings: @clickhouse/client 1.12.1 is the latest stable version with performance improvements and Node.js 24.x support.

infra/docker/clickhouse/init-scripts/01-init.sql (4)

10-27: LGTM! Well-designed trade data table with appropriate indexes.

The trade_data table schema is well-designed:

  • UUID for unique identification
  • DateTime64(3) for millisecond precision timestamps
  • LowCardinality for symbol (efficient for repetitive string values)
  • Float64 for financial precision
  • Enum for side (space-efficient)
  • Proper partitioning by day
  • Strategic indexes on price, user_id, and id

36-93: LGTM! Proper use of AggregatingMergeTree for incremental aggregation.

The state tables correctly use AggregatingMergeTree with appropriate aggregate functions:

  • argMin/argMax for open/close with timestamp ordering
  • max/min for high/low
  • sum for volume and volume-weighted calculations
  • count for trade count

The partitioning strategies are sensible:

  • Monthly partitioning for 1m, 5m candles
  • Daily partitioning for 4h candles
  • Monthly partitioning for 1d candles with Date type

101-162: Verify the time bucketing logic for 5m and 4h candles.

The time bucketing uses intDiv(toUnixTimestamp(...), interval) * interval:

  • 5-minute: intDiv(toUnixTimestamp(engine_timestamp), 300) * 300 (Line 121)
  • 4-hour: intDiv(toUnixTimestamp(engine_timestamp), 14400) * 14400 (Line 137)

Please verify this logic handles timezone and DST transitions correctly. ClickHouse's toStartOfFiveMinutes() and toStartOfInterval() functions are purpose-built for this and may be more robust.

Consider this alternative:

-- Instead of:
toDateTime(intDiv(toUnixTimestamp(engine_timestamp), 300) * 300) AS candle_time

-- Use:
toStartOfInterval(engine_timestamp, INTERVAL 5 MINUTE) AS candle_time

Do you want me to generate a script to verify the time bucketing produces expected results?


182-184: LGTM! Safe handling of zero-volume edge case in VWAP calculation.

The if(sumMerge(volume_state) > 0, ..., 0) pattern correctly prevents division by zero when calculating VWAP, returning 0 as a fallback. This is consistent across all candle views.

"type": "commonjs",
"scripts": {
"dev": "turbo build --filter=@repo/logger && nodemon",
"dev": "nodemon",
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

Verify logger build dependency is handled.

The dev script no longer includes the turbo build --filter=@repo/logger step. Ensure that the logger package is built before running dev, either through workspace dependency resolution or a separate build step.

Run this script to verify the dev workflow:


🏁 Script executed:

#!/bin/bash
# Description: Verify that logger is built when running dev script

# Check if turbo handles workspace dependencies automatically
cd apps/api-gateway
pnpm dev &
DEV_PID=$!
sleep 2
kill $DEV_PID 2>/dev/null

# Check if logger dist exists
if [ -d "../../packages/logger/dist" ]; then
  echo "✓ Logger package is built"
else
  echo "✗ Logger package is not built - dev script may fail"
  exit 1
fi

Length of output: 270


Ensure logger is built before running dev
The dev script in apps/api-gateway/package.json only runs nodemon and no longer includes turbo build --filter=@repo/logger, so packages/logger/dist is missing and pnpm dev fails. Add a pre-build step (e.g. turbo run build --filter=@repo/logger) or configure workspace dependencies to build @repo/logger before dev.

🤖 Prompt for AI Agents
In apps/api-gateway/package.json around line 8, the "dev" script only runs
"nodemon" which fails because packages/logger/dist isn't built; update the
script to build the logger first (for example prepend a pre-step like running
"turbo run build --filter=@repo/logger" or "turbo build --filter=@repo/logger")
so @repo/logger is compiled before nodemon runs, or alternatively configure
workspace/devDependencies so the logger package is built as a workspace
dependency prior to running dev.

Comment thread apps/api-gateway/package.json Outdated
Comment thread apps/api-gateway/src/app.ts Outdated
Comment thread apps/api-gateway/src/server.ts
Comment thread apps/api-gateway/src/services/trade.service.ts Outdated
Comment thread packages/clickhouse/src/repositories/trade.ts Outdated
Comment thread packages/clickhouse/src/repositories/trade.ts Outdated
Comment thread packages/logger/tsconfig.json Outdated
Comment thread packages/types/tsconfig.json Outdated
Comment on lines +21 to +26
"noUnusedLocals": true,
"noUnusedParameters": true,
"exactOptionalPropertyTypes": true,
"verbatimModuleSyntax": false,
"noUncheckedSideEffectImports": true,
"allowImportingTsExtensions": false
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true
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

Verify that new strict checks don't break existing code.

The addition of noUnusedLocals, noUnusedParameters, noImplicitReturns, noFallthroughCasesInSwitch, and noUncheckedIndexedAccess will surface new type errors in existing code.

Run the following script to check for type errors across the workspace:


🏁 Script executed:

#!/bin/bash
# Description: Check for TypeScript errors in all packages that extend this config

echo "Checking for TypeScript errors after strict flag additions..."

# Find all packages that extend this config
fd -t f 'tsconfig.json' -x grep -l '@repo/typescript-config/node.json' {} \; | while read -r tsconfig; do
  dir=$(dirname "$tsconfig")
  echo "Checking $dir..."
  (cd "$dir" && npm run type-check 2>&1) || echo "⚠️  Type errors found in $dir"
done

Length of output: 804


Ensure strict flags are validated in each package
Add typescript as a devDependency and a type-check script (e.g. "type-check": "tsc --noEmit -p tsconfig.json") to every package extending this config, then run npm run type-check (or npx tsc -p tsconfig.json) to catch and fix any type errors introduced by the new strict flags.

🤖 Prompt for AI Agents
In packages/typescript-config/node.json around lines 21 to 26, the strict
TypeScript flags were enabled but downstream packages that extend this config
may not have TypeScript installed or a type-check script; add "typescript" as a
devDependency in each package.json that extends this config and add a
"type-check" script (for example "tsc --noEmit -p tsconfig.json"); run npm run
type-check (or npx tsc -p tsconfig.json) in each package and fix any reported
type errors (unused locals/params, implicit returns, exact optional types,
unchecked indexed access, fallthrough cases) until the type-check passes.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/logger/package.json (1)

21-24: Drop unused tsup dependency.

With the build script now using tsc -p tsconfig.json, tsup is no longer referenced anywhere. Removing it trims install time and keeps the manifest honest.

   "devDependencies": {
     "@repo/eslint-config": "workspace:*",
     "@repo/typescript-config": "workspace:*",
-    "tsup": "^8.5.0",
     "typescript": "^5.9.2"
   },
apps/api-gateway/src/server.ts (1)

43-48: Strengthen error handling to catch all error types.

The catch block only handles Error instances, but JavaScript allows throwing any value. Non-Error throws would silently fail without logging or process exit.

Apply this diff:

   } catch (error) {
-    if (error instanceof Error) {
-      logger.error(error.message);
-      process.exit(1);
-    }
+    logger.error("Failed to start server", error instanceof Error ? error.message : String(error));
+    process.exit(1);
   }
 };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fcc66be and b591f5b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (13)
  • apps/api-gateway/package.json (1 hunks)
  • apps/api-gateway/src/app.ts (1 hunks)
  • apps/api-gateway/src/server.ts (1 hunks)
  • apps/api-gateway/src/services/trade.service.ts (1 hunks)
  • clickhouseInitialData.txt (1 hunks)
  • package.json (1 hunks)
  • packages/clickhouse/package.json (1 hunks)
  • packages/clickhouse/src/client.ts (1 hunks)
  • packages/clickhouse/src/repositories/trade.ts (1 hunks)
  • packages/logger/package.json (1 hunks)
  • packages/logger/tsconfig.json (1 hunks)
  • packages/types/tsconfig.json (1 hunks)
  • packages/ui/package.json (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • clickhouseInitialData.txt
  • packages/types/tsconfig.json
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/clickhouse/package.json
  • packages/clickhouse/src/client.ts
  • packages/clickhouse/src/repositories/trade.ts
  • package.json
  • apps/api-gateway/src/services/trade.service.ts
  • apps/api-gateway/package.json
🧰 Additional context used
🧬 Code graph analysis (2)
apps/api-gateway/src/server.ts (3)
packages/clickhouse/src/client.ts (1)
  • clickHouseManager (65-65)
apps/api-gateway/src/config/dotenv.ts (1)
  • env (9-16)
packages/logger/src/index.ts (1)
  • logger (109-109)
apps/api-gateway/src/app.ts (1)
packages/logger/src/index.ts (1)
  • logger (109-109)
🔇 Additional comments (4)
apps/api-gateway/src/server.ts (2)

6-19: LGTM! Database initialization is properly structured.

The initialization function correctly handles connection setup and provides clear error logging. The immediate exit on failure is appropriate for preventing the application from running without database connectivity.


51-51: LGTM! Non-blocking server start is appropriate.

Using void startServer() correctly indicates the intentionally floating promise for the top-level async invocation.

apps/api-gateway/src/app.ts (2)

1-7: LGTM! Express setup follows best practices.

The app initialization and JSON middleware configuration align with Express 5.x conventions.

Based on learnings


9-13: LGTM! Routes are properly mounted.

The trade router is correctly mounted with versioned API prefix, and the root endpoint provides a functional health check.

Comment on lines +16 to +31
app.use((err: HttpError, _req: Request, res: Response, _next: NextFunction) => {
if (err instanceof Error) {
logger.error(err.message);
const statusCode = err.status || err.statusCode || 500;

res.status(statusCode).json({
error: [
{
type: err.name,
msg: err.message,
path: "",
location: "",
},
],
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Populate error context fields and simplify response structure.

The error handler has several improvement opportunities:

  1. Empty path and location: Lines 26-27 should populate actual request context
  2. Unnecessary array wrapping: Single errors don't need array wrapping
  3. Type narrowing: The instanceof Error check on line 17 is redundant given the HttpError parameter type

Apply this diff to improve the error handler:

 // eslint-disable-next-line @typescript-eslint/no-unused-vars
 app.use((err: HttpError, req: Request, res: Response, _next: NextFunction) => {
-  if (err instanceof Error) {
-    logger.error(err.message);
-    const statusCode = err.status || err.statusCode || 500;
+  logger.error(err.message);
+  const statusCode = err.status || err.statusCode || 500;

-    res.status(statusCode).json({
-      error: [
-        {
-          type: err.name,
-          msg: err.message,
-          path: "",
-          location: "",
-        },
-      ],
-    });
-  }
+  res.status(statusCode).json({
+    error: {
+      type: err.name,
+      msg: err.message,
+      path: req.path,
+      location: req.method,
+    },
+  });
 });

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +22 to +32
process.on("SIGINT", async () => {
logger.info("Shutting down server...");
await clickHouseManager.close();
process.exit(0);
});

process.on("SIGTERM", async () => {
logger.info("Shutting down server...");
await clickHouseManager.close();
process.exit(0);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Extract duplicate shutdown logic into a shared handler.

Both SIGINT and SIGTERM handlers contain identical shutdown code, violating DRY principles. Additionally, the handlers close only the ClickHouse connection but not the HTTP server itself, which could leave open connections.

Apply this diff to deduplicate and improve graceful shutdown:

+// Handle graceful shutdown
+async function gracefulShutdown(signal: string) {
+  logger.info(`Received ${signal}, shutting down gracefully...`);
+  await clickHouseManager.close();
+  // Close HTTP server if it exists
+  if (server) {
+    server.close(() => {
+      logger.info("HTTP server closed");
+      process.exit(0);
+    });
+  } else {
+    process.exit(0);
+  }
+}
+
-// Handle graceful shutdown
-process.on("SIGINT", async () => {
-  logger.info("Shutting down server...");
-  await clickHouseManager.close();
-  process.exit(0);
-});
-
-process.on("SIGTERM", async () => {
-  logger.info("Shutting down server...");
-  await clickHouseManager.close();
-  process.exit(0);
-});
+process.on("SIGINT", () => void gracefulShutdown("SIGINT"));
+process.on("SIGTERM", () => void gracefulShutdown("SIGTERM"));

Then update startServer to store the server instance:

 const startServer = async () => {
   const PORT = env.PORT;

   try {
     await initializeDatabase();

-    app.listen(PORT, () => {
+    server = app.listen(PORT, () => {
       logger.info(`Server is running on port ${PORT}...`);
     });
   } catch (error) {

And declare the server variable at module scope:

 import app from "@/app";
+import type { Server } from "http";
 
+let server: Server | null = null;
+
 async function initializeDatabase() {
🤖 Prompt for AI Agents
In apps/api-gateway/src/server.ts around lines 22 to 32, the SIGINT and SIGTERM
handlers duplicate shutdown logic and only close ClickHouse; extract the shared
shutdown logic into a single async function (e.g., gracefulShutdown) that first
logs shutdown, closes the HTTP server instance (await server.close() or
promisified close) to drain connections, then closes clickHouseManager, and
finally exits the process; declare the server variable at module scope (let
server: http.Server | undefined) and update startServer to assign the returned
server instance to that module-scoped variable so the shared handler can
reference it.

@sameerkrdev sameerkrdev merged commit 4efdaf3 into main Oct 4, 2025
1 check passed
@sameerkrdev sameerkrdev deleted the update/folder-structure branch October 4, 2025 17:46
@coderabbitai coderabbitai Bot mentioned this pull request Feb 1, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Apr 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant