Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🔄 synced file(s) with circlefin/pw-sdk-nodejs-server-internal #3

Merged
merged 5 commits into from
Apr 26, 2024
Merged
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
2 changes: 2 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@

© Circle Internet Financial, LTD 2024. All rights reserved.

SPDX-License-Identifier: Apache-2.0

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ yarn dev
## Additional Resources

- Sample App
- Frontend Repo: <TODO: link to public fe repo>
- Live Demo: <TODO: link to hosted sample app>
- [Frontend Repo](https://github.com/circlefin/w3s-sample-user-controlled-client-web)
- [Live Demo](http://sample-app.circle.com/pw-user-controlled/foundational)
- Walkthrough Video: <TODO: link to video>

- [Circle Web3 Services Node.js SDK](https://developers.circle.com/w3s/docs/nodejs-sdk) supports User-Controlled Wallets, Developer-Controlled Wallets and Smart Contract Platform. See [Programmable Wallets](https://developers.circle.com/w3s/docs/circle-programmable-wallets-an-overview) and [Smart Contract Platform](https://developers.circle.com/w3s/docs/smart-contract-platform) to learn about these features and concepts.
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"scripts": {
"env:config": "cp .env.sample .env",
"test": "jest",
"dev": "nodemon src/index.ts",
"dev": "tsx watch src/index.ts",
"start": "npx ts-node src/index.ts",
"prettier-format": "prettier --config .prettierrc 'src/**/*.ts' --write",
"lint": "eslint . --ext .ts",
Expand Down Expand Up @@ -40,9 +40,9 @@
"eslint-plugin-prettier": "^5.1.3",
"husky": "^9.0.11",
"jest": "^29.7.0",
"nodemon": "^3.1.0",
"prettier": "^3.2.5",
"ts-jest": "^29.1.2"
"ts-jest": "^29.1.2",
"tsx": "^4.7.2"
},
"packageManager": "yarn@1.22.19"
}
}
35 changes: 20 additions & 15 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import cors from 'cors';
import express, { Express, Request, Response } from 'express';
import { Express, Request, Response } from 'express';
import express from 'express';
import { signUp, signIn } from './controllers';
import {
validate,
Expand All @@ -16,20 +17,20 @@ import {
authTransRouter
} from './routers';

const app: Express = express();
const port = process.env.PORT ?? 8080;
export const app: Express = express();
const parentRouter = express.Router();

app.use(cors());
app.use(express.json());

app.get('/', (_req: Request, res: Response) => {
parentRouter.get('/', (_req: Request, res: Response) => {
res.send('Sample Server');
});

/**
* Health check endpoint.
*/
app.get('/ping', (_req: Request, res: Response) => {
parentRouter.get('/ping', (_req: Request, res: Response) => {
res.status(200).send('pong');
});

Expand All @@ -46,7 +47,7 @@ app.get('/ping', (_req: Request, res: Response) => {
* encryptionKey: string - encryption key to use to execute challengeIds
* challengeId: string - used to initiate a challenge flow to setup PIN + Wallet
*/
app.post('/signup', validate(authenticationSchema), signUp);
parentRouter.post('/signup', validate(authenticationSchema), signUp);

/**
* POST - /signIn
Expand All @@ -66,16 +67,20 @@ app.post('/signup', validate(authenticationSchema), signUp);
* If user credentials wrong or don't exist:
* returns 404
*/
app.post('/signin', validate(authenticationSchema), signIn);
parentRouter.post('/signin', validate(authenticationSchema), signIn);

app.use('/users', usersRouter, authUserRouter);
app.use('/tokens', tokensRouter);
app.use('/wallets', authMiddleware, walletsRouter);
app.use('/transactions', transactionsRouter, authTransRouter);
/*
* Add all sub paths to the parent router
*/
parentRouter.use('/users', usersRouter, authUserRouter);
parentRouter.use('/tokens', tokensRouter);
parentRouter.use('/wallets', authMiddleware, walletsRouter);
parentRouter.use('/transactions', transactionsRouter, authTransRouter);

/*
* Add the parent router with ALL paths to the main app
*/
app.use('/pw-user-controlled/foundational', parentRouter);

// Error handling
app.use(errorHandler);

export const server = app.listen(port, () => {
console.log(`[server]: Server is running at http://localhost:${port}`);
});
10 changes: 9 additions & 1 deletion src/controllers/onboarding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { circleUserSdk, userDAO } from '../services';
import { Request, Response, NextFunction } from 'express';
import { User } from '../middleware';
import { hash, compare } from 'bcrypt';
import { CreateUserWithPinChallenge200Response } from '@circle-fin/user-controlled-wallets/dist/types/clients/user-controlled-wallets';
import { TrimDataResponse } from '@circle-fin/user-controlled-wallets/dist/types/clients/core';
import { logger } from '../services/logging/logger';

export const signUpCallback = (req: Request, res: Response) =>
async function (err: Error | null, rows: User[]) {
Expand Down Expand Up @@ -31,6 +34,9 @@ export const signUpCallback = (req: Request, res: Response) =>
req.body.email,
await hash(req.body.password, 10)
);
logger.info(
`New user inserted into DB, userId: ${newUserId}, email: ${req.body.email}`
);
res.status(200).send({
userId: newUserId,
userToken: tokenResponse.data?.userToken,
Expand Down Expand Up @@ -70,7 +76,9 @@ export const signInCallback = (req: Request, res: Response) =>
const userResponse = await circleUserSdk.getUser({
userId: user.userId
});
let challengeResponse = undefined;
let challengeResponse:
| TrimDataResponse<CreateUserWithPinChallenge200Response>
| undefined = undefined;
if (
userResponse.data?.user?.pinStatus !== 'ENABLED' ||
userResponse.data?.user?.securityQuestionStatus !== 'ENABLED'
Expand Down
5 changes: 5 additions & 0 deletions src/controllers/tests/onboarding.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { signUpCallback, signInCallback } from '../onboarding';
import { getMockReq, getMockRes } from '@jest-mock/express';
import { hashSync } from 'bcrypt';
import {
registerLogger,
SampleServerLogger
} from '../../services/logging/logger';

jest.mock('../../services', () => ({
circleUserSdk: {
Expand Down Expand Up @@ -43,6 +47,7 @@ jest.mock('../../services', () => ({
}
}));

registerLogger(new SampleServerLogger());
const hashedPassword = hashSync('123', 10);

const user = {
Expand Down
15 changes: 13 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import { server } from './app';
import { app } from './app';
import { initDB, cleanupDB } from './services/db/sqlite/sqliteDB';
import {
logger,
registerLogger,
SampleServerLogger
} from './services/logging/logger';

registerLogger(new SampleServerLogger());
initDB();

const port = process.env.PORT ?? 8080;
const server = app.listen(port, () => {
logger.info(`Server is running at http://localhost:${port}`);
});

process.on('SIGINT', function () {
cleanupDB();
server.close();
console.log('http server closed successfully.');
logger.info('Server closed successfully');
});
14 changes: 9 additions & 5 deletions src/services/db/sqlite/sqliteDB.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
import { Database } from 'sqlite3';
import { registerUserDAO } from '../dao';
import { SqliteUserDAO } from './sqliteUserDAO';
import { logger } from '../../logging/logger';

const client = new Database(process.env.DATABASE_FILENAME ?? ':memory:');
const userDAO = new SqliteUserDAO(client);

export const createUserTable = (db: Database) => {
db.exec(
'CREATE TABLE IF NOT EXISTS users (userId TEXT PRIMARY KEY, email TEXT UNIQUE, password TEXT, createdAt TEXT DEFAULT CURRENT_TIMESTAMP)'
);
db.serialize(() => {
db.exec(
'CREATE TABLE IF NOT EXISTS users (userId TEXT PRIMARY KEY, email TEXT UNIQUE, password TEXT, createdAt TEXT DEFAULT CURRENT_TIMESTAMP)'
);
});
};

export const initDB = () => {
registerUserDAO(userDAO);
createUserTable(client);
logger.info('Created users table');
};

export const cleanupDB = () => {
client.close((err) => {
if (err) {
return console.error(err.message);
return logger.error(err.message);
}
console.log('Close the database connection.');
logger.info('Database connection closed successfully');
});
};
3 changes: 2 additions & 1 deletion src/services/db/sqlite/tests/sqliteUserDAOIT.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ describe('Sqlite User DAO', () => {
let client: Database;
let userDAO: UserDAO;

beforeAll(() => {
beforeAll((done) => {
client = new Database(':memory:');
userDAO = new SqliteUserDAO(client);
createUserTable(client);
done();
});

afterAll(() => {
Expand Down
27 changes: 27 additions & 0 deletions src/services/logging/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export interface Logger {
info: (message: string) => void;
warn: (message: string) => void;
error: (message: string) => void;
debug: (message: string) => void;
}

export class SampleServerLogger implements Logger {
info(message: string) {
console.log('[SampleServerLogger]: ' + message);
}
warn(message: string) {
console.log('[SampleServerLogger]: ' + message);
}
error(message: string) {
console.log('[SampleServerLogger]: ' + message);
}
debug(message: string) {
console.log('[SampleServerLogger]: ' + message);
}
}

export let logger: Logger;

export const registerLogger = (newLogger: Logger) => {
logger = newLogger;
};
Loading
Loading