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
3 changes: 2 additions & 1 deletion core-web/.vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"angular.ng-template",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"editorconfig.editorconfig"
"editorconfig.editorconfig",
"ms-playwright.playwright"
]
}
100 changes: 68 additions & 32 deletions core-web/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -1,34 +1,70 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch dotCMS UI (Dev Server)",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "yarn",
"runtimeArgs": ["nx", "serve", "dotcms-ui"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"serverReadyAction": {
"pattern": "Local:.*?(https?://[^\\s]+)",
"uriFormat": "%s",
"action": "openExternally"
}
},
{
"name": "Debug dotCMS UI in Chrome",
"type": "chrome",
"request": "launch",
"url": "http://localhost:4200",
"webRoot": "${workspaceFolder}",
"sourceMapPathOverrides": {
"webpack:/*": "${webRoot}/*",
"/./*": "${webRoot}/*",
"/src/*": "${webRoot}/*",
"/*": "*",
"/./~/*": "${webRoot}/node_modules/*"
}
}
]
"version": "0.2.0",
"configurations": [
{
"name": "Launch dotCMS UI (Dev Server)",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "yarn",
"runtimeArgs": ["nx", "serve", "dotcms-ui"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"serverReadyAction": {
"pattern": "Local:.*?(https?://[^\\s]+)",
"uriFormat": "%s",
"action": "openExternally"
}
},
{
"name": "Debug dotCMS UI in Chrome",
"type": "chrome",
"request": "launch",
"url": "http://localhost:4200",
"webRoot": "${workspaceFolder}",
"sourceMapPathOverrides": {
"webpack:/*": "${webRoot}/*",
"/./*": "${webRoot}/*",
"/src/*": "${webRoot}/*",
"/*": "*",
"/./~/*": "${webRoot}/node_modules/*"
}
},
{
"type": "node",
"request": "launch",
"name": "Debug api-dot-ai-chat with Nx",
"runtimeExecutable": "yarn",
"runtimeArgs": ["nx", "serve", "api-dot-ai-chat"],
"env": {
"NODE_OPTIONS": "--inspect=9229"
},
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"skipFiles": ["<node_internals>/**"],
"sourceMaps": true,
"outFiles": [
"${workspaceFolder}/api-dot-ai-chat/dist/**/*.(m|c|)js",
"!**/node_modules/**"
]
},
{
"type": "node",
"request": "launch",
"name": "Debug api-dot-ai-chat with Nx",
"runtimeExecutable": "yarn",
"runtimeArgs": ["nx", "serve", "api-dot-ai-chat"],
"env": {
"NODE_OPTIONS": "--inspect=9230"
},
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"skipFiles": ["<node_internals>/**"],
"sourceMaps": true,
"outFiles": [
"${workspaceFolder}/apps/api-dot-ai-chat/dist/**/*.(m|c|)js",
"!**/node_modules/**"
]
Comment on lines +33 to +67
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

There are two launch configurations with the same name ("Debug api-dot-ai-chat with Nx"), and the first one’s outFiles path points to ${workspaceFolder}/api-dot-ai-chat/dist/... which doesn’t match the app location under apps/api-dot-ai-chat. This can make VS Code debugging pick the wrong config and fail to map sources.

Copilot uses AI. Check for mistakes.
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Duplicate launch configs with same name, one wrong

Low Severity

There are two identical "Debug api-dot-ai-chat with Nx" configurations in launch.json, making them indistinguishable in the VS Code debug dropdown. One of these duplicates also has an incorrect outFiles path, missing the apps/ prefix, which prevents source maps from resolving correctly. This appears to be an unintended leftover configuration.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d6f7fc7. Configure here.

]
}
18 changes: 18 additions & 0 deletions core-web/apps/api-dot-ai-chat-e2e/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": ["../../.eslintrc.base.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
18 changes: 18 additions & 0 deletions core-web/apps/api-dot-ai-chat-e2e/jest.config.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export default {
displayName: 'api-dot-ai-chat-e2e',
preset: '../../jest.preset.js',
globalSetup: '<rootDir>/src/support/global-setup.ts',
globalTeardown: '<rootDir>/src/support/global-teardown.ts',
setupFiles: ['<rootDir>/src/support/test-setup.ts'],
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': [
'ts-jest',
{
tsconfig: '<rootDir>/tsconfig.spec.json'
}
]
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/api-dot-ai-chat-e2e'
};
17 changes: 17 additions & 0 deletions core-web/apps/api-dot-ai-chat-e2e/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "api-dot-ai-chat-e2e",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"implicitDependencies": ["api-dot-ai-chat"],
"targets": {
"e2e": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{e2eProjectRoot}"],
"options": {
"jestConfig": "apps/api-dot-ai-chat-e2e/jest.config.cts",
"passWithNoTests": true
},
"dependsOn": ["api-dot-ai-chat:build", "api-dot-ai-chat:serve"]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import axios from 'axios';

describe('GET /', () => {
it('should return a message', async () => {
const res = await axios.get(`/`);

expect(res.status).toBe(200);
expect(res.data).toEqual({ message: 'Hello API' });
Comment on lines +3 to +8
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

The e2e test expects GET / to return { message: 'Hello API' }, but api-dot-ai-chat currently only defines POST /dotaichat/chat. Either add a GET / (or /health) route in the API app or update the test to hit an actual endpoint.

Suggested change
describe('GET /', () => {
it('should return a message', async () => {
const res = await axios.get(`/`);
expect(res.status).toBe(200);
expect(res.data).toEqual({ message: 'Hello API' });
describe('POST /dotaichat/chat', () => {
it('should hit an existing API endpoint', async () => {
const res = await axios.post(
`/dotaichat/chat`,
{},
{
validateStatus: () => true,
}
);
expect(res.status).not.toBe(404);
expect(res.status).not.toBe(405);

Copilot uses AI. Check for mistakes.
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

E2E spec tests route that doesn't exist

Medium Severity

The api-dot-ai-chat e2e test sends GET / expecting { message: 'Hello API' }. The server only defines POST /dotaichat/chat, so there's no GET / handler. This means the test will always fail with a 404, as its route and expected payload don't match any actual server endpoint.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d6f7fc7. Configure here.

});
16 changes: 16 additions & 0 deletions core-web/apps/api-dot-ai-chat-e2e/src/support/global-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { waitForPortOpen } from '@nx/node/utils';

/* eslint-disable */
var __TEARDOWN_MESSAGE__: string;

module.exports = async function () {
// Start services that that the app needs to run (e.g. database, docker-compose, etc.).
console.log('\nSetting up...\n');

const host = process.env.HOST ?? 'localhost';
const port = process.env.PORT ? Number(process.env.PORT) : 3000;
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

api-dot-ai-chat listens on PORT (default 3333), but the e2e harness defaults to port 3000 for waitForPortOpen. This will cause setup to hang/fail unless PORT is explicitly aligned. Update the defaults (or set PORT for the serve target) so the API and e2e agree on the same port.

Suggested change
const port = process.env.PORT ? Number(process.env.PORT) : 3000;
const port = process.env.PORT ? Number(process.env.PORT) : 3333;

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

E2E default port doesn't match server default

Medium Severity

The e2e test setup (global-setup.ts, test-setup.ts) defaults to port 3000, but the Express server (main.ts) uses 3333. This port mismatch causes waitForPortOpen to hang, leading to test timeouts when the PORT environment variable isn't explicitly configured.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d6f7fc7. Configure here.

await waitForPortOpen(port, { host });

// Hint: Use `globalThis` to pass variables to global teardown.
globalThis.__TEARDOWN_MESSAGE__ = '\nTearing down...\n';
};
10 changes: 10 additions & 0 deletions core-web/apps/api-dot-ai-chat-e2e/src/support/global-teardown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { killPort } from '@nx/node/utils';
/* eslint-disable */

module.exports = async function () {
// Put clean up logic here (e.g. stopping services, docker-compose, etc.).
// Hint: `globalThis` is shared between setup and teardown.
const port = process.env.PORT ? Number(process.env.PORT) : 3000;
await killPort(port);
console.log(globalThis.__TEARDOWN_MESSAGE__);
};
9 changes: 9 additions & 0 deletions core-web/apps/api-dot-ai-chat-e2e/src/support/test-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* eslint-disable */
import axios from 'axios';

module.exports = async function () {
// Configure axios for tests to use.
const host = process.env.HOST ?? 'localhost';
const port = process.env.PORT ?? '3000';
axios.defaults.baseURL = `http://${host}:${port}`;
};
13 changes: 13 additions & 0 deletions core-web/apps/api-dot-ai-chat-e2e/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.spec.json"
}
],
"compilerOptions": {
"esModuleInterop": true
}
}
9 changes: 9 additions & 0 deletions core-web/apps/api-dot-ai-chat-e2e/tsconfig.spec.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": ["jest.config.ts", "src/**/*.ts"]
}
18 changes: 18 additions & 0 deletions core-web/apps/api-dot-ai-chat/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": ["../../.eslintrc.base.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
10 changes: 10 additions & 0 deletions core-web/apps/api-dot-ai-chat/jest.config.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module.exports = {
displayName: 'api-dot-ai-chat',
preset: '../../jest.preset.js',
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }]
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/apps/api-dot-ai-chat'
};
60 changes: 60 additions & 0 deletions core-web/apps/api-dot-ai-chat/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"name": "api-dot-ai-chat",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/api-dot-ai-chat/src",
"projectType": "application",
"tags": [],
"targets": {
"prune-lockfile": {
"dependsOn": ["build"],
"cache": true,
"executor": "@nx/js:prune-lockfile",
"outputs": [
"{workspaceRoot}/dist/apps/api-dot-ai-chat/package.json",
"{workspaceRoot}/dist/apps/api-dot-ai-chat/yarn.lock"
],
"options": {
"buildTarget": "build"
}
},
"copy-workspace-modules": {
"dependsOn": ["build"],
"cache": true,
"outputs": ["{workspaceRoot}/dist/apps/api-dot-ai-chat/workspace_modules"],
"executor": "@nx/js:copy-workspace-modules",
"options": {
"buildTarget": "build"
}
},
"prune": {
"dependsOn": ["prune-lockfile", "copy-workspace-modules"],
"executor": "nx:noop"
},
"serve": {
"continuous": true,
"executor": "@nx/js:node",
"defaultConfiguration": "development",
"dependsOn": ["build"],
"options": {
"buildTarget": "api-dot-ai-chat:build",
"runBuildTargetDependencies": false
},
"configurations": {
"development": {
"buildTarget": "api-dot-ai-chat:build:development"
},
"production": {
"buildTarget": "api-dot-ai-chat:build:production"
}
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "apps/api-dot-ai-chat/jest.config.cts",
"passWithNoTests": true
}
}
}
}
Empty file.
46 changes: 46 additions & 0 deletions core-web/apps/api-dot-ai-chat/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { HashbrownOpenAI } from '@hashbrownai/openai';
import express from 'express';

const apiKey = process.env['OPENAI_API_KEY'];

if (!apiKey) {
throw new Error('OPENAI_API_KEY is not set');
}

const app = express();

app.use(express.json());
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.header('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS');

Comment on lines +13 to +17
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

CORS is configured to allow any origin (*) while this service proxies requests using the server-side OPENAI_API_KEY. If this app is ever run beyond a strictly local setup, it becomes an unauthenticated OpenAI proxy that can be abused. Please restrict allowed origins (at least to localhost) and/or require authentication for /dotaichat/chat.

Copilot uses AI. Check for mistakes.
if (req.method === 'OPTIONS') {
res.sendStatus(204);
return;
}

next();
});

app.post('/dotaichat/chat', async (req, res) => {
const stream = HashbrownOpenAI.stream.text({
apiKey,
request: req.body
});

res.header('Content-Type', 'application/octet-stream');

for await (const chunk of stream) {
res.write(chunk);
}

res.end();
});
Comment on lines +26 to +39
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

There is no GET endpoint (e.g., / or /health) implemented, but the repository’s e2e scaffold is testing GET /. Adding a lightweight health/root route (and keeping it aligned with tests) will make local/dev validation much clearer.

Copilot uses AI. Check for mistakes.

const port = process.env.PORT || 3333;
const server = app.listen(port, () => {
// eslint-disable-next-line no-console
console.log(`Listening at http://localhost:${port}/dotaichat`);
});
server.on('error', console.error);
11 changes: 11 additions & 0 deletions core-web/apps/api-dot-ai-chat/tsconfig.app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"moduleResolution": "node10",
"types": ["node", "express"]
},
"include": ["src/**/*.ts"],
"exclude": ["jest.config.ts", "jest.config.cts", "src/**/*.spec.ts", "src/**/*.test.ts"]
}
Loading
Loading