Skip to content

Commit

Permalink
feat: Adding support for API routes in the NextJS layer + Configurati…
Browse files Browse the repository at this point in the history
…on (#180)
  • Loading branch information
robpolak-cb committed Dec 19, 2023
1 parent 54d5bd9 commit 6f3caf3
Show file tree
Hide file tree
Showing 11 changed files with 179 additions and 4 deletions.
1 change: 1 addition & 0 deletions apps/build-onchain-apps/.env.local.default
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=TEST_1234567890
ENVIRONMENT=localhost
9 changes: 7 additions & 2 deletions apps/build-onchain-apps/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ const config = {
// collectCoverage: false,

// An array of glob patterns indicating a set of files for which coverage information should be collected
collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/onchain/**/index.ts', '!**/node_modules/**'],
collectCoverageFrom: [
'src/**/*.{ts,tsx}',
'pages/api/**/*.{ts,tsx}',
'!src/onchain/**/index.ts',
'!**/node_modules/**',
],

// The directory where Jest should output its coverage files
// coverageDirectory: '<rootDir>/coverage',
Expand Down Expand Up @@ -129,7 +134,7 @@ const config = {
// rootDir: undefined,

// A list of paths to directories that Jest should use to search for files in
roots: ['<rootDir>/src'],
roots: ['<rootDir>/src', '<rootDir>/pages/api'],

// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
Expand Down
11 changes: 11 additions & 0 deletions apps/build-onchain-apps/pages/api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Build Onchain API Routes

## Supported Chains /api/chains/supported

**Method**: GET

**Response**: JSON encoded viem chain object

```json
[{"id":5,"network":"goerli","name":"Goerli"]
```
50 changes: 50 additions & 0 deletions apps/build-onchain-apps/pages/api/chains/supported.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { getChainsForEnvironment } from '../../../src/utils/chainConfiguration';
import { getCurrentEnvironment } from '../../../src/utils/configuration';
import handler from './supported';

jest.mock('../../../src/utils/chainConfiguration', () => ({
getChainsForEnvironment: jest.fn(),
}));

jest.mock('../../../src/utils/configuration', () => ({
getCurrentEnvironment: jest.fn(),
}));

describe('/api/yourApiEndpoint handler', () => {
let req: Partial<NextApiRequest>;
let res: Partial<NextApiResponse>;
let mockStatus: jest.Mock;
let mockJson: jest.Mock;

beforeEach(() => {
mockStatus = jest.fn();
mockJson = jest.fn();
req = {};
res = {
status: mockStatus.mockReturnThis(),
json: mockJson.mockReturnThis(),
};
});

it('returns chains for current environment', async () => {
(getCurrentEnvironment as jest.Mock).mockReturnValue('development');
(getChainsForEnvironment as jest.Mock).mockReturnValue(['chain1', 'chain2']);

await handler(req as NextApiRequest, res as NextApiResponse);

expect(mockStatus).toHaveBeenCalledWith(200);
expect(mockJson).toHaveBeenCalledWith(['chain1', 'chain2']);
});

it('handles errors gracefully', async () => {
(getChainsForEnvironment as jest.Mock).mockImplementation(() => {
throw new Error('Test error');
});

await handler(req as NextApiRequest, res as NextApiResponse);

expect(mockStatus).toHaveBeenCalledWith(500);
expect(mockJson).toHaveBeenCalledWith({ error: 'Internal server error' });
});
});
18 changes: 18 additions & 0 deletions apps/build-onchain-apps/pages/api/chains/supported.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { getChainsForEnvironment } from '../../../src/utils/chainConfiguration';
import type { NextApiRequest, NextApiResponse } from 'next';

/**
* Handler for the /api/chains/supported route, this route will return all the supported
* chains for this application.
* @param req
* @param res
*/
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const chains = getChainsForEnvironment();
res.status(200).json(chains);
} catch (error) {
console.error('Error fetching chains:', error);
res.status(500).json({ error: 'Internal server error' });
}
}
8 changes: 6 additions & 2 deletions apps/build-onchain-apps/src/providers/OnchainProviders.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { ReactNode } from 'react';
import { configureChains, createConfig, WagmiConfig } from 'wagmi';
import { baseGoerli } from 'wagmi/chains';
import { publicProvider } from 'wagmi/providers/public';
import { RainbowKitProvider, connectorsForWallets } from '@rainbow-me/rainbowkit';
import {
Expand All @@ -11,6 +10,7 @@ import {
coinbaseWallet,
trustWallet,
} from '@rainbow-me/rainbowkit/wallets';
import { getChainsForEnvironment } from '../utils/chainConfiguration';

type Props = { children: ReactNode };

Expand All @@ -23,7 +23,11 @@ if (!projectId) {
}

// TODO Docs ~~~
const { chains, publicClient } = configureChains([baseGoerli], [publicProvider()]);
const supportedChains = getChainsForEnvironment();
if (!supportedChains) {
throw new Error('Must configure supported chains in utils/chainConfiguration');
}
const { chains, publicClient } = configureChains(supportedChains, [publicProvider()]);
const connectors = connectorsForWallets([
{
groupName: 'Recommended',
Expand Down
17 changes: 17 additions & 0 deletions apps/build-onchain-apps/src/utils/chainConfiguration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { baseGoerli, goerli } from 'viem/chains';
import { Environment } from './environment';
import { getChainsForEnvironment } from './chainConfiguration';

describe('getCurrentEnvironment', () => {
it('should return testnet for localhost', () => {
expect(getChainsForEnvironment(Environment.localhost)).toEqual([baseGoerli]);
});

it('should default to localhost', () => {
expect(getChainsForEnvironment()).toEqual([baseGoerli]);
});

it('should return mainnet for production', () => {
expect(getChainsForEnvironment(Environment.production)).toEqual([goerli]);
});
});
22 changes: 22 additions & 0 deletions apps/build-onchain-apps/src/utils/chainConfiguration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { baseGoerli, Chain, goerli } from 'viem/chains';
import { Environment } from './environment';
import { getCurrentEnvironment } from './configuration';

// The list of supported Chains for a given environment (e.g. Dev should only have testnet)
export const supportedChains = new Map<Environment, Chain[]>([
[Environment.localhost, [baseGoerli]],
[Environment.development, [baseGoerli]],
[Environment.staging, [goerli]],
[Environment.production, [goerli]],
]);

/**
* Gets the list of supported chains for a given environment. Defaults to the current environment.
* @param env
*/
export function getChainsForEnvironment(env?: Environment): Chain[] | undefined {
if (!env) {
env = getCurrentEnvironment();
}
return supportedChains.get(env);
}
19 changes: 19 additions & 0 deletions apps/build-onchain-apps/src/utils/configuration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { EnvironmentKeys, getCurrentEnvironment } from './configuration';
import { Environment } from './environment';

describe('getCurrentEnvironment', () => {
it('should return valid environment when mapped correctly', () => {
process.env[EnvironmentKeys.environment] = 'staging';
expect(getCurrentEnvironment()).toEqual(Environment.staging);
});

it('should return localhost when not mapped', () => {
delete process.env[EnvironmentKeys.environment];
expect(getCurrentEnvironment()).toEqual(Environment.localhost);
});

it('should return localhost when not mapped correctly', () => {
process.env[EnvironmentKeys.environment] = 'baseIsTheBestL2Chain:)';
expect(getCurrentEnvironment()).toEqual(Environment.localhost);
});
});
18 changes: 18 additions & 0 deletions apps/build-onchain-apps/src/utils/configuration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Environment } from './environment';

export enum EnvironmentKeys {
environment = 'ENVIRONMENT',
}

export function getCurrentEnvironment(): Environment {
const stage: string | undefined = process.env[EnvironmentKeys.environment];

if (stage === undefined) {
return Environment.localhost;
}

// Convert string to ReleaseStage enum value
const releaseStageValue = Object.values(Environment).find((value) => value === stage);

return releaseStageValue ?? Environment.localhost;
}
10 changes: 10 additions & 0 deletions apps/build-onchain-apps/src/utils/environment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Where is this application currently running? This will be used to drive configurations for the application
* based on the environment.
*/
export enum Environment {
localhost = 'localhost', // Local Environment
development = 'development', // Development & Testing Environment
staging = 'staging', // Staging Environment which should mimic production
production = 'production', // Production Environment
}

0 comments on commit 6f3caf3

Please sign in to comment.