feat: add core and common modules with PostgreSQL integration
- Introduced CommonModule for shared services and configurations. - Added CoreModule to manage database connections and configurations. - Implemented PostgresService for PostgreSQL operations. - Created configuration files for database and network provider. - Integrated ethers.js for Ethereum interactions. - Added validation pipes globally in the main application. - Created DeployerModule with initial controller and service structure. - Updated package.json with necessary dependencies for new features.
This commit is contained in:
parent
c6c947a402
commit
228b06f4c0
18
.gitignore
vendored
18
.gitignore
vendored
|
|
@ -54,3 +54,21 @@ pids
|
||||||
|
|
||||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Hardhat files
|
||||||
|
/cache
|
||||||
|
/artifacts
|
||||||
|
|
||||||
|
# TypeChain files
|
||||||
|
/typechain
|
||||||
|
/typechain-types
|
||||||
|
|
||||||
|
# solidity-coverage files
|
||||||
|
/coverage
|
||||||
|
/coverage.json
|
||||||
|
|
||||||
|
# Hardhat Ignition default folder for deployments against a local node
|
||||||
|
ignition/deployments/chain-31337
|
||||||
|
|
|
||||||
32
contracts/main.sol
Normal file
32
contracts/main.sol
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.8.19;
|
||||||
|
|
||||||
|
contract P2PTransferProject {
|
||||||
|
address public owner;
|
||||||
|
string public name;
|
||||||
|
modifier onlyOwner() {
|
||||||
|
require(msg.sender == owner, "Only owner");
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(string memory _name) {
|
||||||
|
owner = msg.sender;
|
||||||
|
name = _name;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBalance() external view returns (uint256) {
|
||||||
|
return address(this).balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setName(string memory _name) external onlyOwner {
|
||||||
|
name = _name;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getName() external view returns (string memory) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOwner() external view returns (address) {
|
||||||
|
return owner;
|
||||||
|
}
|
||||||
|
}
|
||||||
8
hardhat.config.ts
Normal file
8
hardhat.config.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { HardhatUserConfig } from "hardhat/config";
|
||||||
|
import "@nomicfoundation/hardhat-toolbox";
|
||||||
|
|
||||||
|
const config: HardhatUserConfig = {
|
||||||
|
solidity: "0.8.28",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
5688
package-lock.json
generated
5688
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
|
|
@ -23,26 +23,36 @@
|
||||||
"@nestjs/common": "^11.0.1",
|
"@nestjs/common": "^11.0.1",
|
||||||
"@nestjs/core": "^11.0.1",
|
"@nestjs/core": "^11.0.1",
|
||||||
"@nestjs/platform-express": "^11.0.1",
|
"@nestjs/platform-express": "^11.0.1",
|
||||||
|
"@nomicfoundation/hardhat-toolbox": "^6.0.0",
|
||||||
|
"class-transformer": "^0.5.1",
|
||||||
|
"class-validator": "^0.14.2",
|
||||||
|
"ethers": "^6.15.0",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.1"
|
"rxjs": "^7.8.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.2.0",
|
"@eslint/eslintrc": "^3.2.0",
|
||||||
"@eslint/js": "^9.18.0",
|
"@eslint/js": "^9.18.0",
|
||||||
|
"@nestjs/class-validator": "^0.13.4",
|
||||||
"@nestjs/cli": "^11.0.0",
|
"@nestjs/cli": "^11.0.0",
|
||||||
|
"@nestjs/config": "^4.0.2",
|
||||||
"@nestjs/schematics": "^11.0.0",
|
"@nestjs/schematics": "^11.0.0",
|
||||||
"@nestjs/testing": "^11.0.1",
|
"@nestjs/testing": "^11.0.1",
|
||||||
|
"@openzeppelin/contracts": "^5.3.0",
|
||||||
"@swc/cli": "^0.6.0",
|
"@swc/cli": "^0.6.0",
|
||||||
"@swc/core": "^1.10.7",
|
"@swc/core": "^1.10.7",
|
||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
"@types/node": "^22.10.7",
|
"@types/node": "^22.10.7",
|
||||||
|
"@types/pg": "^8.15.5",
|
||||||
"@types/supertest": "^6.0.2",
|
"@types/supertest": "^6.0.2",
|
||||||
"eslint": "^9.18.0",
|
"eslint": "^9.18.0",
|
||||||
"eslint-config-prettier": "^10.0.1",
|
"eslint-config-prettier": "^10.0.1",
|
||||||
"eslint-plugin-prettier": "^5.2.2",
|
"eslint-plugin-prettier": "^5.2.2",
|
||||||
"globals": "^16.0.0",
|
"globals": "^16.0.0",
|
||||||
|
"hardhat": "^2.25.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
|
"pg": "^8.16.3",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"supertest": "^7.0.0",
|
"supertest": "^7.0.0",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,16 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ModulesModule } from './modules/module.module';
|
||||||
|
import { CoreModule } from './core/core.module';
|
||||||
|
import { CommonModule } from './common/common.module';
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [],
|
imports: [
|
||||||
|
ConfigModule.forRoot({isGlobal:true}),
|
||||||
|
CommonModule,
|
||||||
|
CoreModule,
|
||||||
|
ModulesModule
|
||||||
|
],
|
||||||
controllers: [],
|
controllers: [],
|
||||||
providers: [],
|
providers: [],
|
||||||
})
|
})
|
||||||
|
|
|
||||||
15
src/common/common.module.ts
Normal file
15
src/common/common.module.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { Global, Module } from "@nestjs/common";
|
||||||
|
import { ProviderService } from "./utils";
|
||||||
|
import { ConfigModule } from "@nestjs/config";
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({
|
||||||
|
imports: [ConfigModule],
|
||||||
|
providers: [
|
||||||
|
ProviderService
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
ProviderService
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class CommonModule {}
|
||||||
6
src/common/config/db.config.ts
Normal file
6
src/common/config/db.config.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { registerAs } from "@nestjs/config";
|
||||||
|
import { Database } from "../types";
|
||||||
|
|
||||||
|
export default registerAs<Database>('Database', () => ({
|
||||||
|
url: process.env.DATABASE_URL || null,
|
||||||
|
}));
|
||||||
7
src/common/config/provider.config.ts
Normal file
7
src/common/config/provider.config.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { registerAs } from "@nestjs/config";
|
||||||
|
import { NetworkProvider } from "../types";
|
||||||
|
|
||||||
|
export default registerAs<NetworkProvider>('NetworkProvider', ()=>({
|
||||||
|
providerUrl: process.env.NETWORK_PROVIDER || null,
|
||||||
|
walletKey: process.env.WALLET_KEY || null,
|
||||||
|
}));
|
||||||
8
src/common/types/env.type.ts
Normal file
8
src/common/types/env.type.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
export interface NetworkProvider {
|
||||||
|
providerUrl: string | null;
|
||||||
|
walletKey: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Database{
|
||||||
|
url:string | null;
|
||||||
|
}
|
||||||
1
src/common/types/index.ts
Normal file
1
src/common/types/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './env.type';
|
||||||
100
src/common/utils/abis/P2PTransferProject.json
Normal file
100
src/common/utils/abis/P2PTransferProject.json
Normal file
File diff suppressed because one or more lines are too long
1
src/common/utils/index.ts
Normal file
1
src/common/utils/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './provider.service';
|
||||||
35
src/common/utils/provider.service.ts
Normal file
35
src/common/utils/provider.service.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
import { ConfigService } from "@nestjs/config";
|
||||||
|
import { ethers } from "ethers";
|
||||||
|
import * as P2PTransferProject from "./abis/P2PTransferProject.json";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ProviderService {
|
||||||
|
|
||||||
|
constructor(private readonly config: ConfigService) {}
|
||||||
|
|
||||||
|
async getProviderAndWallet(): Promise<{ provider: ethers.JsonRpcProvider, wallet: ethers.Wallet }> {
|
||||||
|
try{
|
||||||
|
const providerUrl = this.config.get<string>('NetworkProvider.providerUrl');
|
||||||
|
const walletKey = this.config.get<string>('NetworkProvider.walletKey');
|
||||||
|
|
||||||
|
if (!providerUrl || !walletKey) {
|
||||||
|
throw new Error('Provider URL or Wallet Key is not configured');
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = new ethers.JsonRpcProvider(providerUrl);
|
||||||
|
const wallet = new ethers.Wallet(walletKey, provider);
|
||||||
|
return { provider, wallet };
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to create provider or wallet: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAbiAndBytecode(){
|
||||||
|
return {
|
||||||
|
abi: P2PTransferProject.abi,
|
||||||
|
bytecode: P2PTransferProject.bytecode
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
25
src/core/core.module.ts
Normal file
25
src/core/core.module.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { Global, Module } from "@nestjs/common";
|
||||||
|
import { PostgresService } from "./psql/postgres.service";
|
||||||
|
import { PostgresModule } from "./psql/postgres.module";
|
||||||
|
import { ConfigModule } from "@nestjs/config";
|
||||||
|
import providerConfig from "src/common/config/provider.config";
|
||||||
|
import dbConfig from "src/common/config/db.config";
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
ConfigModule.forRoot({
|
||||||
|
isGlobal: true,
|
||||||
|
load: [
|
||||||
|
dbConfig,
|
||||||
|
providerConfig
|
||||||
|
],
|
||||||
|
cache: true,
|
||||||
|
expandVariables: true,
|
||||||
|
}),
|
||||||
|
PostgresModule
|
||||||
|
],
|
||||||
|
exports: [PostgresModule],
|
||||||
|
})
|
||||||
|
|
||||||
|
export class CoreModule {}
|
||||||
9
src/core/psql/postgres.module.ts
Normal file
9
src/core/psql/postgres.module.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Global, Module } from "@nestjs/common";
|
||||||
|
import { PostgresService } from "./postgres.service";
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({
|
||||||
|
providers:[PostgresService],
|
||||||
|
exports: [PostgresService],
|
||||||
|
})
|
||||||
|
export class PostgresModule {}
|
||||||
85
src/core/psql/postgres.service.ts
Normal file
85
src/core/psql/postgres.service.ts
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
import { ConfigService } from "@nestjs/config";
|
||||||
|
import { Client } from 'pg'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PostgresService {
|
||||||
|
private client: Client;
|
||||||
|
constructor(private readonly config: ConfigService) {
|
||||||
|
const connStr = this.config.get<string>('DATABASE_URL');
|
||||||
|
if (!connStr) {
|
||||||
|
throw new Error('DATABASE_URL is missing from configuration.');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.client = new Client({
|
||||||
|
connectionString: connStr,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async onModuleInit() {
|
||||||
|
console.log('Connecting to Postgres...');
|
||||||
|
await this.connect(); // or initialize pool
|
||||||
|
}
|
||||||
|
|
||||||
|
async onModuleDestroy() {
|
||||||
|
await this.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect() {
|
||||||
|
await this.client.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
async disconnect() {
|
||||||
|
await this.client.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAll<T = any>(table: string, filters?: Record<string, any>): Promise<T[]> {
|
||||||
|
let query = `SELECT * FROM ${table}`;
|
||||||
|
const values: any[] = [];
|
||||||
|
|
||||||
|
if (filters && Object.keys(filters).length > 0) {
|
||||||
|
const conditions = Object.entries(filters).map(([key, value], idx) => {
|
||||||
|
values.push(value);
|
||||||
|
// use LOWER() for Ethereum address comparison
|
||||||
|
return `LOWER(${key}) = LOWER($${idx + 1})`;
|
||||||
|
});
|
||||||
|
query += ` WHERE ` + conditions.join(' AND ');
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await this.client.query(query, values);
|
||||||
|
return res.rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
async insert<T = any>(table: string, data: Record<string, any>): Promise<T> {
|
||||||
|
const keys = Object.keys(data)
|
||||||
|
const values = Object.values(data)
|
||||||
|
const placeholders = keys.map((_, i) => `$${i + 1}`).join(', ')
|
||||||
|
|
||||||
|
const res = await this.client.query(
|
||||||
|
`INSERT INTO ${table} (${keys.join(', ')}) VALUES (${placeholders}) RETURNING *`,
|
||||||
|
values
|
||||||
|
)
|
||||||
|
return res.rows[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
async update<T = any>(table: string, id: number, data: Record<string, any>): Promise<T> {
|
||||||
|
const keys = Object.keys(data)
|
||||||
|
const values = Object.values(data)
|
||||||
|
const setClause = keys.map((key, i) => `${key} = $${i + 1}`).join(', ')
|
||||||
|
|
||||||
|
const res = await this.client.query(
|
||||||
|
`UPDATE ${table} SET ${setClause} WHERE id = $${keys.length + 1} RETURNING *`,
|
||||||
|
[...values, id]
|
||||||
|
)
|
||||||
|
return res.rows[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete<T = any>(table: string, id: number): Promise<T> {
|
||||||
|
const res = await this.client.query(
|
||||||
|
`DELETE FROM ${table} WHERE id = $1 RETURNING *`,
|
||||||
|
[id]
|
||||||
|
)
|
||||||
|
return res.rows[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
17
src/main.ts
17
src/main.ts
|
|
@ -1,8 +1,25 @@
|
||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
|
import { ValidationPipe } from '@nestjs/common';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
|
|
||||||
|
app.useGlobalPipes(
|
||||||
|
new ValidationPipe({
|
||||||
|
whitelist:true,
|
||||||
|
transform:true,
|
||||||
|
forbidNonWhitelisted:true
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
//CORS configuration
|
||||||
|
// app.enableCors({
|
||||||
|
// origin: process.env.FRONTEND_URL,
|
||||||
|
// credentials: true,
|
||||||
|
// });
|
||||||
|
// app.setGlobalPrefix('api');
|
||||||
|
|
||||||
await app.listen(process.env.PORT ?? 3000);
|
await app.listen(process.env.PORT ?? 3000);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|
|
||||||
6
src/modules/deployer/controllers/deployer.controller.ts
Normal file
6
src/modules/deployer/controllers/deployer.controller.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { Controller } from "@nestjs/common";
|
||||||
|
|
||||||
|
@Controller()
|
||||||
|
export class DeployerController {
|
||||||
|
// Controller methods go here
|
||||||
|
}
|
||||||
1
src/modules/deployer/controllers/index.ts
Normal file
1
src/modules/deployer/controllers/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "./deployer.controller";
|
||||||
9
src/modules/deployer/deployer.module.ts
Normal file
9
src/modules/deployer/deployer.module.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [],
|
||||||
|
controllers: [],
|
||||||
|
providers: [],
|
||||||
|
exports: [],
|
||||||
|
})
|
||||||
|
export class DeployerModule {}
|
||||||
0
src/modules/deployer/dtos/index.ts
Normal file
0
src/modules/deployer/dtos/index.ts
Normal file
6
src/modules/deployer/services/deployer.service.ts
Normal file
6
src/modules/deployer/services/deployer.service.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class DeployerService {
|
||||||
|
// Service methods go here
|
||||||
|
}
|
||||||
1
src/modules/deployer/services/index.ts
Normal file
1
src/modules/deployer/services/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './deployer.service'
|
||||||
4
src/modules/deployer/types/contract.type.ts
Normal file
4
src/modules/deployer/types/contract.type.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export type Contract = {
|
||||||
|
contractaddress: string;
|
||||||
|
useraddress: string;
|
||||||
|
}
|
||||||
1
src/modules/deployer/types/index.ts
Normal file
1
src/modules/deployer/types/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './contract.type';
|
||||||
6
src/modules/module.module.ts
Normal file
6
src/modules/module.module.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: []
|
||||||
|
})
|
||||||
|
export class ModulesModule {}
|
||||||
|
|
@ -6,6 +6,8 @@
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
"target": "ES2023",
|
"target": "ES2023",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
|
|
@ -14,8 +16,8 @@
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": true,
|
||||||
"strictBindCallApply": false,
|
"strictBindCallApply": false,
|
||||||
"noFallthroughCasesInSwitch": false
|
"noFallthroughCasesInSwitch": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user