clone:legacy
This commit is contained in:
50
src/admin/userlist.jsx
Normal file
50
src/admin/userlist.jsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import {
|
||||
Box,
|
||||
Pagination,
|
||||
Table,
|
||||
TableBody,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
Text,
|
||||
} from '@adminjs/design-system';
|
||||
const axios = require('axios');
|
||||
import { useEffect, useState } from 'react';
|
||||
const React = require('react');
|
||||
|
||||
const userListDashboard = (props) => {
|
||||
console.log('UserListDashboard', props);
|
||||
|
||||
const [data, setData] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
const { data: res } = await axios.get(
|
||||
'https://api.b-kon.io/v1/node/admin/rank-list',
|
||||
);
|
||||
setData(res);
|
||||
}
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box variant="container">
|
||||
<Table>
|
||||
<TableHead>ddd</TableHead>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>aa</TableCell>
|
||||
<TableCell>bb</TableCell>
|
||||
<TableCell>cc {data}</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
<Text mt="xl" textAlign="center">
|
||||
<Pagination page={1} perPage={1000} total={10000} onChange={() => {}} />
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default userListDashboard;
|
||||
22
src/app.controller.spec.ts
Normal file
22
src/app.controller.spec.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
describe('AppController', () => {
|
||||
let appController: AppController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const app: TestingModule = await Test.createTestingModule({
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
}).compile();
|
||||
|
||||
appController = app.get<AppController>(AppController);
|
||||
});
|
||||
|
||||
describe('root', () => {
|
||||
it('should return "Hello World!"', () => {
|
||||
// expect(appController.getHello()).toBe('Hello World!');
|
||||
});
|
||||
});
|
||||
});
|
||||
20
src/app.controller.ts
Normal file
20
src/app.controller.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Controller, Get, Post, Param } from '@nestjs/common';
|
||||
import { AppService } from './app.service';
|
||||
import { ApiCreatedResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
@Controller()
|
||||
@ApiTags('기본 API')
|
||||
export class AppController {
|
||||
constructor(private readonly appService: AppService) {}
|
||||
|
||||
@Get('ping')
|
||||
@ApiOperation({ summary: 'Hello World' })
|
||||
getHello(): string {
|
||||
return 'pong';
|
||||
}
|
||||
|
||||
@Get('health')
|
||||
getHealth(): string {
|
||||
return this.appService.getHealth();
|
||||
}
|
||||
}
|
||||
185
src/app.module.ts
Normal file
185
src/app.module.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { APP_INTERCEPTOR } from '@nestjs/core';
|
||||
import { LoggingInterceptor } from './logging';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { UserController } from './user/user.controller';
|
||||
import { SystemController } from './system/system.controller';
|
||||
import { WalletController } from './wallet/wallet.controller';
|
||||
import { NodeController } from './node/node.controller';
|
||||
import { BoardController } from './board/board.controller';
|
||||
import { UserService } from './user/user.service';
|
||||
import { BoardService } from './board/board.service';
|
||||
import { NodeService } from './node/node.service';
|
||||
import { SystemService } from './system/system.service';
|
||||
import { WalletService } from './wallet/wallet.service';
|
||||
import { PrismaModule } from './prisma.module';
|
||||
import { FirebaseService } from './firebase/firebase.service';
|
||||
import { FirebaseController } from './firebase/firebase.controller';
|
||||
import { JwtStrategy } from './middleware/jwt.strategy';
|
||||
import { AuthService } from './auth/auth.service';
|
||||
import { JwtModule, JwtService } from '@nestjs/jwt';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { ServeStaticModule } from '@nestjs/serve-static';
|
||||
import { join } from 'path';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
import { Node2AddressModule } from './node2address/node2address.module';
|
||||
import { LockedCoinModule } from './locked-coin/locked-coin.module';
|
||||
import { TranferHistoriesModule } from './transferhistories/transferhistories.module';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
const DEFAULT_ADMIN = {
|
||||
email: 'code@mnco.dev',
|
||||
password: 'MnCo0310!!@@',
|
||||
};
|
||||
|
||||
const authenticate = async (email: string, password: string) => {
|
||||
if (email === DEFAULT_ADMIN.email && password === DEFAULT_ADMIN.password) {
|
||||
return Promise.resolve(DEFAULT_ADMIN);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
import('@adminjs/nestjs').then(({ AdminModule }) => {
|
||||
return import('adminjs').then(async ({ AdminJS, ComponentLoader }) => {
|
||||
const componentLoader = new ComponentLoader();
|
||||
|
||||
const Components = {
|
||||
MyInput: componentLoader.add(
|
||||
'MyInput',
|
||||
'./components/DecimalComponent',
|
||||
),
|
||||
// other custom components
|
||||
};
|
||||
console.log(Components.MyInput);
|
||||
return import('@adminjs/prisma').then((AdminJSTypeORM) => {
|
||||
AdminJS.registerAdapter({
|
||||
Database: AdminJSTypeORM.Database,
|
||||
Resource: AdminJSTypeORM.Resource,
|
||||
});
|
||||
|
||||
return AdminModule.createAdminAsync({
|
||||
useFactory: () => ({
|
||||
adminJsOptions: {
|
||||
rootPath: '/admin',
|
||||
resources: [
|
||||
{
|
||||
resource: {
|
||||
model: AdminJSTypeORM.getModelByName('Node'),
|
||||
client: prisma,
|
||||
},
|
||||
options: {
|
||||
properties: {
|
||||
balance: {
|
||||
components: {
|
||||
// list: Components.MyInput,
|
||||
// show: Components.MyInput,
|
||||
},
|
||||
},
|
||||
stakedBalance: {
|
||||
components: {
|
||||
// list: Components.MyInput,
|
||||
// show: Components.MyInput,
|
||||
},
|
||||
},
|
||||
computingPower: {
|
||||
components: {
|
||||
// list: Components.MyInput,
|
||||
// show: Components.MyInput,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
resource: {
|
||||
model: AdminJSTypeORM.getModelByName('User'),
|
||||
client: prisma,
|
||||
},
|
||||
options: {},
|
||||
},
|
||||
{
|
||||
resource: {
|
||||
model: AdminJSTypeORM.getModelByName('Category'),
|
||||
client: prisma,
|
||||
},
|
||||
options: {},
|
||||
},
|
||||
{
|
||||
resource: {
|
||||
model: AdminJSTypeORM.getModelByName('LockedCoin'),
|
||||
client: prisma,
|
||||
},
|
||||
options: {},
|
||||
},
|
||||
],
|
||||
componentLoader,
|
||||
},
|
||||
auth: {
|
||||
authenticate,
|
||||
cookieName: 'adminjs',
|
||||
cookiePassword: 'secret!!',
|
||||
},
|
||||
sessionOptions: {
|
||||
resave: true,
|
||||
saveUninitialized: true,
|
||||
secret: 'secret!!',
|
||||
},
|
||||
}),
|
||||
});
|
||||
});
|
||||
});
|
||||
}),
|
||||
ScheduleModule.forRoot(),
|
||||
PrismaModule,
|
||||
ServeStaticModule.forRoot({
|
||||
rootPath: join(__dirname, '..', 'static'),
|
||||
exclude: ['/api*', '/admin*'],
|
||||
}),
|
||||
PassportModule.register({ defaultStrategy: 'jwt' }),
|
||||
ConfigModule,
|
||||
JwtModule.registerAsync({
|
||||
useFactory: async () => ({
|
||||
secretOrPrivateKey: 'MCNOWINS1010!!!!',
|
||||
secret: 'MNCOWINS1010!!!!',
|
||||
signOptions: {
|
||||
expiresIn: `${600000}s`,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
Node2AddressModule,
|
||||
LockedCoinModule,
|
||||
TranferHistoriesModule,
|
||||
],
|
||||
controllers: [
|
||||
AppController,
|
||||
UserController,
|
||||
SystemController,
|
||||
WalletController,
|
||||
NodeController,
|
||||
BoardController,
|
||||
FirebaseController,
|
||||
],
|
||||
providers: [
|
||||
AppService,
|
||||
UserService,
|
||||
BoardService,
|
||||
NodeService,
|
||||
SystemService,
|
||||
WalletService,
|
||||
FirebaseService,
|
||||
AuthService,
|
||||
JwtService,
|
||||
JwtStrategy,
|
||||
{
|
||||
provide: APP_INTERCEPTOR,
|
||||
useClass: LoggingInterceptor,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
25
src/app.service.ts
Normal file
25
src/app.service.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
function readFile(p: string) {
|
||||
try {
|
||||
return fs.readFileSync(p, 'utf8').trim();
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const ROOT = process.cwd();
|
||||
const shaPath = path.join(ROOT, 'commit-sha.txt');
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
getHello(): string {
|
||||
return 'Hello World!';
|
||||
}
|
||||
|
||||
getHealth(): string {
|
||||
return `{'status' : 'ok', 'commit-sha' : ${readFile(shaPath)}}`;
|
||||
}
|
||||
}
|
||||
919
src/assets/defi-abi.ts
Normal file
919
src/assets/defi-abi.ts
Normal file
@@ -0,0 +1,919 @@
|
||||
export default [
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'address',
|
||||
name: '_bkon',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'constructor',
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: true,
|
||||
internalType: 'address',
|
||||
name: 'parent',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
indexed: true,
|
||||
internalType: 'address',
|
||||
name: 'child',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
name: 'Accepted',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: true,
|
||||
internalType: 'address',
|
||||
name: 'node',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
name: 'Activated',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'address',
|
||||
name: 'node',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
name: 'active',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'address',
|
||||
name: '_parent',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
internalType: 'address',
|
||||
name: 'node',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
name: 'activeForce',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'address[]',
|
||||
name: 'nodelist',
|
||||
type: 'address[]',
|
||||
},
|
||||
],
|
||||
name: 'actives',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'uint256',
|
||||
name: 'amount',
|
||||
type: 'uint256',
|
||||
},
|
||||
],
|
||||
name: 'deposit',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'uint256',
|
||||
name: 'round',
|
||||
type: 'uint256',
|
||||
},
|
||||
{
|
||||
internalType: 'uint256',
|
||||
name: 'start',
|
||||
type: 'uint256',
|
||||
},
|
||||
{
|
||||
internalType: 'uint256',
|
||||
name: 'end',
|
||||
type: 'uint256',
|
||||
},
|
||||
],
|
||||
name: 'distribute',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: false,
|
||||
internalType: 'uint256',
|
||||
name: 'round',
|
||||
type: 'uint256',
|
||||
},
|
||||
{
|
||||
indexed: true,
|
||||
internalType: 'address',
|
||||
name: 'to',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
internalType: 'uint256',
|
||||
name: 'amount',
|
||||
type: 'uint256',
|
||||
},
|
||||
],
|
||||
name: 'Distributed',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'bytes32',
|
||||
name: 'role',
|
||||
type: 'bytes32',
|
||||
},
|
||||
{
|
||||
internalType: 'address',
|
||||
name: 'account',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
name: 'grantRole',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'uint256',
|
||||
name: 'round',
|
||||
type: 'uint256',
|
||||
},
|
||||
{
|
||||
internalType: 'address[]',
|
||||
name: 'recipient',
|
||||
type: 'address[]',
|
||||
},
|
||||
{
|
||||
internalType: 'uint256[]',
|
||||
name: 'amount',
|
||||
type: 'uint256[]',
|
||||
},
|
||||
],
|
||||
name: 'inject',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: true,
|
||||
internalType: 'address',
|
||||
name: 'previousOwner',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
indexed: true,
|
||||
internalType: 'address',
|
||||
name: 'newOwner',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
name: 'OwnershipTransferred',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: 'renounceOwnership',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'bytes32',
|
||||
name: 'role',
|
||||
type: 'bytes32',
|
||||
},
|
||||
{
|
||||
internalType: 'address',
|
||||
name: 'account',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
name: 'renounceRole',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'address',
|
||||
name: 'mother',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
name: 'request',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: true,
|
||||
internalType: 'address',
|
||||
name: 'parent',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
indexed: true,
|
||||
internalType: 'address',
|
||||
name: 'child',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
name: 'Request',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'bytes32',
|
||||
name: 'role',
|
||||
type: 'bytes32',
|
||||
},
|
||||
{
|
||||
internalType: 'address',
|
||||
name: 'account',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
name: 'revokeRole',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: true,
|
||||
internalType: 'bytes32',
|
||||
name: 'role',
|
||||
type: 'bytes32',
|
||||
},
|
||||
{
|
||||
indexed: true,
|
||||
internalType: 'bytes32',
|
||||
name: 'previousAdminRole',
|
||||
type: 'bytes32',
|
||||
},
|
||||
{
|
||||
indexed: true,
|
||||
internalType: 'bytes32',
|
||||
name: 'newAdminRole',
|
||||
type: 'bytes32',
|
||||
},
|
||||
],
|
||||
name: 'RoleAdminChanged',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: true,
|
||||
internalType: 'bytes32',
|
||||
name: 'role',
|
||||
type: 'bytes32',
|
||||
},
|
||||
{
|
||||
indexed: true,
|
||||
internalType: 'address',
|
||||
name: 'account',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
indexed: true,
|
||||
internalType: 'address',
|
||||
name: 'sender',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
name: 'RoleGranted',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: true,
|
||||
internalType: 'bytes32',
|
||||
name: 'role',
|
||||
type: 'bytes32',
|
||||
},
|
||||
{
|
||||
indexed: true,
|
||||
internalType: 'address',
|
||||
name: 'account',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
indexed: true,
|
||||
internalType: 'address',
|
||||
name: 'sender',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
name: 'RoleRevoked',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: true,
|
||||
internalType: 'address',
|
||||
name: 'from',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
indexed: true,
|
||||
internalType: 'address',
|
||||
name: 'to',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
internalType: 'uint256',
|
||||
name: 'amount',
|
||||
type: 'uint256',
|
||||
},
|
||||
],
|
||||
name: 'Send',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'uint256',
|
||||
name: '_activeFee',
|
||||
type: 'uint256',
|
||||
},
|
||||
{
|
||||
internalType: 'uint256',
|
||||
name: '_transferFee',
|
||||
type: 'uint256',
|
||||
},
|
||||
],
|
||||
name: 'setFee',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'address',
|
||||
name: 'recipient',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
internalType: 'uint256',
|
||||
name: 'amount',
|
||||
type: 'uint256',
|
||||
},
|
||||
],
|
||||
name: 'transfer',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'address',
|
||||
name: 'sender',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
internalType: 'address',
|
||||
name: 'recipient',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
internalType: 'uint256',
|
||||
name: 'amount',
|
||||
type: 'uint256',
|
||||
},
|
||||
],
|
||||
name: 'transferFrom',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'address',
|
||||
name: 'newOwner',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
name: 'transferOwnership',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'uint256',
|
||||
name: 'round',
|
||||
type: 'uint256',
|
||||
},
|
||||
{
|
||||
internalType: 'uint256',
|
||||
name: 'start',
|
||||
type: 'uint256',
|
||||
},
|
||||
{
|
||||
internalType: 'address[]',
|
||||
name: 'recipient',
|
||||
type: 'address[]',
|
||||
},
|
||||
{
|
||||
internalType: 'uint256[]',
|
||||
name: 'amount',
|
||||
type: 'uint256[]',
|
||||
},
|
||||
],
|
||||
name: 'update',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'uint256',
|
||||
name: 'amount',
|
||||
type: 'uint256',
|
||||
},
|
||||
],
|
||||
name: 'withdraw',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: 'activeFee',
|
||||
outputs: [
|
||||
{
|
||||
internalType: 'uint256',
|
||||
name: '',
|
||||
type: 'uint256',
|
||||
},
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'address',
|
||||
name: '',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
internalType: 'uint256',
|
||||
name: '',
|
||||
type: 'uint256',
|
||||
},
|
||||
],
|
||||
name: 'activeRequest',
|
||||
outputs: [
|
||||
{
|
||||
internalType: 'address',
|
||||
name: '',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'address',
|
||||
name: '',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
name: 'activeRequested',
|
||||
outputs: [
|
||||
{
|
||||
internalType: 'bool',
|
||||
name: '',
|
||||
type: 'bool',
|
||||
},
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: 'ADMIN_ROLE',
|
||||
outputs: [
|
||||
{
|
||||
internalType: 'bytes32',
|
||||
name: '',
|
||||
type: 'bytes32',
|
||||
},
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'address',
|
||||
name: '',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
name: 'balance',
|
||||
outputs: [
|
||||
{
|
||||
internalType: 'uint256',
|
||||
name: '',
|
||||
type: 'uint256',
|
||||
},
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'address',
|
||||
name: 'node',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
name: 'balanceOf',
|
||||
outputs: [
|
||||
{
|
||||
internalType: 'uint256',
|
||||
name: '',
|
||||
type: 'uint256',
|
||||
},
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'address',
|
||||
name: '',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
internalType: 'uint256',
|
||||
name: '',
|
||||
type: 'uint256',
|
||||
},
|
||||
],
|
||||
name: 'child',
|
||||
outputs: [
|
||||
{
|
||||
internalType: 'address',
|
||||
name: '',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: 'DEFAULT_ADMIN_ROLE',
|
||||
outputs: [
|
||||
{
|
||||
internalType: 'bytes32',
|
||||
name: '',
|
||||
type: 'bytes32',
|
||||
},
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: 'DISTRIBUTOR_ROLE',
|
||||
outputs: [
|
||||
{
|
||||
internalType: 'bytes32',
|
||||
name: '',
|
||||
type: 'bytes32',
|
||||
},
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'address',
|
||||
name: '',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
name: 'enabled',
|
||||
outputs: [
|
||||
{
|
||||
internalType: 'bool',
|
||||
name: '',
|
||||
type: 'bool',
|
||||
},
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: 'feeWallet',
|
||||
outputs: [
|
||||
{
|
||||
internalType: 'address',
|
||||
name: '',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'address',
|
||||
name: 'node',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
name: 'getNodeInfo',
|
||||
outputs: [
|
||||
{
|
||||
internalType: 'uint256',
|
||||
name: '',
|
||||
type: 'uint256',
|
||||
},
|
||||
{
|
||||
internalType: 'bool',
|
||||
name: '',
|
||||
type: 'bool',
|
||||
},
|
||||
{
|
||||
internalType: 'address',
|
||||
name: '',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
internalType: 'address[]',
|
||||
name: '',
|
||||
type: 'address[]',
|
||||
},
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'bytes32',
|
||||
name: 'role',
|
||||
type: 'bytes32',
|
||||
},
|
||||
],
|
||||
name: 'getRoleAdmin',
|
||||
outputs: [
|
||||
{
|
||||
internalType: 'bytes32',
|
||||
name: '',
|
||||
type: 'bytes32',
|
||||
},
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'bytes32',
|
||||
name: 'role',
|
||||
type: 'bytes32',
|
||||
},
|
||||
{
|
||||
internalType: 'address',
|
||||
name: 'account',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
name: 'hasRole',
|
||||
outputs: [
|
||||
{
|
||||
internalType: 'bool',
|
||||
name: '',
|
||||
type: 'bool',
|
||||
},
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'uint256',
|
||||
name: '',
|
||||
type: 'uint256',
|
||||
},
|
||||
],
|
||||
name: 'nodes',
|
||||
outputs: [
|
||||
{
|
||||
internalType: 'address',
|
||||
name: '',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: 'owner',
|
||||
outputs: [
|
||||
{
|
||||
internalType: 'address',
|
||||
name: '',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'address',
|
||||
name: '',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
name: 'parent',
|
||||
outputs: [
|
||||
{
|
||||
internalType: 'address',
|
||||
name: '',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'bytes4',
|
||||
name: 'interfaceId',
|
||||
type: 'bytes4',
|
||||
},
|
||||
],
|
||||
name: 'supportsInterface',
|
||||
outputs: [
|
||||
{
|
||||
internalType: 'bool',
|
||||
name: '',
|
||||
type: 'bool',
|
||||
},
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'uint256',
|
||||
name: '',
|
||||
type: 'uint256',
|
||||
},
|
||||
{
|
||||
internalType: 'uint256',
|
||||
name: '',
|
||||
type: 'uint256',
|
||||
},
|
||||
],
|
||||
name: 'targetAddress',
|
||||
outputs: [
|
||||
{
|
||||
internalType: 'address',
|
||||
name: '',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'uint256',
|
||||
name: '',
|
||||
type: 'uint256',
|
||||
},
|
||||
{
|
||||
internalType: 'uint256',
|
||||
name: '',
|
||||
type: 'uint256',
|
||||
},
|
||||
],
|
||||
name: 'targetAmount',
|
||||
outputs: [
|
||||
{
|
||||
internalType: 'uint256',
|
||||
name: '',
|
||||
type: 'uint256',
|
||||
},
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'uint256',
|
||||
name: '',
|
||||
type: 'uint256',
|
||||
},
|
||||
{
|
||||
internalType: 'uint256',
|
||||
name: '',
|
||||
type: 'uint256',
|
||||
},
|
||||
],
|
||||
name: 'targetDropped',
|
||||
outputs: [
|
||||
{
|
||||
internalType: 'bool',
|
||||
name: '',
|
||||
type: 'bool',
|
||||
},
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: 'transferFee',
|
||||
outputs: [
|
||||
{
|
||||
internalType: 'uint256',
|
||||
name: '',
|
||||
type: 'uint256',
|
||||
},
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
];
|
||||
12
src/assets/erc20-abi.ts
Normal file
12
src/assets/erc20-abi.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export default [
|
||||
'function balanceOf(address _owner) view returns (uint256)',
|
||||
'function owner() view returns (address)',
|
||||
'function name() view returns (string)',
|
||||
'function totalSupply() view returns (uint256)',
|
||||
'function decimals() view returns (uint8)',
|
||||
'function symbol() view returns (string)',
|
||||
'function allowance(address owner, address spender) external view returns (uint256)',
|
||||
'function transfer(address _to, uint256 value) returns (bool success)',
|
||||
'event Transfer(address indexed from, address indexed to, uint amount)',
|
||||
'event Approval(address indexed owner, address indexed spender, uint256 value)',
|
||||
];
|
||||
50
src/assets/i18n.ts
Normal file
50
src/assets/i18n.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
export default {
|
||||
'withdraw.title': {
|
||||
ko: '',
|
||||
en: '',
|
||||
zh: '',
|
||||
vi: '',
|
||||
ja: '',
|
||||
th: '',
|
||||
},
|
||||
'deposit.title': {
|
||||
ko: '',
|
||||
en: '',
|
||||
zh: '',
|
||||
vi: '',
|
||||
ja: '',
|
||||
th: '',
|
||||
},
|
||||
'active.title': {
|
||||
ko: '',
|
||||
en: '',
|
||||
zh: '',
|
||||
vi: '',
|
||||
ja: '',
|
||||
th: '',
|
||||
},
|
||||
'withdraw.message': {
|
||||
ko: '',
|
||||
en: '',
|
||||
zh: '',
|
||||
vi: '',
|
||||
ja: '',
|
||||
th: '',
|
||||
},
|
||||
'deposit.message': {
|
||||
ko: '',
|
||||
en: '',
|
||||
zh: '',
|
||||
vi: '',
|
||||
ja: '',
|
||||
th: '',
|
||||
},
|
||||
'active.message': {
|
||||
ko: '',
|
||||
en: '',
|
||||
zh: '',
|
||||
vi: '',
|
||||
ja: '',
|
||||
th: '',
|
||||
},
|
||||
} as { [key: string]: { [key: string]: string } };
|
||||
18
src/assets/multicall-abi.ts
Normal file
18
src/assets/multicall-abi.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export default [
|
||||
'function aggregate(tuple(address target, bytes callData)[] calls) view returns (uint256 blockNumber, bytes[] returnData)',
|
||||
'function aggregate3(tuple(address target, bool allowFailure, bytes callData)[] calls) payable returns (tuple(bool success, bytes returnData)[] returnData)',
|
||||
'function aggregate3Value(tuple(address target, bool allowFailure, uint256 value, bytes callData)[] calls) payable returns (tuple(bool success, bytes returnData)[] returnData)',
|
||||
'function blockAndAggregate(tuple(address target, bytes callData)[] calls) payable returns (uint256 blockNumber, bytes32 blockHash, tuple(bool success, bytes returnData)[] returnData)',
|
||||
'function getBasefee() view returns (uint256 basefee)',
|
||||
'function getBlockHash(uint256 blockNumber) view returns (bytes32 blockHash)',
|
||||
'function getBlockNumber() view returns (uint256 blockNumber)',
|
||||
'function getChainId() view returns (uint256 chainid)',
|
||||
'function getCurrentBlockCoinbase() view returns (address coinbase)',
|
||||
'function getCurrentBlockDifficulty() view returns (uint256 difficulty)',
|
||||
'function getCurrentBlockGasLimit() view returns (uint256 gaslimit)',
|
||||
'function getCurrentBlockTimestamp() view returns (uint256 timestamp)',
|
||||
'function getEthBalance(address addr) view returns (uint256 balance)',
|
||||
'function getLastBlockHash() view returns (bytes32 blockHash)',
|
||||
'function tryAggregate(bool requireSuccess, tuple(address target, bytes callData)[] calls) payable returns (tuple(bool success, bytes returnData)[] returnData)',
|
||||
'function tryBlockAndAggregate(bool requireSuccess, tuple(address target, bytes callData)[] calls) payable returns (uint256 blockNumber, bytes32 blockHash, tuple(bool success, bytes returnData)[] returnData)',
|
||||
];
|
||||
26
src/assets/multisender-abi.ts
Normal file
26
src/assets/multisender-abi.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
const abi = [
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: 'address',
|
||||
name: 'contractAddr',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
internalType: 'address[]',
|
||||
name: 'addrs',
|
||||
type: 'address[]',
|
||||
},
|
||||
{
|
||||
internalType: 'uint256[]',
|
||||
name: 'amounts',
|
||||
type: 'uint256[]',
|
||||
},
|
||||
],
|
||||
name: 'batchTransfer',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function',
|
||||
},
|
||||
];
|
||||
export default abi;
|
||||
30
src/auth/auth.service.ts
Normal file
30
src/auth/auth.service.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { User } from '@prisma/client';
|
||||
import { UserService } from '../user/user.service';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { PrismaService } from '../prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
constructor(
|
||||
private readonly userService: UserService,
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly prisma: PrismaService,
|
||||
) {}
|
||||
|
||||
public async getToken(userId: string) {
|
||||
var user = await this.prisma.user.findUnique({
|
||||
where: {
|
||||
userId: userId,
|
||||
},
|
||||
});
|
||||
const payload = { userId: user.userId };
|
||||
const token = this.jwtService.sign(payload, {
|
||||
secret: 'MNCOWINS1010!!!!',
|
||||
});
|
||||
|
||||
return token;
|
||||
}
|
||||
}
|
||||
18
src/board/board.controller.spec.ts
Normal file
18
src/board/board.controller.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { BoardController } from './board.controller';
|
||||
|
||||
describe('BoardController', () => {
|
||||
let controller: BoardController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [BoardController],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<BoardController>(BoardController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
132
src/board/board.controller.ts
Normal file
132
src/board/board.controller.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import { Controller, Get, Post, Param, Body, Res } from '@nestjs/common';
|
||||
import {
|
||||
ApiCreatedResponse,
|
||||
ApiOperation,
|
||||
ApiTags,
|
||||
ApiBody,
|
||||
ApiProperty,
|
||||
} from '@nestjs/swagger';
|
||||
import { Board as BoardModel } from '@prisma/client';
|
||||
import { BoardService } from './board.service';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { Cron, CronExpression } from '@nestjs/schedule';
|
||||
@Controller({
|
||||
path: 'board',
|
||||
version: '1',
|
||||
})
|
||||
@ApiTags('CS용 API')
|
||||
export class BoardController {
|
||||
constructor(private readonly boardService: BoardService) {}
|
||||
|
||||
@Get('/makeExcel')
|
||||
@ApiOperation({ summary: '엑셀파일 생성', description: '엑셀파일 생성' })
|
||||
@ApiCreatedResponse({
|
||||
description: 'The user has been successfully created.',
|
||||
})
|
||||
async getExcel(@Res() res, @Param('date') date: string): Promise<any> {
|
||||
var mydate = date
|
||||
? dayjs(dayjs(date).format('YYYY-MM-DD'))
|
||||
: dayjs(dayjs().format('YYYY-MM-DD'));
|
||||
res.sendFile(__dirname + `snapshot_${mydate.format('YYYY-MM-DD')}.csv`);
|
||||
}
|
||||
|
||||
@Cron('0 0 0 * * *')
|
||||
@Post('/makeExcel')
|
||||
@ApiOperation({ summary: '엑셀파일 생성', description: '엑셀파일 생성' })
|
||||
@ApiCreatedResponse({
|
||||
description: 'The user has been successfully created.',
|
||||
})
|
||||
async makeExcel(@Body() body: any): Promise<any> {
|
||||
const { date } = body;
|
||||
var mydate = date
|
||||
? dayjs(dayjs(date).format('YYYY-MM-DD'))
|
||||
: dayjs(dayjs().format('YYYY-MM-DD'));
|
||||
return await this.boardService.generateSnapshotExcel(mydate);
|
||||
}
|
||||
|
||||
@Get('/toc')
|
||||
@ApiOperation({
|
||||
summary: '이용약관 조회',
|
||||
description: '이용약관 Board 데이터 회수',
|
||||
})
|
||||
getToc(): Promise<BoardModel> {
|
||||
return this.boardService.getToc();
|
||||
}
|
||||
|
||||
@Get('/terms')
|
||||
@ApiOperation({
|
||||
summary: '전자금융거래약관 조회',
|
||||
description: '전자금융거래약관 Board 데이터 회수',
|
||||
})
|
||||
getTerm(): Promise<BoardModel> {
|
||||
return this.boardService.getTerm();
|
||||
}
|
||||
|
||||
@Get('/privacy')
|
||||
@ApiOperation({
|
||||
summary: '개인정보처리방침 조회',
|
||||
description: '개인정보처리방침 Board 데이터 회수',
|
||||
})
|
||||
getPrivacy(): Promise<BoardModel> {
|
||||
return this.boardService.getPrivacy();
|
||||
}
|
||||
|
||||
@Get('/announce')
|
||||
@ApiOperation({
|
||||
summary: '공지사항 리스트 조회',
|
||||
description: '공지사항 Board 데이터 회수',
|
||||
})
|
||||
getAccounces(): Promise<BoardModel[]> {
|
||||
return this.boardService.getNotices();
|
||||
}
|
||||
|
||||
@Get('/announce/:id')
|
||||
@ApiOperation({
|
||||
summary: '공지사항 조회',
|
||||
description: '공지사항 Board 데이터 회수',
|
||||
})
|
||||
getAccounce(@Param('id') id: number): Promise<BoardModel> {
|
||||
return this.boardService.getNotice(id);
|
||||
}
|
||||
|
||||
@Get('/faq')
|
||||
@ApiOperation({
|
||||
summary: 'FAQ 리스트 조회',
|
||||
description: 'FAQ Board 데이터 회수',
|
||||
})
|
||||
getFaqs(): Promise<BoardModel[]> {
|
||||
return this.boardService.getFaqs();
|
||||
}
|
||||
|
||||
@Get('/faq/:id')
|
||||
@ApiOperation({ summary: 'FAQ 조회', description: '' })
|
||||
getFaq(@Param('id') id: number): Promise<BoardModel> {
|
||||
return this.boardService.getFaq(id);
|
||||
}
|
||||
|
||||
@Get('/banner')
|
||||
@ApiOperation({ summary: '배너 리스트 가져오기', description: '' })
|
||||
getBanners(): Promise<BoardModel[]> {
|
||||
return this.boardService.getBanners();
|
||||
}
|
||||
|
||||
@Get('/banner/id')
|
||||
@ApiOperation({ summary: '배너 가져오기', description: '' })
|
||||
getBanner(@Param('id') id: number): Promise<BoardModel> {
|
||||
return this.boardService.getBanner(id);
|
||||
}
|
||||
|
||||
@Get('/:key')
|
||||
@ApiOperation({
|
||||
summary: '시스템 정보 가져오기',
|
||||
description: 'key에대한 시스템 정보를 가져온다',
|
||||
})
|
||||
async getSystemInfo(@Param('key') key: string): Promise<{ value: string }> {
|
||||
const systemInfo = await this.boardService.getByTitle(key);
|
||||
if (!systemInfo) {
|
||||
throw new Error(`System info with key ${key} not found`);
|
||||
}
|
||||
return { value: systemInfo.body };
|
||||
}
|
||||
}
|
||||
9
src/board/board.module.ts
Normal file
9
src/board/board.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { BoardService } from './board.service';
|
||||
import { BoardController } from './board.controller';
|
||||
|
||||
@Module({
|
||||
providers: [BoardService],
|
||||
controllers: [BoardController],
|
||||
})
|
||||
export class BoardModule {}
|
||||
18
src/board/board.service.spec.ts
Normal file
18
src/board/board.service.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { BoardService } from './board.service';
|
||||
|
||||
describe('BoardService', () => {
|
||||
let service: BoardService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [BoardService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<BoardService>(BoardService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
595
src/board/board.service.ts
Normal file
595
src/board/board.service.ts
Normal file
@@ -0,0 +1,595 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma.service';
|
||||
import { Board } from '@prisma/client';
|
||||
import {
|
||||
findClosestBlockByTimestamp,
|
||||
getDeFiContract,
|
||||
getTokenContract,
|
||||
} from 'src/jsonrpc';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
const wait = require('waait');
|
||||
let converter = require('json-2-csv');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
import Decimal from 'decimal.js';
|
||||
import { Cron, CronExpression } from '@nestjs/schedule';
|
||||
|
||||
@Injectable()
|
||||
export class BoardService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
cache = new Map();
|
||||
async generateSnapshotExcel(date: dayjs.Dayjs) {
|
||||
const ethers = require('ethers');
|
||||
const wait = require('waait');
|
||||
const Decimal = require('decimal.js');
|
||||
let converter = require('json-2-csv');
|
||||
|
||||
const provider = new ethers.JsonRpcProvider('https://api.kon-wallet.com');
|
||||
|
||||
const defiAddress = '0xb4e1b1c8796e7b5f2c607ea43d21ec68a0052bdc';
|
||||
const erc20Address = '0x1aF885C7DF29de8A4678BC8b69e84C980C9ab856';
|
||||
|
||||
/*
|
||||
id,
|
||||
address,
|
||||
balance,
|
||||
stakedBalance,
|
||||
computingPower,
|
||||
parentId,
|
||||
isActivated,
|
||||
nickname,
|
||||
referral,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
userId,
|
||||
childrenAccumulatedBalance,
|
||||
childrenAccumulatedCount,
|
||||
childrenAccumulatedActiveCount,
|
||||
childrenAccumulatedDeactiveCount,
|
||||
childrenAccumulatedStakedBalance,
|
||||
childrenDepth,
|
||||
depth
|
||||
*/
|
||||
|
||||
const defiAbi = [
|
||||
'function balanceOf(address _owner) view returns (uint256)',
|
||||
];
|
||||
const erc20Abi = [
|
||||
'function balanceOf(address _owner) view returns (uint256)',
|
||||
'function owner() view returns (address)',
|
||||
'function name() view returns (string)',
|
||||
'function totalSupply() view returns (uint256)',
|
||||
'function decimals() view returns (uint8)',
|
||||
'function symbol() view returns (string)',
|
||||
'function allowance(address owner, address spender) external view returns (uint256)',
|
||||
'function transfer(address _to, uint256 value) returns (bool success)',
|
||||
'event Transfer(address indexed from, address indexed to, uint amount)',
|
||||
'event Approval(address indexed owner, address indexed spender, uint256 value)',
|
||||
];
|
||||
|
||||
const targets = ['240426'];
|
||||
|
||||
const records: any[] = await this.prisma.node.findMany();
|
||||
|
||||
const defiContract = new ethers.Contract(defiAddress, defiAbi, provider);
|
||||
const erc20Contract = new ethers.Contract(erc20Address, erc20Abi, provider);
|
||||
const addresses = records.map((e) => e.address);
|
||||
|
||||
let list = [];
|
||||
let fetchedBalanceList = [];
|
||||
|
||||
let nodes = {};
|
||||
let node_addr_to_id = {};
|
||||
let child_list = {};
|
||||
|
||||
async function fetch(blockTag) {
|
||||
let cnt = 0;
|
||||
const fetchAddressList = [];
|
||||
for (const address of addresses) {
|
||||
list.push(get(address, blockTag));
|
||||
//console.log(list)
|
||||
fetchAddressList.push(address);
|
||||
if (cnt % 100 == 0 && cnt > 0) {
|
||||
//console.log(cnt)
|
||||
let returnList = [];
|
||||
try {
|
||||
returnList = await Promise.allSettled(list);
|
||||
await wait(1000);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
await wait(1000);
|
||||
returnList = await Promise.allSettled(list);
|
||||
}
|
||||
//console.log(fetchedBalanceList)
|
||||
fetchedBalanceList = [...fetchedBalanceList, ...returnList];
|
||||
list = [];
|
||||
}
|
||||
cnt++;
|
||||
}
|
||||
|
||||
let returnList = [];
|
||||
try {
|
||||
returnList = await Promise.allSettled(list);
|
||||
await wait(1000);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
//await wait(1000)
|
||||
//returnList = await Promise.allSettled(list)
|
||||
}
|
||||
fetchedBalanceList = [...fetchedBalanceList, ...returnList];
|
||||
//console.log(fetchedBalanceList)
|
||||
|
||||
console.log(
|
||||
'equal size : ',
|
||||
fetchAddressList.length,
|
||||
fetchedBalanceList.length,
|
||||
fetchAddressList.length == fetchedBalanceList.length,
|
||||
);
|
||||
for (let i = 0; i < fetchAddressList.length; i++) {
|
||||
if (fetchedBalanceList[i]?.status == 'fulfilled') {
|
||||
const n = nodes[node_addr_to_id[fetchAddressList[i]]];
|
||||
n.stakedBalance = new Decimal(fetchedBalanceList[i].value.toString());
|
||||
nodes[node_addr_to_id[fetchAddressList[i]]] = n;
|
||||
//console.log("inject staked balance >>> ", n)
|
||||
} else {
|
||||
console.error(
|
||||
'cananot fetched balance : ',
|
||||
fetchAddressList[i],
|
||||
fetchedBalanceList[i],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return fetchedBalanceList;
|
||||
}
|
||||
|
||||
async function get(address, blockTag) {
|
||||
return defiContract.balanceOf(address, { blockTag: blockTag });
|
||||
}
|
||||
|
||||
async function get2(address, blockTag) {
|
||||
return erc20Contract.balanceOf(address, { blockTag: blockTag });
|
||||
}
|
||||
|
||||
async function updateSnapshotCPsAll(blockTag) {
|
||||
console.log('>>>>> updateSnapshotCPsAll:::buildNodeStructure start');
|
||||
for (const node of records) {
|
||||
node.stakedBalance = new Decimal(0);
|
||||
node.balance = new Decimal(0);
|
||||
node.computingPower = new Decimal(0);
|
||||
node.childrenAccumulatedBalance = new Decimal(0);
|
||||
node.childrenAccumulatedStakedBalance = new Decimal(0);
|
||||
node['childrenUserAccumulatedCount'] = new Decimal(0);
|
||||
node['childrenUserAccumulatedBalance'] = new Decimal(0);
|
||||
node['childrenUserAccumulatedStakedBalance'] = new Decimal(0);
|
||||
nodes[node.id] = node;
|
||||
node_addr_to_id[node.address] = node.id;
|
||||
console.log('init >>> ', node.id);
|
||||
//console.log("init >>> ", nodes[node.id])
|
||||
}
|
||||
|
||||
await fetch(blockTag);
|
||||
await wait(100);
|
||||
//await fetch2(blockTag)
|
||||
/*2
|
||||
for (const r of result) {
|
||||
if (r.status == 'fulfilled') {
|
||||
console.log(r.value)
|
||||
} else {
|
||||
console.log(r.reason)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
for (const node of records) {
|
||||
if (node.parentId) {
|
||||
if (child_list[node.parentId]) {
|
||||
const newList = child_list[node.parentId];
|
||||
newList.push(node.id);
|
||||
|
||||
child_list[node.parentId] = newList;
|
||||
} else {
|
||||
child_list[node.parentId] = [node.id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const node of records) {
|
||||
//console.log(node.id, ">>>", child_list[node.parentId], ">>>", node.parentId)
|
||||
}
|
||||
|
||||
/*
|
||||
let nodeId = "1";
|
||||
for (const child of child_list[nodeId]) {
|
||||
|
||||
}
|
||||
for (const node of records) {
|
||||
console.log(node.id, node.nickname)
|
||||
let cnt = 1;
|
||||
let parent = nodes[node.parentId]
|
||||
while (parent) {
|
||||
console.log("-".repeat(cnt), parent.id, parent.nickname)
|
||||
parent = nodes[parent.parentId] || null;
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
console.log('>>>>> updateSnapshotCPsAll:::buildNodeStructure done');
|
||||
|
||||
console.log('>>>>> updateSnapshotCPsAll:::updateSnapshotCPsV2 start');
|
||||
for (const n of records) {
|
||||
if (n.stakedBalance > 0) {
|
||||
console.log(n.id, n.stakedBalance);
|
||||
}
|
||||
updateSnapshotCPsV2(n.id);
|
||||
}
|
||||
console.log('>>>>> updateSnapshotCPsAll:::updateSnapshotCPsV2 done');
|
||||
|
||||
/*
|
||||
for (const node of records) {
|
||||
console.log(node.id, node.nickname)
|
||||
let cnt = 1;
|
||||
let parent = nodes[node.parentId]
|
||||
while (parent) {
|
||||
console.log("-".repeat(cnt), parent.id, parent.nickname)
|
||||
parent = nodes[parent.parentId] || null;
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
function updateSnapshotCPsV2(id) {
|
||||
const node = nodes[id];
|
||||
if (!node) {
|
||||
console.error('Node not found : ', id);
|
||||
return;
|
||||
}
|
||||
//const node_ = node[address];
|
||||
updateSnapshotCPV2(node.id);
|
||||
if (node.parentId) {
|
||||
//console.log("go to parent >>> ", node.parentId)
|
||||
let parent = nodes[node.parentId];
|
||||
console.log(node.id, '>>> go to parent >>> ', parent.id);
|
||||
while (parent) {
|
||||
updateSnapshotCPV2(parent.id);
|
||||
if (parent.parentId) {
|
||||
parent = nodes[parent.parentId];
|
||||
} else {
|
||||
console.log('dead end >> : ', parent.parentId);
|
||||
parent = null;
|
||||
}
|
||||
console.log('go to parent of parent >>> ', parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function updateSnapshotCPV2(id) {
|
||||
console.log('update : ', id);
|
||||
const node = nodes[id];
|
||||
if (!node) {
|
||||
console.error('Node not found : ', id);
|
||||
return;
|
||||
}
|
||||
|
||||
node.computingPower = new Decimal(0);
|
||||
node.childrenAccumulatedStakedBalance = new Decimal(0);
|
||||
node.childrenAccumulatedBalance = new Decimal(0);
|
||||
node.childrenUserAccumulatedCount = new Decimal(0);
|
||||
//node.childrenUserAccumulatedCount = new Decimal(0);
|
||||
node.childrenUserAccumulatedStakedBalance = new Decimal(0);
|
||||
const chlidren = child_list[id];
|
||||
// 자식들의 자산 합산
|
||||
if (chlidren && chlidren.length > 0) {
|
||||
for (const childId of chlidren) {
|
||||
const child = nodes[childId];
|
||||
node.childrenAccumulatedStakedBalance =
|
||||
node.childrenAccumulatedStakedBalance
|
||||
.add(child.stakedBalance)
|
||||
.add(child.childrenAccumulatedStakedBalance);
|
||||
node.childrenAccumulatedBalance = node.childrenAccumulatedBalance
|
||||
.add(child.balance)
|
||||
.add(child.childrenAccumulatedBalance);
|
||||
const cp = calcComputingPower(
|
||||
child.childrenAccumulatedStakedBalance.add(child.stakedBalance),
|
||||
);
|
||||
node.computingPower = node.computingPower.add(cp);
|
||||
|
||||
if (child.userId && child.userId != node.userId) {
|
||||
node.childrenUserAccumulatedBalance =
|
||||
node.childrenUserAccumulatedBalance.add(child.balance);
|
||||
node.childrenUserAccumulatedStakedBalance =
|
||||
node.childrenUserAccumulatedStakedBalance.add(
|
||||
child.stakedBalance,
|
||||
);
|
||||
}
|
||||
if (
|
||||
child.userId &&
|
||||
child.stakedBalance.gte(200_000_000_000_000_000_000)
|
||||
) {
|
||||
node.childrenUserAccumulatedCount =
|
||||
node.childrenUserAccumulatedCount.add(1);
|
||||
}
|
||||
node.childrenUserAccumulatedBalance =
|
||||
node.childrenUserAccumulatedBalance.add(
|
||||
child.childrenAccumulatedBalance,
|
||||
);
|
||||
node.childrenUserAccumulatedStakedBalance =
|
||||
node.childrenUserAccumulatedStakedBalance.add(
|
||||
child.childrenAccumulatedStakedBalance,
|
||||
);
|
||||
node.childrenUserAccumulatedCount =
|
||||
node.childrenUserAccumulatedCount.add(
|
||||
child.childrenUserAccumulatedCount,
|
||||
);
|
||||
}
|
||||
|
||||
// 자식 중 최대 컴퓨팅 파워의 경우 세제곱근 연산
|
||||
let max = new Decimal(0);
|
||||
for (const childId of chlidren) {
|
||||
const c = nodes[childId];
|
||||
const childrenTotal = c.childrenAccumulatedStakedBalance.add(
|
||||
c.stakedBalance,
|
||||
);
|
||||
if (max.lt(childrenTotal)) {
|
||||
max = childrenTotal;
|
||||
}
|
||||
}
|
||||
node.computingPower = node.computingPower
|
||||
.add(max.cbrt().mul(10 ** 12))
|
||||
.sub(calcComputingPower(max));
|
||||
}
|
||||
|
||||
nodes[id] = {
|
||||
...nodes[id],
|
||||
computingPower: node.computingPower,
|
||||
childrenAccumulatedBalance: node.childrenAccumulatedBalance,
|
||||
childrenAccumulatedStakedBalance: node.childrenAccumulatedStakedBalance,
|
||||
};
|
||||
//console.log(nodes[id])
|
||||
}
|
||||
|
||||
function calcComputingPower(amount) {
|
||||
// 자식의 파워 계산 (1만까지는 10배수, 그 이후는 1배수)
|
||||
//console.log(amount.div(10**18).toFixed(2), (new Decimal(10_000)).toFixed(2), amount.gte(new Decimal(10_000).mul(10 ** 18)))
|
||||
if (amount.gte(new Decimal(10_000).mul(10 ** 18))) {
|
||||
return amount.add(new Decimal(10_000 * 9).mul(10 ** 18));
|
||||
} else {
|
||||
return amount.mul(10);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
for (const target_date of targets) {
|
||||
list = [];
|
||||
fetchedBalanceList = [];
|
||||
nodes = {};
|
||||
node_addr_to_id = {};
|
||||
child_list = {};
|
||||
|
||||
//const target_date = 231126
|
||||
const blockTag = await findClosestBlockByTimestamp(
|
||||
date.toDate().getTime() / 1000,
|
||||
);
|
||||
await updateSnapshotCPsAll(blockTag);
|
||||
|
||||
const lastList = [];
|
||||
|
||||
let total_staked_amount = new Decimal(0);
|
||||
let total_cp_amount = new Decimal(0);
|
||||
|
||||
for (const address of addresses) {
|
||||
const id = node_addr_to_id[address];
|
||||
if (nodes[id].stakedBalance.gte(200_000_000_000_000_000_000)) {
|
||||
const pruned = nodes[id];
|
||||
total_staked_amount = total_staked_amount.add(pruned.stakedBalance);
|
||||
total_cp_amount = total_cp_amount.add(pruned.computingPower);
|
||||
}
|
||||
}
|
||||
|
||||
for (const address of addresses) {
|
||||
const id = node_addr_to_id[address];
|
||||
|
||||
if (nodes[id].stakedBalance.gte(200_000_000_000_000_000_000)) {
|
||||
//if (nodes[id].stakedBalance.gt(0) || nodes[id].balance.gt(0)) {
|
||||
const pruned = nodes[id];
|
||||
delete pruned.balance;
|
||||
|
||||
pruned['stakedBalance_pruned'] = pruned.stakedBalance
|
||||
.div(10 ** 18)
|
||||
.toString();
|
||||
pruned['computingPower_pruned'] = pruned.computingPower
|
||||
.div(10 ** 18)
|
||||
.toString();
|
||||
pruned['childrenAccumulatedBalance_pruned'] =
|
||||
pruned.childrenAccumulatedBalance.div(10 ** 18).toString();
|
||||
pruned['childrenAccumulatedStakedBalance_pruned'] =
|
||||
pruned.childrenAccumulatedStakedBalance.div(10 ** 18).toString();
|
||||
pruned['childrenUserAccumulatedCount_pruned'] =
|
||||
pruned.childrenUserAccumulatedCount.div(10 ** 18);
|
||||
pruned['childrenUserAccumulatedStakedBalance_pruned'] =
|
||||
pruned.childrenUserAccumulatedStakedBalance
|
||||
.div(10 ** 18)
|
||||
.toString();
|
||||
pruned['childrenUserAccumulatedBalance_pruned'] =
|
||||
pruned.childrenUserAccumulatedBalance.div(10 ** 18).toString();
|
||||
|
||||
pruned.childrenAccumulatedBalance =
|
||||
pruned.childrenAccumulatedBalance.toString();
|
||||
pruned.childrenAccumulatedStakedBalance =
|
||||
pruned.childrenAccumulatedStakedBalance.toString();
|
||||
pruned.childrenUserAccumulatedCount =
|
||||
pruned.childrenUserAccumulatedCount.toString();
|
||||
pruned.childrenUserAccumulatedStakedBalance =
|
||||
pruned.childrenUserAccumulatedStakedBalance.toString();
|
||||
pruned.childrenUserAccumulatedBalance =
|
||||
pruned.childrenUserAccumulatedBalance.toString();
|
||||
//pruned['ranking_estimated_dist'] = pruned.stakedBalance.mul(dist_amount).div(total_staked_amount).toString();
|
||||
//pruned['computingPower_estimated_dist'] = pruned.computingPower.mul(dist_amount).div(total_cp_amount).toString();
|
||||
|
||||
pruned['ranking_estimated_dist'] = -1;
|
||||
pruned['computingPower_estimated_dist'] = -1;
|
||||
pruned['rank'] = -1;
|
||||
pruned.stakedBalance = pruned.stakedBalance.toString();
|
||||
//pruned.balance = pruned.balance.toString();
|
||||
pruned.computingPower = pruned.computingPower.toString();
|
||||
lastList.push(pruned);
|
||||
}
|
||||
}
|
||||
|
||||
const csvFileData = await converter.json2csv(lastList);
|
||||
fs.writeFileSync(
|
||||
__dirname + `snapshot_${date.format('YYYY-MM-DD')}.csv`,
|
||||
csvFileData,
|
||||
);
|
||||
return __dirname + `snapshot_${date.format('YYYY-MM-DD')}.csv`;
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
.then((res) => {
|
||||
console.log('done', res);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
|
||||
async getToc(): Promise<Board | null> {
|
||||
const category = 'toc';
|
||||
if (!this.cache.has(category)) {
|
||||
const res = await this.prisma.category.findFirst({
|
||||
where: { name: category },
|
||||
});
|
||||
this.cache.set(category, res.id);
|
||||
}
|
||||
|
||||
return this.prisma.board.findFirst({
|
||||
where: { category: { id: this.cache.get(category) } },
|
||||
});
|
||||
}
|
||||
|
||||
async getTerm(): Promise<Board | null> {
|
||||
const category = 'term';
|
||||
if (!this.cache.has(category)) {
|
||||
const res = await this.prisma.category.findFirst({
|
||||
where: { name: category },
|
||||
});
|
||||
this.cache.set(category, res.id);
|
||||
}
|
||||
|
||||
return this.prisma.board.findFirst({
|
||||
where: { category: { id: this.cache.get(category) } },
|
||||
});
|
||||
}
|
||||
|
||||
async getPrivacy(): Promise<Board | null> {
|
||||
const category = 'privacy';
|
||||
if (!this.cache.has(category)) {
|
||||
const res = await this.prisma.category.findFirst({
|
||||
where: { name: category },
|
||||
});
|
||||
this.cache.set(category, res.id);
|
||||
}
|
||||
|
||||
return this.prisma.board.findFirst({
|
||||
where: { category: { id: this.cache.get(category) } },
|
||||
});
|
||||
}
|
||||
|
||||
async getNotices(): Promise<Board[] | null> {
|
||||
const category = 'notice';
|
||||
if (!this.cache.has(category)) {
|
||||
const res = await this.prisma.category.findFirst({
|
||||
where: { name: category },
|
||||
});
|
||||
this.cache.set(category, res.id);
|
||||
}
|
||||
|
||||
return this.prisma.board.findMany({
|
||||
where: { category: { id: this.cache.get(category) } },
|
||||
});
|
||||
}
|
||||
|
||||
async getNotice(id: number): Promise<Board | null> {
|
||||
const category = 'notice';
|
||||
if (!this.cache.has(category)) {
|
||||
const res = await this.prisma.category.findFirst({
|
||||
where: { name: category },
|
||||
});
|
||||
this.cache.set(category, res.id);
|
||||
}
|
||||
|
||||
return this.prisma.board.findFirst({
|
||||
where: { category: { id: this.cache.get(category) } },
|
||||
});
|
||||
}
|
||||
|
||||
async getFaqs(): Promise<Board[] | null> {
|
||||
const category = 'faq';
|
||||
if (!this.cache.has(category)) {
|
||||
const res = await this.prisma.category.findFirst({
|
||||
where: { name: category },
|
||||
});
|
||||
this.cache.set(category, res.id);
|
||||
}
|
||||
|
||||
return this.prisma.board.findMany({
|
||||
where: { category: { id: this.cache.get(category) } },
|
||||
});
|
||||
}
|
||||
|
||||
async getFaq(id: number): Promise<Board | null> {
|
||||
const category = 'faq';
|
||||
if (!this.cache.has(category)) {
|
||||
const res = await this.prisma.category.findFirst({
|
||||
where: { name: category },
|
||||
});
|
||||
this.cache.set(category, res.id);
|
||||
}
|
||||
|
||||
return this.prisma.board.findFirst({
|
||||
where: { category: { id: this.cache.get(category) } },
|
||||
});
|
||||
}
|
||||
|
||||
async getBanners(): Promise<Board[] | null> {
|
||||
const category = 'banner';
|
||||
if (!this.cache.has(category)) {
|
||||
const res = await this.prisma.category.findFirst({
|
||||
where: { name: category },
|
||||
});
|
||||
this.cache.set(category, res.id);
|
||||
}
|
||||
|
||||
return this.prisma.board.findMany({
|
||||
where: { category: { id: this.cache.get(category) } },
|
||||
});
|
||||
}
|
||||
|
||||
async getBanner(id: number): Promise<Board | null> {
|
||||
const category = 'banner';
|
||||
if (!this.cache.has(category)) {
|
||||
const res = await this.prisma.category.findFirst({
|
||||
where: { name: category },
|
||||
});
|
||||
this.cache.set(category, res.id);
|
||||
}
|
||||
|
||||
return this.prisma.board.findFirst({
|
||||
where: { category: { id: this.cache.get(category) } },
|
||||
});
|
||||
}
|
||||
|
||||
async getByTitle(title: string): Promise<Board | null> {
|
||||
const board = await this.prisma.board.findFirst({
|
||||
where: { title },
|
||||
});
|
||||
if (!board) {
|
||||
throw new Error(`Board with title ${title} not found`);
|
||||
}
|
||||
return board;
|
||||
}
|
||||
}
|
||||
15
src/components/DecimalComponent.tsx
Normal file
15
src/components/DecimalComponent.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
|
||||
const DecimalComponent: React.FC = (props: any) => {
|
||||
const { record, property } = props;
|
||||
const originalValue = record?.params[property.path] ?? 0;
|
||||
const transformedValue = (originalValue / 1e18).toFixed(4);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span>{transformedValue}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DecimalComponent;
|
||||
204
src/cron.ts
Normal file
204
src/cron.ts
Normal file
@@ -0,0 +1,204 @@
|
||||
import cron from 'node-cron';
|
||||
import {
|
||||
ContractEventName,
|
||||
DeferredTopicFilter,
|
||||
EventLog,
|
||||
TopicFilter,
|
||||
ethers,
|
||||
} from 'ethers';
|
||||
import { PrismaService } from './prisma.service';
|
||||
|
||||
export class cronjob {
|
||||
private prisma = new PrismaService();
|
||||
private jobs = [];
|
||||
constructor() {
|
||||
// this.jobs.push(cron.schedule('*/5 * * * * *', BKONTrnasferEventCron(this.prisma)));
|
||||
}
|
||||
}
|
||||
|
||||
export class JSONRPCCall {
|
||||
private jsonrpc: string;
|
||||
private provider;
|
||||
private ERC20abi = [
|
||||
'function balanceOf(address _owner) view returns (uint256)',
|
||||
'function owner() view returns (address)',
|
||||
'function name() view returns (string)',
|
||||
'function totalSupply() view returns (uint256)',
|
||||
'function decimals() view returns (uint8)',
|
||||
'function symbol() view returns (string)',
|
||||
'function transfer(address _to, uint256 value) returns (bool success)',
|
||||
'event Transfer(address indexed from, address indexed to, uint amount)',
|
||||
];
|
||||
|
||||
private DeFiabi = [
|
||||
'event Transfer(address indexed from, address indexed to, uint256 amount)',
|
||||
'event Distributed(uint256 round, address indexed to, uint256 amount)',
|
||||
'event Request(address indexed parent, address indexed child)',
|
||||
'event Accepted(address indexed parent, address indexed child)',
|
||||
'event Activated(address indexed node)',
|
||||
];
|
||||
|
||||
constructor(jsonrpc: string) {
|
||||
this.jsonrpc = jsonrpc;
|
||||
this.provider = new ethers.JsonRpcProvider(this.jsonrpc);
|
||||
}
|
||||
|
||||
async getERC20TranscationList(
|
||||
contractAddress: string,
|
||||
start: number,
|
||||
end?: number,
|
||||
): Promise<(ethers.EventLog | ethers.Log)[]> {
|
||||
const contract = new ethers.Contract(
|
||||
contractAddress,
|
||||
this.ERC20abi,
|
||||
this.provider,
|
||||
);
|
||||
const filter = contract.filters.Transfer(null, null);
|
||||
|
||||
const txes = await contract.queryFilter(filter, start, end ?? 'latest');
|
||||
return txes;
|
||||
}
|
||||
|
||||
async getDefiDistributedTranscationList(
|
||||
contractAddress: string,
|
||||
start: number,
|
||||
end?: number,
|
||||
): Promise<(ethers.EventLog | ethers.Log)[]> {
|
||||
const contract = new ethers.Contract(
|
||||
contractAddress,
|
||||
this.DeFiabi,
|
||||
this.provider,
|
||||
);
|
||||
const filter_transfer = contract.filters.Transfer(null, null);
|
||||
const filter_distributed = contract.filters.Distributed(null, null);
|
||||
const filter_request = contract.filters.Request(null, null);
|
||||
const filter_accepted = contract.filters.Accepted(null, null);
|
||||
const filter_activated = contract.filters.Activated(null);
|
||||
|
||||
const txes = await contract.queryFilter(
|
||||
filter_distributed,
|
||||
start,
|
||||
end ?? 'latest',
|
||||
);
|
||||
return txes;
|
||||
}
|
||||
|
||||
async getDefiRequestTranscationList(
|
||||
contractAddress: string,
|
||||
start: number,
|
||||
end?: number,
|
||||
): Promise<(ethers.EventLog | ethers.Log)[]> {
|
||||
const contract = new ethers.Contract(
|
||||
contractAddress,
|
||||
this.DeFiabi,
|
||||
this.provider,
|
||||
);
|
||||
const filter_request = contract.filters.Request(null, null);
|
||||
|
||||
const txes = await contract.queryFilter(
|
||||
filter_request,
|
||||
start,
|
||||
end ?? 'latest',
|
||||
);
|
||||
return txes;
|
||||
}
|
||||
|
||||
async getDefiAcceptedTranscationList(
|
||||
contractAddress: string,
|
||||
start: number,
|
||||
end?: number,
|
||||
): Promise<(ethers.EventLog | ethers.Log)[]> {
|
||||
const contract = new ethers.Contract(
|
||||
contractAddress,
|
||||
this.DeFiabi,
|
||||
this.provider,
|
||||
);
|
||||
const filter_accepted = contract.filters.Accepted(null, null);
|
||||
|
||||
const txes = await contract.queryFilter(
|
||||
filter_accepted,
|
||||
start,
|
||||
end ?? 'latest',
|
||||
);
|
||||
return txes;
|
||||
}
|
||||
|
||||
async getDefiActivatedTranscationList(
|
||||
contractAddress: string,
|
||||
start: number,
|
||||
end?: number,
|
||||
): Promise<(ethers.EventLog | ethers.Log)[]> {
|
||||
const contract = new ethers.Contract(
|
||||
contractAddress,
|
||||
this.DeFiabi,
|
||||
this.provider,
|
||||
);
|
||||
const filter_accepted = contract.filters.Accepted(null, null);
|
||||
|
||||
const txes = await contract.queryFilter(
|
||||
filter_accepted,
|
||||
start,
|
||||
end ?? 'latest',
|
||||
);
|
||||
return txes;
|
||||
}
|
||||
|
||||
/*
|
||||
async BKONTrnasferEventCron(prisma: PrismaService) {
|
||||
const jsonrpc = 'https://api.kon-wallet.com';
|
||||
const contractAddress = '0xFa4fcd169a04b94d47CdD9a2dB2637124fcD0Aa3';
|
||||
const contract = new JSONRPCCall(jsonrpc);
|
||||
const txes = await contract.getERC20TranscationList(contractAddress, 0);
|
||||
|
||||
for(const tx of txes) {
|
||||
const _tx = await prisma.txes.findFirst({where:{txid: tx.transactionHash}});
|
||||
const from = await prisma.node.findFirst({where:{address: (tx as EventLog).args.from}});
|
||||
const to = await prisma.node.findFirst({where:{address: (tx as EventLog).args.to}});
|
||||
|
||||
if(_tx == null && (from != null || to != null)) {
|
||||
await prisma.txes.create({
|
||||
data: {
|
||||
txid: tx.transactionHash,
|
||||
from: (tx as EventLog).args.from,
|
||||
to: (tx as EventLog).args.to,
|
||||
type: "transfer",
|
||||
value: (tx as EventLog).args.amount,
|
||||
blockNumber: tx.blockNumber,
|
||||
blockTimestamp: new Date(tx.blockNumber),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async DeFiTrnasferEventCron(prisma: PrismaService) {
|
||||
const jsonrpc = 'https://api.kon-wallet.com';
|
||||
const contractAddress = '0x6b123269F7c048B90d788Bb160ff9554Bf661496';
|
||||
const contract = new JSONRPCCall(jsonrpc);
|
||||
const txes = await contract.getDefiRequestTranscationList(contractAddress, 0);
|
||||
const txes = await contract.getDefiRequestTranscationList(contractAddress, 0);
|
||||
const txes = await contract.getDefiAcceptedTranscationList(contractAddress, 0);
|
||||
const txes = await contract.getDefiRequestTranscationList(contractAddress, 0);
|
||||
|
||||
for(const tx of txes) {
|
||||
const _tx = await prisma.txes.findFirst({where:{txid: tx.transactionHash}});
|
||||
const from = await prisma.node.findFirst({where:{address: (tx as EventLog).args.from}});
|
||||
const to = await prisma.node.findFirst({where:{address: (tx as EventLog).args.to}});
|
||||
|
||||
if(_tx == null && (from != null || to != null)) {
|
||||
await prisma.txes.create({
|
||||
data: {
|
||||
txid: tx.transactionHash,
|
||||
from: (tx as EventLog).args.from,
|
||||
to: (tx as EventLog).args.to,
|
||||
type: "transfer",
|
||||
value: (tx as EventLog).args.amount,
|
||||
blockNumber: tx.blockNumber,
|
||||
blockTimestamp: new Date(tx.blockNumber),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
48
src/firebase/dto/push.req.dto.ts
Normal file
48
src/firebase/dto/push.req.dto.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import {
|
||||
IsEmpty,
|
||||
IsNotEmpty,
|
||||
IsString,
|
||||
IsJSON,
|
||||
IsOptional,
|
||||
IsObject,
|
||||
} from 'class-validator';
|
||||
|
||||
export class PushReqDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({
|
||||
example: 'title',
|
||||
})
|
||||
title: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({
|
||||
example: 'message',
|
||||
})
|
||||
message: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
@ApiProperty({
|
||||
example: {
|
||||
test: 'test',
|
||||
},
|
||||
})
|
||||
data: object;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty({
|
||||
example: 'naver.com',
|
||||
})
|
||||
link: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({
|
||||
enum: ['SYSTEM', 'USER', 'TRANSACTION'],
|
||||
})
|
||||
type: string;
|
||||
}
|
||||
115
src/firebase/firebase.controller.spec.ts
Normal file
115
src/firebase/firebase.controller.spec.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { FirebaseController } from './firebase.controller';
|
||||
import { FirebaseService } from './firebase.service';
|
||||
import { PrismaService } from '../prisma.service';
|
||||
import { UserService } from '../user/user.service';
|
||||
|
||||
describe('FirebaseController', () => {
|
||||
let controller: FirebaseController;
|
||||
let prisma: jest.Mocked<PrismaService>;
|
||||
let firebase: jest.Mocked<FirebaseService>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const prismaMock = {
|
||||
// @ts-expect-error partial mock
|
||||
user: {
|
||||
findMany: jest.fn(),
|
||||
},
|
||||
// @ts-expect-error partial mock
|
||||
userNotification: {
|
||||
createMany: jest.fn(),
|
||||
},
|
||||
} as unknown as jest.Mocked<PrismaService>;
|
||||
|
||||
const firebaseMock = {
|
||||
// @ts-expect-error partial mock
|
||||
sendPushMessage: jest.fn(),
|
||||
} as unknown as jest.Mocked<FirebaseService>;
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [FirebaseController],
|
||||
providers: [
|
||||
{ provide: FirebaseService, useValue: firebaseMock },
|
||||
{ provide: PrismaService, useValue: prismaMock },
|
||||
{ provide: UserService, useValue: {} },
|
||||
],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<FirebaseController>(FirebaseController);
|
||||
prisma = module.get(PrismaService) as any;
|
||||
firebase = module.get(FirebaseService) as any;
|
||||
});
|
||||
|
||||
it('pushNoti sends notifications to user tokens and stores logs', async () => {
|
||||
const userId = 'user-123';
|
||||
const users = [
|
||||
{ userId, fcmToken: 'token-1' },
|
||||
{ userId, fcmToken: null },
|
||||
{ userId, fcmToken: 'token-2' },
|
||||
] as any[];
|
||||
|
||||
(prisma.user.findMany as any).mockResolvedValue(users);
|
||||
|
||||
const body = {
|
||||
title: 'Hello',
|
||||
message: 'World',
|
||||
data: { foo: 'bar' },
|
||||
link: 'https://example.com',
|
||||
type: 'SYSTEM',
|
||||
} as any;
|
||||
|
||||
const sendResult = [['msg-1', 'msg-2']];
|
||||
(firebase.sendPushMessage as any).mockResolvedValue(sendResult);
|
||||
|
||||
const result = await controller.pushNoti(body, userId);
|
||||
|
||||
expect(prisma.user.findMany).toHaveBeenCalledWith({ where: { userId } });
|
||||
expect(firebase.sendPushMessage).toHaveBeenCalledWith({
|
||||
tokens: ['token-1', 'token-2'],
|
||||
title: body.title,
|
||||
message: body.message,
|
||||
data: body.data,
|
||||
link: body.link,
|
||||
});
|
||||
expect(prisma.userNotification.createMany).toHaveBeenCalledWith({
|
||||
data: users.map((user: any) => ({
|
||||
userId: user.userId,
|
||||
title: body.title,
|
||||
body: body.message,
|
||||
data: JSON.stringify(body.data),
|
||||
link: body.link,
|
||||
type: body.type as any,
|
||||
})),
|
||||
});
|
||||
expect(result).toBe(sendResult);
|
||||
});
|
||||
|
||||
it('pushNoti still logs when no tokens are present', async () => {
|
||||
const userId = 'user-abc';
|
||||
const users = [{ userId, fcmToken: null }] as any[];
|
||||
|
||||
(prisma.user.findMany as any).mockResolvedValue(users);
|
||||
|
||||
const body = {
|
||||
title: 'No Tokens',
|
||||
message: 'Test',
|
||||
data: {},
|
||||
link: undefined,
|
||||
type: 'SYSTEM',
|
||||
} as any;
|
||||
|
||||
(firebase.sendPushMessage as any).mockResolvedValue(undefined);
|
||||
|
||||
const result = await controller.pushNoti(body, userId);
|
||||
|
||||
expect(firebase.sendPushMessage).toHaveBeenCalledWith({
|
||||
tokens: [],
|
||||
title: body.title,
|
||||
message: body.message,
|
||||
data: body.data,
|
||||
link: body.link,
|
||||
});
|
||||
expect(prisma.userNotification.createMany).toHaveBeenCalled();
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
91
src/firebase/firebase.controller.ts
Normal file
91
src/firebase/firebase.controller.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { Body, Controller, Param, Post } from '@nestjs/common';
|
||||
import { UserService } from '../user/user.service';
|
||||
import { FirebaseService } from './firebase.service';
|
||||
import { PushReqDto } from './dto/push.req.dto';
|
||||
import { ApiProperty, ApiTags } from '@nestjs/swagger';
|
||||
import { PrismaService } from '../prisma.service';
|
||||
|
||||
@Controller('firebase')
|
||||
@ApiTags('firebase')
|
||||
export class FirebaseController {
|
||||
constructor(
|
||||
readonly userService: UserService,
|
||||
readonly firebaseService: FirebaseService,
|
||||
readonly prisma: PrismaService,
|
||||
) {}
|
||||
|
||||
@ApiProperty()
|
||||
@Post('push')
|
||||
async pushNotiAll(@Body() body: PushReqDto) {
|
||||
const users = await this.prisma.user.findMany({});
|
||||
|
||||
const tokens = users
|
||||
.map((user) => {
|
||||
return user.fcmToken;
|
||||
})
|
||||
.filter((token) => !!token);
|
||||
|
||||
return await this.firebaseService
|
||||
.sendPushMessage({
|
||||
tokens,
|
||||
title: body.title,
|
||||
message: body.message,
|
||||
data: body.data,
|
||||
link: body.link,
|
||||
})
|
||||
.then(async (res) => {
|
||||
return await this.prisma.userNotification.createMany({
|
||||
data: users.map((user) => {
|
||||
return {
|
||||
userId: user.userId,
|
||||
title: body.title,
|
||||
body: body.message,
|
||||
data: JSON.stringify(body.data),
|
||||
link: body.link,
|
||||
type: body.type as any,
|
||||
};
|
||||
}),
|
||||
});
|
||||
});
|
||||
}
|
||||
@ApiProperty()
|
||||
@Post('push/:userId')
|
||||
async pushNoti(@Body() body: PushReqDto, @Param('userId') userId: string) {
|
||||
const users = await this.prisma.user.findMany({
|
||||
where: {
|
||||
userId,
|
||||
},
|
||||
});
|
||||
console.log(users);
|
||||
|
||||
const tokens = users
|
||||
.map((user) => {
|
||||
return user.fcmToken;
|
||||
})
|
||||
.filter((token) => !!token);
|
||||
|
||||
return await this.firebaseService
|
||||
.sendPushMessage({
|
||||
tokens,
|
||||
title: body.title,
|
||||
message: body.message,
|
||||
data: body.data,
|
||||
link: body.link,
|
||||
})
|
||||
.then(async (res) => {
|
||||
await this.prisma.userNotification.createMany({
|
||||
data: users.map((user) => {
|
||||
return {
|
||||
userId: user.userId,
|
||||
title: body.title,
|
||||
body: body.message,
|
||||
data: JSON.stringify(body.data),
|
||||
link: body.link,
|
||||
type: body.type as any,
|
||||
};
|
||||
}),
|
||||
});
|
||||
return res;
|
||||
});
|
||||
}
|
||||
}
|
||||
4
src/firebase/firebase.module.ts
Normal file
4
src/firebase/firebase.module.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
@Module({})
|
||||
export class FirebaseModule {}
|
||||
108
src/firebase/firebase.service.spec.ts
Normal file
108
src/firebase/firebase.service.spec.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { FirebaseService } from './firebase.service';
|
||||
import * as admin from 'firebase-admin';
|
||||
|
||||
// Mock firebase-admin messaging API (Jest hoists this above imports at runtime)
|
||||
jest.mock('firebase-admin', () => ({
|
||||
__esModule: true,
|
||||
messaging: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('FirebaseService.sendPushMessage', () => {
|
||||
let service: FirebaseService;
|
||||
let sendMock: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
service = new FirebaseService();
|
||||
|
||||
sendMock = jest
|
||||
.fn()
|
||||
.mockImplementation(({ token }: { token: string }) =>
|
||||
Promise.resolve(`mock:${token}`),
|
||||
);
|
||||
|
||||
// Each call to admin.messaging() should return an object with our send mock
|
||||
(admin.messaging as unknown as jest.Mock).mockReturnValue({
|
||||
send: sendMock,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('returns undefined and does not call messaging when tokens are empty', async () => {
|
||||
const result = await service.sendPushMessage({
|
||||
tokens: [],
|
||||
data: {},
|
||||
message: 'hello',
|
||||
title: 'title',
|
||||
});
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
expect(admin.messaging).not.toHaveBeenCalled();
|
||||
expect(sendMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('sends a notification for each token and returns results', async () => {
|
||||
const tokens = ['tokA', 'tokB', 'tokC'];
|
||||
|
||||
const result = await service.sendPushMessage({
|
||||
tokens,
|
||||
data: { foo: 'bar' },
|
||||
message: 'Body text',
|
||||
title: 'Title text',
|
||||
link: 'https://example.com',
|
||||
});
|
||||
|
||||
// Single batch result with all message IDs
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
expect(result!.length).toBe(1);
|
||||
expect(result![0]).toEqual(tokens.map((t) => `mock:${t}`));
|
||||
|
||||
// Called once per token
|
||||
expect(admin.messaging).toHaveBeenCalledTimes(tokens.length);
|
||||
expect(sendMock).toHaveBeenCalledTimes(tokens.length);
|
||||
|
||||
// Verify payload shape for first token
|
||||
expect(sendMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
token: 'tokA',
|
||||
notification: {
|
||||
title: 'Title text',
|
||||
body: 'Body text',
|
||||
},
|
||||
data: expect.objectContaining({
|
||||
foo: 'bar',
|
||||
webpush: 'https://example.com',
|
||||
title: 'Title text',
|
||||
body: 'Body text',
|
||||
}),
|
||||
android: expect.objectContaining({ priority: 'high' }),
|
||||
webpush: { fcmOptions: { link: 'https://example.com' } },
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('splits tokens into batches of 1000 and preserves order', async () => {
|
||||
const tokens = Array.from({ length: 1001 }, (_, i) => `t${i}`);
|
||||
|
||||
const result = await service.sendPushMessage({
|
||||
tokens,
|
||||
data: {},
|
||||
message: 'M',
|
||||
title: 'T',
|
||||
});
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result![0]).toHaveLength(1000);
|
||||
expect(result![1]).toHaveLength(1);
|
||||
|
||||
// Flatten and compare order/content
|
||||
const flat = result!.flat();
|
||||
expect(flat).toEqual(tokens.map((t) => `mock:${t}`));
|
||||
|
||||
// Messaging called once per token
|
||||
expect(admin.messaging).toHaveBeenCalledTimes(tokens.length);
|
||||
expect(sendMock).toHaveBeenCalledTimes(tokens.length);
|
||||
});
|
||||
});
|
||||
62
src/firebase/firebase.service.ts
Normal file
62
src/firebase/firebase.service.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import * as admin from 'firebase-admin';
|
||||
|
||||
@Injectable()
|
||||
export class FirebaseService {
|
||||
sendPush(tokens: string[], title: string, body: any) {}
|
||||
async sendPushMessage(pram: {
|
||||
tokens: string[];
|
||||
data: any;
|
||||
message: string;
|
||||
title: string;
|
||||
link?: string;
|
||||
}): Promise<any> {
|
||||
if (pram.tokens.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tokenSlice: string[][] = [];
|
||||
const r = Math.ceil(pram.tokens.length / 1000);
|
||||
for (let index = 0; index < r; index++) {
|
||||
const endIndex =
|
||||
index * 1000 + 1000 > pram.tokens.length - 1
|
||||
? pram.tokens.length
|
||||
: index * 1000 + 1000;
|
||||
tokenSlice.push(pram.tokens.slice(index * 1000, endIndex));
|
||||
}
|
||||
|
||||
const notices = [] as string[][];
|
||||
for (let index = 0; index < tokenSlice.length; index++) {
|
||||
const element = tokenSlice[index];
|
||||
|
||||
const results = await Promise.all(
|
||||
element.map((token) =>
|
||||
admin.messaging().send({
|
||||
token,
|
||||
notification: {
|
||||
title: pram.title,
|
||||
body: pram.message,
|
||||
},
|
||||
data: {
|
||||
...pram.data,
|
||||
webpush: pram.link ?? '',
|
||||
title: pram.title,
|
||||
body: pram.message,
|
||||
},
|
||||
android: {
|
||||
priority: 'high',
|
||||
collapseKey: `${pram.title}`,
|
||||
},
|
||||
...(pram.link
|
||||
? { webpush: { fcmOptions: { link: pram.link } } }
|
||||
: {}),
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
notices.push(results);
|
||||
}
|
||||
|
||||
return notices;
|
||||
}
|
||||
}
|
||||
250
src/index.js
Normal file
250
src/index.js
Normal file
File diff suppressed because one or more lines are too long
138
src/jsonrpc/index.ts
Normal file
138
src/jsonrpc/index.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
import { ethers } from 'ethers';
|
||||
|
||||
import ERC20Abi from '../assets/erc20-abi';
|
||||
import DeFiAbi from '../assets/defi-abi';
|
||||
import ERC20MultisenderAbi from '../assets/multisender-abi';
|
||||
|
||||
let wallet: ethers.Wallet | undefined;
|
||||
let defiContract: ethers.Contract | undefined;
|
||||
let multiSenderContract: ethers.Contract | undefined;
|
||||
let defiAddress: string | undefined;
|
||||
let erc20Address: string | undefined;
|
||||
let multisenderAddress: string | undefined;
|
||||
let privateKey: string | undefined;
|
||||
let initialized = false;
|
||||
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
export const provider = new ethers.JsonRpcProvider(
|
||||
'https://api.kon-wallet.com',
|
||||
);
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
export async function initEthers() {
|
||||
const { value: _defiAddress } =
|
||||
(await prisma.system.findUnique({ where: { key: 'defi-address' } })) || {};
|
||||
const { value: _erc20Address } =
|
||||
(await prisma.system.findUnique({ where: { key: 'erc20-address' } })) || {};
|
||||
const { value: _multisenderAddress } = (await prisma.system.findUnique({
|
||||
where: { key: 'multisender-address' },
|
||||
}))!;
|
||||
const { value: pk } =
|
||||
(await prisma.system.findUnique({ where: { key: 'admin-private-key' } })) ||
|
||||
{};
|
||||
privateKey = pk;
|
||||
defiAddress = _defiAddress;
|
||||
erc20Address = _erc20Address;
|
||||
multisenderAddress = _multisenderAddress;
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
// 이더리움 RPC 프로바이더 설정
|
||||
|
||||
// 주어진 유닉스 타임스탬프와 가장 가까운 블록 높이를 찾는 함수
|
||||
export async function findClosestBlockByTimestamp(targetTimestamp) {
|
||||
let latestBlock = await provider.getBlock('latest');
|
||||
let latestBlockNumber = latestBlock.number;
|
||||
let earliestBlockNumber = 0;
|
||||
|
||||
// 이진 탐색을 사용하여 가장 가까운 블록 찾기
|
||||
while (earliestBlockNumber <= latestBlockNumber) {
|
||||
let middleBlockNumber = Math.floor(
|
||||
(earliestBlockNumber + latestBlockNumber) / 2,
|
||||
);
|
||||
let middleBlock = await provider.getBlock(middleBlockNumber);
|
||||
|
||||
if (middleBlock.timestamp === targetTimestamp) {
|
||||
return middleBlock.number;
|
||||
} else if (middleBlock.timestamp < targetTimestamp) {
|
||||
earliestBlockNumber = middleBlockNumber + 1;
|
||||
} else {
|
||||
latestBlockNumber = middleBlockNumber - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 가장 가까운 블록을 찾기 위한 추가 로직
|
||||
let closestBlockNumber: number;
|
||||
let closestBlockTimestampDifference = Infinity;
|
||||
|
||||
for (let blockNumber of [earliestBlockNumber, latestBlockNumber]) {
|
||||
if (blockNumber >= 0 && blockNumber <= latestBlock.number) {
|
||||
let block = await provider.getBlock(blockNumber);
|
||||
let timestampDifference = Math.abs(block.timestamp - targetTimestamp);
|
||||
if (timestampDifference < closestBlockTimestampDifference) {
|
||||
closestBlockTimestampDifference = timestampDifference;
|
||||
closestBlockNumber = block.number;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return closestBlockNumber;
|
||||
}
|
||||
|
||||
export async function getWallet() {
|
||||
if (!initialized) await initEthers();
|
||||
if (!wallet) wallet = new ethers.Wallet(privateKey!, provider);
|
||||
return wallet;
|
||||
}
|
||||
|
||||
export async function getDeFiContract(): Promise<any> {
|
||||
if (!initialized) await initEthers();
|
||||
if (!wallet) wallet = new ethers.Wallet(privateKey!, provider);
|
||||
if (!defiContract)
|
||||
defiContract = new ethers.Contract(defiAddress!, DeFiAbi, wallet);
|
||||
|
||||
return defiContract;
|
||||
}
|
||||
|
||||
export async function getTokenContract(): Promise<any> {
|
||||
if (!initialized) await initEthers();
|
||||
console.log('pk', privateKey);
|
||||
if (!wallet) wallet = new ethers.Wallet(privateKey!, provider);
|
||||
console.log('wallet:', wallet);
|
||||
if (!defiContract)
|
||||
defiContract = new ethers.Contract(erc20Address, ERC20Abi, wallet);
|
||||
|
||||
return defiContract;
|
||||
}
|
||||
|
||||
export async function updateActive(nodeAddresses: string[]): Promise<string[]> {
|
||||
const defiContract = getDeFiContract();
|
||||
const nodes = await prisma.node.findMany({
|
||||
where: {
|
||||
address: {
|
||||
in: nodeAddresses,
|
||||
},
|
||||
},
|
||||
});
|
||||
console.log(nodes.length);
|
||||
var rtn = [];
|
||||
for (const node of nodes) {
|
||||
try {
|
||||
console.log(node.address);
|
||||
if (!node.isActivated) {
|
||||
const enabled = await (await defiContract).enabled(node.address);
|
||||
console.log(node.address, enabled);
|
||||
if (enabled) {
|
||||
var s = await prisma.node.update({
|
||||
where: { address: node.address },
|
||||
data: { isActivated: true },
|
||||
});
|
||||
rtn.push(node.address);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
return rtn;
|
||||
}
|
||||
18
src/locked-coin/locked-coin.controller.spec.ts
Normal file
18
src/locked-coin/locked-coin.controller.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { LockedCoinController } from './locked-coin.controller';
|
||||
|
||||
describe('LockedCoinController', () => {
|
||||
let controller: LockedCoinController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [LockedCoinController],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<LockedCoinController>(LockedCoinController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
17
src/locked-coin/locked-coin.controller.ts
Normal file
17
src/locked-coin/locked-coin.controller.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Controller, Get, Param, Query } from '@nestjs/common';
|
||||
import { LockedCoinService } from './locked-coin.service';
|
||||
import { ApiProperty, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
@ApiTags('locked-coin')
|
||||
@Controller('locked-coin')
|
||||
export class LockedCoinController {
|
||||
constructor(private readonly lockedCoinService: LockedCoinService) {}
|
||||
@Get()
|
||||
@ApiProperty()
|
||||
async byAddress(
|
||||
@Query('address') address: string,
|
||||
@Query('reason') reason: string,
|
||||
) {
|
||||
return await this.lockedCoinService.queryLockedCoin(address, reason);
|
||||
}
|
||||
}
|
||||
9
src/locked-coin/locked-coin.module.ts
Normal file
9
src/locked-coin/locked-coin.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { LockedCoinService } from './locked-coin.service';
|
||||
import { LockedCoinController } from './locked-coin.controller';
|
||||
|
||||
@Module({
|
||||
providers: [LockedCoinService],
|
||||
controllers: [LockedCoinController],
|
||||
})
|
||||
export class LockedCoinModule {}
|
||||
18
src/locked-coin/locked-coin.service.spec.ts
Normal file
18
src/locked-coin/locked-coin.service.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { LockedCoinService } from './locked-coin.service';
|
||||
|
||||
describe('LockedCoinService', () => {
|
||||
let service: LockedCoinService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [LockedCoinService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<LockedCoinService>(LockedCoinService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
49
src/locked-coin/locked-coin.service.ts
Normal file
49
src/locked-coin/locked-coin.service.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { PrismaService } from 'src/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class LockedCoinService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async queryLockedCoin(address: string, reason: string) {
|
||||
const [lockedCoin, aggregateResult, notSended] =
|
||||
await this.prisma.$transaction([
|
||||
this.prisma.lockedCoin.findMany({
|
||||
where: {
|
||||
address: address,
|
||||
reason: reason,
|
||||
},
|
||||
}),
|
||||
this.prisma.lockedCoin.aggregate({
|
||||
_sum: {
|
||||
amount: true,
|
||||
},
|
||||
where: {
|
||||
address: address,
|
||||
reason: reason,
|
||||
},
|
||||
}),
|
||||
this.prisma.lockedCoin.aggregate({
|
||||
where: {
|
||||
address: address,
|
||||
reason: reason,
|
||||
NOT: [
|
||||
{
|
||||
sendedAt: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
_sum: {
|
||||
amount: true,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
lockedCoin,
|
||||
totalLocked: aggregateResult._sum.amount,
|
||||
reamained: notSended ? notSended._sum.amount : 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
132
src/logging.ts
Normal file
132
src/logging.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* @fileoverview 요청/응답 로깅을 위한 인터셉터
|
||||
* @module logging.interceptor
|
||||
*/
|
||||
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
} from '@nestjs/common';
|
||||
import { Observable, tap } from 'rxjs';
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
/**
|
||||
* 요청과 응답을 로깅하는 인터셉터
|
||||
* @class LoggingInterceptor
|
||||
* @implements {NestInterceptor}
|
||||
* @description
|
||||
* - 모든 HTTP 요청과 응답을 데이터베이스에 로깅
|
||||
* - 특정 경로는 로깅에서 제외
|
||||
* - 민감한 정보(비밀번호 등)는 마스킹 처리
|
||||
*/
|
||||
@Injectable()
|
||||
export class LoggingInterceptor implements NestInterceptor {
|
||||
/** 로깅에서 제외할 경로 목록 */
|
||||
private logExcludePaths: string[] = [];
|
||||
/** 로깅에서 제외할 시작 경로 목록 */
|
||||
private logExcludePathsStartingWith: string[] = [];
|
||||
|
||||
/**
|
||||
* LoggingInterceptor 생성자
|
||||
* @constructor
|
||||
* @param {Repository<Log>} logRepository - 로그 저장소
|
||||
*/
|
||||
constructor() {
|
||||
this.logExcludePaths =
|
||||
process.env.LOG_EXCLUDE_PATHS?.split(',')?.map((v) => v.trim()) || [];
|
||||
this.logExcludePathsStartingWith =
|
||||
process.env.LOG_EXCLUDE_PATHS_STARTING_WITH?.split(',')?.map((v) =>
|
||||
v.trim(),
|
||||
) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 요청을 가로채서 로깅하는 메서드
|
||||
* @method intercept
|
||||
* @param {ExecutionContext} context - 실행 컨텍스트
|
||||
* @param {CallHandler<any>} next - 다음 핸들러
|
||||
* @returns {Promise<Observable<any>>} 응답 옵저버블
|
||||
* @description
|
||||
* - 요청 정보(IP, URL, 메서드, 바디 등) 로깅
|
||||
* - 응답 데이터 로깅
|
||||
* - 에러 발생 시 에러 정보 로깅
|
||||
*/
|
||||
intercept(
|
||||
context: ExecutionContext,
|
||||
next: CallHandler<any>,
|
||||
): Observable<any> {
|
||||
const request: Request = context.switchToHttp().getRequest();
|
||||
// get ip address
|
||||
const ip = request.socket.remoteAddress;
|
||||
const url = request.url;
|
||||
const inExcludePaths = this.logExcludePaths.includes(url.split('?')[0]);
|
||||
const inExcludePathsStartingWith = this.logExcludePathsStartingWith.some(
|
||||
(path) => url.startsWith(path),
|
||||
);
|
||||
|
||||
if (inExcludePaths || inExcludePathsStartingWith) {
|
||||
console.log(
|
||||
'\x1b[93m',
|
||||
new Date().toLocaleString('ko-KR', { hour12: false }),
|
||||
'EXC',
|
||||
request.method.toString().toUpperCase(),
|
||||
'\x1b[0m',
|
||||
request.url,
|
||||
'',
|
||||
ip,
|
||||
);
|
||||
return next.handle();
|
||||
}
|
||||
console.log(
|
||||
'\x1b[94m',
|
||||
new Date().toLocaleString('ko-KR', { hour12: false }),
|
||||
'REQ',
|
||||
request.method.toString().toUpperCase(),
|
||||
'\x1b[0m',
|
||||
request.url,
|
||||
JSON.stringify(request.body),
|
||||
ip,
|
||||
);
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
return next.handle().pipe(
|
||||
tap({
|
||||
next: (response) => {
|
||||
// exclude logs for admin/log in any case to prevent infinite loop
|
||||
if (url.includes('admin/log')) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
'\x1b[93m',
|
||||
new Date().toLocaleString('ko-KR', { hour12: false }),
|
||||
'RES',
|
||||
request.method.toString().toUpperCase(),
|
||||
'\x1b[0m',
|
||||
request.url,
|
||||
JSON.stringify(response),
|
||||
ip,
|
||||
);
|
||||
|
||||
return response;
|
||||
},
|
||||
error: (error) => {
|
||||
console.log(
|
||||
'\x1b[91m',
|
||||
new Date().toLocaleString('ko-KR', { hour12: false }),
|
||||
'ERR',
|
||||
request.method.toString().toUpperCase(),
|
||||
'\x1b[0m',
|
||||
request.url,
|
||||
JSON.stringify(error),
|
||||
ip,
|
||||
);
|
||||
return error;
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
71
src/main.ts
Normal file
71
src/main.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||
import { AppModule } from './app.module';
|
||||
import {
|
||||
ValidationPipe,
|
||||
VersioningOptions,
|
||||
VersioningType,
|
||||
} from '@nestjs/common';
|
||||
import * as admin from 'firebase-admin';
|
||||
import { initializeApp } from 'firebase-admin/app';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
|
||||
declare global {
|
||||
interface BigInt {
|
||||
toJSON(): string;
|
||||
}
|
||||
}
|
||||
|
||||
BigInt.prototype.toJSON = function () {
|
||||
return this.toString();
|
||||
};
|
||||
|
||||
async function bootstrap() {
|
||||
BigInt.prototype['toJSON'] = function () {
|
||||
const int = Number.parseInt(this.toString());
|
||||
return int ?? this.toString();
|
||||
};
|
||||
const params = {
|
||||
projectId: process.env.project_id,
|
||||
privateKeyId: process.env.private_key_id,
|
||||
// privateKey: process.env.private_key.replace(/\\n/g, '\n'),
|
||||
clientEmail: process.env.client_email,
|
||||
clientId: process.env.client_id,
|
||||
authUri: process.env.auth_uri,
|
||||
tokenUri: process.env.token_uri,
|
||||
authProviderX509CertUrl: process.env.auth_provider_x509_cert_url,
|
||||
clientC509CertUrl: process.env.client_x509_cert_url,
|
||||
};
|
||||
|
||||
// await initializeApp({
|
||||
// credential: admin.credential.cert(params),
|
||||
// databaseURL: process.env.firebase_url,
|
||||
// });
|
||||
|
||||
const app = await NestFactory.create(AppModule);
|
||||
app.enableCors();
|
||||
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({
|
||||
transform: true,
|
||||
transformOptions: { enableImplicitConversion: true },
|
||||
}),
|
||||
);
|
||||
app.enableVersioning({
|
||||
type: VersioningType.URI,
|
||||
});
|
||||
|
||||
const config = new DocumentBuilder()
|
||||
.setTitle('BKON API')
|
||||
.setDescription('BKON API Service')
|
||||
.setVersion('1.0.0')
|
||||
.addTag('swagger')
|
||||
.addBearerAuth()
|
||||
.build();
|
||||
|
||||
const document = SwaggerModule.createDocument(app, config);
|
||||
SwaggerModule.setup('swagger', app, document);
|
||||
|
||||
await app.listen(process.env.PORT || 4000);
|
||||
}
|
||||
bootstrap();
|
||||
5
src/middleware/jwt.auth.guard.ts
Normal file
5
src/middleware/jwt.auth.guard.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
|
||||
@Injectable()
|
||||
export class JwtAuthGuard extends AuthGuard('jwt') {}
|
||||
19
src/middleware/jwt.strategy.ts
Normal file
19
src/middleware/jwt.strategy.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
import { UserService } from '../user/user.service';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||
constructor(readonly userService: UserService) {
|
||||
super({
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
ignoreExpiration: false,
|
||||
secretOrKey: 'MNCOWINS1010!!!!',
|
||||
});
|
||||
}
|
||||
|
||||
async validate(payload: any) {
|
||||
return await this.userService.findUserById(payload.userId);
|
||||
}
|
||||
}
|
||||
55
src/node/dto/node.query.dto.ts
Normal file
55
src/node/dto/node.query.dto.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Transform, Type, plainToClass } from 'class-transformer';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsEmpty,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
} from 'class-validator';
|
||||
const optionalBooleanMapper = {
|
||||
true: true,
|
||||
false: false,
|
||||
};
|
||||
export default class NodeQueryDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@ApiProperty({
|
||||
nullable: true,
|
||||
required: false,
|
||||
})
|
||||
readonly status?: any;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@ApiProperty({
|
||||
nullable: true,
|
||||
required: false,
|
||||
})
|
||||
readonly depth?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@ApiProperty({
|
||||
nullable: true,
|
||||
required: false,
|
||||
})
|
||||
readonly address?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@ApiProperty({
|
||||
nullable: true,
|
||||
required: false,
|
||||
})
|
||||
readonly myAddress?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@ApiProperty({
|
||||
nullable: true,
|
||||
required: false,
|
||||
})
|
||||
readonly nickname?: string;
|
||||
}
|
||||
18
src/node/node.controller.spec.ts
Normal file
18
src/node/node.controller.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { NodeController } from './node.controller';
|
||||
|
||||
describe('NodeController', () => {
|
||||
let controller: NodeController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [NodeController],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<NodeController>(NodeController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
289
src/node/node.controller.ts
Normal file
289
src/node/node.controller.ts
Normal file
@@ -0,0 +1,289 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Param,
|
||||
Body,
|
||||
Query,
|
||||
UseGuards,
|
||||
Request,
|
||||
Patch,
|
||||
Delete,
|
||||
Put,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiCreatedResponse,
|
||||
ApiOperation,
|
||||
ApiTags,
|
||||
ApiBody,
|
||||
ApiProperty,
|
||||
ApiBearerAuth,
|
||||
ApiQuery,
|
||||
} from '@nestjs/swagger';
|
||||
import { Node as NodeModel } from '@prisma/client';
|
||||
import { NodeService } from './node.service';
|
||||
import { JwtAuthGuard } from '../middleware/jwt.auth.guard';
|
||||
import NodeQueryDto from './dto/node.query.dto';
|
||||
import { PrismaService } from '../prisma.service';
|
||||
|
||||
export class setReferralDto {
|
||||
@ApiProperty({
|
||||
example: '0xaaabbbccc',
|
||||
description: 'address',
|
||||
required: true,
|
||||
})
|
||||
address: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'refferal data',
|
||||
description: 'refferal',
|
||||
required: true,
|
||||
})
|
||||
referral: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'eth signed checksum',
|
||||
description: 'checksum',
|
||||
required: true,
|
||||
})
|
||||
checksum: string;
|
||||
}
|
||||
|
||||
export class patchNodeDto {
|
||||
@ApiProperty({
|
||||
example: 'refferal data',
|
||||
description: 'nick',
|
||||
required: true,
|
||||
})
|
||||
nickname: string;
|
||||
}
|
||||
export class applyNodeRefferalDto {
|
||||
@ApiProperty({
|
||||
example: 'refferal data',
|
||||
description: 'refferal',
|
||||
required: true,
|
||||
})
|
||||
referralCode: string;
|
||||
}
|
||||
|
||||
export class createNodeDto {
|
||||
@ApiProperty({
|
||||
example: '0xaaabbbccc',
|
||||
description: 'address',
|
||||
required: true,
|
||||
})
|
||||
address: string;
|
||||
}
|
||||
|
||||
export class setNicknameDto {
|
||||
@ApiProperty({
|
||||
example: '0xaaabbbccc',
|
||||
description: 'address',
|
||||
required: true,
|
||||
})
|
||||
address: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'nickname data',
|
||||
description: 'nickname',
|
||||
required: true,
|
||||
})
|
||||
nickname: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'eth signed checksum',
|
||||
description: 'checksum',
|
||||
required: true,
|
||||
})
|
||||
checksum: string;
|
||||
}
|
||||
|
||||
@Controller({
|
||||
path: 'node',
|
||||
version: '1',
|
||||
})
|
||||
@ApiTags('노드 채굴 API')
|
||||
export class NodeController {
|
||||
constructor(
|
||||
private readonly nodeService: NodeService,
|
||||
private readonly prisma: PrismaService,
|
||||
) {}
|
||||
|
||||
@ApiQuery({
|
||||
name: 'nickname',
|
||||
example: 'nickname',
|
||||
|
||||
required: false,
|
||||
})
|
||||
@ApiQuery({
|
||||
name: 'referralCode',
|
||||
example: 'referralCode',
|
||||
required: false,
|
||||
})
|
||||
@Get('/exist')
|
||||
@ApiOperation({ summary: '', description: '' })
|
||||
async checkNodeDuplicate(
|
||||
@Query() query: { nickname?: string; referralCode?: string },
|
||||
): Promise<any> {
|
||||
const nodes = await this.nodeService.getNodeList(query);
|
||||
return {
|
||||
hasDuplicate: nodes.length > 0,
|
||||
};
|
||||
}
|
||||
|
||||
@Put('/need-check')
|
||||
@ApiOperation({ summary: '', description: '' })
|
||||
async getNeedCheckNode(@Body() body): Promise<any> {
|
||||
const address = body.address;
|
||||
|
||||
return await this.nodeService.getNeedCheckNode(address);
|
||||
}
|
||||
|
||||
@ApiProperty({})
|
||||
@Patch('/rmLowerCase')
|
||||
async getReferralList(): Promise<any> {
|
||||
await this.prisma.node.findMany().then(async (nodes) => {
|
||||
for (let node of nodes) {
|
||||
await this.prisma.node.update({
|
||||
where: { id: node.id },
|
||||
data: {
|
||||
referral: node.referral.toUpperCase(),
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Get('/referral/:code')
|
||||
async getReferral(@Param('code') code: string): Promise<any> {
|
||||
return await this.nodeService.findByReferral(code);
|
||||
}
|
||||
|
||||
@Get('/:address')
|
||||
getNodeByAddress(@Param('address') address: string): any {
|
||||
return this.nodeService.getNodeByAddress(address);
|
||||
}
|
||||
|
||||
@Post('/referral')
|
||||
setReferral(@Body() body: setReferralDto): any {
|
||||
return this.nodeService.setReferral(
|
||||
body.address,
|
||||
body.referral,
|
||||
body.checksum,
|
||||
);
|
||||
}
|
||||
|
||||
@Get('/computing-power/:address')
|
||||
getCP(@Param('address') address: string): any {
|
||||
return this.nodeService.getNodeComputingPower(address);
|
||||
}
|
||||
|
||||
@Get('/rank/:address')
|
||||
getRank(@Param('address') address: string): any {
|
||||
return this.nodeService.getNodeRank(address);
|
||||
}
|
||||
|
||||
@Get('/admin/rank-list')
|
||||
getAdminRankList(): any {
|
||||
return this.nodeService.getNodeLevelList();
|
||||
}
|
||||
|
||||
@Get('/active-node/:address')
|
||||
getDeactiveNode(@Param('address') address: string): any {
|
||||
return this.nodeService.getNodeActiveCount(address);
|
||||
}
|
||||
|
||||
@Get('/deactive-node/:address')
|
||||
@ApiOperation({ summary: '해당 유저의 Node 정보 Fetch', description: '' })
|
||||
getActiveNode(@Param('address') address: string): any {
|
||||
return this.nodeService.getNodeDeactiveCount(address);
|
||||
}
|
||||
|
||||
@Get('/children-balance/:address')
|
||||
@ApiOperation({ summary: '해당 유저의 Node 정보 Fetch', description: '' })
|
||||
getChildrenBalance(@Param('address') address: string): any {
|
||||
return this.nodeService.getChildrenStakedBalance(address);
|
||||
}
|
||||
|
||||
@Post('/nickname')
|
||||
@ApiOperation({ summary: '', description: '' })
|
||||
setNodeNickname(@Body() body: setNicknameDto): any {
|
||||
return this.nodeService.setNodeNickname(body.address, body.nickname);
|
||||
}
|
||||
|
||||
@Get('/nickname/:nickname')
|
||||
@ApiOperation({ summary: '', description: '' })
|
||||
getNodeNickname(@Param('nickname') nickname: string): any {
|
||||
return this.nodeService.checkNodeNickname(nickname);
|
||||
}
|
||||
|
||||
@Get('/check-nickname/:nickname')
|
||||
@ApiOperation({ summary: '', description: '' })
|
||||
checkNickname(@Param('nickname') nickname: string): any {
|
||||
return this.nodeService.getNodeNickname(nickname);
|
||||
}
|
||||
|
||||
@Get('/:address')
|
||||
@ApiOperation({ summary: '', description: '' })
|
||||
//@Param('id') id: number
|
||||
getNode(@Param('address') address: string): any {
|
||||
return this.nodeService.getNode(address);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Patch('/:id')
|
||||
@ApiOperation({ summary: '', description: '' })
|
||||
patchNode(@Param('id') id: number, @Body() body: patchNodeDto): any {
|
||||
return this.nodeService.patchNode(id, body);
|
||||
}
|
||||
|
||||
@Post('/')
|
||||
@ApiBody({})
|
||||
@ApiOperation({ summary: '', description: '' })
|
||||
@UseGuards(JwtAuthGuard)
|
||||
createNode(@Request() req: any, @Body() body: createNodeDto): any {
|
||||
return this.nodeService.createNode(req.user.userId, body.address);
|
||||
}
|
||||
|
||||
@Post('/:address/refferal')
|
||||
@ApiOperation({ summary: '', description: '' })
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
applyNodeReferral(
|
||||
@Param('address') address: string,
|
||||
@Request() req: any,
|
||||
@Body() body: applyNodeRefferalDto,
|
||||
): any {
|
||||
return this.nodeService.applyNodeReffal(
|
||||
req.user.userId,
|
||||
address,
|
||||
body.referralCode,
|
||||
);
|
||||
}
|
||||
|
||||
@Get('/:address/refferal')
|
||||
@ApiOperation({ summary: '', description: '' })
|
||||
//@Param('id') id: number
|
||||
getNodeReferral(@Param('address') address: string): any {
|
||||
return this.nodeService.getNodeReferral(address);
|
||||
}
|
||||
|
||||
@Get('/')
|
||||
@ApiOperation({ summary: '', description: '' })
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
getnode(@Request() req: any, @Query() query: NodeQueryDto): any {
|
||||
var userId = req.user.userId;
|
||||
return this.nodeService.getNodeQuery(userId, query);
|
||||
}
|
||||
|
||||
@Delete('/:id')
|
||||
@ApiOperation({ summary: '', description: '' })
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
deleteNode(@Request() req: any, @Param('id') id: number): any {
|
||||
var userId = req.user.userId;
|
||||
return this.nodeService.deleteNode(userId, id);
|
||||
}
|
||||
}
|
||||
9
src/node/node.module.ts
Normal file
9
src/node/node.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { NodeService } from './node.service';
|
||||
import { NodeController } from './node.controller';
|
||||
|
||||
@Module({
|
||||
providers: [NodeService],
|
||||
controllers: [NodeController],
|
||||
})
|
||||
export class NodeModule {}
|
||||
18
src/node/node.service.spec.ts
Normal file
18
src/node/node.service.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { NodeService } from './node.service';
|
||||
|
||||
describe('NodeService', () => {
|
||||
let service: NodeService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [NodeService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<NodeService>(NodeService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
549
src/node/node.service.ts
Normal file
549
src/node/node.service.ts
Normal file
@@ -0,0 +1,549 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma.service';
|
||||
import { Node, Prisma } from '@prisma/client';
|
||||
import { generateRandomString } from '../utils/randomstring';
|
||||
import { createNodeDto, patchNodeDto } from './node.controller';
|
||||
import NodeQueryDto from './dto/node.query.dto';
|
||||
import { redis } from '../redis';
|
||||
import { ethers } from 'ethers';
|
||||
import { updateActive } from '../jsonrpc';
|
||||
import { Decimal } from '@prisma/client/runtime/library';
|
||||
|
||||
const QUERY = function (nodeId, depth) {
|
||||
return `
|
||||
WITH RECURSIVE Subtree AS (
|
||||
SELECT
|
||||
id,
|
||||
address,
|
||||
balance,
|
||||
stakedBalance,
|
||||
depositedBalance,
|
||||
computingPower,
|
||||
childrenAccumulatedBalance,
|
||||
childrenAccumulatedStakedBalance,
|
||||
childrenAccumulatedCount,
|
||||
childrenAccumulatedActiveCount,
|
||||
parentId,
|
||||
isActivated,
|
||||
nickname,
|
||||
referral,
|
||||
depth,
|
||||
childrenDepth,
|
||||
0 AS qdepth
|
||||
FROM
|
||||
Node
|
||||
WHERE
|
||||
id = ${nodeId}
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
n.id,
|
||||
n.address,
|
||||
n.balance,
|
||||
n.stakedBalance,
|
||||
n.depositedBalance,
|
||||
n.computingPower,
|
||||
n.childrenAccumulatedBalance,
|
||||
n.childrenAccumulatedStakedBalance,
|
||||
n.childrenAccumulatedCount,
|
||||
n.childrenAccumulatedActiveCount,
|
||||
n.parentId,
|
||||
n.isActivated,
|
||||
n.nickname,
|
||||
n.referral,
|
||||
n.depth,
|
||||
n.childrenDepth,
|
||||
st.qdepth + 1 AS qdepth
|
||||
FROM
|
||||
Node n
|
||||
INNER JOIN
|
||||
Subtree st ON n.parentId = st.id
|
||||
WHERE
|
||||
st.qdepth < ${depth}
|
||||
)
|
||||
|
||||
SELECT
|
||||
id,
|
||||
address,
|
||||
balance,
|
||||
depositedBalance,
|
||||
stakedBalance,
|
||||
computingPower,
|
||||
childrenAccumulatedBalance,
|
||||
childrenAccumulatedStakedBalance,
|
||||
childrenAccumulatedActiveCount,
|
||||
childrenAccumulatedCount,
|
||||
parentId,
|
||||
isActivated,
|
||||
nickname,
|
||||
referral,
|
||||
depth,
|
||||
childrenDepth
|
||||
FROM
|
||||
Subtree
|
||||
WHERE
|
||||
qdepth > 0;
|
||||
`;
|
||||
};
|
||||
const MAX_DEPTH_QUERY = function (nodeId) {
|
||||
return `
|
||||
WITH RECURSIVE NodeHierarchy AS (
|
||||
SELECT
|
||||
id,
|
||||
parentId,
|
||||
0 AS depth
|
||||
FROM
|
||||
Node
|
||||
WHERE
|
||||
id = ${nodeId}
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
n.id,
|
||||
n.parentId,
|
||||
nh.depth + 1 AS depth
|
||||
FROM
|
||||
Node n
|
||||
INNER JOIN
|
||||
NodeHierarchy nh ON n.parentId = nh.id
|
||||
)
|
||||
|
||||
SELECT
|
||||
MAX(depth) AS max_depth,
|
||||
COUNT(*) AS total_children_count
|
||||
FROM
|
||||
NodeHierarchy;
|
||||
|
||||
`;
|
||||
};
|
||||
|
||||
const NODE_LEVEL_QUERY = `
|
||||
WITH ranking_list as (
|
||||
SELECT
|
||||
n.address,
|
||||
n.stakedBalance,
|
||||
RANK() OVER(ORDER BY stakedBalance) as ranking
|
||||
FROM
|
||||
Node n
|
||||
WHERE
|
||||
n.stakedBalance >= 200000000000000000000
|
||||
ORDER BY stakedBalance DESC
|
||||
) SELECT
|
||||
ranking
|
||||
FROM
|
||||
ranking_list
|
||||
WHERE
|
||||
address = ?;
|
||||
`;
|
||||
|
||||
const NODE_LEVEL_LIST_QUERY = `
|
||||
WITH ranking_list as (
|
||||
SELECT
|
||||
n.address,
|
||||
n.stakedBalance,
|
||||
n.computingPower,
|
||||
n.childrenAccumulatedBalance,
|
||||
RANK() OVER(ORDER BY stakedBalance) as ranking
|
||||
FROM
|
||||
Node n
|
||||
WHERE
|
||||
stakedBalance >= 200000000000000000000
|
||||
ORDER BY stakedBalance DESC
|
||||
) SELECT
|
||||
address,
|
||||
ranking,
|
||||
stakedBalance,
|
||||
computingPower,
|
||||
childrenAccumulatedBalance
|
||||
FROM
|
||||
ranking_list;
|
||||
`;
|
||||
|
||||
const NODE_TOTAL_LEVEL_SUM_QUERY = `
|
||||
WITH ranking_list as (
|
||||
SELECT
|
||||
n.address,
|
||||
n.stakedBalance,
|
||||
RANK() OVER(ORDER BY stakedBalance) as ranking
|
||||
FROM
|
||||
Node n
|
||||
WHERE
|
||||
n.stakedBalance >= 200000000000000000000
|
||||
ORDER BY stakedBalance DESC
|
||||
) SELECT
|
||||
SUM(ranking)
|
||||
FROM
|
||||
ranking_list;
|
||||
`;
|
||||
|
||||
const NODE_TOTAL_COMPUTINGPOWER_SUM_QUERY = `
|
||||
SELECT
|
||||
SUM(computingPower)
|
||||
FROM
|
||||
Node;
|
||||
WHERE
|
||||
isActivated = true AND stakedBalance >= 200000000000000000000;
|
||||
`;
|
||||
|
||||
// const NODE_HEIGHT_QUERY = ( id : number) => `
|
||||
// WITH RECURSIVE NodeHierarchy AS (
|
||||
// SELECT id, parentId, 1 AS height
|
||||
// FROM Node
|
||||
// WHERE id = ${id}
|
||||
// UNION ALL
|
||||
// SELECT n.id, n.parentId, nh.height + 1
|
||||
// FROM NodeHierarchy nh
|
||||
// JOIN Node n ON nh.id = n.parentId
|
||||
// )
|
||||
// SELECT id, height
|
||||
// FROM NodeHierarchy
|
||||
// WHERE id <> ${id};
|
||||
// `
|
||||
|
||||
let checkNodes = [];
|
||||
@Injectable()
|
||||
export class NodeService {
|
||||
async getNeedCheckNode(address: string): Promise<any> {
|
||||
checkNodes.push(address);
|
||||
const remove = await updateActive(checkNodes);
|
||||
|
||||
console.log('remove : ', remove);
|
||||
checkNodes = checkNodes.filter((item) => !remove.includes(item));
|
||||
|
||||
return remove;
|
||||
}
|
||||
async findByReferral(code: string): Promise<any> {
|
||||
console.log('code : ', code);
|
||||
return await this.prisma.node.findFirstOrThrow({
|
||||
where: { referral: code },
|
||||
});
|
||||
}
|
||||
async deleteNode(userId: string, id: number): Promise<any> {
|
||||
return await this.prisma.node.update({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
data: {
|
||||
userId: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getNodeDepth() {}
|
||||
async patchNode(id: number, body: patchNodeDto): Promise<any> {
|
||||
return await this.prisma.node.update({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
data: {
|
||||
nickname: body.nickname,
|
||||
},
|
||||
});
|
||||
}
|
||||
async applyNodeReffal(
|
||||
userId: any,
|
||||
address: any,
|
||||
referralCode: any,
|
||||
): Promise<any> {
|
||||
const node = await this.prisma.node.findFirstOrThrow({
|
||||
where: {
|
||||
userId,
|
||||
address,
|
||||
},
|
||||
});
|
||||
console.log('node : ', node);
|
||||
|
||||
console.log('node : ', referralCode);
|
||||
const referralNode = await this.prisma.node.findFirstOrThrow({
|
||||
where: {
|
||||
referral: referralCode,
|
||||
},
|
||||
});
|
||||
|
||||
console.log('referralNode : ', referralNode);
|
||||
|
||||
if (node.parentId) {
|
||||
throw new Error('Already applied referral');
|
||||
}
|
||||
if (node.address === referralNode.address) {
|
||||
throw new Error('You cannot apply your own referral code');
|
||||
}
|
||||
console.log('referralNode', referralNode.id);
|
||||
console.log('node', node.id);
|
||||
console.log('referralNode', referralNode.depth);
|
||||
|
||||
return await this.prisma.node.update({
|
||||
where: {
|
||||
id: node.id,
|
||||
},
|
||||
data: {
|
||||
parent: {
|
||||
connect: {
|
||||
id: referralNode.id,
|
||||
},
|
||||
},
|
||||
depth: referralNode.depth + 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async getNodeByAddress(address: string): Promise<Object | null> {
|
||||
const { value: dividnece } = await this.prisma.system.findUnique({
|
||||
where: { key: 'distribution-amount' },
|
||||
});
|
||||
const node = await this.prisma.node.findFirst({
|
||||
where: { address: address },
|
||||
include: {
|
||||
children: true,
|
||||
},
|
||||
});
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
async updateChildrenDepth(): Promise<any> {
|
||||
const nodes = await this.prisma.node.findMany({
|
||||
orderBy: {
|
||||
depth: 'desc',
|
||||
},
|
||||
take: 1,
|
||||
include: {
|
||||
parent: true,
|
||||
},
|
||||
});
|
||||
const node = nodes[0];
|
||||
node.childrenDepth = 0;
|
||||
|
||||
node.parent.childrenDepth = node.childrenDepth + 1;
|
||||
}
|
||||
|
||||
async setReferral(
|
||||
address: string,
|
||||
referral: string,
|
||||
checksum: string,
|
||||
): Promise<Object> {
|
||||
const updatedNode = await this.prisma.node.updateMany({
|
||||
where: { address: { equals: address } },
|
||||
data: { referral: referral },
|
||||
});
|
||||
if (updatedNode.count > 0) {
|
||||
return { isSet: true };
|
||||
} else {
|
||||
return { isSet: false };
|
||||
}
|
||||
}
|
||||
|
||||
async getNodeComputingPower(address: string): Promise<number> {
|
||||
const node = await this.prisma.node.findUnique({
|
||||
where: { address: address },
|
||||
});
|
||||
return (
|
||||
Math.floor(
|
||||
node.computingPower.div(new Decimal(10).pow(18)).toNumber() * 100,
|
||||
) / 100 || 0
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
async getChildrenStakedBalance(address: string): Promise<number> {
|
||||
const node = await this.prisma.node.findUnique({
|
||||
where: { address: address },
|
||||
});
|
||||
return (
|
||||
Math.floor(
|
||||
node.childrenAccumulatedBalance
|
||||
.div(new Decimal(10).pow(18))
|
||||
.toNumber() * 100,
|
||||
) / 100 || 0
|
||||
);
|
||||
}
|
||||
|
||||
async getNodeActiveCount(address: string): Promise<number> {
|
||||
const node = await this.prisma.node.findUnique({
|
||||
where: { address: address },
|
||||
});
|
||||
return node.childrenAccumulatedActiveCount.toNumber() || 0;
|
||||
}
|
||||
|
||||
async getNodeDeactiveCount(address: string): Promise<number> {
|
||||
const node = await this.prisma.node.findUnique({
|
||||
where: { address: address },
|
||||
});
|
||||
return node.childrenAccumulatedDeactiveCount.toNumber() || 0;
|
||||
}
|
||||
|
||||
async getNodeLevel(address: string) {
|
||||
const res = await this.prisma.$queryRawUnsafe(NODE_LEVEL_QUERY, address);
|
||||
//console.log(res)
|
||||
return res[0]?.ranking || 0;
|
||||
}
|
||||
|
||||
async getNodeLevelList() {
|
||||
const res: [] = await this.prisma.$queryRawUnsafe(NODE_LEVEL_LIST_QUERY);
|
||||
//console.log(res)
|
||||
return res.map((node: any) => {
|
||||
node.stakedBalance = BigInt(node.stakedBalance.toNumber());
|
||||
node.computingPower = BigInt(node.computingPower.toNumber());
|
||||
node.childrenAccumulatedBalance = BigInt(
|
||||
node.childrenAccumulatedBalance.toNumber(),
|
||||
);
|
||||
return node;
|
||||
});
|
||||
}
|
||||
|
||||
async getNodeRank(address: string): Promise<number | null> {
|
||||
return await this.getNodeLevel(address);
|
||||
}
|
||||
|
||||
async getNodeGraph(address: string, depth: number): Promise<Node | null> {
|
||||
return this.prisma.node.findFirst({
|
||||
where: { address: address },
|
||||
include: { children: true, parent: true },
|
||||
});
|
||||
}
|
||||
|
||||
async setNodeNickname(address: string, nickname: string): Promise<boolean> {
|
||||
const first = await this.prisma.node.findFirst({
|
||||
where: { address: address },
|
||||
});
|
||||
if (!first || !first.nickname) {
|
||||
const res = await this.prisma.node.updateMany({
|
||||
where: { address: address },
|
||||
data: { nickname: nickname },
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async getNodeNickname(address: string): Promise<string | null> {
|
||||
const node = await this.prisma.node.findFirst({
|
||||
where: { address: address },
|
||||
select: { nickname: true },
|
||||
});
|
||||
return node?.nickname ?? null;
|
||||
}
|
||||
|
||||
async checkNodeNickname(nickname: string): Promise<Object> {
|
||||
const node = await this.prisma.node.findFirst({
|
||||
where: { nickname: nickname },
|
||||
});
|
||||
if (node) {
|
||||
return { isNickname: true, address: node.address ?? '' };
|
||||
} else {
|
||||
return { isNickname: false };
|
||||
}
|
||||
}
|
||||
|
||||
async getNodeList(props: {
|
||||
nickname?: string;
|
||||
referralCode?: string;
|
||||
}): Promise<any> {
|
||||
return await this.prisma.node.findMany({
|
||||
where: {
|
||||
nickname: props.nickname,
|
||||
referral: props.referralCode,
|
||||
},
|
||||
});
|
||||
}
|
||||
async getNodeQuery(userId: string, props: NodeQueryDto): Promise<any> {
|
||||
var { address, nickname, depth, status, myAddress } = props;
|
||||
|
||||
status =
|
||||
typeof status === 'undefined'
|
||||
? undefined
|
||||
: status === 'true'
|
||||
? true
|
||||
: false;
|
||||
//console.log("props : ", props);
|
||||
|
||||
var t = await this.prisma.node.findUnique({
|
||||
where: { address: myAddress },
|
||||
});
|
||||
|
||||
var maxCount = await this.prisma.$queryRawUnsafe(MAX_DEPTH_QUERY(t.id));
|
||||
var data = await this.prisma.$queryRawUnsafe<any[]>(
|
||||
QUERY(t.id, depth ?? 100),
|
||||
);
|
||||
|
||||
data = [
|
||||
{
|
||||
...t,
|
||||
isActivated: t.isActivated ? 1 : 0,
|
||||
},
|
||||
...data,
|
||||
];
|
||||
|
||||
if (typeof status !== 'undefined') {
|
||||
data = data.filter((node) => {
|
||||
return status == 1 ? node.isActivated : !node.isActivated;
|
||||
});
|
||||
}
|
||||
|
||||
if (nickname) {
|
||||
data = data.filter((node) => {
|
||||
console.log(node.nickname);
|
||||
return node.nickname?.includes(nickname);
|
||||
});
|
||||
}
|
||||
if (address) {
|
||||
data = data.filter((node) => {
|
||||
console.log(node.address);
|
||||
return node.address.includes(address);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
maxCount,
|
||||
data,
|
||||
};
|
||||
}
|
||||
async getNode(address: string): Promise<Node | null> {
|
||||
return this.prisma.node.findFirst({
|
||||
where: { address: address },
|
||||
include: {
|
||||
children: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async createNode(userId: string, address: string): Promise<Node | null> {
|
||||
const data: Prisma.NodeCreateInput = {
|
||||
address,
|
||||
referral: generateRandomString(7).toUpperCase(),
|
||||
User: {
|
||||
connect: {
|
||||
userId: userId,
|
||||
},
|
||||
},
|
||||
};
|
||||
return await this.prisma.node.upsert({
|
||||
where: { address: address },
|
||||
update: {
|
||||
address: data.address,
|
||||
User: data.User,
|
||||
},
|
||||
create: data,
|
||||
});
|
||||
}
|
||||
|
||||
async getNodeReferral(address: string): Promise<any> {
|
||||
const node = await this.prisma.node.findFirstOrThrow({
|
||||
where: { address: address },
|
||||
});
|
||||
return node;
|
||||
}
|
||||
|
||||
/*
|
||||
async setNodeParent(address: string, parent: string) {
|
||||
const node = await this.prisma.node.updateMany({where:{ address: address}})
|
||||
}
|
||||
|
||||
async setNodeChildren(address: string, children: string[]) {
|
||||
}
|
||||
*/
|
||||
}
|
||||
17
src/node2address/node2address.controller.ts
Normal file
17
src/node2address/node2address.controller.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
|
||||
import { Node2AddressService } from './node2address.service';
|
||||
|
||||
@Controller('node2address')
|
||||
export class Node2AddressController {
|
||||
constructor(private readonly node2AddressService: Node2AddressService) {}
|
||||
|
||||
@Get('/:userId')
|
||||
getHello(@Param('userId') userId: string) {
|
||||
return this.node2AddressService.getN2A(userId);
|
||||
}
|
||||
|
||||
@Post('/:userId')
|
||||
writeN2A(@Param('userId') userId: string, @Body('address') body: any) {
|
||||
return this.node2AddressService.writeN2A(userId, body);
|
||||
}
|
||||
}
|
||||
9
src/node2address/node2address.module.ts
Normal file
9
src/node2address/node2address.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { Node2AddressService } from './node2address.service';
|
||||
import { Node2AddressController } from './node2address.controller';
|
||||
|
||||
@Module({
|
||||
providers: [Node2AddressService],
|
||||
controllers: [Node2AddressController],
|
||||
})
|
||||
export class Node2AddressModule {}
|
||||
31
src/node2address/node2address.service.ts
Normal file
31
src/node2address/node2address.service.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Get, Injectable } from '@nestjs/common';
|
||||
import { skip } from 'node:test';
|
||||
import { PrismaService } from 'src/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class Node2AddressService {
|
||||
static getN2A(userId: string) {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async getN2A(userId: string) {
|
||||
return await this.prisma.userToAddressBackup.findMany({
|
||||
where: {
|
||||
userId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async writeN2A(userId: string, address: string[]) {
|
||||
return await this.prisma.userToAddressBackup.createMany({
|
||||
data: address.map((address) => {
|
||||
return {
|
||||
userId,
|
||||
address,
|
||||
};
|
||||
}),
|
||||
skipDuplicates: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
9
src/prisma.module.ts
Normal file
9
src/prisma.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { PrismaService } from './prisma.service';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [PrismaService],
|
||||
exports: [PrismaService],
|
||||
})
|
||||
export class PrismaModule {}
|
||||
9
src/prisma.service.ts
Normal file
9
src/prisma.service.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
@Injectable()
|
||||
export class PrismaService extends PrismaClient implements OnModuleInit {
|
||||
async onModuleInit() {
|
||||
await this.$connect();
|
||||
}
|
||||
}
|
||||
11
src/redis.ts
Normal file
11
src/redis.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import Redis from 'ioredis';
|
||||
|
||||
const host = process.env.REDIS_HOST || '127.0.0.1';
|
||||
const port = process.env.REDIS_PORT ? Number(process.env.REDIS_PORT) : 6379;
|
||||
const password = process.env.REDIS_PASSWORD || undefined;
|
||||
|
||||
export const redis = new Redis({
|
||||
host,
|
||||
port,
|
||||
password,
|
||||
});
|
||||
221
src/schema.dbml
Normal file
221
src/schema.dbml
Normal file
@@ -0,0 +1,221 @@
|
||||
//// ------------------------------------------------------
|
||||
//// THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
|
||||
//// ------------------------------------------------------
|
||||
|
||||
Project "BKON Wallet Schema" {
|
||||
database_type: 'mysql'
|
||||
Note: 'BKON Wallet Distributor Table'
|
||||
}
|
||||
|
||||
Table Board {
|
||||
id Int [pk, increment]
|
||||
title String
|
||||
body String
|
||||
imgUrl String
|
||||
count Int [not null, default: 0]
|
||||
categoryId Int
|
||||
createdAt DateTime [default: `now()`, not null]
|
||||
updatedAt DateTime [default: `now()`, not null]
|
||||
link String
|
||||
category Category
|
||||
}
|
||||
|
||||
Table Category {
|
||||
id Int [pk, increment]
|
||||
name String [not null]
|
||||
createdAt DateTime [default: `now()`, not null]
|
||||
updatedAt DateTime [default: `now()`, not null]
|
||||
Board Board [not null]
|
||||
}
|
||||
|
||||
Table Node {
|
||||
id Int [pk, increment]
|
||||
address String [unique, not null]
|
||||
balance Decimal [not null, default: 0]
|
||||
stakedBalance Decimal [not null, default: 0]
|
||||
computingPower Decimal [not null, default: 0]
|
||||
parentId Int
|
||||
isActivated Boolean [not null, default: false]
|
||||
nickname String
|
||||
referral String [unique, not null]
|
||||
createdAt DateTime [default: `now()`, not null]
|
||||
updatedAt DateTime [default: `now()`, not null]
|
||||
userId String
|
||||
childrenAccumulatedBalance Decimal [not null, default: 0]
|
||||
childrenAccumulatedCount Decimal [not null, default: 0]
|
||||
childrenAccumulatedActiveCount Decimal [not null, default: 0]
|
||||
childrenAccumulatedDeactiveCount Decimal [not null, default: 0]
|
||||
childrenAccumulatedStakedBalance Decimal [not null, default: 0]
|
||||
childrenDepth Int [not null, default: 0]
|
||||
depth Int [not null, default: 0]
|
||||
depositedBalance Decimal [not null, default: 0]
|
||||
realName String
|
||||
parent Node
|
||||
children Node [not null]
|
||||
User User
|
||||
}
|
||||
|
||||
Table Txes {
|
||||
id Int [pk, increment]
|
||||
txid String [not null]
|
||||
from String [not null]
|
||||
to String [not null]
|
||||
value Decimal [not null, default: 0]
|
||||
fee Decimal [not null, default: 0]
|
||||
internalFee Decimal [not null, default: 0]
|
||||
type String [not null]
|
||||
blockTimestamp DateTime [not null]
|
||||
createdAt DateTime [default: `now()`, not null]
|
||||
updatedAt DateTime [default: `now()`, not null]
|
||||
}
|
||||
|
||||
Table User {
|
||||
userId String [pk]
|
||||
IMEI String
|
||||
mac String
|
||||
nickname String
|
||||
fcmToken String
|
||||
createdAt DateTime [default: `now()`, not null]
|
||||
updatedAt DateTime [default: `now()`, not null]
|
||||
pushAllowAt DateTime
|
||||
language String
|
||||
Node Node [not null]
|
||||
favoriteAddress favoriteAddress [not null]
|
||||
userNotification userNotification [not null]
|
||||
}
|
||||
|
||||
Table favoriteAddress {
|
||||
address String [not null]
|
||||
createdAt DateTime [default: `now()`, not null]
|
||||
updatedAt DateTime [default: `now()`, not null]
|
||||
userId String [not null]
|
||||
nickname String
|
||||
User User [not null]
|
||||
|
||||
indexes {
|
||||
(address, userId) [pk]
|
||||
}
|
||||
}
|
||||
|
||||
Table userNotification {
|
||||
id String [pk]
|
||||
title String [not null]
|
||||
body String [not null]
|
||||
data Json [not null]
|
||||
userId String [not null]
|
||||
type NotificationType [not null]
|
||||
createdAt DateTime [default: `now()`, not null]
|
||||
updatedAt DateTime [default: `now()`, not null]
|
||||
link String [not null]
|
||||
user User [not null]
|
||||
}
|
||||
|
||||
Table userToAddressBackup {
|
||||
userId String [not null]
|
||||
address String [not null]
|
||||
createdAt DateTime [default: `now()`, not null]
|
||||
updatedAt DateTime [default: `now()`, not null]
|
||||
|
||||
indexes {
|
||||
(userId, address) [pk]
|
||||
}
|
||||
}
|
||||
|
||||
Table System {
|
||||
id Int [pk, increment]
|
||||
key String [unique, not null]
|
||||
value String [not null]
|
||||
createdAt DateTime [default: `now()`, not null]
|
||||
updatedAt DateTime [default: `now()`, not null]
|
||||
}
|
||||
|
||||
Table snapshot {
|
||||
id Int [pk]
|
||||
address String [unique, not null]
|
||||
nickname String
|
||||
parentId Int
|
||||
stakedBalance Decimal
|
||||
computingPower Decimal
|
||||
childrenAccumulatedBalance Decimal
|
||||
isActivated Boolean [not null]
|
||||
balance Decimal
|
||||
childrenAccumulatedCount Decimal
|
||||
childrenAccumulatedDeactiveCount Decimal
|
||||
childrenAccumulatedStakedBalance Decimal
|
||||
Txid String
|
||||
isCPSent Boolean [not null]
|
||||
isRankSent Boolean [not null]
|
||||
isCPSendStarted Boolean [not null]
|
||||
isRankSendStarted Boolean [not null]
|
||||
}
|
||||
|
||||
Table distribution {
|
||||
id Int [pk]
|
||||
nickname String
|
||||
address String
|
||||
parentId Int
|
||||
ranking BigInt
|
||||
stakedBalance Float
|
||||
computingPower Float
|
||||
childrenAccumulatedBalance Float
|
||||
stakedBalanceOrig Decimal
|
||||
computingPowerOrig Decimal
|
||||
childrenAccumulatedBalanceOrig Decimal
|
||||
ranking_portion Decimal
|
||||
staked_portion Decimal
|
||||
computingPower_portion Decimal
|
||||
childrenAccumulatedBalance_portion Decimal
|
||||
ranking_estimated_dist Decimal
|
||||
computingPower_estimated_dist Decimal
|
||||
Txid String
|
||||
isCPSent Boolean [not null]
|
||||
isRankSent Boolean [not null]
|
||||
isCPSendStarted Boolean [not null]
|
||||
isRankSendStarted Boolean [not null]
|
||||
}
|
||||
|
||||
Table LockedCoin {
|
||||
id String [pk]
|
||||
address String [not null]
|
||||
amount Decimal [not null, default: 0]
|
||||
createdAt DateTime [default: `now()`, not null]
|
||||
updatedAt DateTime [default: `now()`, not null]
|
||||
unlocked Boolean [not null, default: false]
|
||||
unlockAt DateTime
|
||||
reason String
|
||||
txId String
|
||||
sendedAt DateTime
|
||||
}
|
||||
|
||||
Table TransferHistories {
|
||||
idx BigInt [pk, increment]
|
||||
txhash String [not null]
|
||||
from_address String [not null]
|
||||
to_address String [not null]
|
||||
amount Decimal [not null]
|
||||
memo String
|
||||
timestamp DateTime [default: `now()`, not null]
|
||||
createdAt DateTime [default: `now()`, not null]
|
||||
log_index BigInt [not null]
|
||||
blockNumber BigInt [not null]
|
||||
|
||||
indexes {
|
||||
(txhash, log_index, timestamp) [unique]
|
||||
}
|
||||
}
|
||||
|
||||
Enum NotificationType {
|
||||
SYSTEM
|
||||
USER
|
||||
TRANSACTION
|
||||
}
|
||||
|
||||
Ref: Board.categoryId > Category.id
|
||||
|
||||
Ref: Node.parentId - Node.id
|
||||
|
||||
Ref: Node.userId > User.userId
|
||||
|
||||
Ref: favoriteAddress.userId > User.userId
|
||||
|
||||
Ref: userNotification.userId > User.userId
|
||||
18
src/style.css
Normal file
18
src/style.css
Normal file
@@ -0,0 +1,18 @@
|
||||
body {
|
||||
font-family: helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#cy {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
h1 {
|
||||
opacity: 0.5;
|
||||
font-size: 1em;
|
||||
}
|
||||
18
src/system/system.controller.spec.ts
Normal file
18
src/system/system.controller.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { SystemController } from './system.controller';
|
||||
|
||||
describe('SystemController', () => {
|
||||
let controller: SystemController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [SystemController],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<SystemController>(SystemController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
156
src/system/system.controller.ts
Normal file
156
src/system/system.controller.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import { Controller, Get, Post, Param, Body } from '@nestjs/common';
|
||||
import {
|
||||
ApiCreatedResponse,
|
||||
ApiOperation,
|
||||
ApiTags,
|
||||
ApiBody,
|
||||
ApiProperty,
|
||||
} from '@nestjs/swagger';
|
||||
import { PrismaService } from '../prisma.service';
|
||||
import { ethers } from 'ethers';
|
||||
|
||||
class system {
|
||||
@ApiProperty()
|
||||
bkonUsdPrice: number;
|
||||
@ApiProperty()
|
||||
transferFee: number;
|
||||
@ApiProperty()
|
||||
activationFee: number;
|
||||
@ApiProperty()
|
||||
dividend: number;
|
||||
}
|
||||
|
||||
class price {
|
||||
@ApiProperty()
|
||||
bkonUsdPrice: number;
|
||||
}
|
||||
|
||||
class fee {
|
||||
@ApiProperty()
|
||||
transferFee: number;
|
||||
@ApiProperty()
|
||||
activationFee: number;
|
||||
@ApiProperty()
|
||||
stakingFee: number;
|
||||
@ApiProperty()
|
||||
unstakingFee: number;
|
||||
}
|
||||
|
||||
class systemwallet {
|
||||
@ApiProperty()
|
||||
distributionAddress: string;
|
||||
@ApiProperty()
|
||||
dividendPoolAddress: string;
|
||||
}
|
||||
|
||||
class dividend {
|
||||
@ApiProperty()
|
||||
dividend: number;
|
||||
}
|
||||
|
||||
@Controller({
|
||||
path: 'system',
|
||||
version: '1',
|
||||
})
|
||||
@ApiTags('시스템 API')
|
||||
export class SystemController {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
@Get('/')
|
||||
@ApiOperation({
|
||||
summary: '시스템 정보 가져오기',
|
||||
description: '가격/수수료/배당액을 가져온다',
|
||||
})
|
||||
async getAll(): Promise<system> {
|
||||
const { value: activationFee } = await this.prisma.system.findUnique({
|
||||
where: { key: 'fee-enable-wallet' },
|
||||
});
|
||||
const { value: transferFee } = await this.prisma.system.findUnique({
|
||||
where: { key: 'fee-transfer' },
|
||||
});
|
||||
const { value: bkonUsdPice } = await this.prisma.system.findUnique({
|
||||
where: { key: 'bkon-price' },
|
||||
});
|
||||
const { value: distributionAmount } = await this.prisma.system.findUnique({
|
||||
where: { key: 'distribution-amount' },
|
||||
});
|
||||
return {
|
||||
bkonUsdPrice: parseFloat(bkonUsdPice),
|
||||
transferFee: parseFloat(ethers.formatEther(transferFee)),
|
||||
activationFee: parseFloat(ethers.formatEther(activationFee)),
|
||||
dividend: parseFloat(ethers.formatEther(distributionAmount)),
|
||||
};
|
||||
}
|
||||
|
||||
@Get('/price')
|
||||
@ApiOperation({
|
||||
summary: '가격 정보 가져오기',
|
||||
description: 'BKON 가격 정보를 가져오기',
|
||||
})
|
||||
async getPrice(): Promise<price> {
|
||||
const { value: bkonUsdPice } = await this.prisma.system.findUnique({
|
||||
where: { key: 'bkon-price' },
|
||||
});
|
||||
return {
|
||||
bkonUsdPrice: parseFloat(bkonUsdPice),
|
||||
};
|
||||
}
|
||||
|
||||
@Get('/fee')
|
||||
@ApiOperation({
|
||||
summary: '수수료 정보 가져오기',
|
||||
description: 'BKON 작업 관련 수수료에 대해서 받아온다',
|
||||
})
|
||||
async getFee(): Promise<fee> {
|
||||
const { value: activationFee } = await this.prisma.system.findUnique({
|
||||
where: { key: 'fee-enable-wallet' },
|
||||
});
|
||||
const { value: transferFee } = await this.prisma.system.findUnique({
|
||||
where: { key: 'fee-transfer' },
|
||||
});
|
||||
const { value: stakingFee } = await this.prisma.system.findUnique({
|
||||
where: { key: 'fee-staking' },
|
||||
});
|
||||
const { value: unstakingFee } = await this.prisma.system.findUnique({
|
||||
where: { key: 'fee-unstaking' },
|
||||
});
|
||||
return {
|
||||
transferFee: parseFloat(ethers.formatEther(transferFee)),
|
||||
activationFee: parseFloat(ethers.formatEther(activationFee)),
|
||||
stakingFee: parseFloat(ethers.formatEther(stakingFee)),
|
||||
unstakingFee: parseFloat(ethers.formatEther(unstakingFee)),
|
||||
};
|
||||
}
|
||||
|
||||
@Get('/system-wallet')
|
||||
@ApiOperation({
|
||||
summary: '수수료 정보 가져오기',
|
||||
description: 'BKON 작업 관련 수수료에 대해서 받아온다',
|
||||
})
|
||||
async getWallet(): Promise<systemwallet> {
|
||||
const { value: distributionAddress } = await this.prisma.system.findUnique({
|
||||
where: { key: 'distribution-address' },
|
||||
});
|
||||
const { value: dividendPoolAddress } = await this.prisma.system.findUnique({
|
||||
where: { key: 'dividend-pool-address' },
|
||||
});
|
||||
return {
|
||||
distributionAddress: distributionAddress,
|
||||
dividendPoolAddress: dividendPoolAddress,
|
||||
};
|
||||
}
|
||||
|
||||
@Get('/dividend')
|
||||
@ApiOperation({
|
||||
summary: '배당 정보 가져오기',
|
||||
description: '배당액에 대해서 받아온다',
|
||||
})
|
||||
async getDividend(): Promise<dividend> {
|
||||
const { value: distributionAmount } = await this.prisma.system.findUnique({
|
||||
where: { key: 'distribution-amount' },
|
||||
});
|
||||
return {
|
||||
dividend: parseFloat(ethers.formatEther(distributionAmount)),
|
||||
};
|
||||
}
|
||||
}
|
||||
9
src/system/system.module.ts
Normal file
9
src/system/system.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { SystemService } from './system.service';
|
||||
import { SystemController } from './system.controller';
|
||||
|
||||
@Module({
|
||||
providers: [SystemService],
|
||||
controllers: [SystemController],
|
||||
})
|
||||
export class SystemModule {}
|
||||
18
src/system/system.service.spec.ts
Normal file
18
src/system/system.service.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { SystemService } from './system.service';
|
||||
|
||||
describe('SystemService', () => {
|
||||
let service: SystemService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [SystemService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<SystemService>(SystemService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
4
src/system/system.service.ts
Normal file
4
src/system/system.service.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class SystemService {}
|
||||
35
src/transferhistories/dto/transferhistories.query.dto.ts
Normal file
35
src/transferhistories/dto/transferhistories.query.dto.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsEmpty,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
} from 'class-validator';
|
||||
|
||||
export default class TransferQueryDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@ApiProperty({
|
||||
nullable: true,
|
||||
required: false,
|
||||
})
|
||||
readonly address?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@ApiProperty({
|
||||
nullable: true,
|
||||
required: false,
|
||||
})
|
||||
readonly reason?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@ApiProperty({
|
||||
nullable: true,
|
||||
required: false,
|
||||
})
|
||||
readonly page?: number;
|
||||
}
|
||||
22
src/transferhistories/tranferhistories.controller.spec.ts
Normal file
22
src/transferhistories/tranferhistories.controller.spec.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { TranferHistoriesController } from './transferhhistories.controller';
|
||||
import { TranferHistoriesService } from './transferhistories.service';
|
||||
|
||||
describe('TranferHistoriesController', () => {
|
||||
let controller: TranferHistoriesController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [TranferHistoriesController],
|
||||
providers: [TranferHistoriesService],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<TranferHistoriesController>(
|
||||
TranferHistoriesController,
|
||||
);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
18
src/transferhistories/tranferhistories.service.spec.ts
Normal file
18
src/transferhistories/tranferhistories.service.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { TranferHistoriesService } from './transferhistories.service';
|
||||
|
||||
describe('TranferHistoriesService', () => {
|
||||
let service: TranferHistoriesService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [TranferHistoriesService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<TranferHistoriesService>(TranferHistoriesService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
51
src/transferhistories/transferhhistories.controller.ts
Normal file
51
src/transferhistories/transferhhistories.controller.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Patch,
|
||||
Param,
|
||||
Delete,
|
||||
ParseIntPipe,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { TranferHistoriesService } from './transferhistories.service';
|
||||
import TransferQueryDto from './dto/transferhistories.query.dto';
|
||||
import { ApiOperation } from '@nestjs/swagger';
|
||||
|
||||
@Controller('transferlogs')
|
||||
export class TranferHistoriesController {
|
||||
constructor(
|
||||
private readonly tranferHistoriesService: TranferHistoriesService,
|
||||
) {}
|
||||
|
||||
@Get('/listAll')
|
||||
@ApiOperation({
|
||||
summary: '로그 전체 ',
|
||||
description: '트랜젝션 로그 전체 리스트',
|
||||
})
|
||||
getListAll() {
|
||||
return this.tranferHistoriesService.findAll();
|
||||
}
|
||||
|
||||
@Get('/list')
|
||||
getListPaging(@Query('page', ParseIntPipe) page: number) {
|
||||
const currentPage = Number.isFinite(page) && page > 0 ? page : 1;
|
||||
return this.tranferHistoriesService.findPageList(currentPage);
|
||||
}
|
||||
|
||||
@Get('/list/filter')
|
||||
getListFilterPaging(
|
||||
@Query('page') page: string,
|
||||
@Query('address') address: string,
|
||||
@Query('reason') reason: string,
|
||||
@Query('timestamp') timestamp: string,
|
||||
) {
|
||||
return this.tranferHistoriesService.findListPaging(
|
||||
page,
|
||||
address,
|
||||
reason,
|
||||
timestamp,
|
||||
);
|
||||
}
|
||||
}
|
||||
11
src/transferhistories/transferhistories.module.ts
Normal file
11
src/transferhistories/transferhistories.module.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TranferHistoriesService } from './transferhistories.service';
|
||||
import { TranferHistoriesController } from './transferhhistories.controller';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule],
|
||||
controllers: [TranferHistoriesController],
|
||||
providers: [TranferHistoriesService],
|
||||
})
|
||||
export class TranferHistoriesModule {}
|
||||
90
src/transferhistories/transferhistories.service.ts
Normal file
90
src/transferhistories/transferhistories.service.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from 'src/prisma.service';
|
||||
import TransferQueryDto from './dto/transferhistories.query.dto';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { formatUnits } from 'ethers';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import {
|
||||
TRANSFERLOGS_QUERY,
|
||||
TRANSFERLOGS_UNIONALL_QUERY,
|
||||
} from './transferhistories.sql';
|
||||
|
||||
@Injectable()
|
||||
export class TranferHistoriesService {
|
||||
private pageSize: number;
|
||||
|
||||
constructor(
|
||||
private prisma: PrismaService,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
this.pageSize = Number(this.configService.get<number>('PAGE_SIZE', 50));
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
return await this.prisma.transferHistories.findMany({
|
||||
orderBy: { idx: 'desc' },
|
||||
});
|
||||
}
|
||||
|
||||
async findPageList(page: number) {
|
||||
return await this.prisma.transferHistories.findMany({
|
||||
orderBy: { idx: 'desc' },
|
||||
take: this.pageSize,
|
||||
skip: ((page ?? 1) - 1) * this.pageSize,
|
||||
});
|
||||
}
|
||||
|
||||
async findList(body: TransferQueryDto) {
|
||||
const where: any = {};
|
||||
|
||||
if (body.reason) {
|
||||
where.memo = body.reason;
|
||||
}
|
||||
|
||||
if (body.address) {
|
||||
where.OR = [
|
||||
{ from_address: { contains: body.address } },
|
||||
{ to_address: { contains: body.address } },
|
||||
];
|
||||
}
|
||||
|
||||
return await this.prisma.transferHistories.findMany({
|
||||
where,
|
||||
orderBy: { idx: 'desc' },
|
||||
});
|
||||
}
|
||||
|
||||
async findListPaging(
|
||||
page: string,
|
||||
address: string,
|
||||
reason: string,
|
||||
timestamp: string,
|
||||
) {
|
||||
const parsed = page ? parseInt(page, 10) : NaN;
|
||||
const currentPage =
|
||||
!page || Number.isNaN(parsed) || parsed <= 0 ? 1 : parsed;
|
||||
|
||||
const take = this.pageSize;
|
||||
const skip = (currentPage - 1) * take;
|
||||
const ts = timestamp ? new Date(timestamp) : undefined;
|
||||
|
||||
let rows = [];
|
||||
|
||||
if (!address) {
|
||||
rows = await this.prisma.$queryRaw(
|
||||
TRANSFERLOGS_QUERY(reason, ts, take, skip),
|
||||
);
|
||||
} else {
|
||||
rows = await this.prisma.$queryRaw(
|
||||
TRANSFERLOGS_UNIONALL_QUERY(reason, address, ts, take, skip),
|
||||
);
|
||||
}
|
||||
|
||||
const mapped = rows.map((r) => ({
|
||||
...r,
|
||||
amount: parseFloat(formatUnits(r.amount.toFixed(0), 18)).toString(),
|
||||
}));
|
||||
|
||||
return mapped;
|
||||
}
|
||||
}
|
||||
89
src/transferhistories/transferhistories.sql.ts
Normal file
89
src/transferhistories/transferhistories.sql.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
export const TRANSFERLOGS_UNIONALL_QUERY = (
|
||||
reason: string,
|
||||
address: string,
|
||||
ts: Date,
|
||||
take: number,
|
||||
skip: number,
|
||||
) => {
|
||||
return Prisma.sql`
|
||||
SELECT
|
||||
idx,
|
||||
txhash,
|
||||
blockNumber,
|
||||
log_index,
|
||||
from_address,
|
||||
to_address,
|
||||
amount,
|
||||
memo,
|
||||
timestamp,
|
||||
createdAt
|
||||
FROM (
|
||||
SELECT
|
||||
idx,
|
||||
txhash,
|
||||
blockNumber,
|
||||
log_index,
|
||||
from_address,
|
||||
to_address,
|
||||
amount,
|
||||
memo,
|
||||
timestamp,
|
||||
createdAt
|
||||
FROM TransferHistories
|
||||
WHERE 1=1
|
||||
${reason ? Prisma.sql`AND memo = ${reason}` : Prisma.empty}
|
||||
${address ? Prisma.sql`AND from_address = ${address}` : Prisma.empty}
|
||||
${ts ? Prisma.sql`AND timestamp >= ${ts}` : Prisma.empty}
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
idx,
|
||||
txhash,
|
||||
blockNumber,
|
||||
log_index,
|
||||
from_address,
|
||||
to_address,
|
||||
amount,
|
||||
memo,
|
||||
timestamp,
|
||||
createdAt
|
||||
FROM TransferHistories
|
||||
WHERE 1=1
|
||||
${reason ? Prisma.sql`AND memo = ${reason}` : Prisma.empty}
|
||||
${address ? Prisma.sql`AND to_address = ${address}` : Prisma.empty}
|
||||
${ts ? Prisma.sql`AND timestamp >= ${ts}` : Prisma.empty}
|
||||
) AS t
|
||||
ORDER BY t.idx DESC
|
||||
LIMIT ${take} OFFSET ${skip}
|
||||
`;
|
||||
};
|
||||
|
||||
export const TRANSFERLOGS_QUERY = (
|
||||
reason: string,
|
||||
ts: Date,
|
||||
take: number,
|
||||
skip: number,
|
||||
) => {
|
||||
return Prisma.sql`
|
||||
SELECT
|
||||
idx,
|
||||
txhash,
|
||||
blockNumber,
|
||||
log_index,
|
||||
from_address,
|
||||
to_address,
|
||||
amount,
|
||||
memo,
|
||||
timestamp,
|
||||
createdAt
|
||||
FROM TransferHistories
|
||||
WHERE 1=1
|
||||
${reason ? Prisma.sql`AND memo = ${reason}` : Prisma.empty}
|
||||
${ts ? Prisma.sql`AND timestamp >= ${ts}` : Prisma.empty}
|
||||
ORDER BY idx DESC
|
||||
LIMIT ${take} OFFSET ${skip}
|
||||
`;
|
||||
};
|
||||
34
src/user/dto/create.user.req.ts
Normal file
34
src/user/dto/create.user.req.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsEmpty, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export default class CreateUserReq {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty()
|
||||
readonly address: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty()
|
||||
readonly nickname: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty()
|
||||
readonly referralCode: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty()
|
||||
readonly IMEI: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty()
|
||||
readonly mac: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty()
|
||||
readonly pushAllowAt: string;
|
||||
}
|
||||
12
src/user/dto/favorite.req.ts
Normal file
12
src/user/dto/favorite.req.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsEmpty, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export default class FavorieterReq {
|
||||
@IsString()
|
||||
@ApiProperty()
|
||||
readonly favorite: string;
|
||||
@IsString()
|
||||
@ApiProperty()
|
||||
@IsOptional()
|
||||
readonly nickname: string;
|
||||
}
|
||||
34
src/user/dto/update.user.req.ts
Normal file
34
src/user/dto/update.user.req.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsEmpty, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export default class UpdateUserReq {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty()
|
||||
readonly nickname: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty()
|
||||
readonly IMEI: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty()
|
||||
readonly mac: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty()
|
||||
readonly fcmToken: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty()
|
||||
readonly pushAllowAt: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty()
|
||||
language: string;
|
||||
}
|
||||
18
src/user/user.controller.spec.ts
Normal file
18
src/user/user.controller.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { UserController } from './user.controller';
|
||||
|
||||
describe('UserController', () => {
|
||||
let controller: UserController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [UserController],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<UserController>(UserController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
197
src/user/user.controller.ts
Normal file
197
src/user/user.controller.ts
Normal file
@@ -0,0 +1,197 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Param,
|
||||
Body,
|
||||
Headers,
|
||||
Query,
|
||||
Put,
|
||||
Delete,
|
||||
Patch,
|
||||
UseGuards,
|
||||
Request,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiCreatedResponse,
|
||||
ApiOperation,
|
||||
ApiTags,
|
||||
ApiBody,
|
||||
ApiProperty,
|
||||
ApiHeader,
|
||||
ApiQuery,
|
||||
ApiBearerAuth,
|
||||
} from '@nestjs/swagger';
|
||||
import CreateUserReq from './dto/create.user.req';
|
||||
import { UserService } from './user.service';
|
||||
import { get } from 'http';
|
||||
import { PrismaService } from '../prisma.service';
|
||||
import FavorieterReq from './dto/favorite.req';
|
||||
import UpdateUserReq from './dto/update.user.req';
|
||||
import { JwtAuthGuard } from '../middleware/jwt.auth.guard';
|
||||
import { AuthService } from '../auth/auth.service';
|
||||
import { User } from '@prisma/client';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
|
||||
@Controller({
|
||||
path: 'user',
|
||||
version: '1',
|
||||
})
|
||||
@ApiTags('유저 API')
|
||||
export class UserController {
|
||||
constructor(
|
||||
readonly userService: UserService,
|
||||
|
||||
readonly authService: AuthService,
|
||||
readonly jwtService: JwtService,
|
||||
private readonly prisma: PrismaService,
|
||||
) {}
|
||||
|
||||
@ApiProperty()
|
||||
@Post()
|
||||
@ApiOperation({ summary: 'Create user', description: 'Create a new user' })
|
||||
@ApiCreatedResponse({
|
||||
description: 'The user has been successfully created.',
|
||||
})
|
||||
async createUser(@Body() body: CreateUserReq): Promise<any> {
|
||||
const data: any = await this.userService.createUser(body);
|
||||
const accessToken = await this.authService.getToken(data.userId);
|
||||
const refreshToken = this.jwtService.sign(
|
||||
{
|
||||
time: new Date().getTime(),
|
||||
},
|
||||
{
|
||||
secret: 'MNCOWINS1010!!!!',
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
data,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
};
|
||||
}
|
||||
|
||||
@Get('/referral-list')
|
||||
@ApiOperation({ summary: 'get my Referral', description: '' })
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
async getRefferalList(@Request() req: any): Promise<any> {
|
||||
return await this.userService.getReffalUser(req.user.userId);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
async getUser(@Request() req: any): Promise<any> {
|
||||
return await this.userService.findUserById(req.user.userId);
|
||||
}
|
||||
|
||||
@Get('exist')
|
||||
@ApiOperation({
|
||||
summary: 'find user',
|
||||
description: '해당 유저가 존재 하면 true, 아님 false (AND연산)',
|
||||
})
|
||||
@ApiQuery({
|
||||
name: 'address',
|
||||
example: '0x1111111',
|
||||
required: false,
|
||||
})
|
||||
@ApiQuery({
|
||||
name: 'nickname',
|
||||
example: 'nickname',
|
||||
|
||||
required: false,
|
||||
})
|
||||
@ApiQuery({
|
||||
name: 'referralCode',
|
||||
example: 'referralCode',
|
||||
required: false,
|
||||
})
|
||||
async findUser(
|
||||
@Query('address') address?: string,
|
||||
@Query('nickname') nickname?: string,
|
||||
@Query('referralCode') referralCode?: string,
|
||||
): Promise<any> {
|
||||
const user = await this.prisma.user.findFirst({
|
||||
where: {
|
||||
nickname,
|
||||
Node:
|
||||
address || referralCode
|
||||
? {
|
||||
some: {
|
||||
address,
|
||||
referral: referralCode,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
});
|
||||
return {
|
||||
hasDuplicate: !!user,
|
||||
};
|
||||
}
|
||||
|
||||
@Patch()
|
||||
@ApiOperation({ summary: 'update user', description: '' })
|
||||
@ApiBody({
|
||||
type: UpdateUserReq,
|
||||
})
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
async updateUser(
|
||||
@Request() req: any,
|
||||
@Body() body: UpdateUserReq,
|
||||
): Promise<any> {
|
||||
return await this.userService.updateUser(req.user.userId, body);
|
||||
}
|
||||
|
||||
@Put('favorite')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'add favorite', description: '' })
|
||||
@ApiProperty()
|
||||
async addFavorite(
|
||||
@Request() req: any,
|
||||
@Body() body: FavorieterReq,
|
||||
): Promise<any> {
|
||||
return await this.userService.addFavorite(
|
||||
req.user.userId,
|
||||
body.favorite,
|
||||
body.nickname,
|
||||
);
|
||||
}
|
||||
|
||||
@Delete('favorite')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
async deleteFavorite(@Request() req: any, @Body() body: FavorieterReq) {
|
||||
return await this.userService.deleteFavorite(
|
||||
req.user.userId,
|
||||
body.favorite,
|
||||
);
|
||||
}
|
||||
|
||||
@Get('favorite')
|
||||
@ApiOperation({ summary: 'get favorite', description: '' })
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
async getFavorite(@Request() req: any): Promise<any> {
|
||||
return await this.userService.getFavorite(req.user.userId);
|
||||
}
|
||||
|
||||
@Get('notification')
|
||||
@ApiOperation({ summary: 'get notification', description: '' })
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
async getNotification(
|
||||
@Request() req: any,
|
||||
@Query('take') take: number,
|
||||
@Query('skip') skip: number,
|
||||
): Promise<any> {
|
||||
return await this.userService.getUsernotification(req.user.userId, {
|
||||
take,
|
||||
skip,
|
||||
});
|
||||
}
|
||||
}
|
||||
7
src/user/user.module.ts
Normal file
7
src/user/user.module.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { UserService } from './user.service';
|
||||
|
||||
@Module({
|
||||
providers: [UserService],
|
||||
})
|
||||
export class UserModule {}
|
||||
18
src/user/user.service.spec.ts
Normal file
18
src/user/user.service.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { UserService } from './user.service';
|
||||
|
||||
describe('UserService', () => {
|
||||
let service: UserService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [UserService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<UserService>(UserService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
234
src/user/user.service.ts
Normal file
234
src/user/user.service.ts
Normal file
@@ -0,0 +1,234 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma.service';
|
||||
import { User } from './user';
|
||||
import CreateUserReq from './dto/create.user.req';
|
||||
import UpdateUserReq from './dto/update.user.req';
|
||||
import { userNotification } from '@prisma/client';
|
||||
import { generateRandomString } from '../utils/randomstring';
|
||||
import { AuthService } from '../auth/auth.service';
|
||||
import { uuidV4 } from 'ethers';
|
||||
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
async findUserById(userId: string) {
|
||||
return await this.prisma.user.findUnique({
|
||||
where: {
|
||||
userId,
|
||||
},
|
||||
include: {
|
||||
Node: {
|
||||
include: {
|
||||
parent: {},
|
||||
children: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async addFavorite(userId: string, address: string, nickname: string) {
|
||||
return await this.prisma.favoriteAddress.upsert({
|
||||
where: {
|
||||
address_userId: {
|
||||
userId,
|
||||
address,
|
||||
},
|
||||
},
|
||||
update: {
|
||||
nickname,
|
||||
},
|
||||
create: {
|
||||
userId,
|
||||
address,
|
||||
nickname,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async deleteFavorite(userId: string, address: string) {
|
||||
return await this.prisma.favoriteAddress.delete({
|
||||
where: {
|
||||
address_userId: {
|
||||
userId,
|
||||
address,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getFavorite(userId: string) {
|
||||
return await this.prisma.favoriteAddress.findMany({
|
||||
where: {
|
||||
userId,
|
||||
},
|
||||
select: {
|
||||
address: true,
|
||||
createdAt: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findByAddress(address: string): Promise<User> {
|
||||
return await this.prisma.user.findFirst({
|
||||
where: {
|
||||
Node: {
|
||||
some: {
|
||||
address: address,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async createUser(data: CreateUserReq): Promise<User> {
|
||||
const { address, nickname, referralCode, IMEI, mac, pushAllowAt } = data;
|
||||
|
||||
let referralUser;
|
||||
let refNode;
|
||||
|
||||
if (referralCode) {
|
||||
const ref = await this.prisma.user.findFirst({
|
||||
where: {
|
||||
Node: {
|
||||
some: {
|
||||
referral: referralCode,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
refNode = await this.prisma.node.findFirst({
|
||||
where: {
|
||||
referral: referralCode,
|
||||
},
|
||||
});
|
||||
referralUser = ref;
|
||||
}
|
||||
|
||||
console.log(referralUser);
|
||||
|
||||
console.log(refNode);
|
||||
const dd = await this.getUserByAddress(address);
|
||||
if (dd) {
|
||||
return dd;
|
||||
}
|
||||
|
||||
const user = await this.prisma.user.create({
|
||||
data: {
|
||||
Node: {
|
||||
connectOrCreate: {
|
||||
where: {
|
||||
address,
|
||||
},
|
||||
create: {
|
||||
depth: !!refNode ? refNode.depth + 1 : 0,
|
||||
address,
|
||||
referral: generateRandomString(7),
|
||||
parent: referralCode
|
||||
? {
|
||||
connect: {
|
||||
referral: referralCode,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
nickname: nickname ?? 'unknown',
|
||||
IMEI,
|
||||
mac,
|
||||
pushAllowAt,
|
||||
|
||||
// referralUser : referralCode ? {
|
||||
// connect : {
|
||||
// userId :referralUser.userId,
|
||||
// }
|
||||
// }: undefined,
|
||||
},
|
||||
include: {
|
||||
Node: {
|
||||
include: {
|
||||
parent: true,
|
||||
children: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
async getUserByAddress(address: string): Promise<User> {
|
||||
return await this.prisma.user.findFirst({
|
||||
where: {
|
||||
Node: {
|
||||
some: {
|
||||
address: address,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
Node: {
|
||||
include: {
|
||||
parent: true,
|
||||
children: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async updateUser(userId: string, data: UpdateUserReq): Promise<any> {
|
||||
return await this.prisma.user.update({
|
||||
where: {
|
||||
userId: userId,
|
||||
},
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
async getReffalUser(userId: string): Promise<User> {
|
||||
var nodes = await this.prisma.node.findMany({
|
||||
where: {
|
||||
userId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
var refferal = await this.prisma.node.findMany({
|
||||
where: {
|
||||
parent: {
|
||||
parentId: {
|
||||
in: nodes.map((node) => {
|
||||
return node.id;
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
address: true,
|
||||
},
|
||||
});
|
||||
return refferal;
|
||||
}
|
||||
|
||||
async getUsernotification(
|
||||
userId: string,
|
||||
prop: {
|
||||
skip: number;
|
||||
take: number;
|
||||
},
|
||||
): Promise<userNotification[]> {
|
||||
const { skip, take } = prop;
|
||||
return await this.prisma.userNotification.findMany({
|
||||
where: {
|
||||
userId,
|
||||
},
|
||||
skip,
|
||||
take,
|
||||
});
|
||||
}
|
||||
}
|
||||
7
src/user/user.spec.ts
Normal file
7
src/user/user.spec.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { User } from './user';
|
||||
|
||||
describe('User', () => {
|
||||
it('should be defined', () => {
|
||||
expect(new User()).toBeDefined();
|
||||
});
|
||||
});
|
||||
1
src/user/user.ts
Normal file
1
src/user/user.ts
Normal file
@@ -0,0 +1 @@
|
||||
export class User {}
|
||||
10
src/utils/randomstring.ts
Normal file
10
src/utils/randomstring.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export function generateRandomString(length) {
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
const randomIndex = Math.floor(Math.random() * characters.length);
|
||||
result += characters[randomIndex];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
13
src/utils/response.ts
Normal file
13
src/utils/response.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export type Response = {
|
||||
result: boolean;
|
||||
code: number;
|
||||
data?: Payload | Payload[];
|
||||
message?: string;
|
||||
count?: number;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
};
|
||||
|
||||
export type Payload = {
|
||||
[key: string]: any;
|
||||
};
|
||||
18
src/wallet/wallet.controller.spec.ts
Normal file
18
src/wallet/wallet.controller.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { WalletController } from './wallet.controller';
|
||||
|
||||
describe('WalletController', () => {
|
||||
let controller: WalletController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [WalletController],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<WalletController>(WalletController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
88
src/wallet/wallet.controller.ts
Normal file
88
src/wallet/wallet.controller.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { Controller, Get, Post, Param, Body } from '@nestjs/common';
|
||||
import {
|
||||
ApiCreatedResponse,
|
||||
ApiOperation,
|
||||
ApiTags,
|
||||
ApiBody,
|
||||
ApiProperty,
|
||||
} from '@nestjs/swagger';
|
||||
|
||||
class Transaction {
|
||||
@ApiProperty()
|
||||
form: string;
|
||||
@ApiProperty()
|
||||
to: string;
|
||||
@ApiProperty()
|
||||
fee: string;
|
||||
@ApiProperty()
|
||||
value: string;
|
||||
@ApiProperty()
|
||||
txid: string;
|
||||
@ApiProperty()
|
||||
type: string;
|
||||
@ApiProperty()
|
||||
internalFee: string;
|
||||
@ApiProperty()
|
||||
excutedFunction: string;
|
||||
}
|
||||
|
||||
@Controller('wallet')
|
||||
export class WalletController {
|
||||
@Get('/history/')
|
||||
@ApiOperation({ summary: '', description: '' })
|
||||
getWalletHistory(@Param('address') address: string): any {
|
||||
return {
|
||||
result: true,
|
||||
data: [
|
||||
{
|
||||
from: '0x1111111',
|
||||
to: '0x2222222',
|
||||
fee: '0.0',
|
||||
value: '100.00',
|
||||
txid: '0x0123456789abcdef',
|
||||
type: 'in',
|
||||
internalFee: '0.01',
|
||||
excutedFunction: 'transfer',
|
||||
},
|
||||
{
|
||||
from: '0x1111111',
|
||||
to: '0x2222222',
|
||||
fee: '0.0',
|
||||
value: '100.00',
|
||||
txid: '0x0123456789abcdef',
|
||||
type: 'in',
|
||||
internalFee: '0.01',
|
||||
excutedFunction: 'transfer',
|
||||
},
|
||||
{
|
||||
from: '0x1111111',
|
||||
to: '0x2222222',
|
||||
fee: '0.0',
|
||||
value: '100.00',
|
||||
txid: '0x0123456789abcdef',
|
||||
type: 'in',
|
||||
internalFee: '0.01',
|
||||
excutedFunction: 'transfer',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@Get('/history/:txid')
|
||||
@ApiOperation({ summary: '', description: '' })
|
||||
getWalletHistoryTxid(@Param('txid') txid: string): any {
|
||||
return {
|
||||
result: true,
|
||||
data: {
|
||||
from: '0x1111111',
|
||||
to: '0x2222222',
|
||||
fee: '0.0',
|
||||
value: '100.00',
|
||||
txid: '0x0123456789abcdef',
|
||||
type: 'in',
|
||||
internalFee: '0.01',
|
||||
excutedFunction: 'transfer',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
9
src/wallet/wallet.module.ts
Normal file
9
src/wallet/wallet.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { WalletService } from './wallet.service';
|
||||
import { WalletController } from './wallet.controller';
|
||||
|
||||
@Module({
|
||||
providers: [WalletService],
|
||||
controllers: [WalletController],
|
||||
})
|
||||
export class WalletModule {}
|
||||
18
src/wallet/wallet.service.spec.ts
Normal file
18
src/wallet/wallet.service.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { WalletService } from './wallet.service';
|
||||
|
||||
describe('WalletService', () => {
|
||||
let service: WalletService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [WalletService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<WalletService>(WalletService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
4
src/wallet/wallet.service.ts
Normal file
4
src/wallet/wallet.service.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class WalletService {}
|
||||
Reference in New Issue
Block a user