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
111 changes: 0 additions & 111 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,19 @@ If your PR aligns with our development direction and passes checks we’ll merge

---

## GenAI Implementation Guide

The API now includes a public guide endpoint for planning a LangChain-based GenAI feature.

- `GET /api/v1/gen-ai-implementation` returns the full guide for single-agent, multi-agent, and LangGraph approaches.
- `GET /api/v1/gen-ai-implementation?mode=single` returns only the single-agent blueprint.
- `GET /api/v1/gen-ai-implementation?mode=multi` returns the multi-agent blueprint.
- `GET /api/v1/gen-ai-implementation?mode=langgraph` returns the LangGraph blueprint.

The response is structured so a UI can render hero copy, theme data, comparison cards, and implementation steps.

---

## Found a Bug or Issue?

If you encounter any problems or have suggestions:
Expand Down
115 changes: 115 additions & 0 deletions src/__tests__/gen-ai-implementation.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import request from 'supertest';
import app from '../app.js';

describe('GenAI Implementation API Routes', () => {
describe('GET /api/v1/gen-ai-implementation', () => {
it('should return the full implementation guide by default', async () => {
const response = await request(app)
.get('/api/v1/gen-ai-implementation')
.expect(200);

expect(response.body.success).toBe(true);
expect(response.body.message).toBe(
'GenAI implementation guide retrieved successfully'
);
expect(response.body.data).toHaveProperty('feature');
expect(response.body.data).toHaveProperty('uiTheme');
expect(response.body.data).toHaveProperty('overview');
expect(response.body.data).toHaveProperty('implementationModes');
expect(response.body.data.implementationModes).toHaveProperty('single');
expect(response.body.data.implementationModes).toHaveProperty('multi');
expect(response.body.data.implementationModes).toHaveProperty(
'langgraph'
);
});

it('should return a single-agent guide when requested', async () => {
const response = await request(app)
.get('/api/v1/gen-ai-implementation?mode=single')
.expect(200);

expect(response.body.success).toBe(true);
expect(response.body.data.implementation.slug).toBe('single');
expect(response.body.data.implementation.title).toBe(
'Single-Agent LangChain'
);
});

it('should return a LangGraph guide when requested', async () => {
const response = await request(app)
.get('/api/v1/gen-ai-implementation?mode=langgraph')
.expect(200);

expect(response.body.success).toBe(true);
expect(response.body.data.implementation.slug).toBe('langgraph');
expect(response.body.data.implementation.codeSnippet).toContain(
'StateGraph'
);
});

it('should return 400 for invalid mode values', async () => {
const response = await request(app)
.get('/api/v1/gen-ai-implementation?mode=unknown')
.expect(400);

expect(response.body.success).toBe(false);
expect(response.body.message).toBe(
'Invalid mode. Use single, multi, langgraph, or all.'
);
});
});

describe('POST /api/v1/gen-ai-implementation/*/ask', () => {
it('should return single-agent demo output', async () => {
const response = await request(app)
.post('/api/v1/gen-ai-implementation/single/ask')
.send({ question: 'How to start with LangChain?' })
.expect(200);

expect(response.body.success).toBe(true);
expect(response.body.message).toBe(
'Single-agent demo response generated successfully'
);
expect(response.body.data.mode).toBe('single');
expect(response.body.data).toHaveProperty('response');
});

it('should return multi-agent demo output', async () => {
const response = await request(app)
.post('/api/v1/gen-ai-implementation/multi/ask')
.send({ question: 'How to design multi-agent pipeline?' })
.expect(200);

expect(response.body.success).toBe(true);
expect(response.body.message).toBe(
'Multi-agent demo response generated successfully'
);
expect(response.body.data.mode).toBe('multi');
expect(response.body.data).toHaveProperty('researchNotes');
});

it('should return langgraph demo output', async () => {
const response = await request(app)
.post('/api/v1/gen-ai-implementation/langgraph/ask')
.send({ question: 'How to add retries in workflow?' })
.expect(200);

expect(response.body.success).toBe(true);
expect(response.body.message).toBe(
'LangGraph demo response generated successfully'
);
expect(response.body.data.mode).toBe('langgraph');
expect(response.body.data).toHaveProperty('graphTrace');
});

it('should return 400 when question is missing', async () => {
const response = await request(app)
.post('/api/v1/gen-ai-implementation/single/ask')
.send({ question: '' })
.expect(400);

expect(response.body.success).toBe(false);
expect(response.body.message).toBe('Question is required.');
});
});
});
2 changes: 2 additions & 0 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ import userRoutes from './routes/user.routes.js';
import productRoutes from './routes/product.routes.js';
import cartRoutes from './routes/cart.routes.js';
import paymentRoutes from './routes/payment.routes.js';
import genAiImplementationRoutes from './routes/gen-ai-implementation.routes.js';

app.use('/api/v1/auth', authRoutes);
app.use('/api/v1/users', userRoutes);
app.use('/api/v1/store/products', productRoutes);
app.use('/api/v1/store/cart', cartRoutes);
app.use('/api/v1/payments', paymentRoutes);
app.use('/api/v1/gen-ai-implementation', genAiImplementationRoutes);

// // Simple route for checking server status
app.get('/', (req, res) => {
Expand Down
6 changes: 3 additions & 3 deletions src/config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ const _config = {
};

let finalConfig;
if (process.env.NODE_ENV === 'testing') {
finalConfig = _config; // Don't freeze in testing mode
} else {
if (process.env.NODE_ENV === 'production') {
finalConfig = Object.freeze(_config);
} else {
finalConfig = _config; // Keep config mutable outside production for tests and local overrides
}

export default finalConfig;
27 changes: 20 additions & 7 deletions src/config/email.config.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
import nodemailer from 'nodemailer';
import { google } from 'googleapis';
import config from './config.js';
import AppError from '../utils/appError.js';

const OAuth2 = google.auth.OAuth2;

const oAuth2Client = new OAuth2(
config.GOOGLE_CLIENT_ID,
config.GOOGLE_CLIENT_SECRET,
'https://developers.google.com/oauthplayground'
);
export const createTransporter = async () => {
if (
!config.GOOGLE_CLIENT_ID ||
!config.GOOGLE_CLIENT_SECRET ||
!config.GOOGLE_REFRESH_TOKEN ||
!config.GMAIL_USER
) {
throw new AppError(
'Email service is not configured. Set GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REFRESH_TOKEN, and GMAIL_USER.',
503
);
}

oAuth2Client.setCredentials({ refresh_token: config.GOOGLE_REFRESH_TOKEN });
const oAuth2Client = new OAuth2(
config.GOOGLE_CLIENT_ID,
config.GOOGLE_CLIENT_SECRET,
'https://developers.google.com/oauthplayground'
);

oAuth2Client.setCredentials({ refresh_token: config.GOOGLE_REFRESH_TOKEN });

export const createTransporter = async () => {
const { token } = await oAuth2Client.getAccessToken();

return nodemailer.createTransport({
Expand Down
Loading