diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b77d9eb..1ab19f8 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -25,7 +25,9 @@ // "csstools.postcss", // "vscodeshift.mui-snippets", "mikestead.dotenv", - "vscode-icons-team.vscode-icons" + "vscode-icons-team.vscode-icons", + "prisma.prisma", + "ckolkman.vscode-postgres", ] } }, diff --git a/.devcontainer/docker-compose.yaml b/.devcontainer/docker-compose.yaml index d9917e3..af5516b 100644 --- a/.devcontainer/docker-compose.yaml +++ b/.devcontainer/docker-compose.yaml @@ -2,10 +2,12 @@ version: '3' services: - # redis: - # image: redis/redis-stack-server:7.0.6-RC8 - # ports: - # - "6379:6379" + db: + image: postgres:alpine + ports: + - "5432:5432" + environment: + - POSTGRES_PASSWORD=password app: # See https://aka.ms/vscode-remote/containers/non-root for details. user: node diff --git a/package-lock.json b/package-lock.json index e66a40d..7d5d152 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,10 +10,12 @@ "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { + "@prisma/client": "^5.22.0", "@types/express": "^5", "axios": "^1.7.7", "dotenv": "^16.4.5", "express": "^5", + "prisma": "^5.22.0", "winston": "^3.13.0" }, "devDependencies": { @@ -1106,6 +1108,69 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@prisma/client": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz", + "integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.13" + }, + "peerDependencies": { + "prisma": "*" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + } + } + }, + "node_modules/@prisma/debug": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz", + "integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz", + "integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.22.0", + "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "@prisma/fetch-engine": "5.22.0", + "@prisma/get-platform": "5.22.0" + } + }, + "node_modules/@prisma/engines-version": { + "version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz", + "integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/fetch-engine": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz", + "integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.22.0", + "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "@prisma/get-platform": "5.22.0" + } + }, + "node_modules/@prisma/get-platform": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz", + "integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.22.0" + } + }, "node_modules/@scure/base": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", @@ -3199,11 +3264,11 @@ "dev": true }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -4714,6 +4779,25 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prisma": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz", + "integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/engines": "5.22.0" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=16.13" + }, + "optionalDependencies": { + "fsevents": "2.3.3" + } + }, "node_modules/proper-lockfile": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", @@ -6772,6 +6856,51 @@ } } }, + "@prisma/client": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz", + "integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==", + "requires": {} + }, + "@prisma/debug": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz", + "integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==" + }, + "@prisma/engines": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz", + "integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==", + "requires": { + "@prisma/debug": "5.22.0", + "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "@prisma/fetch-engine": "5.22.0", + "@prisma/get-platform": "5.22.0" + } + }, + "@prisma/engines-version": { + "version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz", + "integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==" + }, + "@prisma/fetch-engine": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz", + "integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==", + "requires": { + "@prisma/debug": "5.22.0", + "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "@prisma/get-platform": "5.22.0" + } + }, + "@prisma/get-platform": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz", + "integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==", + "requires": { + "@prisma/debug": "5.22.0" + } + }, "@scure/base": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", @@ -8368,10 +8497,9 @@ "dev": true }, "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "optional": true }, "function-bind": { @@ -9411,6 +9539,15 @@ "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true }, + "prisma": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz", + "integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==", + "requires": { + "@prisma/engines": "5.22.0", + "fsevents": "2.3.3" + } + }, "proper-lockfile": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", diff --git a/package.json b/package.json index d0e2ddb..5fda8ec 100644 --- a/package.json +++ b/package.json @@ -21,10 +21,12 @@ "typescript": "^5.4.5" }, "dependencies": { + "@prisma/client": "^5.22.0", "@types/express": "^5", "axios": "^1.7.7", "dotenv": "^16.4.5", "express": "^5", + "prisma": "^5.22.0", "winston": "^3.13.0" } } diff --git a/prisma/migrations/20241114134457_init/migration.sql b/prisma/migrations/20241114134457_init/migration.sql new file mode 100644 index 0000000..b43f1b5 --- /dev/null +++ b/prisma/migrations/20241114134457_init/migration.sql @@ -0,0 +1,16 @@ +-- CreateTable +CREATE TABLE "Nft" ( + "nftId" BIGSERIAL NOT NULL, + "parentNftId" BIGINT NOT NULL DEFAULT 0, + "objectType" TEXT NOT NULL, + "objectAddress" TEXT NOT NULL, + "owner" TEXT NOT NULL, + "created_blockNumber" INTEGER NOT NULL, + "created_txHash" TEXT NOT NULL, + "created_from" TEXT NOT NULL, + "modified_blockNumber" INTEGER NOT NULL, + "modified_txHash" TEXT NOT NULL, + "modified_from" TEXT NOT NULL, + + CONSTRAINT "Nft_pkey" PRIMARY KEY ("nftId") +); diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..fbffa92 --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..4e46c7f --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,22 @@ +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +generator client { + provider = "prisma-client-js" +} + +model Nft { + nftId BigInt @id @default(autoincrement()) + parentNftId BigInt @default(0) + objectType String + objectAddress String + owner String + created_blockNumber Int + created_txHash String + created_from String + modified_blockNumber Int + modified_txHash String + modified_from String +} diff --git a/src/main.ts b/src/main.ts index 9718945..e67fe08 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,6 +6,8 @@ import { logger } from './logger'; import { DecodedLogEntry } from './types/logdata'; import { Nft } from './types/nft'; import { getObjectType, ObjectType } from './types/objecttype'; +import { notStrictEqual } from 'assert'; +import { PrismaClient } from '@prisma/client'; dotenv.config(); @@ -17,7 +19,10 @@ BigInt.prototype.toJSON = function () { class Main { - constructor() { + private prisma: PrismaClient; + + constructor(prisma: PrismaClient) { + this.prisma = prisma; } public async main(): Promise { @@ -32,7 +37,7 @@ class Main { let nfts = await this.processNftRegistrationEvents(nftRegistrationEvents); nfts = await this.processNftTransferEvents(nftTransferEvents, nfts); - + await this.persistNfts(nfts); // print one log per event nfts.forEach(event => { @@ -45,6 +50,36 @@ class Main { // logger.info(`Latest block: ${latestBaseBlock}`); } + async persistNfts(nfts: Nft[]): Promise { + for (const nft of nfts) { + await this.prisma.nft.upsert({ + where: { nftId: nft.nftId as bigint }, + update: { + parentNftId: nft.parentNftId as bigint, + objectType: ObjectType[nft.objectType], + objectAddress: nft.objectAddress, + owner: nft.owner, + modified_blockNumber: nft.modified.blockNumber, + modified_txHash: nft.modified.txHash, + modified_from: nft.modified.from + }, + create: { + nftId: nft.nftId as bigint, + parentNftId: nft.parentNftId as bigint, + objectType: ObjectType[nft.objectType], + objectAddress: nft.objectAddress, + owner: nft.owner, + created_blockNumber: nft.created.blockNumber, + created_txHash: nft.created.txHash, + created_from: nft.created.from, + modified_blockNumber: nft.modified.blockNumber, + modified_txHash: nft.modified.txHash, + modified_from: nft.modified.from + } + }); + } + } + async processNftRegistrationEvents(nftRegistrationEvents: Array): Promise> { return nftRegistrationEvents.map(event => { logger.info(`Processing nft registration event ${event.tx_hash} - ${event.event_name} - ${event.data}`); @@ -217,4 +252,9 @@ class Main { } -new Main().main(); +const prisma = new PrismaClient() +try { + new Main(prisma).main(); +} finally { + prisma.$disconnect(); +}