diff --git a/Dockerfile b/Dockerfile index 13567b10..6e9244a4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,34 +1,20 @@ -FROM node:20-bookworm-slim AS deps -WORKDIR /app - -# TODO(deployment): This image currently installs full dependency sets and the -# runtime entrypoint uses dev tooling (ts-node + vite preview). For production, -# split build/runtime deps: keep dev deps in builder stages only, then use -# `npm ci --omit=dev` in the runtime stage and run compiled server JS. -COPY package*.json ./ -RUN npm ci - +FROM node:20-bookworm-slim AS builder WORKDIR /app/server + COPY server/package*.json ./ RUN npm ci -FROM node:20-bookworm-slim AS frontend-builder -WORKDIR /app - -COPY --from=deps /app/node_modules ./node_modules -COPY . . +COPY server/ ./ RUN npm run build +RUN npm prune --omit=dev FROM node:20-bookworm-slim AS runtime -WORKDIR /app +WORKDIR /app/server -COPY --from=deps /app/node_modules ./node_modules -COPY --from=deps /app/server/node_modules ./server/node_modules -COPY . . -COPY --from=frontend-builder /app/dist ./dist +ENV NODE_ENV=production -RUN chmod +x /app/scripts/start.sh +COPY --from=builder /app/server /app/server -EXPOSE 4000 5173 +EXPOSE 8080 -CMD ["/app/scripts/start.sh"] +CMD ["node", "index.js"] diff --git a/server/auth/routes.js b/server/auth/routes.js index 6a8c923a..d0cafaf1 100644 --- a/server/auth/routes.js +++ b/server/auth/routes.js @@ -40,7 +40,7 @@ router.get('/google', passport_1.default.authenticate('google', { // 2. Google OAuth callback // Google redirects here after the user grants permission router.get('/google/callback', passport_1.default.authenticate('google', { failureMessage: true }), (req, res) => { - res.redirect('http://localhost:5173'); + res.redirect(process.env.CLIENT_URL || 'http://localhost:5173'); }); router.get('/me', (req, res) => { if (req.isAuthenticated && req.isAuthenticated()) { diff --git a/server/config/passport.js b/server/config/passport.js index 8f60b5b3..15b398a5 100644 --- a/server/config/passport.js +++ b/server/config/passport.js @@ -30,7 +30,7 @@ passport_1.default.deserializeUser((id, done) => __awaiter(void 0, void 0, void passport_1.default.use(new passport_google_oauth20_1.Strategy({ clientID: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, - callbackURL: 'http://localhost:4000/auth/google/callback', + callbackURL: process.env.GOOGLE_CALLBACK_URL || `${process.env.SERVER_PUBLIC_URL || 'http://localhost:4000'}/auth/google/callback`, }, (accessToken, refreshToken, profile, done) => __awaiter(void 0, void 0, void 0, function* () { var _a, _b; try { diff --git a/server/controllers/authController.js b/server/controllers/authController.js index 38c929f6..ab8e8ed8 100644 --- a/server/controllers/authController.js +++ b/server/controllers/authController.js @@ -143,7 +143,8 @@ exports.login = login; * Redirect the user to your frontend app. */ const googleAuthCallback = (res) => { - return res.redirect('http://localhost:5173'); + const clientUrl = process.env.CLIENT_URL || 'http://localhost:5173'; + return res.redirect(clientUrl); }; exports.googleAuthCallback = googleAuthCallback; const checkAuth = (req, res) => __awaiter(void 0, void 0, void 0, function* () { diff --git a/server/controllers/graphController.js b/server/controllers/graphController.js index 7678eae8..ed6d313e 100644 --- a/server/controllers/graphController.js +++ b/server/controllers/graphController.js @@ -13,14 +13,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); exports.semanticSearchGraph = exports.searchGraph = exports.getNodeMetadata = exports.getUserProfile = exports.generateConversationResponse = exports.verifyAnswer = exports.generateQuestionsWithAnswers = exports.viewGraph = void 0; -const axios_1 = __importDefault(require("axios")); const openai_1 = __importDefault(require("openai")); -const dotenv_1 = __importDefault(require("dotenv")); -const path_1 = __importDefault(require("path")); const ConceptProgress_1 = __importDefault(require("../models/ConceptProgress")); const axiosConfig_1 = require("../utils/axiosConfig"); -// Load environment variables -dotenv_1.default.config({ path: path_1.default.resolve(__dirname, '../.env') }); // Initialize OpenAI with explicit API key const openai = new openai_1.default({ apiKey: process.env.OPENAI_API_KEY || '' // Provide empty string as fallback @@ -56,7 +51,9 @@ const viewGraph = (req, res) => __awaiter(void 0, void 0, void 0, function* () { if (!graph_id) { return res.status(400).json({ error: 'graph_id is required' }); } - const response = yield axios_1.default.get(`http://localhost:8000/view-graph?graph_id=${graph_id}`); + const response = yield axiosConfig_1.pythonServiceClient.get('/view-graph', { + params: { graph_id: String(graph_id) } + }); res.json(response.data); } catch (error) { @@ -312,9 +309,11 @@ const getUserProfile = (req, res) => __awaiter(void 0, void 0, void 0, function* return res.status(400).json({ error: 'graph_id is required' }); } try { - const url = `http://localhost:8000/user/${userId}/profile?graph_id=${graph_id}`; + const url = `/user/${userId}/profile`; console.log(`Fetching user profile from AI server: ${url}`); - const response = yield axios_1.default.get(url); + const response = yield axiosConfig_1.pythonServiceClient.get(url, { + params: { graph_id: String(graph_id) } + }); // Forward the data from the AI server directly to the frontend res.json(response.data); } @@ -337,8 +336,9 @@ const getNodeMetadata = (req, res) => __awaiter(void 0, void 0, void 0, function if (!conceptIdsRaw) return res.status(400).json({ error: 'concept_ids or concept_id is required' }); const wanted = String(conceptIdsRaw).split(',').map(s => s.trim()).filter(Boolean); - const url = `http://localhost:8000/view-graph?graph_id=${encodeURIComponent(String(graph_id))}`; - const response = yield axios_1.default.get(url); + const response = yield axiosConfig_1.pythonServiceClient.get('/view-graph', { + params: { graph_id: String(graph_id) } + }); const nodes = ((_b = (_a = response.data) === null || _a === void 0 ? void 0 : _a.graph) === null || _b === void 0 ? void 0 : _b.nodes) || []; const matches = nodes.filter((node) => { const props = node.properties || {}; diff --git a/server/controllers/uploadController.js b/server/controllers/uploadController.js index eb825f71..4cf3a3b3 100644 --- a/server/controllers/uploadController.js +++ b/server/controllers/uploadController.js @@ -17,6 +17,7 @@ const axios_1 = __importDefault(require("axios")); const multer_1 = __importDefault(require("multer")); const form_data_1 = __importDefault(require("form-data")); const crypto_1 = require("crypto"); +const axiosConfig_1 = require("../utils/axiosConfig"); // Configure multer for handling file uploads exports.upload = (0, multer_1.default)({ storage: multer_1.default.memoryStorage(), @@ -167,8 +168,9 @@ const processPdf = (req, res) => __awaiter(void 0, void 0, void 0, function* () sendEvent({ type: 'progress', percent: rounded, message: getProgressMessage(rounded) }); } }, 1500); - // Send to processing server, passing the abort signal so we can cancel mid-flight - const response = yield axios_1.default.post(`${PYTHON_SERVICE_URL}/process-pdf`, formData, { + // Send to processing server + const response = yield axiosConfig_1.pythonServiceClient.post('/process-pdf', formData, { + params: req.query, headers: Object.assign({}, formData.getHeaders()), maxBodyLength: Infinity, maxContentLength: Infinity, diff --git a/server/index.js b/server/index.js index a0dd896f..069eff8c 100644 --- a/server/index.js +++ b/server/index.js @@ -14,8 +14,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) { Object.defineProperty(exports, "__esModule", { value: true }); const dotenv_1 = __importDefault(require("dotenv")); const path_1 = __importDefault(require("path")); +const fs_1 = __importDefault(require("fs")); // Load environment variables before any other imports -dotenv_1.default.config({ path: path_1.default.resolve(__dirname, '.env') }); +const envCandidates = [ + path_1.default.resolve(__dirname, '.env'), + path_1.default.resolve(__dirname, '../.env'), +]; +const resolvedEnvPath = envCandidates.find((candidate) => fs_1.default.existsSync(candidate)); +if (resolvedEnvPath) { + dotenv_1.default.config({ path: resolvedEnvPath }); +} +else { + dotenv_1.default.config(); +} const mongoose_1 = __importDefault(require("mongoose")); const express_1 = __importDefault(require("express")); const passport_1 = __importDefault(require("passport")); @@ -27,10 +38,17 @@ const chatRoutes_1 = __importDefault(require("./routes/chatRoutes")); const progressRoutes_1 = __importDefault(require("./routes/progressRoutes")); const quizHistoryRoutes_1 = __importDefault(require("./routes/quizHistoryRoutes")); const express_session_1 = __importDefault(require("express-session")); +const connect_mongo_1 = __importDefault(require("connect-mongo")); const axios_1 = __importDefault(require("axios")); const User_1 = __importDefault(require("./models/User")); require("./config/passport"); const app = (0, express_1.default)(); +const rawPort = process.env.PORT; +const parsedPort = rawPort ? parseInt(rawPort, 10) : 4000; +const port = Number.isNaN(parsedPort) ? 4000 : parsedPort; +const corsOriginConfig = process.env.CORS_ORIGINS || process.env.CLIENT_URL || 'http://localhost:5173'; +const corsOrigins = corsOriginConfig.split(',').map((origin) => origin.trim()).filter(Boolean); +const isProduction = process.env.NODE_ENV === 'production' || Boolean(process.env.K_SERVICE); // Connect to Mongo const connectDB = () => __awaiter(void 0, void 0, void 0, function* () { try { @@ -45,17 +63,25 @@ const connectDB = () => __awaiter(void 0, void 0, void 0, function* () { connectDB(); // CORS app.use((0, cors_1.default)({ - origin: 'http://localhost:5173', + origin: corsOrigins.length <= 1 ? corsOrigins[0] : corsOrigins, credentials: true, })); app.use(express_1.default.json()); // Session middleware (before passport middleware) +app.set('trust proxy', 1); app.use((0, express_session_1.default)({ secret: process.env.SESSION_SECRET || 'your-secret-key', + proxy: isProduction, resave: false, saveUninitialized: false, + store: connect_mongo_1.default.create({ + mongoUrl: process.env.MONGO_URI, + collectionName: 'sessions', + ttl: 14 * 24 * 60 * 60, + }), cookie: { - secure: process.env.NODE_ENV === 'production', // Only use secure in production + secure: isProduction, + sameSite: isProduction ? 'none' : 'lax', httpOnly: true, maxAge: 24 * 60 * 60 * 1000 // 24 hours } @@ -105,6 +131,6 @@ app.get('/flask/hi', (req, res) => __awaiter(void 0, void 0, void 0, function* ( } })); // Start server -app.listen(4000, () => { - console.log('Server running on http://localhost:4000'); +app.listen(port, () => { + console.log(`Server running on http://localhost:${port}`); }); diff --git a/server/index.ts b/server/index.ts index 0539de6f..1414d37c 100644 --- a/server/index.ts +++ b/server/index.ts @@ -25,6 +25,7 @@ import chatRoutes from './routes/chatRoutes'; import progressRoutes from './routes/progressRoutes'; import quizHistoryRoutes from './routes/quizHistoryRoutes'; import session from 'express-session'; +import MongoStore from 'connect-mongo'; import axios from 'axios'; import User from './models/User'; @@ -36,6 +37,7 @@ const parsedPort = rawPort ? parseInt(rawPort, 10) : 4000; const port = Number.isNaN(parsedPort) ? 4000 : parsedPort; const corsOriginConfig = process.env.CORS_ORIGINS || process.env.CLIENT_URL || 'http://localhost:5173'; const corsOrigins = corsOriginConfig.split(',').map((origin) => origin.trim()).filter(Boolean); +const isProduction = process.env.NODE_ENV === 'production' || Boolean(process.env.K_SERVICE); // Connect to Mongo const connectDB = async () => { @@ -58,12 +60,20 @@ app.use(cors({ app.use(express.json()); // Session middleware (before passport middleware) +app.set('trust proxy', 1); app.use(session({ secret: process.env.SESSION_SECRET || 'your-secret-key', + proxy: isProduction, resave: false, saveUninitialized: false, + store: MongoStore.create({ + mongoUrl: process.env.MONGO_URI!, + collectionName: 'sessions', + ttl: 14 * 24 * 60 * 60, + }), cookie: { - secure: process.env.NODE_ENV === 'production', // Only use secure in production + secure: isProduction, + sameSite: isProduction ? 'none' : 'lax', httpOnly: true, maxAge: 24 * 60 * 60 * 1000 // 24 hours } diff --git a/server/package-lock.json b/server/package-lock.json index 2c56e13c..c622ca50 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -13,6 +13,7 @@ "@types/uuid": "^10.0.0", "axios": "^1.8.4", "bcrypt": "^5.1.1", + "connect-mongo": "^6.0.0", "cors": "^2.8.5", "dotenv": "^16.4.7", "express": "^4.21.2", @@ -28,9 +29,12 @@ "uuid": "^11.1.0" }, "devDependencies": { + "@types/bcrypt": "^6.0.0", + "@types/cors": "^2.8.19", "@types/dotenv": "^6.1.1", "@types/express": "^4.17.21", "@types/express-session": "^1.18.1", + "@types/jest": "^30.0.0", "@types/mongoose": "^5.11.96", "@types/passport-google-oauth20": "^2.0.16", "concurrently": "^9.1.2", @@ -39,6 +43,31 @@ "typescript": "^5.9.3" } }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -52,6 +81,85 @@ "node": ">=12" } }, + "node_modules/@jest/diff-sequences": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz", + "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.3.0.tgz", + "integrity": "sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -109,6 +217,13 @@ "sparse-bitfield": "^3.0.3" } }, + "node_modules/@sinclair/typebox": { + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", + "dev": true, + "license": "MIT" + }, "node_modules/@tsconfig/node10": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", @@ -137,6 +252,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/bcrypt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/body-parser": { "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", @@ -156,6 +281,16 @@ "@types/node": "*" } }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/dotenv": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/@types/dotenv/-/dotenv-6.1.1.tgz", @@ -206,6 +341,44 @@ "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", "license": "MIT" }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -336,6 +509,13 @@ "@types/node": "*" } }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", @@ -371,6 +551,23 @@ "@types/webidl-conversions": "*" } }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -567,6 +764,18 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -626,6 +835,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "license": "MIT" + }, "node_modules/body-parser": { "version": "1.20.4", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", @@ -801,6 +1016,22 @@ "node": ">=10" } }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -903,6 +1134,46 @@ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, + "node_modules/connect-mongo": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-6.0.0.tgz", + "integrity": "sha512-mHxfnTiWk7ZtxmHdcrFBKlr7fCtgGoFpx/oe9jFW0yb2NinagsxEeuol78nUWMpnWyYK0nnuXMlU9wrgUjTE6g==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "kruptein": "3.0.8" + }, + "engines": { + "node": ">=20.8.0" + }, + "peerDependencies": { + "express-session": "^1.17.1", + "mongodb": ">=5.0.0" + } + }, + "node_modules/connect-mongo/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/connect-mongo/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -1155,6 +1426,16 @@ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -1173,6 +1454,24 @@ "node": ">=6" } }, + "node_modules/expect": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.3.0", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/express": { "version": "4.22.1", "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", @@ -1514,6 +1813,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -1749,6 +2055,135 @@ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "license": "MIT" }, + "node_modules/jest-diff": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", + "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.3.0", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz", + "integrity": "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.3.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", + "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.3.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3", + "pretty-format": "30.3.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/jest-mock": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", + "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/kareem": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", @@ -1758,6 +2193,18 @@ "node": ">=12.0.0" } }, + "node_modules/kruptein": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/kruptein/-/kruptein-3.0.8.tgz", + "integrity": "sha512-0CyalFA0Cjp3jnziMp0u1uLZW2/ouhQ0mEMfYlroBXNe86na1RwAuwBcdRAegeWZNMfQy/G5fN47g/Axjtqrfw==", + "license": "MIT", + "dependencies": { + "asn1.js": "^5.4.1" + }, + "engines": { + "node": ">8" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -1864,6 +2311,12 @@ "node": ">= 0.6" } }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2451,6 +2904,13 @@ "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -2464,6 +2924,34 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pretty-format": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -2555,6 +3043,13 @@ "node": ">= 0.8" } }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -2838,6 +3333,16 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -2868,6 +3373,19 @@ "memory-pager": "^1.0.2" } }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", diff --git a/server/package.json b/server/package.json index c34238dd..b19b567a 100644 --- a/server/package.json +++ b/server/package.json @@ -17,6 +17,7 @@ "@types/uuid": "^10.0.0", "axios": "^1.8.4", "bcrypt": "^5.1.1", + "connect-mongo": "^6.0.0", "cors": "^2.8.5", "dotenv": "^16.4.7", "express": "^4.21.2", @@ -32,9 +33,12 @@ "uuid": "^11.1.0" }, "devDependencies": { + "@types/bcrypt": "^6.0.0", + "@types/cors": "^2.8.19", "@types/dotenv": "^6.1.1", "@types/express": "^4.17.21", "@types/express-session": "^1.18.1", + "@types/jest": "^30.0.0", "@types/mongoose": "^5.11.96", "@types/passport-google-oauth20": "^2.0.16", "concurrently": "^9.1.2", diff --git a/server/utils/axiosConfig.js b/server/utils/axiosConfig.js index 07356a63..2c1244d7 100644 --- a/server/utils/axiosConfig.js +++ b/server/utils/axiosConfig.js @@ -7,7 +7,7 @@ exports.pythonServiceClient = void 0; const axios_1 = __importDefault(require("axios")); // Configure axios instance for Python service with proper timeouts // Using 127.0.0.1 instead of localhost to avoid IPv6 issues -const PYTHON_SERVICE_URL = process.env.PYTHON_SERVICE_URL || 'http://127.0.0.1:8000'; +const PYTHON_SERVICE_URL = process.env.PYTHON_SERVICE_URL || 'https://smartpath-backend-361386464842.us-east1.run.app'; exports.pythonServiceClient = axios_1.default.create({ baseURL: PYTHON_SERVICE_URL, timeout: 120000, // 120 seconds (2 minutes) timeout for AI operations diff --git a/src/components/GraphVisualization.tsx b/src/components/GraphVisualization.tsx index ef8b8fe1..ff3c49e6 100644 --- a/src/components/GraphVisualization.tsx +++ b/src/components/GraphVisualization.tsx @@ -14,6 +14,7 @@ import ReactFlow, { } from 'reactflow'; import 'reactflow/dist/style.css'; import { Search, X } from 'lucide-react'; +import { API_BASE_URL } from '../config/api'; interface GraphData { status: string; @@ -723,7 +724,7 @@ const GraphVisualization: React.FC = ({ data, conceptPr setIsSearching(true); try { const endpoint = searchMode === 'semantic' ? 'semantic-search-graph' : 'search-graph'; - const url = `http://localhost:4000/api/${endpoint}?graph_id=${graphId}&query=${encodeURIComponent(query)}`; + const url = `${API_BASE_URL}/api/${endpoint}?graph_id=${graphId}&query=${encodeURIComponent(query)}`; const response = await fetch(url, { credentials: 'include' }); const result = await response.json(); if (result.status === 'success') { diff --git a/src/config/api.ts b/src/config/api.ts new file mode 100644 index 00000000..18dc4345 --- /dev/null +++ b/src/config/api.ts @@ -0,0 +1,2 @@ +export const API_BASE_URL = + import.meta.env.VITE_API_BASE_URL || 'https://smartpath-node-backend-361386464842.us-east1.run.app'; diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx index 2b6c0e5f..a5c5624d 100644 --- a/src/context/AuthContext.tsx +++ b/src/context/AuthContext.tsx @@ -1,4 +1,5 @@ import React, { createContext, useContext, useState, useEffect } from 'react'; +import { API_BASE_URL } from '../config/api'; interface AuthContextType { isAuthenticated: boolean; @@ -25,7 +26,7 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => { setIsLoading(true); try { - const response = await fetch('http://localhost:4000/auth/check-auth', { + const response = await fetch(`${API_BASE_URL}/auth/check-auth`, { credentials: 'include', }); const data = await response.json(); @@ -53,7 +54,7 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => { const signup = async (email: string, password: string, name: string) => { try { - const response = await fetch('http://localhost:4000/auth/signup', { + const response = await fetch(`${API_BASE_URL}/auth/signup`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password, name }), @@ -83,7 +84,7 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => { const login = async (email: string, password: string) => { try { - const response = await fetch('http://localhost:4000/auth/login', { + const response = await fetch(`${API_BASE_URL}/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }), @@ -107,12 +108,12 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => { }; const loginWithGoogle = () => { - window.location.href = 'http://localhost:4000/auth/google'; + window.location.href = `${API_BASE_URL}/auth/google`; }; const logout = async () => { try { - const response = await fetch('http://localhost:4000/auth/logout', { + const response = await fetch(`${API_BASE_URL}/auth/logout`, { method: 'POST', credentials: 'include', }); diff --git a/src/context/ProgressContext.tsx b/src/context/ProgressContext.tsx index efa7c498..dbfa999b 100644 --- a/src/context/ProgressContext.tsx +++ b/src/context/ProgressContext.tsx @@ -1,6 +1,7 @@ import React, { createContext, useContext, useState, useEffect, useCallback } from 'react'; import axios from 'axios'; import { useAuth } from './AuthContext'; +import { API_BASE_URL } from '../config/api'; interface IConceptProgress { conceptId: string; @@ -32,7 +33,7 @@ export const ProgressProvider: React.FC<{ children: React.ReactNode }> = ({ chil if (isAuthenticated) { try { setLoading(true); - const response = await axios.get('http://localhost:4000/api/concept-progress', { withCredentials: true }); + const response = await axios.get(`${API_BASE_URL}/api/concept-progress`, { withCredentials: true }); const progressData = response.data; setProgress(progressData); @@ -41,18 +42,18 @@ export const ProgressProvider: React.FC<{ children: React.ReactNode }> = ({ chil console.log('[Progress] ⚠️ No progress records found, checking for quiz history to process...'); try { // First check if there's any quiz history - const quizHistoryResponse = await axios.get('http://localhost:4000/api/quiz-history', { withCredentials: true }); + const quizHistoryResponse = await axios.get(`${API_BASE_URL}/api/quiz-history`, { withCredentials: true }); const quizHistories = quizHistoryResponse.data?.quizHistories || []; console.log(`[Progress] Found ${quizHistories.length} quiz history records`); if (quizHistories.length > 0) { console.log('[Progress] 🔄 Processing quiz history to create progress records...'); - const processResponse = await axios.post('http://localhost:4000/api/quiz-history/process-all', {}, { withCredentials: true }); + const processResponse = await axios.post(`${API_BASE_URL}/api/quiz-history/process-all`, {}, { withCredentials: true }); console.log('[Progress] ✅ Processed quiz history:', processResponse.data); // Refetch progress after processing console.log('[Progress] 🔄 Refetching progress after processing...'); - const newResponse = await axios.get('http://localhost:4000/api/concept-progress', { withCredentials: true }); + const newResponse = await axios.get(`${API_BASE_URL}/api/concept-progress`, { withCredentials: true }); const newProgressData = newResponse.data; console.log(`[Progress] ✅ Now have ${newProgressData.length} progress records`); setProgress(newProgressData); @@ -82,7 +83,7 @@ export const ProgressProvider: React.FC<{ children: React.ReactNode }> = ({ chil const updateProgress = async (conceptId: string, isCorrect: boolean, isRetry: boolean) => { try { - await axios.post('http://localhost:4000/api/progress/update', { + await axios.post(`${API_BASE_URL}/api/progress/update`, { conceptId, isCorrect, isRetry, diff --git a/src/main.tsx b/src/main.tsx index 7399ef24..c48e3462 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,9 +1,12 @@ import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; +import axios from 'axios'; import App from './App.tsx'; import './index.css'; +axios.defaults.withCredentials = true; + createRoot(document.getElementById('root')!).render( diff --git a/src/pages/Chat.tsx b/src/pages/Chat.tsx index ee9d2793..7a3e3630 100644 --- a/src/pages/Chat.tsx +++ b/src/pages/Chat.tsx @@ -9,6 +9,7 @@ import { import { useNavigate } from 'react-router-dom'; import axios from 'axios'; import GraphVisualization from '../components/GraphVisualization'; +import { API_BASE_URL } from '../config/api'; interface Message { id: string; @@ -56,8 +57,6 @@ interface ConceptProgress { lastAttempted?: Date; } -const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:4000'; - function App() { const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false); const [currentChatId, setCurrentChatId] = useState(''); diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index aeff86e2..a3310452 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import { Brain, ArrowLeft } from 'lucide-react'; import { Link, useNavigate } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; +import { API_BASE_URL } from '../config/api'; export default function Login() { const [email, setEmail] = useState(''); @@ -11,7 +12,7 @@ export default function Login() { const [loginError, setLoginError] = useState(''); const navigate = useNavigate(); - const { login, loginWithGoogle } = useAuth(); + const { login } = useAuth(); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -29,7 +30,7 @@ export default function Login() { // Simplified Google login - direct redirect const handleGoogleLogin = () => { - window.location.href = 'http://localhost:4000/auth/google'; + window.location.href = `${API_BASE_URL}/auth/google`; }; return ( diff --git a/src/pages/ProgressPage.tsx b/src/pages/ProgressPage.tsx index c2bc7f7a..b57bddae 100644 --- a/src/pages/ProgressPage.tsx +++ b/src/pages/ProgressPage.tsx @@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'; import axios from 'axios'; import { useNavigate } from 'react-router-dom'; import { BookOpen, TrendingDown, ChevronLeft } from 'lucide-react'; +import { API_BASE_URL } from '../config/api'; interface ProgressItem { concept_id: string; @@ -30,7 +31,7 @@ const ProgressPage: React.FC = () => { let progressList: any[] = []; try { console.log('[ProgressPage] Fetching progress from /api/concept-progress endpoint...'); - const progressRes = await axios.get('http://localhost:4000/api/concept-progress', { withCredentials: true }); + const progressRes = await axios.get(`${API_BASE_URL}/api/concept-progress`, { withCredentials: true }); progressList = Array.isArray(progressRes.data) ? progressRes.data : []; console.log(`[ProgressPage] Received ${progressList.length} progress records from backend`); if (progressList.length > 0) { @@ -51,7 +52,7 @@ const ProgressPage: React.FC = () => { // Fetch all graph IDs for the user let graphIds: string[] = []; try { - const graphIdsRes = await axios.get('http://localhost:4000/chat/graph-ids', { withCredentials: true }); + const graphIdsRes = await axios.get(`${API_BASE_URL}/chat/graph-ids`, { withCredentials: true }); graphIds = graphIdsRes.data?.graphIds || []; console.log('ProgressPage: Successfully fetched graph IDs:', graphIds); } catch (graphIdsError: any) { @@ -108,7 +109,7 @@ const ProgressPage: React.FC = () => { // Fetch all graphs in parallel const graphPromises = graphIds.map(id => - axios.get('http://localhost:4000/api/view-graph', { + axios.get(`${API_BASE_URL}/api/view-graph`, { params: { graph_id: id }, withCredentials: true }).catch(err => { @@ -269,7 +270,7 @@ const ProgressPage: React.FC = () => { // Fetch metadata from all graphs const metaPromises = graphIds.map(id => - axios.get('http://localhost:4000/api/node-metadata', { + axios.get(`${API_BASE_URL}/api/node-metadata`, { params: { graph_id: id, concept_ids: idsParam }, withCredentials: true, }).catch(err => { @@ -318,7 +319,7 @@ const ProgressPage: React.FC = () => { if (stillUnknown.length > 0) { try { console.log(`[ProgressPage] Attempting to find topic names from quiz history for ${stillUnknown.length} concepts...`); - const quizHistoryRes = await axios.get('http://localhost:4000/api/quiz-history', { withCredentials: true }); + const quizHistoryRes = await axios.get(`${API_BASE_URL}/api/quiz-history`, { withCredentials: true }); const quizHistories = Array.isArray(quizHistoryRes.data?.quizHistories) ? quizHistoryRes.data.quizHistories : []; diff --git a/src/pages/Signup.tsx b/src/pages/Signup.tsx index 748d5e0a..d56bdde5 100644 --- a/src/pages/Signup.tsx +++ b/src/pages/Signup.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import { Brain, ArrowLeft, Check, X } from 'lucide-react'; import { Link, useNavigate } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; +import { API_BASE_URL } from '../config/api'; const validatePassword = (password: string): { isValid: boolean; error: string } => { @@ -88,7 +89,7 @@ export default function Signup() { }; const handleGoogleSignup = () => { - window.location.href = 'http://localhost:4000/auth/google'; + window.location.href = `${API_BASE_URL}/auth/google`; }; // Add password check on input change