From d3969bb776f852eb38b7de4b4185b725f0947021 Mon Sep 17 00:00:00 2001 From: Jonath-z Date: Tue, 17 May 2022 18:58:07 +0200 Subject: [PATCH 01/13] create user DTO added --- src/modules/users/users.dto.ts | 54 ++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/src/modules/users/users.dto.ts b/src/modules/users/users.dto.ts index c80bc2e..8a68929 100644 --- a/src/modules/users/users.dto.ts +++ b/src/modules/users/users.dto.ts @@ -4,53 +4,81 @@ export class UserDto { readonly walletAddress: string; } -export class UserUpdateProfileDto { +export class CreateUserDto { @IsString() - readonly walletAddress: string; + walletAddress: string; @IsString() @IsOptional() - readonly username?: string; + username?: string; @IsString() @IsOptional() - readonly userBio: string; + usernameLowercase?: string; @IsString() @IsOptional() - readonly userAvatarUrl?: string; + avatarUrl?: string; @IsString() @IsOptional() - readonly userAvatarUrlCompressed?: string; + avatarUrlThumbnail?: string; @IsString() @IsOptional() - readonly userAvatarUrlThumbnail?: string; + avatarUrlCompressed?: string; @IsString() @IsOptional() - readonly coverUrl?: string; + coverUrl?: string; @IsString() @IsOptional() - readonly coverThumbnailUrl?: string; + coverThumbnailUrl?: string; + + @IsString() + @IsOptional() + userBio?: string; +} + +export class UserUpdateProfileDto { + @IsString() + readonly id: number; + + @IsString() + readonly walletAddress: string; @IsString() @IsOptional() - readonly socialUrl?: string; + readonly username?: string; + + @IsString() + @IsOptional() + readonly usernameLowercase?: string; + + @IsString() + @IsOptional() + readonly userBio: string; @IsString() @IsOptional() - readonly twitterUrl?: string; + readonly avatarUrl?: string; @IsString() @IsOptional() - readonly instagramUrl?: string; + readonly avatarUrlCompressed?: string; @IsString() @IsOptional() - readonly facebookUrl?: string; + readonly avatarUrlThumbnail?: string; + + @IsString() + @IsOptional() + readonly coverUrl?: string; + + @IsString() + @IsOptional() + readonly coverThumbnailUrl?: string; @IsString() signature?: string; From bc49b43b740b8b00a2e194fd1c6996aa59376c46 Mon Sep 17 00:00:00 2001 From: Jonath-z Date: Tue, 17 May 2022 19:00:25 +0200 Subject: [PATCH 02/13] user prefix removed on userAvater, and userBio --- src/modules/users/users.entity.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/users/users.entity.ts b/src/modules/users/users.entity.ts index cb08a97..3375963 100644 --- a/src/modules/users/users.entity.ts +++ b/src/modules/users/users.entity.ts @@ -16,13 +16,13 @@ export class User { usernameLowercase: string; @Column({ nullable: true, type: 'varchar' }) - userAvatarUrl: string; + avatarUrl: string; @Column({ nullable: true, type: 'varchar' }) - userAvatarUrlThumbnail: string; + avatarUrlThumbnail: string; @Column({ nullable: true, type: 'varchar' }) - userAvatarUrlCompressed: string; + avatarUrlCompressed: string; @Column({ nullable: true, type: 'varchar' }) coverUrl: string; From cc163dd75fe6201295ad1a5f97b0b26615dd69f2 Mon Sep 17 00:00:00 2001 From: Jonath-z Date: Tue, 17 May 2022 19:00:59 +0200 Subject: [PATCH 03/13] social media urls columns removed --- src/modules/users/users.entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/users/users.entity.ts b/src/modules/users/users.entity.ts index 3375963..0a84eca 100644 --- a/src/modules/users/users.entity.ts +++ b/src/modules/users/users.entity.ts @@ -31,7 +31,7 @@ export class User { coverThumbnailUrl: string; @Column({ nullable: true, type: 'varchar' }) - userBio: string; + bio: string; @Column({ nullable: true, type: 'boolean', default: false }) banned: boolean; From c8ab3fc653162c166e3fb8926f683700cd1c3294 Mon Sep 17 00:00:00 2001 From: Jonath-z Date: Tue, 17 May 2022 19:01:57 +0200 Subject: [PATCH 04/13] create and update user services added --- src/modules/users/users.controller.ts | 146 ++++++++++++++------------ 1 file changed, 81 insertions(+), 65 deletions(-) diff --git a/src/modules/users/users.controller.ts b/src/modules/users/users.controller.ts index 3c18f98..3e36e0c 100644 --- a/src/modules/users/users.controller.ts +++ b/src/modules/users/users.controller.ts @@ -1,74 +1,90 @@ -import { Controller, Get, Req, Param, Put, Body, BadRequestException, CacheTTL } from '@nestjs/common'; - +import { Controller, Get, Param, Put, Body, CacheTTL, UseGuards, Post } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; +import { WalletSignatureGuard } from 'src/guards/walletSignature.guard'; import { NftsService } from '../nfts/nfts.service'; import { UsersService } from './users.service'; -import { UserUpdateProfileDto } from './users.dto'; +import { UserUpdateProfileDto, CreateUserDto } from './users.dto'; + @ApiTags('users') @Controller('users') +@UseGuards(WalletSignatureGuard) export class UsersController { constructor(private readonly userService: UsersService, private readonly nftService: NftsService) {} - // @Put('/profile') - // async updateProfile(@Body() userData: UserUpdateProfileDto, @Req() req, @Headers('cf-ipcountry') countryISO) { - // const { polyglot } = req; - - // const checksumAddress = Web3Helper.getAddressChecksum(userData.walletAddress); - - // const foundUser = await this.userService.findByAddress(checksumAddress); - - // if (!checksumAddress || !foundUser) { - // throw new BadRequestException(polyglot.t('Could not update user data - Error logged')); - // } - - // if ( - // !(await validateWalletSignature({ - // data: userData, - // walletAddress: userData.walletAddress, - // signature: userData.signature, - // })) - // ) { - // throw new BadRequestException(polyglot.t('Could not update user data - Error logged')); - // } - - // if (userData.username && foundUser?.username?.toLocaleLowerCase() !== userData?.username?.toLocaleLowerCase()) { - // const newFoundUser = await this.userService.findByUsername(userData.username); - - // if (newFoundUser && foundUser.walletAddress !== newFoundUser.walletAddress) { - // throw new BadRequestException( - // polyglot.t('Username is already taken: %{username} - %{address} Attempt', { - // username: userData.username, - // address: userData.walletAddress, - // }), - // ); - // } - // } - - // if (userData.signature) { - // delete userData.signature; - // } - - // await this.userService.updateById(foundUser.id, { - // username: userData?.username || foundUser?.username || null, - // userBio: userData?.userBio || foundUser?.userBio || null, - // userAvatarUrl: userData?.userAvatarUrl || foundUser?.userAvatarUrl || null, - // userAvatarUrlCompressed: userData?.userAvatarUrl - // ? userData?.userAvatarUrlCompressed || null - // : foundUser?.userAvatarUrlCompressed || null, - // userAvatarUrlThumbnail: userData?.userAvatarUrl - // ? userData?.userAvatarUrlThumbnail || null - // : foundUser?.userAvatarUrlThumbnail || null, - // coverUrl: userData?.coverUrl ? userData?.coverUrl || null : foundUser?.coverUrl || null, - // coverThumbnailUrl: userData?.coverThumbnailUrl - // ? userData?.coverThumbnailUrl || null - // : foundUser?.coverThumbnailUrl || null, - // usernameLowercase: (userData?.username || foundUser?.username || '')?.toLocaleLowerCase(), - // }); - - // return { - // statusCode: 200, - // }; - // } + @Post('/createUser') + async saveNewUser(@Body() data: CreateUserDto) { + await this.saveNewUser(data); + + return { + statusCode: 200, + }; + } + + @Put('/profile') + async updateProfile(@Body() data: UserUpdateProfileDto) { + const { + id, + username, + bio, + avatarUrl, + avatarUrlCompressed, + avatarUrlThumbnail, + coverThumbnailUrl, + coverUrl, + usernameLowercase, + } = data; + // const { polyglot } = req; + + // const checksumAddress = Web3Helper.getAddressChecksum(userData.walletAddress); + + // const foundUser = await this.userService.findByAddress(checksumAddress); + + // if (!checksumAddress || !foundUser) { + // throw new BadRequestException(polyglot.t('Could not update user data - Error logged')); + // } + + // if ( + // !(await validateWalletSignature({ + // data: userData, + // walletAddress: userData.walletAddress, + // signature: userData.signature, + // })) + // ) { + // throw new BadRequestException(polyglot.t('Could not update user data - Error logged')); + // } + + // if (userData.username && foundUser?.username?.toLocaleLowerCase() !== userData?.username?.toLocaleLowerCase()) { + // const newFoundUser = await this.userService.findByUsername(userData.username); + + // if (newFoundUser && foundUser.walletAddress !== newFoundUser.walletAddress) { + // throw new BadRequestException( + // polyglot.t('Username is already taken: %{username} - %{address} Attempt', { + // username: userData.username, + // address: userData.walletAddress, + // }), + // ); + // } + // } + + // if (userData.signature) { + // delete userData.signature; + // } + + await this.userService.updateById(id, { + username, + bio, + avatarUrl, + avatarUrlCompressed, + avatarUrlThumbnail, + coverUrl, + coverThumbnailUrl, + usernameLowercase, + }); + + return { + statusCode: 200, + }; + } @CacheTTL(60) @Get('/:addressOrUsername') @@ -77,8 +93,8 @@ export class UsersController { if (!user) { user = await this.userService.saveNewUser({ - userAvatarUrl: '', - userBio: '', + avatarUrl: '', + bio: '', username: '', walletAddress: addressOrUsername, }); From 3889d74bcf8636c1bff33b415a004d473bef31a1 Mon Sep 17 00:00:00 2001 From: Jonath-z Date: Tue, 17 May 2022 19:02:35 +0200 Subject: [PATCH 05/13] user prefix removed on userBio --- src/modules/users/users.dto.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/users/users.dto.ts b/src/modules/users/users.dto.ts index 8a68929..0c0df9c 100644 --- a/src/modules/users/users.dto.ts +++ b/src/modules/users/users.dto.ts @@ -38,7 +38,7 @@ export class CreateUserDto { @IsString() @IsOptional() - userBio?: string; + bio?: string; } export class UserUpdateProfileDto { @@ -58,7 +58,7 @@ export class UserUpdateProfileDto { @IsString() @IsOptional() - readonly userBio: string; + readonly bio: string; @IsString() @IsOptional() From 72b2f345e929d8f5b6af171d183fa3992a233fe2 Mon Sep 17 00:00:00 2001 From: Jonath-z Date: Tue, 17 May 2022 19:03:11 +0200 Subject: [PATCH 06/13] user prefix removed on userBio --- src/database/migrations/1651143888123-create_users_table.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database/migrations/1651143888123-create_users_table.ts b/src/database/migrations/1651143888123-create_users_table.ts index a2f86cc..f62b3bb 100644 --- a/src/database/migrations/1651143888123-create_users_table.ts +++ b/src/database/migrations/1651143888123-create_users_table.ts @@ -15,7 +15,7 @@ export class createUsersTable1651143888123 implements MigrationInterface { "avatarUrlCompressed" varchar, "coverUrl" varchar, "coverThumbnailUrl" varchar, - "userBio" varchar, + "bio" varchar, "nftsCount" int4 DEFAULT 0, "buysCount" int4 DEFAULT 0, "salesCount" int4 DEFAULT 0, From 8c3cf2ed6265c5845bc4be52e539099a1ccd1190 Mon Sep 17 00:00:00 2001 From: Jonath-z Date: Tue, 17 May 2022 21:10:22 +0200 Subject: [PATCH 07/13] bio renamed as userBio --- .../migrations/1651143888123-create_users_table.ts | 2 +- src/modules/users/users.controller.ts | 12 ++++++------ src/modules/users/users.dto.ts | 4 ++-- src/modules/users/users.entity.ts | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/database/migrations/1651143888123-create_users_table.ts b/src/database/migrations/1651143888123-create_users_table.ts index f62b3bb..a2f86cc 100644 --- a/src/database/migrations/1651143888123-create_users_table.ts +++ b/src/database/migrations/1651143888123-create_users_table.ts @@ -15,7 +15,7 @@ export class createUsersTable1651143888123 implements MigrationInterface { "avatarUrlCompressed" varchar, "coverUrl" varchar, "coverThumbnailUrl" varchar, - "bio" varchar, + "userBio" varchar, "nftsCount" int4 DEFAULT 0, "buysCount" int4 DEFAULT 0, "salesCount" int4 DEFAULT 0, diff --git a/src/modules/users/users.controller.ts b/src/modules/users/users.controller.ts index 3e36e0c..856236e 100644 --- a/src/modules/users/users.controller.ts +++ b/src/modules/users/users.controller.ts @@ -1,19 +1,19 @@ import { Controller, Get, Param, Put, Body, CacheTTL, UseGuards, Post } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { WalletSignatureGuard } from 'src/guards/walletSignature.guard'; +// import { WalletSignatureGuard } from 'src/guards/walletSignature.guard'; import { NftsService } from '../nfts/nfts.service'; import { UsersService } from './users.service'; import { UserUpdateProfileDto, CreateUserDto } from './users.dto'; @ApiTags('users') @Controller('users') -@UseGuards(WalletSignatureGuard) +// @UseGuards(WalletSignatureGuard) export class UsersController { constructor(private readonly userService: UsersService, private readonly nftService: NftsService) {} @Post('/createUser') async saveNewUser(@Body() data: CreateUserDto) { - await this.saveNewUser(data); + await this.userService.saveNewUser(data); return { statusCode: 200, @@ -25,7 +25,7 @@ export class UsersController { const { id, username, - bio, + userBio, avatarUrl, avatarUrlCompressed, avatarUrlThumbnail, @@ -72,7 +72,7 @@ export class UsersController { await this.userService.updateById(id, { username, - bio, + userBio, avatarUrl, avatarUrlCompressed, avatarUrlThumbnail, @@ -94,7 +94,7 @@ export class UsersController { if (!user) { user = await this.userService.saveNewUser({ avatarUrl: '', - bio: '', + userBio: '', username: '', walletAddress: addressOrUsername, }); diff --git a/src/modules/users/users.dto.ts b/src/modules/users/users.dto.ts index 0c0df9c..8a68929 100644 --- a/src/modules/users/users.dto.ts +++ b/src/modules/users/users.dto.ts @@ -38,7 +38,7 @@ export class CreateUserDto { @IsString() @IsOptional() - bio?: string; + userBio?: string; } export class UserUpdateProfileDto { @@ -58,7 +58,7 @@ export class UserUpdateProfileDto { @IsString() @IsOptional() - readonly bio: string; + readonly userBio: string; @IsString() @IsOptional() diff --git a/src/modules/users/users.entity.ts b/src/modules/users/users.entity.ts index 0a84eca..3375963 100644 --- a/src/modules/users/users.entity.ts +++ b/src/modules/users/users.entity.ts @@ -31,7 +31,7 @@ export class User { coverThumbnailUrl: string; @Column({ nullable: true, type: 'varchar' }) - bio: string; + userBio: string; @Column({ nullable: true, type: 'boolean', default: false }) banned: boolean; From ebdaed5dc64101d2fcab83496b19ac9b6a2a8eef Mon Sep 17 00:00:00 2001 From: Jonath-z Date: Tue, 17 May 2022 22:11:51 +0200 Subject: [PATCH 08/13] user create and update profile endpoints added --- src/modules/users/users.controller.ts | 40 ++------------------------- src/modules/users/users.dto.ts | 4 +-- 2 files changed, 4 insertions(+), 40 deletions(-) diff --git a/src/modules/users/users.controller.ts b/src/modules/users/users.controller.ts index 856236e..8855ecb 100644 --- a/src/modules/users/users.controller.ts +++ b/src/modules/users/users.controller.ts @@ -1,13 +1,13 @@ import { Controller, Get, Param, Put, Body, CacheTTL, UseGuards, Post } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -// import { WalletSignatureGuard } from 'src/guards/walletSignature.guard'; +import { WalletSignatureGuard } from 'src/guards/walletSignature.guard'; import { NftsService } from '../nfts/nfts.service'; import { UsersService } from './users.service'; import { UserUpdateProfileDto, CreateUserDto } from './users.dto'; @ApiTags('users') @Controller('users') -// @UseGuards(WalletSignatureGuard) +@UseGuards(WalletSignatureGuard) export class UsersController { constructor(private readonly userService: UsersService, private readonly nftService: NftsService) {} @@ -33,42 +33,6 @@ export class UsersController { coverUrl, usernameLowercase, } = data; - // const { polyglot } = req; - - // const checksumAddress = Web3Helper.getAddressChecksum(userData.walletAddress); - - // const foundUser = await this.userService.findByAddress(checksumAddress); - - // if (!checksumAddress || !foundUser) { - // throw new BadRequestException(polyglot.t('Could not update user data - Error logged')); - // } - - // if ( - // !(await validateWalletSignature({ - // data: userData, - // walletAddress: userData.walletAddress, - // signature: userData.signature, - // })) - // ) { - // throw new BadRequestException(polyglot.t('Could not update user data - Error logged')); - // } - - // if (userData.username && foundUser?.username?.toLocaleLowerCase() !== userData?.username?.toLocaleLowerCase()) { - // const newFoundUser = await this.userService.findByUsername(userData.username); - - // if (newFoundUser && foundUser.walletAddress !== newFoundUser.walletAddress) { - // throw new BadRequestException( - // polyglot.t('Username is already taken: %{username} - %{address} Attempt', { - // username: userData.username, - // address: userData.walletAddress, - // }), - // ); - // } - // } - - // if (userData.signature) { - // delete userData.signature; - // } await this.userService.updateById(id, { username, diff --git a/src/modules/users/users.dto.ts b/src/modules/users/users.dto.ts index 8a68929..baccf61 100644 --- a/src/modules/users/users.dto.ts +++ b/src/modules/users/users.dto.ts @@ -1,4 +1,4 @@ -import { IsString, IsOptional } from 'class-validator'; +import { IsString, IsOptional, IsNumber } from 'class-validator'; export class UserDto { readonly walletAddress: string; @@ -42,7 +42,7 @@ export class CreateUserDto { } export class UserUpdateProfileDto { - @IsString() + @IsNumber() readonly id: number; @IsString() From ef14be9833b779a34b86199ad627c59b873c8e5f Mon Sep 17 00:00:00 2001 From: Jonath-z Date: Fri, 27 May 2022 17:33:50 +0200 Subject: [PATCH 09/13] get one drop by dropID done --- .env.example | 7 +- src/app.module.ts | 5 + .../1653602182632-create_nfts_drop.ts | 41 ++++++++ .../1653603128214-nfts_add_dropId.ts | 15 +++ .../nfts-drops/nfts-drops.controller.spec.ts | 18 ++++ .../nfts-drops/nfts-drops.controller.ts | 29 ++++++ src/modules/nfts-drops/nfts-drops.dto.ts | 35 +++++++ src/modules/nfts-drops/nfts-drops.entity.ts | 96 +++++++++++++++++++ src/modules/nfts-drops/nfts-drops.module.ts | 16 ++++ .../nfts-drops/nfts-drops.repository.ts | 5 + .../nfts-drops/nfts-drops.service.spec.ts | 18 ++++ src/modules/nfts-drops/nfts-drops.service.ts | 68 +++++++++++++ src/modules/nfts/nfts.entity.ts | 5 + src/modules/users/users.controller.ts | 49 ++++++++-- src/modules/users/users.dto.ts | 4 - src/modules/users/users.service.ts | 8 ++ 16 files changed, 408 insertions(+), 11 deletions(-) create mode 100644 src/database/migrations/1653602182632-create_nfts_drop.ts create mode 100644 src/database/migrations/1653603128214-nfts_add_dropId.ts create mode 100644 src/modules/nfts-drops/nfts-drops.controller.spec.ts create mode 100644 src/modules/nfts-drops/nfts-drops.controller.ts create mode 100644 src/modules/nfts-drops/nfts-drops.dto.ts create mode 100644 src/modules/nfts-drops/nfts-drops.entity.ts create mode 100644 src/modules/nfts-drops/nfts-drops.module.ts create mode 100644 src/modules/nfts-drops/nfts-drops.repository.ts create mode 100644 src/modules/nfts-drops/nfts-drops.service.spec.ts create mode 100644 src/modules/nfts-drops/nfts-drops.service.ts diff --git a/.env.example b/.env.example index ebc0a76..ace2ec9 100644 --- a/.env.example +++ b/.env.example @@ -13,10 +13,15 @@ POSTGRES_DB=ongama-nfts REDIS_URL=redis://:p4d67adf53b95e0c46cca837d2b4da6d86b4f6867fccf89ac7d826bb06e16c3b5@ec2-34-200-100-197.compute-1.amazonaws.com:7219 # Blockchain -CHAIN_WEB_SOCKET_URL=wss://ws-nd-622-214-640.p2pify.com/e5981fb9579526eefd3126c763e63655 +CHAIN_WEB_SOCKET_URL=wss://ws-nd-801-935-229.p2pify.com/3b49b33557a73c552bae4a15c1843439 CHAIN_ID=80001 MINT_CONTRACT_ADDRESS=0xf2a94BDE9c04B4e15B493BA8aa04BD64b26bde45 DEPLOYER_ADDRESS=0x8182677790c76d5a3CC8298d9643Cf8D4566C564 WEB3_NODE_RPC=https://matic-mumbai.chainstacklabs.com SUBGRAPH_GRAPHQL_ENDPOINT=https://api.thegraph.com/subgraphs/name/verdotte/ongama + +# Drops +MAX_NFT_IN_DROP=12 +DROP_DEFAULT_ACTIVE_DURATION_DAYS=5 +DROP_CREATION_FEE_IN_MATIC=0.5 diff --git a/src/app.module.ts b/src/app.module.ts index 20ebe5e..5365d08 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -17,6 +17,7 @@ import { NftsService } from './modules/nfts/nfts.service'; import { NftRepository } from './modules/nfts/nfts.repository'; import { UsersRepository } from './modules/users/users.repository'; import NftTransferListener from './web-socket/listener/nft-transfer.listener'; +import { NftsDropsModule } from './modules/nfts-drops/nfts-drops.module'; const isProd = process.env.NODE_ENV === 'production'; const providers: any = [ @@ -61,12 +62,16 @@ const redisOptions = isProd ? { url: process.env.REDIS_URL.toString() } : { host DEPLOYER_ADDRESS: Joi.string().required(), SUBGRAPH_GRAPHQL_ENDPOINT: Joi.string().required(), WEB3_NODE_RPC: Joi.string().required(), + MAX_NFT_IN_DROP: Joi.number().required(), + DROP_DEFAULT_ACTIVE_DURATION_DAYS: Joi.number().required(), + DROP_CREATION_FEE_IN_MATIC: Joi.number().required(), }), }), ScheduleModule.forRoot(), DatabaseModule, UsersModule, NftsModule, + NftsDropsModule, ], controllers: [AppController], providers, diff --git a/src/database/migrations/1653602182632-create_nfts_drop.ts b/src/database/migrations/1653602182632-create_nfts_drop.ts new file mode 100644 index 0000000..68b8de1 --- /dev/null +++ b/src/database/migrations/1653602182632-create_nfts_drop.ts @@ -0,0 +1,41 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class createNftsDrop1653602182632 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + ` + -- Table Definition + CREATE TABLE "nfts_drops" ( + "id" BIGSERIAL PRIMARY KEY, + "dropID" varchar NOT NULL, + "creatorId" int8, + "creationFeeTransactionHash" varchar, + "creatorUsername" varchar, + "creatorAddress" varchar, + "title" varchar NOT NULL, + "description" varchar, + "imageUrl" varchar, + "imageUrlThumbnail" varchar, + "published" boolean DEFAULT false, + "active" boolean DEFAULT false, + "collection" boolean DEFAULT false, + "priority" int4 DEFAULT 1, + "nftTotalNumber" int4 DEFAULT 0, + "nftOwnerTotalNumber" int4 DEFAULT 0, + "nftFloorPrice" int4 DEFAULT 0, + "nftHighPrice" int4 DEFAULT 0, + "nftTradedVolume" int4 DEFAULT 0, + "deactivatedAt" timestamptz, + + "createdAt" timestamptz NOT NULL DEFAULT now(), + "updatedAt" timestamptz NOT NULL DEFAULT now() + ); + `, + undefined, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query('DROP TABLE "nfts_drops";', undefined); + } +} diff --git a/src/database/migrations/1653603128214-nfts_add_dropId.ts b/src/database/migrations/1653603128214-nfts_add_dropId.ts new file mode 100644 index 0000000..6fa6e26 --- /dev/null +++ b/src/database/migrations/1653603128214-nfts_add_dropId.ts @@ -0,0 +1,15 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class nftsAddDropId1653603128214 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE nfts ADD COLUMN "dropId" int8; + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE nfts DROP COLUMN "dropId"; + `); + } +} diff --git a/src/modules/nfts-drops/nfts-drops.controller.spec.ts b/src/modules/nfts-drops/nfts-drops.controller.spec.ts new file mode 100644 index 0000000..07af0b9 --- /dev/null +++ b/src/modules/nfts-drops/nfts-drops.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { NftsDropsController } from './nfts-drops.controller'; + +describe('NftsDropsController', () => { + let controller: NftsDropsController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [NftsDropsController], + }).compile(); + + controller = module.get(NftsDropsController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/modules/nfts-drops/nfts-drops.controller.ts b/src/modules/nfts-drops/nfts-drops.controller.ts new file mode 100644 index 0000000..e901abc --- /dev/null +++ b/src/modules/nfts-drops/nfts-drops.controller.ts @@ -0,0 +1,29 @@ +import { Controller, Post, Body, Get, Param } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { NftsDropsService } from './nfts-drops.service'; +import { NftsService } from '../nfts/nfts.service'; +import { CreateDropDto } from './nfts-drops.dto'; + +@ApiTags('nfts-drops') +@Controller('nfts-drops') +export class NftsDropsController { + constructor(private readonly dropService: NftsDropsService, private readonly nftService: NftsService) {} + + @Get('/:dropID') + async findOneByDropId(@Param('dropID') dropID: string) { + const drop = await this.dropService.findOneByDropID(dropID); + // const dropRelatedNfts = await this.nftService. + return { + drop, + // dropRelatedNfts, + }; + } + + @Post('/') + async createDrop(@Body() dropData: CreateDropDto) { + const drop = await this.dropService.save(dropData); + return { + drop, + }; + } +} diff --git a/src/modules/nfts-drops/nfts-drops.dto.ts b/src/modules/nfts-drops/nfts-drops.dto.ts new file mode 100644 index 0000000..8e40b54 --- /dev/null +++ b/src/modules/nfts-drops/nfts-drops.dto.ts @@ -0,0 +1,35 @@ +import { IsNumber, IsString, IsBoolean, IsOptional } from 'class-validator'; + +export class CreateDropDto { + @IsString() + creationFeeTransactionHash: string; + + @IsNumber() + creatorId: number; + + @IsString() + creatorAddress: string; + + @IsString() + creatorUsername: string; + + @IsString() + description: string; + + @IsString() + dropID: string; + + @IsString() + imageUrl: string; + + @IsString() + @IsOptional() + imageUrlThumbnail?: string; + + @IsString() + title: string; + + @IsBoolean() + @IsOptional() + collection?: boolean; +} diff --git a/src/modules/nfts-drops/nfts-drops.entity.ts b/src/modules/nfts-drops/nfts-drops.entity.ts new file mode 100644 index 0000000..4d6287c --- /dev/null +++ b/src/modules/nfts-drops/nfts-drops.entity.ts @@ -0,0 +1,96 @@ +import 'dotenv/config'; +import { + Entity, + Column, + PrimaryGeneratedColumn, + UpdateDateColumn, + CreateDateColumn, + ManyToOne, + JoinColumn, + OneToMany, +} from 'typeorm'; +import dayjs from 'dayjs'; +import { Nft } from '../nfts/nfts.entity'; +import { User } from '../users/users.entity'; + +const { DROP_DEFAULT_ACTIVE_DURATION_DAYS } = process.env; +@Entity('nfts_drops') +export class NftDrop { + @PrimaryGeneratedColumn({ type: 'bigint' }) + readonly id: number; + + @Column({ nullable: false, type: 'varchar', unique: true }) + dropID: string; + + @Column({ nullable: true, type: 'int8', default: null }) + creatorId: number; + + @Column({ nullable: true, type: 'varchar' }) + creatorAddress: string; + + @Column({ nullable: false, type: 'varchar' }) + title: string; + + @Column({ nullable: false, type: 'varchar' }) + description: string; + + @Column({ nullable: true, type: 'varchar' }) + imageUrl: string; + + @Column({ nullable: true, type: 'varchar' }) + imageUrlThumbnail: string; + + @Column({ nullable: true, type: 'varchar' }) + creationFeeTransactionHash: string; + + @Column({ nullable: true, type: 'bool', default: false }) + published: boolean; + + @Column({ nullable: true, type: 'bool', default: true }) + active: boolean; + + @Column({ nullable: true, type: 'int4', default: 1 }) + priority: number; + + @Column({ nullable: true, type: 'int4', default: 0 }) + nftTotalNumber: number; + + @Column({ nullable: true, type: 'int4', default: 0 }) + nftOwnerTotalNumber: number; + + @Column({ nullable: true, type: 'float8' }) + nftFloorPrice: number; + + @Column({ nullable: true, type: 'float8' }) + nftHighPrice: number; + + @Column({ nullable: true, type: 'float8' }) + nftTradedVolume: number; + + @Column({ nullable: true, type: 'bool', default: false }) + collection: boolean; + + @ManyToOne(() => User, (creator: User) => creator.nftsCreated) + @JoinColumn() + creator: Partial; + + @OneToMany(() => Nft, (nft: Nft) => nft.drop) + public nfts: Nft[]; + + @Column({ + nullable: true, + type: 'timestamptz', + default: dayjs().add(Number(DROP_DEFAULT_ACTIVE_DURATION_DAYS), 'days').format(), + }) + deactivatedAt: Date | string; + + @CreateDateColumn({ + default: () => new Date(), + }) + createdAt: Date | string; + + @UpdateDateColumn({ + default: () => new Date(), + }) + updatedAt: Date | string; +} diff --git a/src/modules/nfts-drops/nfts-drops.module.ts b/src/modules/nfts-drops/nfts-drops.module.ts new file mode 100644 index 0000000..125461b --- /dev/null +++ b/src/modules/nfts-drops/nfts-drops.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { NftsDropsService } from './nfts-drops.service'; +import { NftsDropsRepository } from './nfts-drops.repository'; +import { NftsDropsController } from './nfts-drops.controller'; +import { NftsService } from '../nfts/nfts.service'; +import { Nft } from '../nfts/nfts.entity'; +import { UsersRepository } from '../users/users.repository'; +import { UsersService } from '../users/users.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([NftsDropsRepository, Nft, UsersRepository])], + providers: [NftsDropsService, NftsService, UsersService], + controllers: [NftsDropsController], +}) +export class NftsDropsModule {} diff --git a/src/modules/nfts-drops/nfts-drops.repository.ts b/src/modules/nfts-drops/nfts-drops.repository.ts new file mode 100644 index 0000000..d30d63e --- /dev/null +++ b/src/modules/nfts-drops/nfts-drops.repository.ts @@ -0,0 +1,5 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { NftDrop } from './nfts-drops.entity'; + +@EntityRepository(NftDrop) +export class NftsDropsRepository extends Repository {} diff --git a/src/modules/nfts-drops/nfts-drops.service.spec.ts b/src/modules/nfts-drops/nfts-drops.service.spec.ts new file mode 100644 index 0000000..8bdbbf6 --- /dev/null +++ b/src/modules/nfts-drops/nfts-drops.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { NftsDropsService } from './nfts-drops.service'; + +describe('NftsDropsService', () => { + let service: NftsDropsService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [NftsDropsService], + }).compile(); + + service = module.get(NftsDropsService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/modules/nfts-drops/nfts-drops.service.ts b/src/modules/nfts-drops/nfts-drops.service.ts new file mode 100644 index 0000000..a857320 --- /dev/null +++ b/src/modules/nfts-drops/nfts-drops.service.ts @@ -0,0 +1,68 @@ +import 'dotenv/config'; +import { Injectable } from '@nestjs/common'; +import { MoreThan, UpdateResult } from 'typeorm'; +import dayjs from 'dayjs'; +import { NftsDropsRepository } from './nfts-drops.repository'; +import { NftDrop } from './nfts-drops.entity'; +import { UsersRepository } from '../users/users.repository'; +import { NftRepository } from '../nfts/nfts.repository'; + +@Injectable() +export class NftsDropsService { + constructor( + public readonly dropRepository: NftsDropsRepository, + public readonly userRepository: UsersRepository, + public readonly nftRepository: NftRepository, + ) {} + + findActiveDrops(): Promise { + return this.dropRepository.find({ + order: { + priority: 'DESC', + id: 'DESC', + }, + where: { + active: true, + published: true, + priority: MoreThan(0), + deactivatedAt: MoreThan(dayjs().format()), + }, + take: 16, + }); + } + + findOneByDropID(dropID: string): Promise { + return this.dropRepository.findOne({ + relations: ['creator'], + where: { dropID }, + }); + } + + findOneByID(id: number): Promise { + return this.dropRepository.findOne({ + where: { id }, + }); + } + + async save(data: Partial): Promise { + return this.dropRepository.save({ + createdAt: new Date(), + updatedAt: new Date(), + priority: 1, + active: true, + published: false, + deactivatedAt: dayjs().add(Number(process.env.DROP_DEFAULT_ACTIVE_DURATION_DAYS), 'days').format(), + ...data, + }); + } + + publish(id: number, published = true): Promise { + return this.dropRepository.update(id, { + published, + }); + } + + updateDrop(id: number, data: Partial): Promise { + return this.dropRepository.update(id, data); + } +} diff --git a/src/modules/nfts/nfts.entity.ts b/src/modules/nfts/nfts.entity.ts index f057cd6..7e8d86e 100644 --- a/src/modules/nfts/nfts.entity.ts +++ b/src/modules/nfts/nfts.entity.ts @@ -7,6 +7,7 @@ import { ManyToOne, JoinColumn, } from 'typeorm'; +import { NftDrop } from '../nfts-drops/nfts-drops.entity'; import { User } from '../users/users.entity'; @Entity('nfts') export class Nft { @@ -99,6 +100,10 @@ export class Nft { @JoinColumn() owner: User; + @ManyToOne(() => NftDrop, (drop: NftDrop) => drop.nfts) + @JoinColumn() + drop: NftDrop; + @CreateDateColumn({ default: () => new Date(), }) diff --git a/src/modules/users/users.controller.ts b/src/modules/users/users.controller.ts index 4f12b45..32f52d8 100644 --- a/src/modules/users/users.controller.ts +++ b/src/modules/users/users.controller.ts @@ -1,10 +1,11 @@ -import { Controller, Get, Param, Put, Body, CacheTTL, UseGuards, BadRequestException } from '@nestjs/common'; +import { Controller, Get, Param, Put, Body, BadRequestException, CacheTTL, UseGuards } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { WalletSignatureGuard } from 'src/guards/walletSignature.guard'; import { isValidAddress } from 'src/utils/Utils'; import { NftsService } from '../nfts/nfts.service'; import { UsersService } from './users.service'; import { UserUpdateProfileDto } from './users.dto'; +import { Web3Helper } from '../../utils/web3Helper'; @ApiTags('users') @Controller('users') @@ -12,10 +13,47 @@ import { UserUpdateProfileDto } from './users.dto'; export class UsersController { constructor(private readonly userService: UsersService, private readonly nftService: NftsService) {} + @UseGuards(WalletSignatureGuard) @Put('/profile') - async updateProfile(@Body() data: UserUpdateProfileDto) { - delete data.signature; - await this.userService.updateById(data.id, data); + async updateProfile(@Body() userData: UserUpdateProfileDto) { + const checksumAddress = Web3Helper.getAddressChecksum(userData.walletAddress); + + const foundUser = await this.userService.findByAddress(checksumAddress); + + if (userData.username && foundUser?.username?.toLocaleLowerCase() !== userData?.username?.toLocaleLowerCase()) { + const newFoundUser = await this.userService.findByUsername(userData.username); + + if (newFoundUser && foundUser.walletAddress !== newFoundUser.walletAddress) { + throw new BadRequestException( + 'Username is already taken: %{username} - %{address} Attempt', + JSON.stringify({ + username: userData.username, + address: userData.walletAddress, + }), + ); + } + } + + if (userData.signature) { + delete userData.signature; + } + + await this.userService.updateById(foundUser.id, { + username: userData?.username || foundUser?.username || null, + userBio: userData?.userBio || foundUser?.userBio || null, + avatarUrl: userData?.avatarUrl || foundUser?.avatarUrl || null, + avatarUrlCompressed: userData?.avatarUrl + ? userData?.avatarUrlCompressed || null + : foundUser?.avatarUrlCompressed || null, + avatarUrlThumbnail: userData?.avatarUrl + ? userData?.avatarUrlThumbnail || null + : foundUser?.avatarUrlThumbnail || null, + coverUrl: userData?.coverUrl ? userData?.coverUrl || null : foundUser?.coverUrl || null, + coverThumbnailUrl: userData?.coverThumbnailUrl + ? userData?.coverThumbnailUrl || null + : foundUser?.coverThumbnailUrl || null, + usernameLowercase: (userData?.username || foundUser?.username || '')?.toLocaleLowerCase(), + }); return { statusCode: 200, @@ -28,8 +66,7 @@ export class UsersController { let user = await this.userService.findByAddress(addressOrUsername); if (!user) { - const valid = isValidAddress(addressOrUsername); - if (!valid) { + if (!isValidAddress(addressOrUsername)) { throw new BadRequestException('The address or username is not valid'); } user = await this.userService.saveNewUser({ diff --git a/src/modules/users/users.dto.ts b/src/modules/users/users.dto.ts index 9486007..669a530 100644 --- a/src/modules/users/users.dto.ts +++ b/src/modules/users/users.dto.ts @@ -15,10 +15,6 @@ export class UserUpdateProfileDto { @IsOptional() readonly username?: string; - @IsString() - @IsOptional() - readonly usernameLowercase?: string; - @IsString() @IsOptional() readonly userBio: string; diff --git a/src/modules/users/users.service.ts b/src/modules/users/users.service.ts index 03a8178..8437720 100644 --- a/src/modules/users/users.service.ts +++ b/src/modules/users/users.service.ts @@ -18,6 +18,14 @@ export class UsersService { }); } + findByUsername(username: string): Promise { + return this.userRepository.findOne({ + where: { + username, + }, + }); + } + findAll(): Promise { return this.userRepository.find(); } From bfcaa757699213b4790d98cba1466304e8129866 Mon Sep 17 00:00:00 2001 From: Jonath-z Date: Sat, 28 May 2022 15:58:06 +0200 Subject: [PATCH 10/13] listedOnchain corrected into listedOnChain in order to match the entity, creatorUsername added --- src/database/migrations/1651143902595-create_nfts_table.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/database/migrations/1651143902595-create_nfts_table.ts b/src/database/migrations/1651143902595-create_nfts_table.ts index 2dfe294..31a9d8b 100644 --- a/src/database/migrations/1651143902595-create_nfts_table.ts +++ b/src/database/migrations/1651143902595-create_nfts_table.ts @@ -14,13 +14,14 @@ export class createNftsTable1651143902595 implements MigrationInterface { "ownerId" int8, "creatorAddress" varchar NOT NULL, "creatorId" int8, + "creatorUsername" varchar, "ownerUsername" varchar, "name" varchar NOT NULL, "description" varchar NOT NULL, "fileSize" float4 DEFAULT 0.0, "fileType" varchar, "listed" bool DEFAULT false, - "listedOnchain" bool DEFAULT false, + "listedOnChain" bool DEFAULT false, "verified" bool DEFAULT false, "isVideo" bool DEFAULT false, "image" varchar, From d1a71bc0ac2507d0f3f859b50eaa4e521c5a1fef Mon Sep 17 00:00:00 2001 From: Jonath-z Date: Sat, 28 May 2022 15:58:40 +0200 Subject: [PATCH 11/13] get single drop added and tested --- src/modules/nfts-drops/nfts-drops.controller.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/modules/nfts-drops/nfts-drops.controller.ts b/src/modules/nfts-drops/nfts-drops.controller.ts index e901abc..13ec259 100644 --- a/src/modules/nfts-drops/nfts-drops.controller.ts +++ b/src/modules/nfts-drops/nfts-drops.controller.ts @@ -12,10 +12,8 @@ export class NftsDropsController { @Get('/:dropID') async findOneByDropId(@Param('dropID') dropID: string) { const drop = await this.dropService.findOneByDropID(dropID); - // const dropRelatedNfts = await this.nftService. return { drop, - // dropRelatedNfts, }; } From 96323eba8b98cdd9114182c37e660222fc3f9259 Mon Sep 17 00:00:00 2001 From: Jonath-z Date: Sat, 28 May 2022 15:59:41 +0200 Subject: [PATCH 12/13] nfts added to relations in find drop by Id service --- src/modules/nfts-drops/nfts-drops.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/nfts-drops/nfts-drops.service.ts b/src/modules/nfts-drops/nfts-drops.service.ts index a857320..cb4767c 100644 --- a/src/modules/nfts-drops/nfts-drops.service.ts +++ b/src/modules/nfts-drops/nfts-drops.service.ts @@ -33,7 +33,7 @@ export class NftsDropsService { findOneByDropID(dropID: string): Promise { return this.dropRepository.findOne({ - relations: ['creator'], + relations: ['creator', 'nfts'], where: { dropID }, }); } From 8dc794e65f6dd99c05c2025449d7c5ee746ef331 Mon Sep 17 00:00:00 2001 From: Jonath-z Date: Sat, 28 May 2022 16:00:16 +0200 Subject: [PATCH 13/13] drop id column added --- src/modules/nfts/nfts.entity.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/modules/nfts/nfts.entity.ts b/src/modules/nfts/nfts.entity.ts index 7e8d86e..9b96362 100644 --- a/src/modules/nfts/nfts.entity.ts +++ b/src/modules/nfts/nfts.entity.ts @@ -100,6 +100,9 @@ export class Nft { @JoinColumn() owner: User; + @Column({ nullable: true, type: 'int8', default: null }) + dropId: number; + @ManyToOne(() => NftDrop, (drop: NftDrop) => drop.nfts) @JoinColumn() drop: NftDrop;