From 33f367207096e33695d69f5dff12a47fe8d5283a Mon Sep 17 00:00:00 2001 From: harisato Date: Wed, 7 Aug 2024 09:52:43 +0700 Subject: [PATCH 01/50] feat: add creator role --- docker-compose-authorizer.yml | 7 ++++--- .../down.sql | 4 ++++ .../up.sql | 2 ++ .../down.sql | 1 + .../up.sql | 1 + .../down.sql | 2 ++ .../up.sql | 2 ++ 7 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 hasura/migrations/punkga-pg/1722914237311_alter_table_public_creators_add_column_email/down.sql create mode 100644 hasura/migrations/punkga-pg/1722914237311_alter_table_public_creators_add_column_email/up.sql create mode 100644 hasura/migrations/punkga-pg/1722914246578_alter_table_public_creators_alter_column_email/down.sql create mode 100644 hasura/migrations/punkga-pg/1722914246578_alter_table_public_creators_alter_column_email/up.sql create mode 100644 hasura/migrations/punkga-pg/1722914254121_alter_table_public_creators_add_unique_email/down.sql create mode 100644 hasura/migrations/punkga-pg/1722914254121_alter_table_public_creators_add_unique_email/up.sql diff --git a/docker-compose-authorizer.yml b/docker-compose-authorizer.yml index 3cb3ce76..7c4236d9 100644 --- a/docker-compose-authorizer.yml +++ b/docker-compose-authorizer.yml @@ -1,8 +1,8 @@ version: '3.6' services: authorizer: - image: ghcr.io/aura-nw/authorizer:0.1.22.4 - # image: quochai194/authorizer:0.1.20.8 + # image: ghcr.io/aura-nw/authorizer:0.1.22.4 + image: quochai194/authorizer:0.1.23 # image: lakhansamani/authorizer:1.1.50 restart: always ports: @@ -18,8 +18,9 @@ services: ADMIN_SECRET: BtfpvmBDJ8b3SGsw ORGANIZATION_NAME: AuraNetwork ORGANIZATION_LOGO: 'https://aura-explorer-assets.s3.ap-southeast-1.amazonaws.com/aura.png' + ROLES: user,creator,admin DEFAULT_ROLES: user - PROTECTED_ROLES: admin + PROTECTED_ROLES: creator,admin ACCESS_TOKEN_EXPIRY_TIME: 3h CUSTOM_ACCESS_TOKEN_SCRIPT: function(user,tokenPayload) {var data = tokenPayload; data['https://hasura.io/jwt/claims'] = {'x-hasura-user-id':user.id,'x-hasura-default-role':tokenPayload.allowed_roles[0], 'x-hasura-allowed-roles':user.roles }; return data;} ZALO_APP_ID: 2318977898294005000 diff --git a/hasura/migrations/punkga-pg/1722914237311_alter_table_public_creators_add_column_email/down.sql b/hasura/migrations/punkga-pg/1722914237311_alter_table_public_creators_add_column_email/down.sql new file mode 100644 index 00000000..0dfed3ae --- /dev/null +++ b/hasura/migrations/punkga-pg/1722914237311_alter_table_public_creators_add_column_email/down.sql @@ -0,0 +1,4 @@ +-- Could not auto-generate a down migration. +-- Please write an appropriate down migration for the SQL below: +-- alter table "public"."creators" add column "email" text +-- null; diff --git a/hasura/migrations/punkga-pg/1722914237311_alter_table_public_creators_add_column_email/up.sql b/hasura/migrations/punkga-pg/1722914237311_alter_table_public_creators_add_column_email/up.sql new file mode 100644 index 00000000..50eed9a6 --- /dev/null +++ b/hasura/migrations/punkga-pg/1722914237311_alter_table_public_creators_add_column_email/up.sql @@ -0,0 +1,2 @@ +alter table "public"."creators" add column "email" text + null; diff --git a/hasura/migrations/punkga-pg/1722914246578_alter_table_public_creators_alter_column_email/down.sql b/hasura/migrations/punkga-pg/1722914246578_alter_table_public_creators_alter_column_email/down.sql new file mode 100644 index 00000000..8c6580d2 --- /dev/null +++ b/hasura/migrations/punkga-pg/1722914246578_alter_table_public_creators_alter_column_email/down.sql @@ -0,0 +1 @@ +alter table "public"."creators" drop constraint "creators_email_key"; diff --git a/hasura/migrations/punkga-pg/1722914246578_alter_table_public_creators_alter_column_email/up.sql b/hasura/migrations/punkga-pg/1722914246578_alter_table_public_creators_alter_column_email/up.sql new file mode 100644 index 00000000..0b00948b --- /dev/null +++ b/hasura/migrations/punkga-pg/1722914246578_alter_table_public_creators_alter_column_email/up.sql @@ -0,0 +1 @@ +alter table "public"."creators" add constraint "creators_email_key" unique ("email"); diff --git a/hasura/migrations/punkga-pg/1722914254121_alter_table_public_creators_add_unique_email/down.sql b/hasura/migrations/punkga-pg/1722914254121_alter_table_public_creators_add_unique_email/down.sql new file mode 100644 index 00000000..f1493738 --- /dev/null +++ b/hasura/migrations/punkga-pg/1722914254121_alter_table_public_creators_add_unique_email/down.sql @@ -0,0 +1,2 @@ +alter table "public"."creators" drop constraint "creators_email_key"; +alter table "public"."creators" add constraint "creators_email_key" unique ("email"); diff --git a/hasura/migrations/punkga-pg/1722914254121_alter_table_public_creators_add_unique_email/up.sql b/hasura/migrations/punkga-pg/1722914254121_alter_table_public_creators_add_unique_email/up.sql new file mode 100644 index 00000000..f1493738 --- /dev/null +++ b/hasura/migrations/punkga-pg/1722914254121_alter_table_public_creators_add_unique_email/up.sql @@ -0,0 +1,2 @@ +alter table "public"."creators" drop constraint "creators_email_key"; +alter table "public"."creators" add constraint "creators_email_key" unique ("email"); From ec5e0b28154d766e6bbe2f61f4f1487f322ae86e Mon Sep 17 00:00:00 2001 From: harisato Date: Wed, 7 Aug 2024 11:12:02 +0700 Subject: [PATCH 02/50] feat: update contest api --- .../punkga-pg/tables/public_contest.yaml | 9 +++++ .../punkga-pg/tables/public_i18n.yaml | 6 ++++ hasura/metadata/query_collections.yaml | 34 +++++++++++------- .../down.sql | 4 +++ .../up.sql | 2 ++ .../down.sql | 1 + .../up.sql | 5 +++ .../down.sql | 1 + .../up.sql | 1 + .../down.sql | 4 +++ .../up.sql | 2 ++ src/modules/files/dto/upload.dto.ts | 11 ++++++ src/modules/files/files.controller.ts | 36 +++++++++++++++++++ src/modules/files/files.module.ts | 4 +++ 14 files changed, 107 insertions(+), 13 deletions(-) create mode 100644 hasura/migrations/punkga-pg/1723000761110_alter_table_public_i18n_add_column_contest_id/down.sql create mode 100644 hasura/migrations/punkga-pg/1723000761110_alter_table_public_i18n_add_column_contest_id/up.sql create mode 100644 hasura/migrations/punkga-pg/1723000779882_set_fk_public_i18n_contest_id/down.sql create mode 100644 hasura/migrations/punkga-pg/1723000779882_set_fk_public_i18n_contest_id/up.sql create mode 100644 hasura/migrations/punkga-pg/1723000792267_alter_table_public_i18n_add_unique_language_id_contest_id/down.sql create mode 100644 hasura/migrations/punkga-pg/1723000792267_alter_table_public_i18n_add_unique_language_id_contest_id/up.sql create mode 100644 hasura/migrations/punkga-pg/1723000872444_alter_table_public_contest_add_column_isLive/down.sql create mode 100644 hasura/migrations/punkga-pg/1723000872444_alter_table_public_contest_add_column_isLive/up.sql create mode 100644 src/modules/files/dto/upload.dto.ts create mode 100644 src/modules/files/files.controller.ts diff --git a/hasura/metadata/databases/punkga-pg/tables/public_contest.yaml b/hasura/metadata/databases/punkga-pg/tables/public_contest.yaml index 6f9c3881..ef100b64 100644 --- a/hasura/metadata/databases/punkga-pg/tables/public_contest.yaml +++ b/hasura/metadata/databases/punkga-pg/tables/public_contest.yaml @@ -9,6 +9,13 @@ array_relationships: table: name: artworks schema: public + - name: contest_i18ns + using: + foreign_key_constraint_on: + column: contest_id + table: + name: i18n + schema: public - name: contest_mangas using: foreign_key_constraint_on: @@ -20,6 +27,7 @@ select_permissions: - role: anonymous permission: columns: + - isLive - id - slug - created_at @@ -31,6 +39,7 @@ select_permissions: - role: user permission: columns: + - isLive - id - slug - created_at diff --git a/hasura/metadata/databases/punkga-pg/tables/public_i18n.yaml b/hasura/metadata/databases/punkga-pg/tables/public_i18n.yaml index b4d39d00..bcb495ce 100644 --- a/hasura/metadata/databases/punkga-pg/tables/public_i18n.yaml +++ b/hasura/metadata/databases/punkga-pg/tables/public_i18n.yaml @@ -9,9 +9,12 @@ select_permissions: - role: anonymous permission: columns: + - banner_id - campaign_id + - contest_id - id - language_id + - launchpad_id - quest_id - data - created_at @@ -20,9 +23,12 @@ select_permissions: - role: user permission: columns: + - banner_id - campaign_id + - contest_id - id - language_id + - launchpad_id - quest_id - data - created_at diff --git a/hasura/metadata/query_collections.yaml b/hasura/metadata/query_collections.yaml index 684fa51e..28cbaeda 100644 --- a/hasura/metadata/query_collections.yaml +++ b/hasura/metadata/query_collections.yaml @@ -1321,32 +1321,40 @@ } } } - - name: Public - Get contest by creator id + - name: Public - Get artwork by creator and contest query: | - query contest ($creator_id: Int!, $limit: Int = 10, $offset: Int = 0) { - contest(where: {_or:[{contest_artworks:{creator_id:{_eq:$creator_id}}},{contest_mangas:{manga_creators:{creator_id:{_eq:$creator_id}}}}]}, limit: $limit, offset: $offset) { + query artwork ($contest_id: Int!, $id: Int!, $limit: Int = 100, $offset: Int = 0) { + artworks(where: {contest_id:{_eq:$contest_id},creator_id:{_eq:$id}}, limit: $limit, offset: $offset) { id - start_date - end_date - slug + url + source_url created_at } - contest_aggregate(where: {_or:[{contest_artworks:{creator_id:{_eq:$creator_id}}},{contest_mangas:{manga_creators:{creator_id:{_eq:$creator_id}}}}]}, limit: $limit, offset: $offset) { + artworks_aggregate(where: {contest_id:{_eq:$contest_id},creator_id:{_eq:$id}}, limit: $limit, offset: $offset) { aggregate { count } } } - - name: Public - Get artwork by creator and contest + - name: Public - Get contest by creator id query: | - query artwork ($contest_id: Int!, $id: Int!, $limit: Int = 100, $offset: Int = 0) { - artworks(where: {contest_id:{_eq:$contest_id},creator_id:{_eq:$id}}, limit: $limit, offset: $offset) { + query contest ($creator_id: Int!, $limit: Int = 10, $offset: Int = 0) { + contest(where: {_or:[{contest_artworks:{creator_id:{_eq:$creator_id}}},{contest_mangas:{manga_creators:{creator_id:{_eq:$creator_id}}}}]}, limit: $limit, offset: $offset) { id - url - source_url + start_date + end_date + slug created_at + contest_i18ns { + data + i18n_language { + id + is_main + symbol + } + } } - artworks_aggregate(where: {contest_id:{_eq:$contest_id},creator_id:{_eq:$id}}, limit: $limit, offset: $offset) { + contest_aggregate(where: {_or:[{contest_artworks:{creator_id:{_eq:$creator_id}}},{contest_mangas:{manga_creators:{creator_id:{_eq:$creator_id}}}}]}, limit: $limit, offset: $offset) { aggregate { count } diff --git a/hasura/migrations/punkga-pg/1723000761110_alter_table_public_i18n_add_column_contest_id/down.sql b/hasura/migrations/punkga-pg/1723000761110_alter_table_public_i18n_add_column_contest_id/down.sql new file mode 100644 index 00000000..b18e81f6 --- /dev/null +++ b/hasura/migrations/punkga-pg/1723000761110_alter_table_public_i18n_add_column_contest_id/down.sql @@ -0,0 +1,4 @@ +-- Could not auto-generate a down migration. +-- Please write an appropriate down migration for the SQL below: +-- alter table "public"."i18n" add column "contest_id" integer +-- null; diff --git a/hasura/migrations/punkga-pg/1723000761110_alter_table_public_i18n_add_column_contest_id/up.sql b/hasura/migrations/punkga-pg/1723000761110_alter_table_public_i18n_add_column_contest_id/up.sql new file mode 100644 index 00000000..62a2ddc7 --- /dev/null +++ b/hasura/migrations/punkga-pg/1723000761110_alter_table_public_i18n_add_column_contest_id/up.sql @@ -0,0 +1,2 @@ +alter table "public"."i18n" add column "contest_id" integer + null; diff --git a/hasura/migrations/punkga-pg/1723000779882_set_fk_public_i18n_contest_id/down.sql b/hasura/migrations/punkga-pg/1723000779882_set_fk_public_i18n_contest_id/down.sql new file mode 100644 index 00000000..61d3d427 --- /dev/null +++ b/hasura/migrations/punkga-pg/1723000779882_set_fk_public_i18n_contest_id/down.sql @@ -0,0 +1 @@ +alter table "public"."i18n" drop constraint "i18n_contest_id_fkey"; diff --git a/hasura/migrations/punkga-pg/1723000779882_set_fk_public_i18n_contest_id/up.sql b/hasura/migrations/punkga-pg/1723000779882_set_fk_public_i18n_contest_id/up.sql new file mode 100644 index 00000000..453df4c4 --- /dev/null +++ b/hasura/migrations/punkga-pg/1723000779882_set_fk_public_i18n_contest_id/up.sql @@ -0,0 +1,5 @@ +alter table "public"."i18n" + add constraint "i18n_contest_id_fkey" + foreign key ("contest_id") + references "public"."contest" + ("id") on update cascade on delete cascade; diff --git a/hasura/migrations/punkga-pg/1723000792267_alter_table_public_i18n_add_unique_language_id_contest_id/down.sql b/hasura/migrations/punkga-pg/1723000792267_alter_table_public_i18n_add_unique_language_id_contest_id/down.sql new file mode 100644 index 00000000..b162f1b5 --- /dev/null +++ b/hasura/migrations/punkga-pg/1723000792267_alter_table_public_i18n_add_unique_language_id_contest_id/down.sql @@ -0,0 +1 @@ +alter table "public"."i18n" drop constraint "i18n_language_id_contest_id_key"; diff --git a/hasura/migrations/punkga-pg/1723000792267_alter_table_public_i18n_add_unique_language_id_contest_id/up.sql b/hasura/migrations/punkga-pg/1723000792267_alter_table_public_i18n_add_unique_language_id_contest_id/up.sql new file mode 100644 index 00000000..18cb35bd --- /dev/null +++ b/hasura/migrations/punkga-pg/1723000792267_alter_table_public_i18n_add_unique_language_id_contest_id/up.sql @@ -0,0 +1 @@ +alter table "public"."i18n" add constraint "i18n_language_id_contest_id_key" unique ("language_id", "contest_id"); diff --git a/hasura/migrations/punkga-pg/1723000872444_alter_table_public_contest_add_column_isLive/down.sql b/hasura/migrations/punkga-pg/1723000872444_alter_table_public_contest_add_column_isLive/down.sql new file mode 100644 index 00000000..246dd840 --- /dev/null +++ b/hasura/migrations/punkga-pg/1723000872444_alter_table_public_contest_add_column_isLive/down.sql @@ -0,0 +1,4 @@ +-- Could not auto-generate a down migration. +-- Please write an appropriate down migration for the SQL below: +-- alter table "public"."contest" add column "isLive" boolean +-- null default 'false'; diff --git a/hasura/migrations/punkga-pg/1723000872444_alter_table_public_contest_add_column_isLive/up.sql b/hasura/migrations/punkga-pg/1723000872444_alter_table_public_contest_add_column_isLive/up.sql new file mode 100644 index 00000000..cd2188b2 --- /dev/null +++ b/hasura/migrations/punkga-pg/1723000872444_alter_table_public_contest_add_column_isLive/up.sql @@ -0,0 +1,2 @@ +alter table "public"."contest" add column "isLive" boolean + null default 'false'; diff --git a/src/modules/files/dto/upload.dto.ts b/src/modules/files/dto/upload.dto.ts new file mode 100644 index 00000000..bd8d9882 --- /dev/null +++ b/src/modules/files/dto/upload.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class UploadImageS3Dto { + @ApiProperty() + @IsString() + key: string; + + @ApiProperty({ type: 'string', format: 'binary' }) + file: Express.Multer.File; +} diff --git a/src/modules/files/files.controller.ts b/src/modules/files/files.controller.ts new file mode 100644 index 00000000..8dda55e3 --- /dev/null +++ b/src/modules/files/files.controller.ts @@ -0,0 +1,36 @@ +import { + Body, + Controller, + Post, + UploadedFile, + UseGuards, + UseInterceptors, +} from '@nestjs/common'; +import { AuthGuard } from '../../auth/auth.guard'; +import { ApiBearerAuth, ApiConsumes, ApiTags } from '@nestjs/swagger'; +import { AuthUserInterceptor } from '../../interceptors/auth-user.interceptor'; +import { FileInterceptor } from '@nestjs/platform-express'; +import { Roles } from '../../auth/roles.decorator'; +import { Role } from '../../auth/role.enum'; +import { RolesGuard } from '../../auth/role.guard'; +import { FilesService } from './files.service'; +import { UploadImageS3Dto } from './dto/upload.dto'; + +@Controller('files') +@ApiTags('files') +export class FileController { + constructor(private readonly fileSvc: FilesService) {} + + @UseGuards(AuthGuard, RolesGuard) + @ApiBearerAuth() + @Roles(Role.Admin) + @Post() + @ApiConsumes('multipart/form-data') + @UseInterceptors(AuthUserInterceptor, FileInterceptor('file')) + create( + @Body() data: UploadImageS3Dto, + @UploadedFile() file: Express.Multer.File + ) { + return this.fileSvc.uploadImageToS3(data.key, file); + } +} diff --git a/src/modules/files/files.module.ts b/src/modules/files/files.module.ts index d6383b7c..8822b30d 100644 --- a/src/modules/files/files.module.ts +++ b/src/modules/files/files.module.ts @@ -1,8 +1,12 @@ import { Module } from '@nestjs/common'; import { FilesService } from './files.service'; +import { FileController } from './files.controller'; +import { JwtModule } from '@nestjs/jwt'; @Module({ + imports: [JwtModule], providers: [FilesService], + controllers: [FileController], exports: [FilesService], }) export class FilesModule {} From bb419ce133f1e81f34c46070122a80fc5002ffdf Mon Sep 17 00:00:00 2001 From: ThienLK Date: Wed, 7 Aug 2024 15:40:38 +0700 Subject: [PATCH 03/50] add manga collection table --- .../tables/public_manga_collection.yaml | 3 +++ .../databases/punkga-pg/tables/tables.yaml | 1 + .../down.sql | 1 + .../up.sql | 17 +++++++++++++++++ .../down.sql | 1 + .../up.sql | 1 + 6 files changed, 24 insertions(+) create mode 100644 hasura/metadata/databases/punkga-pg/tables/public_manga_collection.yaml create mode 100644 hasura/migrations/punkga-pg/1723018204117_create_table_public_manga_collection/down.sql create mode 100644 hasura/migrations/punkga-pg/1723018204117_create_table_public_manga_collection/up.sql create mode 100644 hasura/migrations/punkga-pg/1723018279264_alter_table_public_manga_collection_add_unique_manga_id_launchpad_id/down.sql create mode 100644 hasura/migrations/punkga-pg/1723018279264_alter_table_public_manga_collection_add_unique_manga_id_launchpad_id/up.sql diff --git a/hasura/metadata/databases/punkga-pg/tables/public_manga_collection.yaml b/hasura/metadata/databases/punkga-pg/tables/public_manga_collection.yaml new file mode 100644 index 00000000..5107402f --- /dev/null +++ b/hasura/metadata/databases/punkga-pg/tables/public_manga_collection.yaml @@ -0,0 +1,3 @@ +table: + name: manga_collection + schema: public diff --git a/hasura/metadata/databases/punkga-pg/tables/tables.yaml b/hasura/metadata/databases/punkga-pg/tables/tables.yaml index d0ecea56..4f4ab8a4 100644 --- a/hasura/metadata/databases/punkga-pg/tables/tables.yaml +++ b/hasura/metadata/databases/punkga-pg/tables/tables.yaml @@ -16,6 +16,7 @@ - "!include public_launchpad.yaml" - "!include public_likes.yaml" - "!include public_manga.yaml" +- "!include public_manga_collection.yaml" - "!include public_manga_creator.yaml" - "!include public_manga_languages.yaml" - "!include public_manga_tag.yaml" diff --git a/hasura/migrations/punkga-pg/1723018204117_create_table_public_manga_collection/down.sql b/hasura/migrations/punkga-pg/1723018204117_create_table_public_manga_collection/down.sql new file mode 100644 index 00000000..a6539226 --- /dev/null +++ b/hasura/migrations/punkga-pg/1723018204117_create_table_public_manga_collection/down.sql @@ -0,0 +1 @@ +DROP TABLE "public"."manga_collection"; diff --git a/hasura/migrations/punkga-pg/1723018204117_create_table_public_manga_collection/up.sql b/hasura/migrations/punkga-pg/1723018204117_create_table_public_manga_collection/up.sql new file mode 100644 index 00000000..1b93eb6d --- /dev/null +++ b/hasura/migrations/punkga-pg/1723018204117_create_table_public_manga_collection/up.sql @@ -0,0 +1,17 @@ +CREATE TABLE "public"."manga_collection" ("id" serial NOT NULL, "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "manga_id" integer NOT NULL, "launchpad_id" integer NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("launchpad_id") REFERENCES "public"."launchpad"("id") ON UPDATE cascade ON DELETE cascade, FOREIGN KEY ("manga_id") REFERENCES "public"."manga"("id") ON UPDATE cascade ON DELETE cascade); +CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"() +RETURNS TRIGGER AS $$ +DECLARE + _new record; +BEGIN + _new := NEW; + _new."updated_at" = NOW(); + RETURN _new; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER "set_public_manga_collection_updated_at" +BEFORE UPDATE ON "public"."manga_collection" +FOR EACH ROW +EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"(); +COMMENT ON TRIGGER "set_public_manga_collection_updated_at" ON "public"."manga_collection" +IS 'trigger to set value of column "updated_at" to current timestamp on row update'; diff --git a/hasura/migrations/punkga-pg/1723018279264_alter_table_public_manga_collection_add_unique_manga_id_launchpad_id/down.sql b/hasura/migrations/punkga-pg/1723018279264_alter_table_public_manga_collection_add_unique_manga_id_launchpad_id/down.sql new file mode 100644 index 00000000..1339370f --- /dev/null +++ b/hasura/migrations/punkga-pg/1723018279264_alter_table_public_manga_collection_add_unique_manga_id_launchpad_id/down.sql @@ -0,0 +1 @@ +alter table "public"."manga_collection" drop constraint "manga_collection_manga_id_launchpad_id_key"; diff --git a/hasura/migrations/punkga-pg/1723018279264_alter_table_public_manga_collection_add_unique_manga_id_launchpad_id/up.sql b/hasura/migrations/punkga-pg/1723018279264_alter_table_public_manga_collection_add_unique_manga_id_launchpad_id/up.sql new file mode 100644 index 00000000..c7e457c4 --- /dev/null +++ b/hasura/migrations/punkga-pg/1723018279264_alter_table_public_manga_collection_add_unique_manga_id_launchpad_id/up.sql @@ -0,0 +1 @@ +alter table "public"."manga_collection" add constraint "manga_collection_manga_id_launchpad_id_key" unique ("manga_id", "launchpad_id"); From c9987024571db6a3f86c079e3ba07712b364326b Mon Sep 17 00:00:00 2001 From: harisato Date: Wed, 7 Aug 2024 17:21:35 +0700 Subject: [PATCH 04/50] feat: resize artwork --- package.json | 2 + src/modules/artwork/artwork.service.ts | 166 ++++++++++-------- src/modules/chapter/chapter.controller.ts | 2 +- src/modules/files/files.service.ts | 27 +-- yarn.lock | 196 +++++++++++++++++++++- 5 files changed, 310 insertions(+), 83 deletions(-) diff --git a/package.json b/package.json index c8eb5d3e..ac24c744 100644 --- a/package.json +++ b/package.json @@ -70,12 +70,14 @@ "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.2.0", + "sharp": "^0.33.4", "siwe": "^2.3.2", "typeorm": "^0.3.20" }, "devDependencies": { "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", + "@types/sharp": "^0.32.0", "@types/cron": "^2.0.1", "@types/decompress": "^4.2.4", "@types/express": "^4.17.13", diff --git a/src/modules/artwork/artwork.service.ts b/src/modules/artwork/artwork.service.ts index 139d40f2..b8f38c72 100644 --- a/src/modules/artwork/artwork.service.ts +++ b/src/modules/artwork/artwork.service.ts @@ -9,6 +9,7 @@ import { parse } from 'csv-parse/sync'; import axios from 'axios'; import { GoogleAuth } from 'google-auth-library'; import { google } from 'googleapis'; +import sharp from 'sharp'; import { ContextProvider } from '../../providers/contex.provider'; import { FilesService } from '../files/files.service'; @@ -51,73 +52,108 @@ export class ArtworkService implements OnModuleInit { artworks: [record[2].trim(), record[3]?.trim() || ''], })); - creatorArtworks.forEach(async ({ creator, artworks }) => { - // insert creator - const insertCreatorResult = await this.artworkGraphql.insertCreator( + // chunk array to small array + const chunkSize = 20; + for (let i = 0; i < creatorArtworks.length; i += chunkSize) { + const chunked = creatorArtworks.slice(i, i + chunkSize); + this.logger.debug(`Upload process: ${i}/${creatorArtworks.length}`); + + await Promise.all( + chunked.map(({ creator, artworks }) => { + return this.importProcess( + token, + contest_id, + contest_round, + creator, + artworks + ); + }) + ); + } + + return creatorArtworks; + } + + private async importProcess( + token: string, + contest_id: number, + contest_round: number, + creator: string, + artworks: string[] + ) { + const insertCreatorResult = await this.artworkGraphql.insertCreator( + { + object: { + name: creator, + pen_name: creator, + slug: generateSlug(creator), + }, + }, + token + ); + + const vaidArtworks = artworks.filter((str) => str !== ''); + + if (!insertCreatorResult.errors) { + const creatorId = insertCreatorResult.data.insert_creators_one.id; + + // upload image to s3 + const crawlPromises = vaidArtworks.map(async (artwork: string) => { + return this.crawlImage(artwork); + }); + const crawlImageResult = (await Promise.all(crawlPromises)).filter( + (result) => result.buffer + ); + + const s3SubFolder = + this.configService.get('aws.s3SubFolder') || 'images'; + + // resize + const resizedArtworks = await Promise.all( + crawlImageResult.map(async (image) => { + return sharp(image.buffer) + .resize(1366, 768, { fit: 'inside' }) + .png({ quality: 80 }) + .toBuffer(); + }) + ); + + // upload images + await Promise.all( + crawlImageResult.map((image, index) => { + const keyName = `${s3SubFolder}/creator-${creatorId}/artworks/${contest_round}-${index}.jpg`; + return this.fileService.uploadToS3( + keyName, + resizedArtworks[index], + image.mimeType + ); + }) + ); + + const newArtworks = vaidArtworks.map( + (artwork: string, index: number) => ({ + contest_id, + contest_round, + creator_id: creatorId, + source_url: artwork, + url: new URL( + `${s3SubFolder}/creator-${creatorId}/artworks/${contest_round}-${index}.jpg`, + this.configService.get('aws.queryEndpoint') + ).href, + }) + ); + + const insertArtworkResult = await this.artworkGraphql.insertArtwork( { - object: { - name: creator, - pen_name: creator, - slug: generateSlug(creator), - }, + objects: newArtworks, }, token ); - if (!insertCreatorResult.errors) { - const creatorId = insertCreatorResult.data.insert_creators_one.id; - - // upload image to s3 - const crawlImageResult = artworks - .filter((str) => str !== '') - .map(async (artwork: string) => { - const result = await this.crawlImage(artwork); - return result; - }); - // const crawlImageResult = await Promise.all(crawlPromises); - - const s3SubFolder = - this.configService.get('aws.s3SubFolder') || 'images'; - - await Promise.all( - crawlImageResult - .filter((result) => result.buffer) - .map((image, index) => { - const keyName = `${s3SubFolder}/creator-${creatorId}/artworks/${contest_round}-${index}.jpg`; - return this.fileService.uploadToS3( - keyName, - image.buffer, - image.mimeType - ); - }) - ); - - const newArtworks = artworks - .filter((str) => str !== '') - .map((artwork: string, index: number) => ({ - contest_id, - contest_round, - creator_id: creatorId, - source_url: artwork, - url: new URL( - `${s3SubFolder}/creator-${creatorId}/artworks/${contest_round}-${index}.jpg`, - this.configService.get('aws.queryEndpoint') - ).href, - })); - - const insertArtworkResult = await this.artworkGraphql.insertArtwork( - { - objects: newArtworks, - }, - token - ); - - this.logger.debug(insertArtworkResult); - } else { - console.log(creator); - } - }); - return creatorArtworks; + this.logger.debug(insertArtworkResult); + } else { + console.log(creator); + } } private async crawlImage(artWorkUrl: string) { @@ -153,12 +189,6 @@ export class ArtworkService implements OnModuleInit { } private async crawlGoogleDriveImage(url: string) { - // const auth = new GoogleAuth({ - // scopes: 'https://www.googleapis.com/auth/drive', - // keyFilename: this.configService.get('google.analytics.keyFile'), - // }) as any; - // const service = google.drive({ version: 'v3', auth }); - const fileId = url.split('/')?.[5]; try { const file = (await this.googleService.files.get( diff --git a/src/modules/chapter/chapter.controller.ts b/src/modules/chapter/chapter.controller.ts index 08b024eb..a572b1e9 100644 --- a/src/modules/chapter/chapter.controller.ts +++ b/src/modules/chapter/chapter.controller.ts @@ -30,7 +30,7 @@ import { ViewProtectedChapterRequestDto } from './dto/view-chapter-request.dto'; @Controller('chapter') @ApiTags('chapter') export class ChapterController { - constructor(private readonly chapterSvc: ChapterService) { } + constructor(private readonly chapterSvc: ChapterService) {} @UseGuards(AuthGuard, RolesGuard) @ApiBearerAuth() diff --git a/src/modules/files/files.service.ts b/src/modules/files/files.service.ts index 1334edc1..2e8d6352 100644 --- a/src/modules/files/files.service.ts +++ b/src/modules/files/files.service.ts @@ -19,8 +19,20 @@ import { IFileInfo } from '../chapter/interfaces'; export class FilesService implements OnModuleInit { private readonly logger = new Logger(FilesService.name); private ipfsClient: IPFSHTTPClient; + private s3Client: S3Client; - constructor(private configService: ConfigService) {} + constructor(private configService: ConfigService) { + this.s3Client = new S3Client({ + endpoint: + this.configService.get('aws.s3endpoint') || + 'https://s3.ap-southeast-1.amazonaws.com', + region: this.configService.get('aws.region'), + credentials: { + accessKeyId: this.configService.get('aws.keyid'), + secretAccessKey: this.configService.get('aws.secretAccessKey'), + }, + }); + } onModuleInit() { const ipfsUrl = this.configService.get('network.ipfsUrl'); @@ -153,17 +165,6 @@ export class FilesService implements OnModuleInit { const file = typeof filePath === 'string' ? readFileSync(filePath) : filePath; - const client = new S3Client({ - endpoint: - this.configService.get('aws.s3endpoint') || - 'https://s3.ap-southeast-1.amazonaws.com', - region: this.configService.get('aws.region'), - credentials: { - accessKeyId: this.configService.get('aws.keyid'), - secretAccessKey: this.configService.get('aws.secretAccessKey'), - }, - }); - const bucketName = this.configService.get('aws.bucketName'); this.logger.debug(`Upload key: ${keyName} to bucket ${bucketName}`); @@ -176,7 +177,7 @@ export class FilesService implements OnModuleInit { // Create a promise on S3 service object const command = new PutObjectCommand(input); - return client.send(command); + return this.s3Client.send(command); } async downloadFromS3(keyName: string): Promise { diff --git a/yarn.lock b/yarn.lock index eec60be8..e921488a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1259,6 +1259,13 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@emnapi/runtime@^1.1.1": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.2.0.tgz#71d018546c3a91f3b51106530edbc056b9f2f2e3" + integrity sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ== + dependencies: + tslib "^2.4.0" + "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -1336,6 +1343,119 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@img/sharp-darwin-arm64@0.33.4": + version "0.33.4" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.4.tgz#a1cf4a7febece334f16e0328b9689f05797d7aec" + integrity sha512-p0suNqXufJs9t3RqLBO6vvrgr5OhgbWp76s5gTRvdmxmuv9E1rcaqGUsl3l4mKVmXPkTkTErXediAui4x+8PSA== + optionalDependencies: + "@img/sharp-libvips-darwin-arm64" "1.0.2" + +"@img/sharp-darwin-x64@0.33.4": + version "0.33.4" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.4.tgz#f77be2d7c3609d3e77cd337b199a772e07b87bd2" + integrity sha512-0l7yRObwtTi82Z6ebVI2PnHT8EB2NxBgpK2MiKJZJ7cz32R4lxd001ecMhzzsZig3Yv9oclvqqdV93jo9hy+Dw== + optionalDependencies: + "@img/sharp-libvips-darwin-x64" "1.0.2" + +"@img/sharp-libvips-darwin-arm64@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.2.tgz#b69f49fecbe9572378675769b189410721b0fa53" + integrity sha512-tcK/41Rq8IKlSaKRCCAuuY3lDJjQnYIW1UXU1kxcEKrfL8WR7N6+rzNoOxoQRJWTAECuKwgAHnPvqXGN8XfkHA== + +"@img/sharp-libvips-darwin-x64@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.2.tgz#5665da7360d8e5ed7bee314491c8fe736b6a3c39" + integrity sha512-Ofw+7oaWa0HiiMiKWqqaZbaYV3/UGL2wAPeLuJTx+9cXpCRdvQhCLG0IH8YGwM0yGWGLpsF4Su9vM1o6aer+Fw== + +"@img/sharp-libvips-linux-arm64@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.2.tgz#8a05e5e9e9b760ff46561e32f19bd5e035fa881c" + integrity sha512-x7kCt3N00ofFmmkkdshwj3vGPCnmiDh7Gwnd4nUwZln2YjqPxV1NlTyZOvoDWdKQVDL911487HOueBvrpflagw== + +"@img/sharp-libvips-linux-arm@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.2.tgz#0fd33b9bf3221948ce0ca7a5a725942626577a03" + integrity sha512-iLWCvrKgeFoglQxdEwzu1eQV04o8YeYGFXtfWU26Zr2wWT3q3MTzC+QTCO3ZQfWd3doKHT4Pm2kRmLbupT+sZw== + +"@img/sharp-libvips-linux-s390x@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.2.tgz#4b89150ec91b256ee2cbb5bb125321bf029a4770" + integrity sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog== + +"@img/sharp-libvips-linux-x64@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.2.tgz#947ccc22ca5bc8c8cfe921b39a5fdaebc5e39f3f" + integrity sha512-E441q4Qdb+7yuyiADVi5J+44x8ctlrqn8XgkDTwr4qPJzWkaHwD489iZ4nGDgcuya4iMN3ULV6NwbhRZJ9Z7SQ== + +"@img/sharp-libvips-linuxmusl-arm64@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.2.tgz#821d58ce774f0f8bed065b69913a62f65d512f2f" + integrity sha512-3CAkndNpYUrlDqkCM5qhksfE+qSIREVpyoeHIU6jd48SJZViAmznoQQLAv4hVXF7xyUB9zf+G++e2v1ABjCbEQ== + +"@img/sharp-libvips-linuxmusl-x64@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.2.tgz#4309474bd8b728a61af0b3b4fad0c476b5f3ccbe" + integrity sha512-VI94Q6khIHqHWNOh6LLdm9s2Ry4zdjWJwH56WoiJU7NTeDwyApdZZ8c+SADC8OH98KWNQXnE01UdJ9CSfZvwZw== + +"@img/sharp-linux-arm64@0.33.4": + version "0.33.4" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.4.tgz#bd390113e256487041411b988ded13a26cfc5f95" + integrity sha512-2800clwVg1ZQtxwSoTlHvtm9ObgAax7V6MTAB/hDT945Tfyy3hVkmiHpeLPCKYqYR1Gcmv1uDZ3a4OFwkdBL7Q== + optionalDependencies: + "@img/sharp-libvips-linux-arm64" "1.0.2" + +"@img/sharp-linux-arm@0.33.4": + version "0.33.4" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.4.tgz#14ecc81f38f75fb4cd7571bc83311746d6745fca" + integrity sha512-RUgBD1c0+gCYZGCCe6mMdTiOFS0Zc/XrN0fYd6hISIKcDUbAW5NtSQW9g/powkrXYm6Vzwd6y+fqmExDuCdHNQ== + optionalDependencies: + "@img/sharp-libvips-linux-arm" "1.0.2" + +"@img/sharp-linux-s390x@0.33.4": + version "0.33.4" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.4.tgz#119e8081e2c6741b5ac908fe02244e4c559e525f" + integrity sha512-h3RAL3siQoyzSoH36tUeS0PDmb5wINKGYzcLB5C6DIiAn2F3udeFAum+gj8IbA/82+8RGCTn7XW8WTFnqag4tQ== + optionalDependencies: + "@img/sharp-libvips-linux-s390x" "1.0.2" + +"@img/sharp-linux-x64@0.33.4": + version "0.33.4" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.4.tgz#21d4c137b8da9a313b069ff5c920ded709f853d7" + integrity sha512-GoR++s0XW9DGVi8SUGQ/U4AeIzLdNjHka6jidVwapQ/JebGVQIpi52OdyxCNVRE++n1FCLzjDovJNozif7w/Aw== + optionalDependencies: + "@img/sharp-libvips-linux-x64" "1.0.2" + +"@img/sharp-linuxmusl-arm64@0.33.4": + version "0.33.4" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.4.tgz#f3fde68fd67b85a32da6f1155818c3b58b8e7ae0" + integrity sha512-nhr1yC3BlVrKDTl6cO12gTpXMl4ITBUZieehFvMntlCXFzH2bvKG76tBL2Y/OqhupZt81pR7R+Q5YhJxW0rGgQ== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-arm64" "1.0.2" + +"@img/sharp-linuxmusl-x64@0.33.4": + version "0.33.4" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.4.tgz#44373724aecd7b69900e0578228144e181db7892" + integrity sha512-uCPTku0zwqDmZEOi4ILyGdmW76tH7dm8kKlOIV1XC5cLyJ71ENAAqarOHQh0RLfpIpbV5KOpXzdU6XkJtS0daw== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-x64" "1.0.2" + +"@img/sharp-wasm32@0.33.4": + version "0.33.4" + resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.33.4.tgz#88e3f18d7e7cd8cfe1af98e9963db4d7b6491435" + integrity sha512-Bmmauh4sXUsUqkleQahpdNXKvo+wa1V9KhT2pDA4VJGKwnKMJXiSTGphn0gnJrlooda0QxCtXc6RX1XAU6hMnQ== + dependencies: + "@emnapi/runtime" "^1.1.1" + +"@img/sharp-win32-ia32@0.33.4": + version "0.33.4" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.4.tgz#b1c772dd2952e983980b1eb85808fa8129484d46" + integrity sha512-99SJ91XzUhYHbx7uhK3+9Lf7+LjwMGQZMDlO/E/YVJ7Nc3lyDFZPGhjwiYdctoH2BOzW9+TnfqcaMKt0jHLdqw== + +"@img/sharp-win32-x64@0.33.4": + version "0.33.4" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.4.tgz#106f911134035b4157ec92a0c154a6b6f88fa4c1" + integrity sha512-3QLocdTRVIrFNye5YocZl+KKpYKP+fksi1QhmOArgx7GyhIbQp/WrJRu176jm8IxromS7RIkzMiMINVdBtC8Aw== + "@ioredis/commands@^1.1.1": version "1.2.0" resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.2.0.tgz#6d61b3097470af1fdbbe622795b8921d42018e11" @@ -2666,6 +2786,13 @@ "@types/mime" "*" "@types/node" "*" +"@types/sharp@^0.32.0": + version "0.32.0" + resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.32.0.tgz#fc3ac6df6b456319bae807c3d24efdc6631cdd6f" + integrity sha512-OOi3kL+FZDnPhVzsfD37J88FNeZh6gQsGcLc95NbeURRGvmSjeXiDcyWzF2o3yh/gQAUn2uhh/e+CPCa5nwAxw== + dependencies: + sharp "*" + "@types/stack-utils@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" @@ -3599,11 +3726,27 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== -color-name@~1.1.4: +color-name@^1.0.0, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +color-string@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" + integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== + dependencies: + color-convert "^2.0.1" + color-string "^1.9.0" + combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -3930,6 +4073,11 @@ destroy@1.2.0: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== +detect-libc@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" + integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== + detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -5162,6 +5310,11 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -7101,6 +7254,11 @@ semver@^7.3.2, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3: dependencies: lru-cache "^6.0.0" +semver@^7.6.0: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + send@0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" @@ -7172,6 +7330,35 @@ sha3@^2.1.1: dependencies: buffer "6.0.3" +sharp@*, sharp@^0.33.4: + version "0.33.4" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.33.4.tgz#b88e6e843e095c6ab5e1a0c59c4885e580cd8405" + integrity sha512-7i/dt5kGl7qR4gwPRD2biwD2/SvBn3O04J77XKFgL2OnZtQw+AG9wnuS/csmu80nPRHLYE9E41fyEiG8nhH6/Q== + dependencies: + color "^4.2.3" + detect-libc "^2.0.3" + semver "^7.6.0" + optionalDependencies: + "@img/sharp-darwin-arm64" "0.33.4" + "@img/sharp-darwin-x64" "0.33.4" + "@img/sharp-libvips-darwin-arm64" "1.0.2" + "@img/sharp-libvips-darwin-x64" "1.0.2" + "@img/sharp-libvips-linux-arm" "1.0.2" + "@img/sharp-libvips-linux-arm64" "1.0.2" + "@img/sharp-libvips-linux-s390x" "1.0.2" + "@img/sharp-libvips-linux-x64" "1.0.2" + "@img/sharp-libvips-linuxmusl-arm64" "1.0.2" + "@img/sharp-libvips-linuxmusl-x64" "1.0.2" + "@img/sharp-linux-arm" "0.33.4" + "@img/sharp-linux-arm64" "0.33.4" + "@img/sharp-linux-s390x" "0.33.4" + "@img/sharp-linux-x64" "0.33.4" + "@img/sharp-linuxmusl-arm64" "0.33.4" + "@img/sharp-linuxmusl-x64" "0.33.4" + "@img/sharp-wasm32" "0.33.4" + "@img/sharp-win32-ia32" "0.33.4" + "@img/sharp-win32-x64" "0.33.4" + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -7218,6 +7405,13 @@ signal-exit@^4.0.1: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== + dependencies: + is-arrayish "^0.3.1" + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" From 53bfdff7dd686ca3af46968c7dca8b6aef33ba78 Mon Sep 17 00:00:00 2001 From: ThienLK Date: Wed, 7 Aug 2024 17:29:19 +0700 Subject: [PATCH 05/50] add manga collection --- .../punkga-pg/tables/public_manga.yaml | 7 + .../tables/public_manga_collection.yaml | 7 + hasura/metadata/query_collections.yaml | 138 ++++++++++-------- .../manage-manga-collection-request.dto.ts | 13 ++ src/modules/manga/manga.controller.ts | 16 +- src/modules/manga/manga.graphql.ts | 20 +++ src/modules/manga/manga.service.ts | 26 ++++ 7 files changed, 166 insertions(+), 61 deletions(-) create mode 100644 src/modules/manga/dto/manage-manga-collection-request.dto.ts diff --git a/hasura/metadata/databases/punkga-pg/tables/public_manga.yaml b/hasura/metadata/databases/punkga-pg/tables/public_manga.yaml index 03f7c3dc..f066b67e 100644 --- a/hasura/metadata/databases/punkga-pg/tables/public_manga.yaml +++ b/hasura/metadata/databases/punkga-pg/tables/public_manga.yaml @@ -28,6 +28,13 @@ array_relationships: table: name: chapters schema: public + - name: manga_collections + using: + foreign_key_constraint_on: + column: manga_id + table: + name: manga_collection + schema: public - name: manga_creators using: foreign_key_constraint_on: diff --git a/hasura/metadata/databases/punkga-pg/tables/public_manga_collection.yaml b/hasura/metadata/databases/punkga-pg/tables/public_manga_collection.yaml index 5107402f..910e9ffe 100644 --- a/hasura/metadata/databases/punkga-pg/tables/public_manga_collection.yaml +++ b/hasura/metadata/databases/punkga-pg/tables/public_manga_collection.yaml @@ -1,3 +1,10 @@ table: name: manga_collection schema: public +object_relationships: + - name: collection_manga + using: + foreign_key_constraint_on: manga_id + - name: manga_collection + using: + foreign_key_constraint_on: launchpad_id diff --git a/hasura/metadata/query_collections.yaml b/hasura/metadata/query_collections.yaml index 28cbaeda..815003a4 100644 --- a/hasura/metadata/query_collections.yaml +++ b/hasura/metadata/query_collections.yaml @@ -267,66 +267,6 @@ affected_rows } } - - name: Admin - Get manga detail - query: | - query GetMangaReadingDetail ($id: Int = 1) { - manga_by_pk(id: $id) { - id - poster - banner - status - contract_addresses - publish_date - release_date - created_at - manga_languages { - title - description - is_main_language - language_id - } - chapters_aggregate { - aggregate { - sum { - views - likes - } - count - } - } - manga_subscribers_aggregate { - aggregate { - count - } - } - manga_tags { - tag { - tag_languages { - tag_id - language_id - value - } - } - } - chapters(order_by: {chapter_number:desc_nulls_last}) { - id - chapter_number - chapter_name - chapter_type - thumbnail_url - pushlish_date - status - } - manga_creators { - creator { - id - slug - name - pen_name - } - } - } - } - name: Public - Query User query: | query QueryUser ($user_id: bpchar = "") { @@ -1360,3 +1300,81 @@ } } } + - name: Admin - Get manga detail + query: | + query GetMangaReadingDetail ($id: Int = 1) { + manga_by_pk(id: $id) { + id + poster + banner + status + contract_addresses + publish_date + release_date + created_at + manga_languages { + title + description + is_main_language + language_id + } + chapters_aggregate { + aggregate { + sum { + views + likes + } + count + } + } + manga_subscribers_aggregate { + aggregate { + count + } + } + manga_tags { + tag { + tag_languages { + tag_id + language_id + value + } + } + } + chapters(order_by: {chapter_number:desc_nulls_last}) { + id + chapter_number + chapter_name + chapter_type + thumbnail_url + pushlish_date + status + } + manga_creators { + creator { + id + slug + name + pen_name + } + } + manga_collections { + launchpad_id + manga_id + manga_collection { + contract_address + launchpad_creator { + name + slug + } + id + launchpad_i18ns { + data(path: "name") + } + slug + updated_at + created_at + } + } + } + } diff --git a/src/modules/manga/dto/manage-manga-collection-request.dto.ts b/src/modules/manga/dto/manage-manga-collection-request.dto.ts new file mode 100644 index 00000000..5b9a794e --- /dev/null +++ b/src/modules/manga/dto/manage-manga-collection-request.dto.ts @@ -0,0 +1,13 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNumber } from 'class-validator'; + +export class MangaCollection { + @ApiProperty({ type: [Number], example: [1] }) + collectionIds: number[]; +} + +export class MangaCollectionParamDto { + @ApiProperty() + @IsNumber() + mangaId: number; +} diff --git a/src/modules/manga/manga.controller.ts b/src/modules/manga/manga.controller.ts index 7425eee2..4c59b4e3 100644 --- a/src/modules/manga/manga.controller.ts +++ b/src/modules/manga/manga.controller.ts @@ -33,6 +33,7 @@ import { GetChapterByMangaQueryDto, } from './dto/get-chapter-by-manga-request.dto'; import { CacheInterceptor } from '@nestjs/cache-manager'; +import { MangaCollection, MangaCollectionParamDto } from './dto/manage-manga-collection-request.dto'; @Controller('manga') @ApiTags('manga') @@ -66,7 +67,7 @@ export class MangaController { ) { // console.log(data); return this.mangaSvc.create(data, files); - } + } @UseGuards(AuthGuard, RolesGuard) @ApiBearerAuth() @@ -92,4 +93,17 @@ export class MangaController { const { mangaId } = param; return this.mangaSvc.getAccess(mangaId); } + + // @UseGuards(AuthGuard, RolesGuard) + // @ApiBearerAuth() + // @Roles(Role.Admin) + @Put('manga-collection/:mangaId') + // @UseInterceptors(AuthUserInterceptor) + addMangaCollection( + @Param() param: MangaCollectionParamDto, + @Body() data: MangaCollection, + ) { + const { mangaId } = param; + return this.mangaSvc.addMangaCollection(mangaId, data.collectionIds); + } } diff --git a/src/modules/manga/manga.graphql.ts b/src/modules/manga/manga.graphql.ts index f5b6217d..e31899f0 100644 --- a/src/modules/manga/manga.graphql.ts +++ b/src/modules/manga/manga.graphql.ts @@ -286,4 +286,24 @@ export class MangaGraphql { variables ); } + + createMangaCollection(variables: any) { + const headers = { + 'x-hasura-admin-secret': this.configSvc.get( + 'graphql.adminSecret' + ), + }; + return this.graphqlSvc.query( + this.configSvc.get('graphql.endpoint'), + "", + `mutation insert_manga_collection($objects: [manga_collection_insert_input!] = {}) { + insert_manga_collection(objects: $objects, on_conflict: {constraint: manga_collection_manga_id_launchpad_id_key, update_columns: updated_at}) { + affected_rows + } + }`, + 'insert_manga_collection', + variables, + headers + ); + } } diff --git a/src/modules/manga/manga.service.ts b/src/modules/manga/manga.service.ts index ae33147b..d52a18c5 100644 --- a/src/modules/manga/manga.service.ts +++ b/src/modules/manga/manga.service.ts @@ -265,4 +265,30 @@ export class MangaService { }; } } + + async addMangaCollection(mangaId: Number, collectionIdList: number[]) { + try { + // const { token } = ContextProvider.getAuthUser(); + const objects = []; + // update manga collection in DB + await Promise.all( + collectionIdList.map((collectionId) => { + const o = { + manga_id: mangaId, + launchpad_id: collectionId, + }; + objects.push(o); + }) + ); + const updateResponse = await this.mangaGraphql.createMangaCollection({ + objects, + }); + + return updateResponse; + } catch (errors) { + return { + errors, + }; + } + } } From 38c62d5eca340282521937f8d4909cd890b0b5c8 Mon Sep 17 00:00:00 2001 From: ThienLK Date: Thu, 8 Aug 2024 11:10:11 +0700 Subject: [PATCH 06/50] add manga collection --- .../tables/public_manga_collection.yaml | 21 +++++++++++++++++++ hasura/metadata/query_collections.yaml | 7 +++++++ hasura/metadata/rest_endpoints.yaml | 9 ++++++++ src/modules/manga/manga.controller.ts | 8 +++---- 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/hasura/metadata/databases/punkga-pg/tables/public_manga_collection.yaml b/hasura/metadata/databases/punkga-pg/tables/public_manga_collection.yaml index 910e9ffe..8ca8ff72 100644 --- a/hasura/metadata/databases/punkga-pg/tables/public_manga_collection.yaml +++ b/hasura/metadata/databases/punkga-pg/tables/public_manga_collection.yaml @@ -8,3 +8,24 @@ object_relationships: - name: manga_collection using: foreign_key_constraint_on: launchpad_id +select_permissions: + - role: anonymous + permission: + columns: + - id + - launchpad_id + - manga_id + - created_at + - updated_at + filter: {} + allow_aggregations: true + - role: user + permission: + columns: + - id + - launchpad_id + - manga_id + - created_at + - updated_at + filter: {} + allow_aggregations: true diff --git a/hasura/metadata/query_collections.yaml b/hasura/metadata/query_collections.yaml index 815003a4..9c275a38 100644 --- a/hasura/metadata/query_collections.yaml +++ b/hasura/metadata/query_collections.yaml @@ -1378,3 +1378,10 @@ } } } + - name: Admin - Delete Manga Collection + query: | + mutation delete_manga_collection_by_pk ($id: Int!) { + delete_manga_collection_by_pk(id: $id) { + id + } + } diff --git a/hasura/metadata/rest_endpoints.yaml b/hasura/metadata/rest_endpoints.yaml index f50b5585..57b03922 100644 --- a/hasura/metadata/rest_endpoints.yaml +++ b/hasura/metadata/rest_endpoints.yaml @@ -70,6 +70,15 @@ - GET name: Admin - Query List Manga url: admin/manga +- comment: Delete Manga Collection + definition: + query: + collection_name: allowed-queries + query_name: Admin - Delete Manga Collection + methods: + - DELETE + name: Admin - Delete Manga Collection + url: admin/manga-collection/:id - comment: "" definition: query: diff --git a/src/modules/manga/manga.controller.ts b/src/modules/manga/manga.controller.ts index 4c59b4e3..7a997831 100644 --- a/src/modules/manga/manga.controller.ts +++ b/src/modules/manga/manga.controller.ts @@ -94,11 +94,11 @@ export class MangaController { return this.mangaSvc.getAccess(mangaId); } - // @UseGuards(AuthGuard, RolesGuard) - // @ApiBearerAuth() - // @Roles(Role.Admin) + @UseGuards(AuthGuard, RolesGuard) + @ApiBearerAuth() + @Roles(Role.Admin) @Put('manga-collection/:mangaId') - // @UseInterceptors(AuthUserInterceptor) + @UseInterceptors(AuthUserInterceptor) addMangaCollection( @Param() param: MangaCollectionParamDto, @Body() data: MangaCollection, From 9411ed9062770c57e3facf2602d9c0a89f04b063 Mon Sep 17 00:00:00 2001 From: harisato Date: Thu, 8 Aug 2024 11:17:18 +0700 Subject: [PATCH 07/50] feat: import artwork from google drive folder --- src/modules/artwork/artwork.service.ts | 81 +++++++++++++++++++++----- 1 file changed, 65 insertions(+), 16 deletions(-) diff --git a/src/modules/artwork/artwork.service.ts b/src/modules/artwork/artwork.service.ts index b8f38c72..24a6da6c 100644 --- a/src/modules/artwork/artwork.service.ts +++ b/src/modules/artwork/artwork.service.ts @@ -58,17 +58,21 @@ export class ArtworkService implements OnModuleInit { const chunked = creatorArtworks.slice(i, i + chunkSize); this.logger.debug(`Upload process: ${i}/${creatorArtworks.length}`); - await Promise.all( - chunked.map(({ creator, artworks }) => { - return this.importProcess( - token, - contest_id, - contest_round, - creator, - artworks - ); - }) - ); + try { + await Promise.all( + chunked.map(({ creator, artworks }) => { + return this.importProcess( + token, + contest_id, + contest_round, + creator, + artworks + ); + }) + ); + } catch (error) { + this.logger.error(error); + } } return creatorArtworks; @@ -98,8 +102,21 @@ export class ArtworkService implements OnModuleInit { const creatorId = insertCreatorResult.data.insert_creators_one.id; // upload image to s3 - const crawlPromises = vaidArtworks.map(async (artwork: string) => { - return this.crawlImage(artwork); + const crawlPromises = []; + const urls = []; + vaidArtworks.forEach(async (artwork: string) => { + if (artwork.indexOf('drive.google.com/drive/folders') > 0) { + // get all file in folder + const files = await this.crawlGoogleDriveFolder(artwork); + files.forEach((file) => { + const fileUrl = `https://drive.google.com/file/d/${file.id}`; + urls.push(fileUrl); + crawlPromises.push(this.crawlImage(fileUrl)); + }); + } else { + urls.push(artwork); + crawlPromises.push(this.crawlImage(artwork)); + } }); const crawlImageResult = (await Promise.all(crawlPromises)).filter( (result) => result.buffer @@ -110,11 +127,16 @@ export class ArtworkService implements OnModuleInit { // resize const resizedArtworks = await Promise.all( - crawlImageResult.map(async (image) => { + crawlImageResult.map(async (image, index) => { return sharp(image.buffer) .resize(1366, 768, { fit: 'inside' }) .png({ quality: 80 }) - .toBuffer(); + .toBuffer() + .catch(function (err) { + console.log('Error occured ', err); + console.log(urls[index]); + return image.buffer; + }); }) ); @@ -161,7 +183,7 @@ export class ArtworkService implements OnModuleInit { return this.crawlImgurImage(artWorkUrl + '.jpg'); } - if (artWorkUrl.indexOf('drive.google.com') > 0) { + if (artWorkUrl.indexOf('drive.google.com/file') > 0) { return this.crawlGoogleDriveImage(artWorkUrl); } } @@ -216,4 +238,31 @@ export class ArtworkService implements OnModuleInit { }; } } + + private async crawlGoogleDriveFolder(url: string) { + const folderId = url.split('/')?.[5]; + const files: any[] = []; + try { + const res: any = await this.googleService.files.list({ + q: `'${folderId}' in parents`, + fields: 'nextPageToken, files(id, name)', + spaces: 'drive', + }); + + files.push(res.files); + + res.data.files.forEach(function (file: any) { + console.log('Found file:', file.name, file.id); + }); + + return res.data.files; + } catch (error) { + this.logger.error(`cannot get image from url: ${url}`); + return { + errors: { + message: JSON.stringify(error), + }, + }; + } + } } From 59ac9aa4ac607a77c489207304f53da3ada45c7e Mon Sep 17 00:00:00 2001 From: harisato Date: Thu, 8 Aug 2024 11:24:24 +0700 Subject: [PATCH 08/50] fix: install sharp ignore-engines --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index cd5f686f..88ef1ad7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,7 @@ COPY package.json yarn.lock ./ RUN yarn install --ignore-scripts RUN yarn add mmmagic +RUN yarn add sharp@^0.33.4 --ignore-engines RUN yarn global add pm2 COPY . . From 1a55e9b745d0ef943705f827e342480937833bae Mon Sep 17 00:00:00 2001 From: harisato Date: Thu, 8 Aug 2024 14:08:58 +0700 Subject: [PATCH 09/50] feat: import artwork from imgur album --- src/modules/artwork/artwork.service.ts | 31 +++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/modules/artwork/artwork.service.ts b/src/modules/artwork/artwork.service.ts index 24a6da6c..461f5222 100644 --- a/src/modules/artwork/artwork.service.ts +++ b/src/modules/artwork/artwork.service.ts @@ -106,13 +106,21 @@ export class ArtworkService implements OnModuleInit { const urls = []; vaidArtworks.forEach(async (artwork: string) => { if (artwork.indexOf('drive.google.com/drive/folders') > 0) { - // get all file in folder + // get all file in google drive folder const files = await this.crawlGoogleDriveFolder(artwork); files.forEach((file) => { const fileUrl = `https://drive.google.com/file/d/${file.id}`; urls.push(fileUrl); crawlPromises.push(this.crawlImage(fileUrl)); }); + } else if (artwork.indexOf('imgur.com/a/') > 0) { + // get all file in imgur album + const files = await this.crawlImgurAlbum(artwork); + files.forEach((file) => { + const fileUrl = `https://i.imgur.com/${file.id}.jpg`; + urls.push(fileUrl); + crawlPromises.push(this.crawlImage(fileUrl)); + }); } else { urls.push(artwork); crawlPromises.push(this.crawlImage(artwork)); @@ -210,6 +218,27 @@ export class ArtworkService implements OnModuleInit { } } + private async crawlImgurAlbum(url: string) { + const api = this.configService.get('imgur.api'); + const clientId = this.configService.get('imgur.clientId'); + const albumHash = url.split('/')?.[4]; + try { + const response = await axios.get(`${api}/3/album/${albumHash}/images`, { + headers: { + Authorization: `Client-ID ${clientId}`, + }, + }); + + return response.data?.data.map((data) => ({ id: data.id })); + } catch (error) { + return { + errors: { + message: JSON.stringify(error), + }, + }; + } + } + private async crawlGoogleDriveImage(url: string) { const fileId = url.split('/')?.[5]; try { From f99209aaa22844c8691a2bdd2d97930c71d4b314 Mon Sep 17 00:00:00 2001 From: ThienLK Date: Thu, 8 Aug 2024 14:27:20 +0700 Subject: [PATCH 10/50] add chapter collection --- .../tables/public_chapter_collection.yaml | 10 ++++++++++ .../databases/punkga-pg/tables/tables.yaml | 1 + hasura/metadata/query_collections.yaml | 20 +++++++++++++++++++ hasura/metadata/rest_endpoints.yaml | 18 +++++++++++++++++ .../down.sql | 1 + .../up.sql | 17 ++++++++++++++++ 6 files changed, 67 insertions(+) create mode 100644 hasura/metadata/databases/punkga-pg/tables/public_chapter_collection.yaml create mode 100644 hasura/migrations/punkga-pg/1723100828427_create_table_public_chapter_collection/down.sql create mode 100644 hasura/migrations/punkga-pg/1723100828427_create_table_public_chapter_collection/up.sql diff --git a/hasura/metadata/databases/punkga-pg/tables/public_chapter_collection.yaml b/hasura/metadata/databases/punkga-pg/tables/public_chapter_collection.yaml new file mode 100644 index 00000000..ee96ee05 --- /dev/null +++ b/hasura/metadata/databases/punkga-pg/tables/public_chapter_collection.yaml @@ -0,0 +1,10 @@ +table: + name: chapter_collection + schema: public +object_relationships: + - name: chapter_collection + using: + foreign_key_constraint_on: launchpad_id + - name: collection_chapter + using: + foreign_key_constraint_on: chapter_id diff --git a/hasura/metadata/databases/punkga-pg/tables/tables.yaml b/hasura/metadata/databases/punkga-pg/tables/tables.yaml index 4f4ab8a4..d5223063 100644 --- a/hasura/metadata/databases/punkga-pg/tables/tables.yaml +++ b/hasura/metadata/databases/punkga-pg/tables/tables.yaml @@ -5,6 +5,7 @@ - "!include public_banners.yaml" - "!include public_campaign.yaml" - "!include public_chains.yaml" +- "!include public_chapter_collection.yaml" - "!include public_chapter_languages.yaml" - "!include public_chapter_total_likes.yaml" - "!include public_chapters.yaml" diff --git a/hasura/metadata/query_collections.yaml b/hasura/metadata/query_collections.yaml index 9c275a38..5ebeeed9 100644 --- a/hasura/metadata/query_collections.yaml +++ b/hasura/metadata/query_collections.yaml @@ -1385,3 +1385,23 @@ id } } + - name: Admin - Delete Chapter Collection + query: | + mutation delete_chapter_collection_by_pk ($id: Int!) { + delete_chapter_collection_by_pk(id: $id) { + id + } + } + - name: Admin - Insert Chapter Collection + query: | + mutation MyMutation ($objects: [chapter_collection_insert_input!] = {}) { + insert_chapter_collection(objects: $objects, on_conflict: {constraint:chapter_collection_chapter_id_launchpad_id_key,update_columns:chapter_id}) { + affected_rows + returning { + chapter_id + created_at + id + launchpad_id + } + } + } diff --git a/hasura/metadata/rest_endpoints.yaml b/hasura/metadata/rest_endpoints.yaml index 57b03922..981da4a5 100644 --- a/hasura/metadata/rest_endpoints.yaml +++ b/hasura/metadata/rest_endpoints.yaml @@ -34,6 +34,24 @@ - GET name: Admin - Query campaign detail url: admin/campaign/:id +- comment: Delete Chapter Collection + definition: + query: + collection_name: allowed-queries + query_name: Admin - Delete Chapter Collection + methods: + - DELETE + name: Admin - Delete Chapter Collection + url: admin/chapter-collection +- comment: "Insert Chapter Collection\nchapter_collection_insert_input:{\nchapter_id: Int!, \nlaunchpad_id: Int!\n}" + definition: + query: + collection_name: allowed-queries + query_name: Admin - Insert Chapter Collection + methods: + - POST + name: Admin - Insert Chapter Collection + url: admin/chapter-collection - comment: "" definition: query: diff --git a/hasura/migrations/punkga-pg/1723100828427_create_table_public_chapter_collection/down.sql b/hasura/migrations/punkga-pg/1723100828427_create_table_public_chapter_collection/down.sql new file mode 100644 index 00000000..31f912b5 --- /dev/null +++ b/hasura/migrations/punkga-pg/1723100828427_create_table_public_chapter_collection/down.sql @@ -0,0 +1 @@ +DROP TABLE "public"."chapter_collection"; diff --git a/hasura/migrations/punkga-pg/1723100828427_create_table_public_chapter_collection/up.sql b/hasura/migrations/punkga-pg/1723100828427_create_table_public_chapter_collection/up.sql new file mode 100644 index 00000000..f3bb9336 --- /dev/null +++ b/hasura/migrations/punkga-pg/1723100828427_create_table_public_chapter_collection/up.sql @@ -0,0 +1,17 @@ +CREATE TABLE "public"."chapter_collection" ("id" serial NOT NULL, "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "chapter_id" integer NOT NULL, "launchpad_id" integer NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("chapter_id") REFERENCES "public"."chapters"("id") ON UPDATE cascade ON DELETE cascade, FOREIGN KEY ("launchpad_id") REFERENCES "public"."launchpad"("id") ON UPDATE cascade ON DELETE cascade, UNIQUE ("chapter_id", "launchpad_id")); +CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"() +RETURNS TRIGGER AS $$ +DECLARE + _new record; +BEGIN + _new := NEW; + _new."updated_at" = NOW(); + RETURN _new; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER "set_public_chapter_collection_updated_at" +BEFORE UPDATE ON "public"."chapter_collection" +FOR EACH ROW +EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"(); +COMMENT ON TRIGGER "set_public_chapter_collection_updated_at" ON "public"."chapter_collection" +IS 'trigger to set value of column "updated_at" to current timestamp on row update'; From 082e5630a0dc96dba51030624eb29f522e368576 Mon Sep 17 00:00:00 2001 From: ThienLK Date: Thu, 8 Aug 2024 15:06:42 +0700 Subject: [PATCH 11/50] chapter collection --- .../tables/public_chapter_collection.yaml | 21 ++++++ .../punkga-pg/tables/public_chapters.yaml | 7 ++ hasura/metadata/query_collections.yaml | 74 ++++++++++++++----- 3 files changed, 85 insertions(+), 17 deletions(-) diff --git a/hasura/metadata/databases/punkga-pg/tables/public_chapter_collection.yaml b/hasura/metadata/databases/punkga-pg/tables/public_chapter_collection.yaml index ee96ee05..211853e3 100644 --- a/hasura/metadata/databases/punkga-pg/tables/public_chapter_collection.yaml +++ b/hasura/metadata/databases/punkga-pg/tables/public_chapter_collection.yaml @@ -8,3 +8,24 @@ object_relationships: - name: collection_chapter using: foreign_key_constraint_on: chapter_id +select_permissions: + - role: anonymous + permission: + columns: + - chapter_id + - id + - launchpad_id + - created_at + - updated_at + filter: {} + allow_aggregations: true + - role: user + permission: + columns: + - chapter_id + - id + - launchpad_id + - created_at + - updated_at + filter: {} + allow_aggregations: true diff --git a/hasura/metadata/databases/punkga-pg/tables/public_chapters.yaml b/hasura/metadata/databases/punkga-pg/tables/public_chapters.yaml index 43b4ba26..2c218cf8 100644 --- a/hasura/metadata/databases/punkga-pg/tables/public_chapters.yaml +++ b/hasura/metadata/databases/punkga-pg/tables/public_chapters.yaml @@ -15,6 +15,13 @@ object_relationships: using: foreign_key_constraint_on: manga_id array_relationships: + - name: chapter_collections + using: + foreign_key_constraint_on: + column: chapter_id + table: + name: chapter_collection + schema: public - name: chapter_languages using: foreign_key_constraint_on: diff --git a/hasura/metadata/query_collections.yaml b/hasura/metadata/query_collections.yaml index 5ebeeed9..c1d1bc00 100644 --- a/hasura/metadata/query_collections.yaml +++ b/hasura/metadata/query_collections.yaml @@ -197,23 +197,6 @@ } } } - - name: Admin - Get chapter detail - query: | - query GetChapterDetail ($manga_id: Int = 18, $chapter_number: Int = 1) { - chapters(where: {_and:{chapter_number:{_eq:$chapter_number},manga:{id:{_eq:$manga_id}}}}) { - id - chapter_number - chapter_name - chapter_type - thumbnail_url - status - pushlish_date - chapter_languages { - language_id - detail - } - } - } - name: Public - Get Chapter Comments query: | query GetChapterComments ($chapter_id: Int!) { @@ -1405,3 +1388,60 @@ } } } + - name: Admin - Get chapter detail + query: | + query GetChapterReadingDetail ($slug: String, $chapter_number: Int = 1, $user_id: bpchar = "") { + chapters(where: {_and:{chapter_number:{_eq:$chapter_number},manga:{_and:{slug:{_eq:$slug},_or:[{status:{_ilike:"On-going"}},{status:{_eq:"Finished"}}]}}}}) { + id + chapter_number + chapter_name + chapter_type + thumbnail_url + status + pushlish_date + chapter_languages(where: {chapter:{status:{_eq:"Published"}}}) { + language_id + detail + } + comments: social_activities_aggregate { + aggregate { + count + } + } + views + chapters_likes_aggregate { + aggregate { + count + } + } + chapters_likes(where: {user_id:{_eq:$user_id}}) { + id + created_at + user_id + chapter_id + } + chapter_collections { + id + launchpad_id + created_at + updated_at + chapter_collection { + contract_address + created_at + creator_id + id + launchpad_creator { + avatar_url + name + slug + } + slug + status + launchpad_i18ns { + data(path: "name") + language_id + } + } + } + } + } From 461ab528c41f333b3b6127301e2a067af71671e3 Mon Sep 17 00:00:00 2001 From: harisato Date: Thu, 8 Aug 2024 15:34:52 +0700 Subject: [PATCH 12/50] fix: update contest manga --- hasura/metadata/query_collections.yaml | 70 ++++++++++++++++++++------ 1 file changed, 55 insertions(+), 15 deletions(-) diff --git a/hasura/metadata/query_collections.yaml b/hasura/metadata/query_collections.yaml index c1d1bc00..6664bac1 100644 --- a/hasura/metadata/query_collections.yaml +++ b/hasura/metadata/query_collections.yaml @@ -1229,21 +1229,6 @@ } } } - - name: Public - Get manga by creator and contest - query: | - query manga ($contest_id: Int!, $id: Int!, $limit: Int = 100, $offset: Int = 0) { - manga(where: {contest_id:{_eq:$contest_id},manga_creators:{creator_id:{_eq:$id}}}, limit: $limit, offset: $offset) { - banner - id - slug - poster - } - manga_aggregate(where: {contest_id:{_eq:$contest_id},manga_creators:{creator_id:{_eq:$id}}}, limit: $limit, offset: $offset) { - aggregate { - count - } - } - } - name: Public - Get artwork by creator and contest query: | query artwork ($contest_id: Int!, $id: Int!, $limit: Int = 100, $offset: Int = 0) { @@ -1445,3 +1430,58 @@ } } } + - name: Public - Get manga by creator and contest + query: | + query manga ($contest_id: Int!, $id: Int!, $limit: Int = 100, $offset: Int = 0) { + manga(where: {contest_id:{_eq:$contest_id},manga_creators:{creator_id:{_eq:$id}}}, limit: $limit, offset: $offset) { + id + slug + latest_published + manga_total_views { + views + } + manga_total_likes { + likes + } + manga_creators { + creator { + id + slug + name + pen_name + isActive + } + } + status + chapters(limit: 1, order_by: {chapter_number:desc}, where: {status:{_eq:"Published"}}) { + id + chapter_number + pushlish_date + status + updated_at + } + manga_tags(limit: 5) { + tag { + id + tag_languages { + language_id + tag_id + value + } + } + } + manga_languages { + title + description + is_main_language + language_id + } + banner + poster + } + manga_aggregate(where: {contest_id:{_eq:$contest_id},manga_creators:{creator_id:{_eq:$id}}}, limit: $limit, offset: $offset) { + aggregate { + count + } + } + } From 41fb9034a2c17931660c1536de190bc09eb806f0 Mon Sep 17 00:00:00 2001 From: harisato Date: Fri, 9 Aug 2024 14:26:17 +0700 Subject: [PATCH 13/50] fix: upload artwork --- src/modules/artwork/artwork.service.ts | 29 +++++++++++++------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/modules/artwork/artwork.service.ts b/src/modules/artwork/artwork.service.ts index 461f5222..b95aa05f 100644 --- a/src/modules/artwork/artwork.service.ts +++ b/src/modules/artwork/artwork.service.ts @@ -104,7 +104,8 @@ export class ArtworkService implements OnModuleInit { // upload image to s3 const crawlPromises = []; const urls = []; - vaidArtworks.forEach(async (artwork: string) => { + for (const artwork of vaidArtworks) { + // vaidArtworks.forEach(async (artwork: string) => { if (artwork.indexOf('drive.google.com/drive/folders') > 0) { // get all file in google drive folder const files = await this.crawlGoogleDriveFolder(artwork); @@ -125,7 +126,7 @@ export class ArtworkService implements OnModuleInit { urls.push(artwork); crawlPromises.push(this.crawlImage(artwork)); } - }); + } const crawlImageResult = (await Promise.all(crawlPromises)).filter( (result) => result.buffer ); @@ -149,7 +150,7 @@ export class ArtworkService implements OnModuleInit { ); // upload images - await Promise.all( + const uploadResult = await Promise.all( crawlImageResult.map((image, index) => { const keyName = `${s3SubFolder}/creator-${creatorId}/artworks/${contest_round}-${index}.jpg`; return this.fileService.uploadToS3( @@ -160,18 +161,16 @@ export class ArtworkService implements OnModuleInit { }) ); - const newArtworks = vaidArtworks.map( - (artwork: string, index: number) => ({ - contest_id, - contest_round, - creator_id: creatorId, - source_url: artwork, - url: new URL( - `${s3SubFolder}/creator-${creatorId}/artworks/${contest_round}-${index}.jpg`, - this.configService.get('aws.queryEndpoint') - ).href, - }) - ); + const newArtworks = uploadResult.map((data, index: number) => ({ + contest_id, + contest_round, + creator_id: creatorId, + source_url: vaidArtworks.join(','), + url: new URL( + `${s3SubFolder}/creator-${creatorId}/artworks/${contest_round}-${index}.jpg`, + this.configService.get('aws.queryEndpoint') + ).href, + })); const insertArtworkResult = await this.artworkGraphql.insertArtwork( { From 9ef760d40b3da118fc9c2f2ddaedc3b74cc6bcdc Mon Sep 17 00:00:00 2001 From: harisato Date: Fri, 9 Aug 2024 14:54:01 +0700 Subject: [PATCH 14/50] fix: upload artwork --- src/modules/artwork/artwork.service.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/modules/artwork/artwork.service.ts b/src/modules/artwork/artwork.service.ts index b95aa05f..13d8a8a4 100644 --- a/src/modules/artwork/artwork.service.ts +++ b/src/modules/artwork/artwork.service.ts @@ -150,9 +150,14 @@ export class ArtworkService implements OnModuleInit { ); // upload images + const filenames: string[] = []; const uploadResult = await Promise.all( crawlImageResult.map((image, index) => { - const keyName = `${s3SubFolder}/creator-${creatorId}/artworks/${contest_round}-${index}.jpg`; + const filename = `${contest_round}-${index}-${Number( + new Date() + ).toString()}.jpg`; + filenames.push(filename); + const keyName = `${s3SubFolder}/creator-${creatorId}/artworks/${filename}`; return this.fileService.uploadToS3( keyName, resizedArtworks[index], @@ -161,13 +166,13 @@ export class ArtworkService implements OnModuleInit { }) ); - const newArtworks = uploadResult.map((data, index: number) => ({ + const newArtworks = filenames.map((filename, index: number) => ({ contest_id, contest_round, creator_id: creatorId, source_url: vaidArtworks.join(','), url: new URL( - `${s3SubFolder}/creator-${creatorId}/artworks/${contest_round}-${index}.jpg`, + `${s3SubFolder}/creator-${creatorId}/artworks/${filename}`, this.configService.get('aws.queryEndpoint') ).href, })); From e9b9ef1e67ff644882eb31a6ac78fd8be6554584 Mon Sep 17 00:00:00 2001 From: ThienLK Date: Mon, 12 Aug 2024 11:27:37 +0700 Subject: [PATCH 15/50] fix manga detail --- hasura/metadata/query_collections.yaml | 159 ++++++++++---------- hasura/metadata/rest_endpoints.yaml | 2 +- src/modules/collection/launchpad.service.ts | 6 +- 3 files changed, 85 insertions(+), 82 deletions(-) diff --git a/hasura/metadata/query_collections.yaml b/hasura/metadata/query_collections.yaml index c1d1bc00..5ff06b3c 100644 --- a/hasura/metadata/query_collections.yaml +++ b/hasura/metadata/query_collections.yaml @@ -1283,84 +1283,6 @@ } } } - - name: Admin - Get manga detail - query: | - query GetMangaReadingDetail ($id: Int = 1) { - manga_by_pk(id: $id) { - id - poster - banner - status - contract_addresses - publish_date - release_date - created_at - manga_languages { - title - description - is_main_language - language_id - } - chapters_aggregate { - aggregate { - sum { - views - likes - } - count - } - } - manga_subscribers_aggregate { - aggregate { - count - } - } - manga_tags { - tag { - tag_languages { - tag_id - language_id - value - } - } - } - chapters(order_by: {chapter_number:desc_nulls_last}) { - id - chapter_number - chapter_name - chapter_type - thumbnail_url - pushlish_date - status - } - manga_creators { - creator { - id - slug - name - pen_name - } - } - manga_collections { - launchpad_id - manga_id - manga_collection { - contract_address - launchpad_creator { - name - slug - } - id - launchpad_i18ns { - data(path: "name") - } - slug - updated_at - created_at - } - } - } - } - name: Admin - Delete Manga Collection query: | mutation delete_manga_collection_by_pk ($id: Int!) { @@ -1445,3 +1367,84 @@ } } } + - name: Admin - Get manga detail + query: | + query GetMangaReadingDetail ($id: Int = 1) { + manga_by_pk(id: $id) { + id + poster + banner + status + contract_addresses + publish_date + release_date + created_at + manga_languages { + title + description + is_main_language + language_id + } + chapters_aggregate { + aggregate { + sum { + views + likes + } + count + } + } + manga_subscribers_aggregate { + aggregate { + count + } + } + manga_tags { + tag { + tag_languages { + tag_id + language_id + value + } + } + } + chapters(order_by: {chapter_number:desc_nulls_last}) { + id + chapter_number + chapter_name + chapter_type + thumbnail_url + pushlish_date + status + } + manga_creators { + creator { + id + slug + name + pen_name + } + } + manga_collections { + launchpad_id + manga_id + manga_collection { + contract_address + launchpad_creator { + name + slug + id + } + id + launchpad_i18ns { + data(path: "name") + language_id + id + } + slug + updated_at + created_at + } + } + } + } diff --git a/hasura/metadata/rest_endpoints.yaml b/hasura/metadata/rest_endpoints.yaml index 981da4a5..48dd23cc 100644 --- a/hasura/metadata/rest_endpoints.yaml +++ b/hasura/metadata/rest_endpoints.yaml @@ -115,7 +115,7 @@ - PUT name: Admin - Update manga status url: admin/manga/:id -- comment: "" +- comment: Get manga detail definition: query: collection_name: allowed-queries diff --git a/src/modules/collection/launchpad.service.ts b/src/modules/collection/launchpad.service.ts index f2b866f4..9631b83a 100644 --- a/src/modules/collection/launchpad.service.ts +++ b/src/modules/collection/launchpad.service.ts @@ -53,7 +53,6 @@ export class LaunchpadService { fund, contract_address, } = data; - const slug = generateSlug(name); const relate_key_words = [name, name_in_vn]; // insert db const result = await this.launchpadGraphql.insert({ @@ -62,11 +61,11 @@ export class LaunchpadService { status: LaunchpadStatus.Draft, fund, contract_address, - slug, relate_key_words, }, }); - + const mangaId = result.data.insert_launchpad_one.id; + const slug = generateSlug(name,mangaId); if (result.errors) return result; const launchpadId = result.data.insert_launchpad_one.id; @@ -123,6 +122,7 @@ export class LaunchpadService { const updateResult = await this.launchpadGraphql.update({ id: launchpadId, data: { + slug, featured_images, }, }); From 6a532c3de8c6a9a5e92f257bfda7a118a1ebf2e9 Mon Sep 17 00:00:00 2001 From: harisato Date: Mon, 12 Aug 2024 14:07:35 +0700 Subject: [PATCH 16/50] feat: update creator profile --- docker-compose-authorizer.yml | 2 +- .../tables/public_authorizer_users.yaml | 9 +++++ .../punkga-pg/tables/public_creators.yaml | 27 +++++++++++++++ src/auth/role.enum.ts | 1 + src/modules/creator/creator.controller.ts | 33 +++++++++++++++++-- src/modules/creator/creator.graphql.ts | 17 ++++++++++ src/modules/creator/creator.service.ts | 26 ++++++++++++--- 7 files changed, 107 insertions(+), 8 deletions(-) diff --git a/docker-compose-authorizer.yml b/docker-compose-authorizer.yml index 7c4236d9..85c459d6 100644 --- a/docker-compose-authorizer.yml +++ b/docker-compose-authorizer.yml @@ -22,7 +22,7 @@ services: DEFAULT_ROLES: user PROTECTED_ROLES: creator,admin ACCESS_TOKEN_EXPIRY_TIME: 3h - CUSTOM_ACCESS_TOKEN_SCRIPT: function(user,tokenPayload) {var data = tokenPayload; data['https://hasura.io/jwt/claims'] = {'x-hasura-user-id':user.id,'x-hasura-default-role':tokenPayload.allowed_roles[0], 'x-hasura-allowed-roles':user.roles }; return data;} + CUSTOM_ACCESS_TOKEN_SCRIPT: function(user,tokenPayload) {var data = tokenPayload; data['https://hasura.io/jwt/claims'] = {'x-hasura-user-id':user.id,'x-hasura-default-role':tokenPayload.allowed_roles[0], 'x-hasura-allowed-roles':user.roles, 'x-hasura-user-email':user.email }; return data;} ZALO_APP_ID: 2318977898294005000 ZALO_APP_SECRET: JmS4L81B99c5cp2sbnSw PORT: 8090 diff --git a/hasura/metadata/databases/punkga-pg/tables/public_authorizer_users.yaml b/hasura/metadata/databases/punkga-pg/tables/public_authorizer_users.yaml index 8307a6d5..4a6a134e 100644 --- a/hasura/metadata/databases/punkga-pg/tables/public_authorizer_users.yaml +++ b/hasura/metadata/databases/punkga-pg/tables/public_authorizer_users.yaml @@ -9,6 +9,15 @@ object_relationships: table: name: user_wallet schema: public + - name: creator + using: + manual_configuration: + column_mapping: + email: email + insertion_order: null + remote_table: + name: creators + schema: public array_relationships: - name: authorizer_users_social_activities using: diff --git a/hasura/metadata/databases/punkga-pg/tables/public_creators.yaml b/hasura/metadata/databases/punkga-pg/tables/public_creators.yaml index f8483ee9..2ebbbcef 100644 --- a/hasura/metadata/databases/punkga-pg/tables/public_creators.yaml +++ b/hasura/metadata/databases/punkga-pg/tables/public_creators.yaml @@ -66,3 +66,30 @@ select_permissions: filter: {} limit: 20 allow_aggregations: true +update_permissions: + - role: creator + permission: + columns: + - isActive + - id + - subcribers + - socials + - avatar_url + - bio + - dob + - email + - gender + - name + - pen_name + - slug + - wallet_address + - created_at + - updated_at + filter: + email: + _eq: X-Hasura-User-Email + check: null +delete_permissions: + - role: creator + permission: + filter: {} diff --git a/src/auth/role.enum.ts b/src/auth/role.enum.ts index 285b9c5d..9c0f0e09 100644 --- a/src/auth/role.enum.ts +++ b/src/auth/role.enum.ts @@ -1,4 +1,5 @@ export enum Role { User = 'user', + Creator = 'creator', Admin = 'admin', } diff --git a/src/modules/creator/creator.controller.ts b/src/modules/creator/creator.controller.ts index 77d389fb..bd991317 100644 --- a/src/modules/creator/creator.controller.ts +++ b/src/modules/creator/creator.controller.ts @@ -11,7 +11,12 @@ import { UseInterceptors, } from '@nestjs/common'; import { AnyFilesInterceptor } from '@nestjs/platform-express'; -import { ApiBearerAuth, ApiConsumes, ApiTags } from '@nestjs/swagger'; +import { + ApiBearerAuth, + ApiConsumes, + ApiOperation, + ApiTags, +} from '@nestjs/swagger'; import { AuthGuard } from '../../auth/auth.guard'; import { Role } from '../../auth/role.enum'; @@ -26,11 +31,15 @@ import { UpdateCreatorRequestDto, } from './dto/update-creator-request.dto'; import { CacheInterceptor } from '@nestjs/cache-manager'; +import { CreatorGraphql } from './creator.graphql'; @Controller('creator') @ApiTags('creator') export class CreatorController { - constructor(private readonly creatorSvc: CreatorService) { } + constructor( + private readonly creatorSvc: CreatorService, + private readonly creatorGraphql: CreatorGraphql + ) {} @Get(':slug') @UseInterceptors(CacheInterceptor) @@ -56,6 +65,7 @@ export class CreatorController { @Roles(Role.Admin) @Put(':creatorId') @ApiConsumes('multipart/form-data') + @ApiOperation({ summary: 'for admin role' }) @UseInterceptors( AuthUserInterceptor, ClassSerializerInterceptor, @@ -69,4 +79,23 @@ export class CreatorController { const { creatorId } = param; return this.creatorSvc.update(creatorId, data, files); } + + @UseGuards(AuthGuard, RolesGuard) + @ApiBearerAuth() + @Roles(Role.Creator) + @Put() + @ApiConsumes('multipart/form-data') + @ApiOperation({ summary: 'for creator role' }) + @UseInterceptors( + AuthUserInterceptor, + ClassSerializerInterceptor, + AnyFilesInterceptor() + ) + updateCreator( + @Param() param: UpdateCreatorParamDto, + @Body() data: UpdateCreatorRequestDto, + @UploadedFiles() files: Array + ) { + return this.creatorSvc.updateCreator(data, files); + } } diff --git a/src/modules/creator/creator.graphql.ts b/src/modules/creator/creator.graphql.ts index 5771d6a6..da5bc5ea 100644 --- a/src/modules/creator/creator.graphql.ts +++ b/src/modules/creator/creator.graphql.ts @@ -9,6 +9,23 @@ export class CreatorGraphql { private graphqlSvc: GraphqlService ) {} + queryCreatorIdByUserId(variables: any) { + return this.graphqlSvc.query( + this.configSvc.get('graphql.endpoint'), + '', + `query getCreatorIdByUserId($id: bpchar!) { + authorizer_users_by_pk(id: $id) { + creator { + id + } + } + } + `, + 'getCreatorIdByUserId', + variables + ); + } + queryCreatorByIdOrSlug(variables: any) { return this.graphqlSvc.query( this.configSvc.get('graphql.endpoint'), diff --git a/src/modules/creator/creator.service.ts b/src/modules/creator/creator.service.ts index aa45532c..9b52da17 100644 --- a/src/modules/creator/creator.service.ts +++ b/src/modules/creator/creator.service.ts @@ -15,7 +15,7 @@ export class CreatorService { constructor( private filesService: FilesService, private creatorGraphql: CreatorGraphql - ) { } + ) {} async get(slug: string) { const { id, slug: mangaSlug } = detectSlugOrId(slug); @@ -34,7 +34,8 @@ export class CreatorService { ) { try { const { token } = ContextProvider.getAuthUser(); - const { name, bio, socials, pen_name, gender, dob, wallet_address } = data; + const { name, bio, socials, pen_name, gender, dob, wallet_address } = + data; // insert creator to DB const result = await this.creatorGraphql.addCreator(token, { @@ -44,7 +45,7 @@ export class CreatorService { pen_name, gender, dob, - wallet_address + wallet_address, }); if (result.errors && result.errors.length > 0) { @@ -80,6 +81,20 @@ export class CreatorService { } } + async updateCreator( + data: UpdateCreatorRequestDto, + files: Array + ) { + const { userId, token } = ContextProvider.getAuthUser(); + const result = await this.creatorGraphql.queryCreatorIdByUserId({ + id: userId, + }); + if (result.errors) return result; + + const creatorId = result.data.authorizer_users_by_pk.creator.id; + return this.update(creatorId, data, files); + } + async update( creatorId: number, data: UpdateCreatorRequestDto, @@ -87,7 +102,8 @@ export class CreatorService { ) { try { const { token } = ContextProvider.getAuthUser(); - const { name, socials, pen_name, bio, gender, dob, wallet_address } = data; + const { name, socials, pen_name, bio, gender, dob, wallet_address } = + data; const result = await this.creatorGraphql.queryCreatorById(token, { id: creatorId, @@ -121,7 +137,7 @@ export class CreatorService { gender, dob, avatar_url: avatarUrl, - wallet_address + wallet_address, }); return updateResult; From c6ba97f416f0b63ed3cfac214bffb56cf2503bf9 Mon Sep 17 00:00:00 2001 From: ThienLK Date: Mon, 12 Aug 2024 15:20:29 +0700 Subject: [PATCH 17/50] add status --- hasura/metadata/query_collections.yaml | 71 ++++++-------------------- 1 file changed, 16 insertions(+), 55 deletions(-) diff --git a/hasura/metadata/query_collections.yaml b/hasura/metadata/query_collections.yaml index c1e3a2fd..cba4f936 100644 --- a/hasura/metadata/query_collections.yaml +++ b/hasura/metadata/query_collections.yaml @@ -1229,6 +1229,21 @@ } } } + - name: Public - Get manga by creator and contest + query: | + query manga ($contest_id: Int!, $id: Int!, $limit: Int = 100, $offset: Int = 0) { + manga(where: {contest_id:{_eq:$contest_id},manga_creators:{creator_id:{_eq:$id}}}, limit: $limit, offset: $offset) { + banner + id + slug + poster + } + manga_aggregate(where: {contest_id:{_eq:$contest_id},manga_creators:{creator_id:{_eq:$id}}}, limit: $limit, offset: $offset) { + aggregate { + count + } + } + } - name: Public - Get artwork by creator and contest query: | query artwork ($contest_id: Int!, $id: Int!, $limit: Int = 100, $offset: Int = 0) { @@ -1427,64 +1442,10 @@ id } slug + status updated_at created_at } } } } - - name: Public - Get manga by creator and contest - query: | - query manga ($contest_id: Int!, $id: Int!, $limit: Int = 100, $offset: Int = 0) { - manga(where: {contest_id:{_eq:$contest_id},manga_creators:{creator_id:{_eq:$id}}}, limit: $limit, offset: $offset) { - id - slug - latest_published - manga_total_views { - views - } - manga_total_likes { - likes - } - manga_creators { - creator { - id - slug - name - pen_name - isActive - } - } - status - chapters(limit: 1, order_by: {chapter_number:desc}, where: {status:{_eq:"Published"}}) { - id - chapter_number - pushlish_date - status - updated_at - } - manga_tags(limit: 5) { - tag { - id - tag_languages { - language_id - tag_id - value - } - } - } - manga_languages { - title - description - is_main_language - language_id - } - banner - poster - } - manga_aggregate(where: {contest_id:{_eq:$contest_id},manga_creators:{creator_id:{_eq:$id}}}, limit: $limit, offset: $offset) { - aggregate { - count - } - } - } From 3838d99f24426ff86102a409b53c22df590be471 Mon Sep 17 00:00:00 2001 From: ThienLK Date: Mon, 12 Aug 2024 15:34:28 +0700 Subject: [PATCH 18/50] edit delete chapter collection --- hasura/metadata/query_collections.yaml | 14 +++++++------- hasura/metadata/rest_endpoints.yaml | 18 +++++++++--------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/hasura/metadata/query_collections.yaml b/hasura/metadata/query_collections.yaml index cba4f936..c050a800 100644 --- a/hasura/metadata/query_collections.yaml +++ b/hasura/metadata/query_collections.yaml @@ -1290,13 +1290,6 @@ id } } - - name: Admin - Delete Chapter Collection - query: | - mutation delete_chapter_collection_by_pk ($id: Int!) { - delete_chapter_collection_by_pk(id: $id) { - id - } - } - name: Admin - Insert Chapter Collection query: | mutation MyMutation ($objects: [chapter_collection_insert_input!] = {}) { @@ -1449,3 +1442,10 @@ } } } + - name: Admin - Delete Chapter Collection + query: | + mutation delete_chapter_collection_by_pk ($id: Int!) { + delete_chapter_collection_by_pk(id: $id) { + id + } + } diff --git a/hasura/metadata/rest_endpoints.yaml b/hasura/metadata/rest_endpoints.yaml index 48dd23cc..7d5a8866 100644 --- a/hasura/metadata/rest_endpoints.yaml +++ b/hasura/metadata/rest_endpoints.yaml @@ -34,15 +34,6 @@ - GET name: Admin - Query campaign detail url: admin/campaign/:id -- comment: Delete Chapter Collection - definition: - query: - collection_name: allowed-queries - query_name: Admin - Delete Chapter Collection - methods: - - DELETE - name: Admin - Delete Chapter Collection - url: admin/chapter-collection - comment: "Insert Chapter Collection\nchapter_collection_insert_input:{\nchapter_id: Int!, \nlaunchpad_id: Int!\n}" definition: query: @@ -52,6 +43,15 @@ - POST name: Admin - Insert Chapter Collection url: admin/chapter-collection +- comment: Delete Chapter Collection + definition: + query: + collection_name: allowed-queries + query_name: Admin - Delete Chapter Collection + methods: + - DELETE + name: Admin - Delete Chapter Collection + url: admin/chapter-collection/:id - comment: "" definition: query: From 12ea78b9b54a32f92f6c831a36b3c9bb6856c95f Mon Sep 17 00:00:00 2001 From: ThienLK Date: Mon, 12 Aug 2024 16:10:44 +0700 Subject: [PATCH 19/50] fix manga detail --- hasura/metadata/query_collections.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/hasura/metadata/query_collections.yaml b/hasura/metadata/query_collections.yaml index c050a800..b47353b4 100644 --- a/hasura/metadata/query_collections.yaml +++ b/hasura/metadata/query_collections.yaml @@ -1360,9 +1360,16 @@ } } } + - name: Admin - Delete Chapter Collection + query: | + mutation delete_chapter_collection_by_pk ($id: Int!) { + delete_chapter_collection_by_pk(id: $id) { + id + } + } - name: Admin - Get manga detail query: | - query GetMangaReadingDetail ($id: Int = 1) { + query GetMangaReadingDetail ($id: Int = 28) { manga_by_pk(id: $id) { id poster @@ -1428,7 +1435,6 @@ slug id } - id launchpad_i18ns { data(path: "name") language_id @@ -1439,13 +1445,7 @@ updated_at created_at } + id } } } - - name: Admin - Delete Chapter Collection - query: | - mutation delete_chapter_collection_by_pk ($id: Int!) { - delete_chapter_collection_by_pk(id: $id) { - id - } - } From 15e6208b0af25d8327b479a7c9b38784e6397a30 Mon Sep 17 00:00:00 2001 From: ThienLK Date: Mon, 12 Aug 2024 16:35:35 +0700 Subject: [PATCH 20/50] fix admin get chapter detail --- hasura/metadata/query_collections.yaml | 96 +++++++++++--------------- 1 file changed, 39 insertions(+), 57 deletions(-) diff --git a/hasura/metadata/query_collections.yaml b/hasura/metadata/query_collections.yaml index b47353b4..39012403 100644 --- a/hasura/metadata/query_collections.yaml +++ b/hasura/metadata/query_collections.yaml @@ -1303,63 +1303,6 @@ } } } - - name: Admin - Get chapter detail - query: | - query GetChapterReadingDetail ($slug: String, $chapter_number: Int = 1, $user_id: bpchar = "") { - chapters(where: {_and:{chapter_number:{_eq:$chapter_number},manga:{_and:{slug:{_eq:$slug},_or:[{status:{_ilike:"On-going"}},{status:{_eq:"Finished"}}]}}}}) { - id - chapter_number - chapter_name - chapter_type - thumbnail_url - status - pushlish_date - chapter_languages(where: {chapter:{status:{_eq:"Published"}}}) { - language_id - detail - } - comments: social_activities_aggregate { - aggregate { - count - } - } - views - chapters_likes_aggregate { - aggregate { - count - } - } - chapters_likes(where: {user_id:{_eq:$user_id}}) { - id - created_at - user_id - chapter_id - } - chapter_collections { - id - launchpad_id - created_at - updated_at - chapter_collection { - contract_address - created_at - creator_id - id - launchpad_creator { - avatar_url - name - slug - } - slug - status - launchpad_i18ns { - data(path: "name") - language_id - } - } - } - } - } - name: Admin - Delete Chapter Collection query: | mutation delete_chapter_collection_by_pk ($id: Int!) { @@ -1449,3 +1392,42 @@ } } } + - name: Admin - Get chapter detail + query: | + query GetChapterDetail ($manga_id: Int = 18, $chapter_number: Int = 1) { + chapters(where: {_and:{chapter_number:{_eq:$chapter_number},manga:{id:{_eq:$manga_id}}}}) { + id + chapter_number + chapter_name + chapter_type + thumbnail_url + status + pushlish_date + chapter_languages { + language_id + detail + } + chapter_collections { + id + chapter_collection { + contract_address + launchpad_creator { + name + slug + id + } + launchpad_i18ns { + data(path: "name") + language_id + id + } + slug + status + updated_at + created_at + } + chapter_id + launchpad_id + } + } + } From b584811f5be883f512bef16cf3bffe236f809079 Mon Sep 17 00:00:00 2001 From: harisato Date: Tue, 13 Aug 2024 10:38:05 +0700 Subject: [PATCH 21/50] feat: add get creator profile api in portal --- src/modules/creator/creator.controller.ts | 12 +++++++++- src/modules/creator/creator.graphql.ts | 6 ++--- src/modules/creator/creator.service.ts | 27 ++++++++++++++++++----- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/modules/creator/creator.controller.ts b/src/modules/creator/creator.controller.ts index bd991317..2b07af9e 100644 --- a/src/modules/creator/creator.controller.ts +++ b/src/modules/creator/creator.controller.ts @@ -43,10 +43,20 @@ export class CreatorController { @Get(':slug') @UseInterceptors(CacheInterceptor) - get(@Param() param: GetCreatorParamDto) { + getBySlug(@Param() param: GetCreatorParamDto) { return this.creatorSvc.get(param.slug); } + @Get() + @UseGuards(AuthGuard, RolesGuard) + @ApiBearerAuth() + @Roles(Role.Creator) + @ApiOperation({ summary: 'for creator role' }) + @UseInterceptors(CacheInterceptor, AuthUserInterceptor) + get() { + return this.creatorSvc.getCreator(); + } + @UseGuards(AuthGuard, RolesGuard) @ApiBearerAuth() @Roles(Role.Admin) diff --git a/src/modules/creator/creator.graphql.ts b/src/modules/creator/creator.graphql.ts index da5bc5ea..ead00a6c 100644 --- a/src/modules/creator/creator.graphql.ts +++ b/src/modules/creator/creator.graphql.ts @@ -26,12 +26,12 @@ export class CreatorGraphql { ); } - queryCreatorByIdOrSlug(variables: any) { + queryCreatorByIdOrSlug(param: string, condition: string, variables: any) { return this.graphqlSvc.query( this.configSvc.get('graphql.endpoint'), '', - `query QueryCreatorByIdOrSlug($id: Int = 0, $slug: String = "") { - creators(where: {_or: [{id: {_eq: $id}}, {slug: {_eq: $slug}}]}) { + `query QueryCreatorByIdOrSlug(${param}) { + creators(where: {${condition}}) { id slug avatar_url diff --git a/src/modules/creator/creator.service.ts b/src/modules/creator/creator.service.ts index 9b52da17..79fdee5b 100644 --- a/src/modules/creator/creator.service.ts +++ b/src/modules/creator/creator.service.ts @@ -17,13 +17,28 @@ export class CreatorService { private creatorGraphql: CreatorGraphql ) {} - async get(slug: string) { - const { id, slug: mangaSlug } = detectSlugOrId(slug); - - const result = await this.creatorGraphql.queryCreatorByIdOrSlug({ - id, - slug: mangaSlug, + async getCreator() { + const { userId, token } = ContextProvider.getAuthUser(); + const result = await this.creatorGraphql.queryCreatorIdByUserId({ + id: userId, }); + if (result.errors) return result; + + const creatorId = result.data.authorizer_users_by_pk.creator.id; + return this.get(creatorId); + } + + async get(keyword: string) { + const { id, slug } = detectSlugOrId(keyword); + + const param = id > 0 ? '$id: Int!' : '$slug: String!'; + const whereCondition = id > 0 ? 'id: {_eq: $id}' : 'slug: {_eq: $slug}'; + const variables = id > 0 ? { id } : { slug }; + const result = await this.creatorGraphql.queryCreatorByIdOrSlug( + param, + whereCondition, + variables + ); return result; } From b35f2603355e50580e28fa7eb3dfa5d93732d467 Mon Sep 17 00:00:00 2001 From: harisato Date: Tue, 13 Aug 2024 10:55:44 +0700 Subject: [PATCH 22/50] feat: add get creator profile api in portal --- src/modules/creator/creator.controller.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modules/creator/creator.controller.ts b/src/modules/creator/creator.controller.ts index 2b07af9e..c23e2cc4 100644 --- a/src/modules/creator/creator.controller.ts +++ b/src/modules/creator/creator.controller.ts @@ -102,7 +102,6 @@ export class CreatorController { AnyFilesInterceptor() ) updateCreator( - @Param() param: UpdateCreatorParamDto, @Body() data: UpdateCreatorRequestDto, @UploadedFiles() files: Array ) { From 28ebd30a5421b4f4baacb3b91d6f8bd8f36b3993 Mon Sep 17 00:00:00 2001 From: ThienLK Date: Tue, 13 Aug 2024 14:44:59 +0700 Subject: [PATCH 23/50] update create chapter --- hasura/metadata/query_collections.yaml | 26 +++++++------- src/modules/chapter/chapter.graphql.ts | 28 ++++++++++++++- src/modules/chapter/chapter.service.ts | 36 ++++++++++++++++++- .../chapter/dto/create-chapter-request.dto.ts | 3 ++ 4 files changed, 78 insertions(+), 15 deletions(-) diff --git a/hasura/metadata/query_collections.yaml b/hasura/metadata/query_collections.yaml index 39012403..3560254c 100644 --- a/hasura/metadata/query_collections.yaml +++ b/hasura/metadata/query_collections.yaml @@ -1290,19 +1290,6 @@ id } } - - name: Admin - Insert Chapter Collection - query: | - mutation MyMutation ($objects: [chapter_collection_insert_input!] = {}) { - insert_chapter_collection(objects: $objects, on_conflict: {constraint:chapter_collection_chapter_id_launchpad_id_key,update_columns:chapter_id}) { - affected_rows - returning { - chapter_id - created_at - id - launchpad_id - } - } - } - name: Admin - Delete Chapter Collection query: | mutation delete_chapter_collection_by_pk ($id: Int!) { @@ -1431,3 +1418,16 @@ } } } + - name: Admin - Insert Chapter Collection + query: | + mutation insert_chapter_collection ($objects: [chapter_collection_insert_input!] = {}) { + insert_chapter_collection(objects: $objects, on_conflict: {constraint:chapter_collection_chapter_id_launchpad_id_key,update_columns:chapter_id}) { + affected_rows + returning { + chapter_id + created_at + id + launchpad_id + } + } + } diff --git a/src/modules/chapter/chapter.graphql.ts b/src/modules/chapter/chapter.graphql.ts index 950fd553..a78aecba 100644 --- a/src/modules/chapter/chapter.graphql.ts +++ b/src/modules/chapter/chapter.graphql.ts @@ -15,7 +15,7 @@ export class ChapterGraphql { token, `query GetChapterInfo($id: Int!) { chapters_by_pk(id: $id) { - manga_id + chapter_id chapter_number thumbnail_url chapter_languages { @@ -125,4 +125,30 @@ export class ChapterGraphql { } ); } + + createChapterCollection(variables: any) { + const headers = { + 'x-hasura-admin-secret': this.configService.get( + 'graphql.adminSecret' + ), + }; + return this.graphqlSvc.query( + this.configService.get('graphql.endpoint'), + "", + `mutation insert_chapter_collection($objects: [chapter_collection_insert_input!] = {}) { + insert_chapter_collection(objects: $objects, on_conflict: {constraint: chapter_collection_chapter_id_launchpad_id_key, update_columns: chapter_id}) { + affected_rows + returning { + chapter_id + created_at + id + launchpad_id + } + } + }`, + 'insert_chapter_collection', + variables, + headers + ); + } } diff --git a/src/modules/chapter/chapter.service.ts b/src/modules/chapter/chapter.service.ts index 1f011976..b1741e6a 100644 --- a/src/modules/chapter/chapter.service.ts +++ b/src/modules/chapter/chapter.service.ts @@ -92,6 +92,7 @@ export class ChapterService { chapter_type, pushlish_date, status, + collection_ids, } = data; const chapter_images = plainToInstance( ChapterImage, @@ -171,7 +172,15 @@ export class ChapterService { return updateResult; } } - + const collectionIdListStr = collection_ids.toString().split(','); + let collectionIdList = Array.from(collectionIdListStr,Number) + const updateResult = await this.addChapterCollection( + chapterId, + collectionIdList + ); + if (updateResult.errors && updateResult.errors.length > 0) { + return updateResult; + } return result.data; } catch (errors) { return { @@ -403,4 +412,29 @@ export class ChapterService { }; } } + async addChapterCollection(chapterId: Number, collectionIdList: number[]) { + try { + // const { token } = ContextProvider.getAuthUser(); + const objects = []; + // update chapter collection in DB + await Promise.all( + collectionIdList.map((collectionId) => { + const o = { + chapter_id: chapterId, + launchpad_id: collectionId, + }; + objects.push(o); + }) + ); + const updateResponse = await this.chapterGraphql.createChapterCollection({ + objects, + }); + + return updateResponse; + } catch (errors) { + return { + errors, + }; + } + } } diff --git a/src/modules/chapter/dto/create-chapter-request.dto.ts b/src/modules/chapter/dto/create-chapter-request.dto.ts index 21909e53..8a97ee23 100644 --- a/src/modules/chapter/dto/create-chapter-request.dto.ts +++ b/src/modules/chapter/dto/create-chapter-request.dto.ts @@ -47,4 +47,7 @@ export class CreateChapterRequestDto { @ApiPropertyOptional({ type: ['string'], format: 'binary' }) files: Express.Multer.File[]; + + @ApiPropertyOptional({ type: [Number], example: [1] }) + collection_ids: number[]; } From 5f698ff6897c6bfcd84ffab79759da5e44e12d58 Mon Sep 17 00:00:00 2001 From: ThienLK Date: Tue, 13 Aug 2024 17:08:12 +0700 Subject: [PATCH 24/50] add verify when view chapter --- src/modules/chapter/chapter.controller.ts | 2 +- src/modules/chapter/chapter.graphql.ts | 109 +++++++++++++++++++++- src/modules/chapter/chapter.service.ts | 65 +++++++++++-- 3 files changed, 167 insertions(+), 9 deletions(-) diff --git a/src/modules/chapter/chapter.controller.ts b/src/modules/chapter/chapter.controller.ts index a572b1e9..579deaba 100644 --- a/src/modules/chapter/chapter.controller.ts +++ b/src/modules/chapter/chapter.controller.ts @@ -76,7 +76,7 @@ export class ChapterController { @UseGuards(AuthGuard, RolesGuard) @ApiBearerAuth() - @Roles(Role.User) + @Roles(Role.User,Role.Admin) @Get(':chapterId/protected') @UseInterceptors(AuthUserInterceptor) view(@Param() data: ViewProtectedChapterRequestDto) { diff --git a/src/modules/chapter/chapter.graphql.ts b/src/modules/chapter/chapter.graphql.ts index a78aecba..928acedb 100644 --- a/src/modules/chapter/chapter.graphql.ts +++ b/src/modules/chapter/chapter.graphql.ts @@ -1,6 +1,6 @@ import { ConfigService } from '@nestjs/config'; import { GraphqlService } from '../graphql/graphql.service'; -import { Injectable } from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; @Injectable() export class ChapterGraphql { @@ -15,13 +15,18 @@ export class ChapterGraphql { token, `query GetChapterInfo($id: Int!) { chapters_by_pk(id: $id) { - chapter_id + id chapter_number thumbnail_url chapter_languages { detail language_id } + chapter_collections { + chapter_collection { + contract_address + } + } } }`, 'GetChapterInfo', @@ -151,4 +156,104 @@ export class ChapterGraphql { headers ); } + + queryErc721Tokens(token: string, network: string, variables: any) { + return this.graphqlSvc.query( + this.configService.get('horosope.endpoint'), + token, + `query QueryCw721Tokens($owner: String = "", $smart_contracts: [String!] = "") { + ${network} { + erc721_contract(where: {evm_smart_contract: {address: {_in: $smart_contracts}}}) { + evm_smart_contract { + id + } + erc721_tokens(where: {owner: {_eq: $owner}}) { + owner + token_id + } + } + } + }`, + 'QueryCw721Tokens', + variables + ); + } + + async queryUserAddress(token: string): Promise { + const result = await this.graphqlSvc.query( + this.configService.get('graphql.endpoint'), + token, + `query GetUserProfile { + authorizer_users(limit: 1) { + id + email + email_verified_at + bio + birthdate + gender + active_wallet_address: active_evm_address + wallet_address + nickname + picture + signup_methods + levels { + xp + level + user_level_chain { + id + name + punkga_config + } + } + authorizer_users_user_wallet { + address + } + user_quests_aggregate { + aggregate { + count + } + } + user_quests(order_by: {created_at:desc}, limit: 20) { + created_at + status + user_quest_rewards { + tx_hash + } + quest { + id + name + quests_campaign { + campaign_chain { + punkga_config + } + } + quests_i18n { + id + quest_id + language_id + data + i18n_language { + id + description + icon + is_main + symbol + } + } + reward + } + } + } + } + `, + 'GetUserProfile', + {} + ); + + if (result.data.authorizer_users[0]?.active_wallet_address) { + return result.data.authorizer_users[0]?.active_wallet_address; + } else { + throw new NotFoundException('wallet address not found'); + } + } } diff --git a/src/modules/chapter/chapter.service.ts b/src/modules/chapter/chapter.service.ts index b1741e6a..ddfadfc3 100644 --- a/src/modules/chapter/chapter.service.ts +++ b/src/modules/chapter/chapter.service.ts @@ -4,7 +4,7 @@ import * as _ from 'lodash'; import md5 from 'md5'; import rimraf from 'rimraf'; -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable, Logger, NotFoundException } from '@nestjs/common'; import { ContextProvider } from '../../providers/contex.provider'; import { MangaService } from '../manga/manga.service'; @@ -13,6 +13,7 @@ import { ChapterImage, CreateChapterRequestDto, } from './dto/create-chapter-request.dto'; +import { ConfigService } from '@nestjs/config'; import { UpdateChapterImage, UpdateChapterParamDto, @@ -30,7 +31,8 @@ export class ChapterService { constructor( private mangaService: MangaService, private chapterGraphql: ChapterGraphql, - private uploadChapterService: UploadChapterService + private uploadChapterService: UploadChapterService, + private configSvc: ConfigService ) {} async upload(data: UploadInputDto, file: Express.Multer.File) { @@ -173,7 +175,7 @@ export class ChapterService { } } const collectionIdListStr = collection_ids.toString().split(','); - let collectionIdList = Array.from(collectionIdListStr,Number) + let collectionIdList = Array.from(collectionIdListStr, Number); const updateResult = await this.addChapterCollection( chapterId, collectionIdList @@ -391,11 +393,24 @@ export class ChapterService { if (result.errors && result.errors.length > 0) { return result; } + const chapterInfor = await this.chapterGraphql.getChapterInfo( + token, + chapterId + ); + if (chapterInfor.errors && chapterInfor.errors.length > 0) { + return chapterInfor; + } + const chapterCollectionAddress = + chapterInfor.chapter_collections.map((c) => { + return c.chapter_collection.contract_address; + }); + const walletAddress = await this.chapterGraphql.queryUserAddress(token); + if (walletAddress === null) { + throw new NotFoundException('wallet address not found'); + } if (result.data.chapters[0].chapter_type === 'NFTs only') { - const access = await this.mangaService.getAccess( - result.data.chapters[0].manga_id - ); + const access = await this.getAccess(walletAddress, chapterCollectionAddress); this.logger.debug(`Access ${JSON.stringify(access)}`); @@ -412,6 +427,7 @@ export class ChapterService { }; } } + async addChapterCollection(chapterId: Number, collectionIdList: number[]) { try { // const { token } = ContextProvider.getAuthUser(); @@ -437,4 +453,41 @@ export class ChapterService { }; } } + + async getAccess(walletAddress: string, contractAddresses: string[]) { + try { + const network = this.configSvc.get('horosope.network'); + let nft = false; + const { token } = ContextProvider.getAuthUser(); + + // check data on horoscope + const getCw721TokenResult = await this.chapterGraphql.queryErc721Tokens( + token, + network, + { + smart_contracts: contractAddresses.map((address) => + address.toLowerCase() + ), + owner: walletAddress.toLowerCase(), + } + ); + + if ( + getCw721TokenResult.data[`${network}`].erc721_contract.length > 0 && + getCw721TokenResult.data[`${network}`].erc721_contract.find( + (contract) => contract.erc721_tokens.length > 0 + ) + ) { + nft = true; + } + + return { + nft, + }; + } catch (errors) { + return { + errors, + }; + } + } } From a596d20f6636335a2641d01939fbe000f6e06ea7 Mon Sep 17 00:00:00 2001 From: harisato Date: Tue, 13 Aug 2024 17:13:24 +0700 Subject: [PATCH 25/50] feat: add album table --- .../punkga-pg/tables/public_albums.yaml | 3 +++ .../databases/punkga-pg/tables/tables.yaml | 1 + .../down.sql | 1 + .../up.sql | 17 +++++++++++++++++ .../down.sql | 4 ++++ .../up.sql | 2 ++ .../down.sql | 1 + .../up.sql | 1 + .../down.sql | 1 + .../up.sql | 5 +++++ .../down.sql | 1 + .../up.sql | 1 + .../down.sql | 1 + .../up.sql | 1 + .../down.sql | 4 ++++ .../up.sql | 2 ++ .../down.sql | 1 + .../up.sql | 5 +++++ 18 files changed, 52 insertions(+) create mode 100644 hasura/metadata/databases/punkga-pg/tables/public_albums.yaml create mode 100644 hasura/migrations/punkga-pg/1723536552395_create_table_public_albums/down.sql create mode 100644 hasura/migrations/punkga-pg/1723536552395_create_table_public_albums/up.sql create mode 100644 hasura/migrations/punkga-pg/1723536614071_alter_table_public_albums_add_column_creator_id/down.sql create mode 100644 hasura/migrations/punkga-pg/1723536614071_alter_table_public_albums_add_column_creator_id/up.sql create mode 100644 hasura/migrations/punkga-pg/1723536630287_alter_table_public_albums_add_unique_name_creator_id/down.sql create mode 100644 hasura/migrations/punkga-pg/1723536630287_alter_table_public_albums_add_unique_name_creator_id/up.sql create mode 100644 hasura/migrations/punkga-pg/1723536658390_set_fk_public_albums_creator_id/down.sql create mode 100644 hasura/migrations/punkga-pg/1723536658390_set_fk_public_albums_creator_id/up.sql create mode 100644 hasura/migrations/punkga-pg/1723536779907_alter_table_public_albums_alter_column_storage_folder/down.sql create mode 100644 hasura/migrations/punkga-pg/1723536779907_alter_table_public_albums_alter_column_storage_folder/up.sql create mode 100644 hasura/migrations/punkga-pg/1723536796576_insert_into_public_albums/down.sql create mode 100644 hasura/migrations/punkga-pg/1723536796576_insert_into_public_albums/up.sql create mode 100644 hasura/migrations/punkga-pg/1723536823558_alter_table_public_artworks_add_column_album_id/down.sql create mode 100644 hasura/migrations/punkga-pg/1723536823558_alter_table_public_artworks_add_column_album_id/up.sql create mode 100644 hasura/migrations/punkga-pg/1723537126789_set_fk_public_artworks_album_id/down.sql create mode 100644 hasura/migrations/punkga-pg/1723537126789_set_fk_public_artworks_album_id/up.sql diff --git a/hasura/metadata/databases/punkga-pg/tables/public_albums.yaml b/hasura/metadata/databases/punkga-pg/tables/public_albums.yaml new file mode 100644 index 00000000..5718de51 --- /dev/null +++ b/hasura/metadata/databases/punkga-pg/tables/public_albums.yaml @@ -0,0 +1,3 @@ +table: + name: albums + schema: public diff --git a/hasura/metadata/databases/punkga-pg/tables/tables.yaml b/hasura/metadata/databases/punkga-pg/tables/tables.yaml index d5223063..59a8deaf 100644 --- a/hasura/metadata/databases/punkga-pg/tables/tables.yaml +++ b/hasura/metadata/databases/punkga-pg/tables/tables.yaml @@ -1,3 +1,4 @@ +- "!include public_albums.yaml" - "!include public_artworks.yaml" - "!include public_authorizer_sessions.yaml" - "!include public_authorizer_users.yaml" diff --git a/hasura/migrations/punkga-pg/1723536552395_create_table_public_albums/down.sql b/hasura/migrations/punkga-pg/1723536552395_create_table_public_albums/down.sql new file mode 100644 index 00000000..e9cc94da --- /dev/null +++ b/hasura/migrations/punkga-pg/1723536552395_create_table_public_albums/down.sql @@ -0,0 +1 @@ +DROP TABLE "public"."albums"; diff --git a/hasura/migrations/punkga-pg/1723536552395_create_table_public_albums/up.sql b/hasura/migrations/punkga-pg/1723536552395_create_table_public_albums/up.sql new file mode 100644 index 00000000..7f967854 --- /dev/null +++ b/hasura/migrations/punkga-pg/1723536552395_create_table_public_albums/up.sql @@ -0,0 +1,17 @@ +CREATE TABLE "public"."albums" ("id" serial NOT NULL, "name" text NOT NULL, "description" text, "show" boolean NOT NULL DEFAULT false, "storage_folder" text NOT NULL, "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), PRIMARY KEY ("id") );COMMENT ON TABLE "public"."albums" IS E'albums of artwork'; +CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"() +RETURNS TRIGGER AS $$ +DECLARE + _new record; +BEGIN + _new := NEW; + _new."updated_at" = NOW(); + RETURN _new; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER "set_public_albums_updated_at" +BEFORE UPDATE ON "public"."albums" +FOR EACH ROW +EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"(); +COMMENT ON TRIGGER "set_public_albums_updated_at" ON "public"."albums" +IS 'trigger to set value of column "updated_at" to current timestamp on row update'; diff --git a/hasura/migrations/punkga-pg/1723536614071_alter_table_public_albums_add_column_creator_id/down.sql b/hasura/migrations/punkga-pg/1723536614071_alter_table_public_albums_add_column_creator_id/down.sql new file mode 100644 index 00000000..4aaade51 --- /dev/null +++ b/hasura/migrations/punkga-pg/1723536614071_alter_table_public_albums_add_column_creator_id/down.sql @@ -0,0 +1,4 @@ +-- Could not auto-generate a down migration. +-- Please write an appropriate down migration for the SQL below: +-- alter table "public"."albums" add column "creator_id" integer +-- null; diff --git a/hasura/migrations/punkga-pg/1723536614071_alter_table_public_albums_add_column_creator_id/up.sql b/hasura/migrations/punkga-pg/1723536614071_alter_table_public_albums_add_column_creator_id/up.sql new file mode 100644 index 00000000..fd280861 --- /dev/null +++ b/hasura/migrations/punkga-pg/1723536614071_alter_table_public_albums_add_column_creator_id/up.sql @@ -0,0 +1,2 @@ +alter table "public"."albums" add column "creator_id" integer + null; diff --git a/hasura/migrations/punkga-pg/1723536630287_alter_table_public_albums_add_unique_name_creator_id/down.sql b/hasura/migrations/punkga-pg/1723536630287_alter_table_public_albums_add_unique_name_creator_id/down.sql new file mode 100644 index 00000000..2a456252 --- /dev/null +++ b/hasura/migrations/punkga-pg/1723536630287_alter_table_public_albums_add_unique_name_creator_id/down.sql @@ -0,0 +1 @@ +alter table "public"."albums" drop constraint "albums_name_creator_id_key"; diff --git a/hasura/migrations/punkga-pg/1723536630287_alter_table_public_albums_add_unique_name_creator_id/up.sql b/hasura/migrations/punkga-pg/1723536630287_alter_table_public_albums_add_unique_name_creator_id/up.sql new file mode 100644 index 00000000..666c847c --- /dev/null +++ b/hasura/migrations/punkga-pg/1723536630287_alter_table_public_albums_add_unique_name_creator_id/up.sql @@ -0,0 +1 @@ +alter table "public"."albums" add constraint "albums_name_creator_id_key" unique ("name", "creator_id"); diff --git a/hasura/migrations/punkga-pg/1723536658390_set_fk_public_albums_creator_id/down.sql b/hasura/migrations/punkga-pg/1723536658390_set_fk_public_albums_creator_id/down.sql new file mode 100644 index 00000000..31006eda --- /dev/null +++ b/hasura/migrations/punkga-pg/1723536658390_set_fk_public_albums_creator_id/down.sql @@ -0,0 +1 @@ +alter table "public"."albums" drop constraint "albums_creator_id_fkey"; diff --git a/hasura/migrations/punkga-pg/1723536658390_set_fk_public_albums_creator_id/up.sql b/hasura/migrations/punkga-pg/1723536658390_set_fk_public_albums_creator_id/up.sql new file mode 100644 index 00000000..df7b3001 --- /dev/null +++ b/hasura/migrations/punkga-pg/1723536658390_set_fk_public_albums_creator_id/up.sql @@ -0,0 +1,5 @@ +alter table "public"."albums" + add constraint "albums_creator_id_fkey" + foreign key ("creator_id") + references "public"."creators" + ("id") on update cascade on delete cascade; diff --git a/hasura/migrations/punkga-pg/1723536779907_alter_table_public_albums_alter_column_storage_folder/down.sql b/hasura/migrations/punkga-pg/1723536779907_alter_table_public_albums_alter_column_storage_folder/down.sql new file mode 100644 index 00000000..2e4386f0 --- /dev/null +++ b/hasura/migrations/punkga-pg/1723536779907_alter_table_public_albums_alter_column_storage_folder/down.sql @@ -0,0 +1 @@ +alter table "public"."albums" alter column "storage_folder" set not null; diff --git a/hasura/migrations/punkga-pg/1723536779907_alter_table_public_albums_alter_column_storage_folder/up.sql b/hasura/migrations/punkga-pg/1723536779907_alter_table_public_albums_alter_column_storage_folder/up.sql new file mode 100644 index 00000000..06484c69 --- /dev/null +++ b/hasura/migrations/punkga-pg/1723536779907_alter_table_public_albums_alter_column_storage_folder/up.sql @@ -0,0 +1 @@ +alter table "public"."albums" alter column "storage_folder" drop not null; diff --git a/hasura/migrations/punkga-pg/1723536796576_insert_into_public_albums/down.sql b/hasura/migrations/punkga-pg/1723536796576_insert_into_public_albums/down.sql new file mode 100644 index 00000000..9880c4de --- /dev/null +++ b/hasura/migrations/punkga-pg/1723536796576_insert_into_public_albums/down.sql @@ -0,0 +1 @@ +DELETE FROM "public"."albums" WHERE "id" = 1; diff --git a/hasura/migrations/punkga-pg/1723536796576_insert_into_public_albums/up.sql b/hasura/migrations/punkga-pg/1723536796576_insert_into_public_albums/up.sql new file mode 100644 index 00000000..564680a3 --- /dev/null +++ b/hasura/migrations/punkga-pg/1723536796576_insert_into_public_albums/up.sql @@ -0,0 +1 @@ +INSERT INTO "public"."albums"("id", "name", "description", "show", "storage_folder", "created_at", "updated_at", "creator_id") VALUES (1, E'ALL', null, true, null, E'2024-08-13T08:13:16.455164+00:00', E'2024-08-13T08:13:16.455164+00:00', null); diff --git a/hasura/migrations/punkga-pg/1723536823558_alter_table_public_artworks_add_column_album_id/down.sql b/hasura/migrations/punkga-pg/1723536823558_alter_table_public_artworks_add_column_album_id/down.sql new file mode 100644 index 00000000..95e06952 --- /dev/null +++ b/hasura/migrations/punkga-pg/1723536823558_alter_table_public_artworks_add_column_album_id/down.sql @@ -0,0 +1,4 @@ +-- Could not auto-generate a down migration. +-- Please write an appropriate down migration for the SQL below: +-- alter table "public"."artworks" add column "album_id" integer +-- null default '1'; diff --git a/hasura/migrations/punkga-pg/1723536823558_alter_table_public_artworks_add_column_album_id/up.sql b/hasura/migrations/punkga-pg/1723536823558_alter_table_public_artworks_add_column_album_id/up.sql new file mode 100644 index 00000000..9f01cea7 --- /dev/null +++ b/hasura/migrations/punkga-pg/1723536823558_alter_table_public_artworks_add_column_album_id/up.sql @@ -0,0 +1,2 @@ +alter table "public"."artworks" add column "album_id" integer + null default '1'; diff --git a/hasura/migrations/punkga-pg/1723537126789_set_fk_public_artworks_album_id/down.sql b/hasura/migrations/punkga-pg/1723537126789_set_fk_public_artworks_album_id/down.sql new file mode 100644 index 00000000..392faf58 --- /dev/null +++ b/hasura/migrations/punkga-pg/1723537126789_set_fk_public_artworks_album_id/down.sql @@ -0,0 +1 @@ +alter table "public"."artworks" drop constraint "artworks_album_id_fkey"; diff --git a/hasura/migrations/punkga-pg/1723537126789_set_fk_public_artworks_album_id/up.sql b/hasura/migrations/punkga-pg/1723537126789_set_fk_public_artworks_album_id/up.sql new file mode 100644 index 00000000..93d6ca14 --- /dev/null +++ b/hasura/migrations/punkga-pg/1723537126789_set_fk_public_artworks_album_id/up.sql @@ -0,0 +1,5 @@ +alter table "public"."artworks" + add constraint "artworks_album_id_fkey" + foreign key ("album_id") + references "public"."albums" + ("id") on update cascade on delete cascade; From 554a16014fa4b7a32374a04c8cf279a4fc634d05 Mon Sep 17 00:00:00 2001 From: harisato Date: Tue, 13 Aug 2024 17:23:53 +0700 Subject: [PATCH 26/50] feat: update album --- .../databases/punkga-pg/tables/public_albums.yaml | 8 ++++++++ .../databases/punkga-pg/tables/public_artworks.yaml | 4 ++++ .../down.sql | 4 ++++ .../up.sql | 2 ++ 4 files changed, 18 insertions(+) create mode 100644 hasura/migrations/punkga-pg/1723544203165_alter_table_public_albums_add_column_disable/down.sql create mode 100644 hasura/migrations/punkga-pg/1723544203165_alter_table_public_albums_add_column_disable/up.sql diff --git a/hasura/metadata/databases/punkga-pg/tables/public_albums.yaml b/hasura/metadata/databases/punkga-pg/tables/public_albums.yaml index 5718de51..c130778b 100644 --- a/hasura/metadata/databases/punkga-pg/tables/public_albums.yaml +++ b/hasura/metadata/databases/punkga-pg/tables/public_albums.yaml @@ -1,3 +1,11 @@ table: name: albums schema: public +array_relationships: + - name: artworks + using: + foreign_key_constraint_on: + column: album_id + table: + name: artworks + schema: public diff --git a/hasura/metadata/databases/punkga-pg/tables/public_artworks.yaml b/hasura/metadata/databases/punkga-pg/tables/public_artworks.yaml index e5992b93..7fd975ac 100644 --- a/hasura/metadata/databases/punkga-pg/tables/public_artworks.yaml +++ b/hasura/metadata/databases/punkga-pg/tables/public_artworks.yaml @@ -1,6 +1,10 @@ table: name: artworks schema: public +object_relationships: + - name: album + using: + foreign_key_constraint_on: album_id select_permissions: - role: anonymous permission: diff --git a/hasura/migrations/punkga-pg/1723544203165_alter_table_public_albums_add_column_disable/down.sql b/hasura/migrations/punkga-pg/1723544203165_alter_table_public_albums_add_column_disable/down.sql new file mode 100644 index 00000000..a290b567 --- /dev/null +++ b/hasura/migrations/punkga-pg/1723544203165_alter_table_public_albums_add_column_disable/down.sql @@ -0,0 +1,4 @@ +-- Could not auto-generate a down migration. +-- Please write an appropriate down migration for the SQL below: +-- alter table "public"."albums" add column "disable" boolean +-- null default 'false'; diff --git a/hasura/migrations/punkga-pg/1723544203165_alter_table_public_albums_add_column_disable/up.sql b/hasura/migrations/punkga-pg/1723544203165_alter_table_public_albums_add_column_disable/up.sql new file mode 100644 index 00000000..0829805a --- /dev/null +++ b/hasura/migrations/punkga-pg/1723544203165_alter_table_public_albums_add_column_disable/up.sql @@ -0,0 +1,2 @@ +alter table "public"."albums" add column "disable" boolean + null default 'false'; From c4605683f162b54147122abe0ff3b86c8a07a42c Mon Sep 17 00:00:00 2001 From: ThienLK Date: Wed, 14 Aug 2024 11:15:25 +0700 Subject: [PATCH 27/50] add collection ids to update chapter --- src/modules/chapter/chapter.service.ts | 22 +++++++++++++++---- .../chapter/dto/update-chapter-request.dto.ts | 5 ++++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/modules/chapter/chapter.service.ts b/src/modules/chapter/chapter.service.ts index ddfadfc3..86a3e58c 100644 --- a/src/modules/chapter/chapter.service.ts +++ b/src/modules/chapter/chapter.service.ts @@ -208,6 +208,7 @@ export class ChapterService { chapter_type, pushlish_date, status, + collection_ids, } = data; // get chapter info @@ -252,6 +253,15 @@ export class ChapterService { JSON.parse(data.chapter_images) ); + const collectionIdListStr = collection_ids.toString().split(','); + let collectionIdList = Array.from(collectionIdListStr, Number); + const updateResult = await this.addChapterCollection( + chapter_id, + collectionIdList + ); + if (updateResult.errors && updateResult.errors.length > 0) { + return updateResult; + } // upload chapter languages const uploadChapterResult = await this.uploadChapterService.uploadChapterLanguagesFiles({ @@ -400,17 +410,21 @@ export class ChapterService { if (chapterInfor.errors && chapterInfor.errors.length > 0) { return chapterInfor; } - const chapterCollectionAddress = - chapterInfor.chapter_collections.map((c) => { + const chapterCollectionAddress = chapterInfor.chapter_collections.map( + (c) => { return c.chapter_collection.contract_address; - }); + } + ); const walletAddress = await this.chapterGraphql.queryUserAddress(token); if (walletAddress === null) { throw new NotFoundException('wallet address not found'); } if (result.data.chapters[0].chapter_type === 'NFTs only') { - const access = await this.getAccess(walletAddress, chapterCollectionAddress); + const access = await this.getAccess( + walletAddress, + chapterCollectionAddress + ); this.logger.debug(`Access ${JSON.stringify(access)}`); diff --git a/src/modules/chapter/dto/update-chapter-request.dto.ts b/src/modules/chapter/dto/update-chapter-request.dto.ts index 126b2fab..767c6428 100644 --- a/src/modules/chapter/dto/update-chapter-request.dto.ts +++ b/src/modules/chapter/dto/update-chapter-request.dto.ts @@ -1,4 +1,4 @@ -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsNumber, IsString } from 'class-validator'; import { ChapterStatus, ChapterType } from '../../../common/enum'; import { ChapterLanguage } from './create-chapter-request.dto'; @@ -42,6 +42,9 @@ export class UpdateChapterRequestDto { @ApiProperty({ type: ['string'], format: 'binary' }) files: Express.Multer.File[]; + + @ApiPropertyOptional({ type: [Number], example: [1] }) + collection_ids: number[]; } export class UpdateChapterParamDto { From 604a592f6c36de7889db3cd7e19693974b76fcc8 Mon Sep 17 00:00:00 2001 From: harisato Date: Thu, 15 Aug 2024 17:19:50 +0700 Subject: [PATCH 28/50] feat: create & list creator album --- .../down.sql | 4 + .../up.sql | 2 + src/app.module.ts | 2 + src/modules/album/album.controller.ts | 49 ++++ src/modules/album/album.graphql.ts | 213 ++++++++++++++++++ src/modules/album/album.module.ts | 23 ++ src/modules/album/album.service.ts | 130 +++++++++++ .../album/dto/create-album-request.dto.ts | 18 ++ .../album/dto/query-album-query.dto.ts | 9 + src/modules/creator/creator.module.ts | 1 + src/modules/creator/creator.service.ts | 23 +- 11 files changed, 465 insertions(+), 9 deletions(-) create mode 100644 hasura/migrations/punkga-pg/1723714646964_alter_table_public_albums_add_column_thumbnail_url/down.sql create mode 100644 hasura/migrations/punkga-pg/1723714646964_alter_table_public_albums_add_column_thumbnail_url/up.sql create mode 100644 src/modules/album/album.controller.ts create mode 100644 src/modules/album/album.graphql.ts create mode 100644 src/modules/album/album.module.ts create mode 100644 src/modules/album/album.service.ts create mode 100644 src/modules/album/dto/create-album-request.dto.ts create mode 100644 src/modules/album/dto/query-album-query.dto.ts diff --git a/hasura/migrations/punkga-pg/1723714646964_alter_table_public_albums_add_column_thumbnail_url/down.sql b/hasura/migrations/punkga-pg/1723714646964_alter_table_public_albums_add_column_thumbnail_url/down.sql new file mode 100644 index 00000000..5266fff4 --- /dev/null +++ b/hasura/migrations/punkga-pg/1723714646964_alter_table_public_albums_add_column_thumbnail_url/down.sql @@ -0,0 +1,4 @@ +-- Could not auto-generate a down migration. +-- Please write an appropriate down migration for the SQL below: +-- alter table "public"."albums" add column "thumbnail_url" text +-- null; diff --git a/hasura/migrations/punkga-pg/1723714646964_alter_table_public_albums_add_column_thumbnail_url/up.sql b/hasura/migrations/punkga-pg/1723714646964_alter_table_public_albums_add_column_thumbnail_url/up.sql new file mode 100644 index 00000000..cfe27888 --- /dev/null +++ b/hasura/migrations/punkga-pg/1723714646964_alter_table_public_albums_add_column_thumbnail_url/up.sql @@ -0,0 +1,2 @@ +alter table "public"."albums" add column "thumbnail_url" text + null; diff --git a/src/app.module.ts b/src/app.module.ts index b30f20f0..b6a87d47 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -25,6 +25,7 @@ import { LaunchpadModule } from './modules/collection/launchpad.module'; import { ChainModule } from './modules/chain/chain.module'; import { ChainGateWayModule } from './chain-gateway/chain-gateway.module'; import { ArtworkModule } from './modules/artwork/artwork.module'; +import { AlbumModule } from './modules/album/album.module'; @Module({ imports: [ @@ -73,6 +74,7 @@ import { ArtworkModule } from './modules/artwork/artwork.module'; ChainModule, ChainGateWayModule, ArtworkModule, + AlbumModule, ], controllers: [], providers: [ diff --git a/src/modules/album/album.controller.ts b/src/modules/album/album.controller.ts new file mode 100644 index 00000000..2ade2852 --- /dev/null +++ b/src/modules/album/album.controller.ts @@ -0,0 +1,49 @@ +import { + Body, + Controller, + Get, + Post, + Query, + UploadedFiles, + UseGuards, + UseInterceptors, +} from '@nestjs/common'; +import { AnyFilesInterceptor } from '@nestjs/platform-express'; +import { ApiBearerAuth, ApiConsumes, ApiTags } from '@nestjs/swagger'; + +import { AuthGuard } from '../../auth/auth.guard'; +import { Role } from '../../auth/role.enum'; +import { RolesGuard } from '../../auth/role.guard'; +import { Roles } from '../../auth/roles.decorator'; +import { AuthUserInterceptor } from '../../interceptors/auth-user.interceptor'; +import { AlbumService } from './album.service'; +import { CreateAlbumRequestDto } from './dto/create-album-request.dto'; +import { QueryAlbumDto } from './dto/query-album-query.dto'; + +@Controller('album') +@ApiTags('album') +export class AlbumController { + constructor(private readonly albumSvc: AlbumService) {} + + @Get() + @UseGuards(AuthGuard, RolesGuard) + @ApiBearerAuth() + @Roles(Role.Creator) + @UseInterceptors(AuthUserInterceptor) + list(@Query() query: QueryAlbumDto) { + return this.albumSvc.getAll(query); + } + + @UseGuards(AuthGuard, RolesGuard) + @ApiBearerAuth() + @Roles(Role.Creator) + @Post() + @ApiConsumes('multipart/form-data') + @UseInterceptors(AuthUserInterceptor, AnyFilesInterceptor()) + create( + @Body() data: CreateAlbumRequestDto, + @UploadedFiles() files: Array + ) { + return this.albumSvc.create(data, files); + } +} diff --git a/src/modules/album/album.graphql.ts b/src/modules/album/album.graphql.ts new file mode 100644 index 00000000..b1b9893f --- /dev/null +++ b/src/modules/album/album.graphql.ts @@ -0,0 +1,213 @@ +import { ConfigService } from '@nestjs/config'; +import { GraphqlService } from '../graphql/graphql.service'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class AlbumGraphql { + constructor( + private configSvc: ConfigService, + private graphqlSvc: GraphqlService + ) {} + + getListAlbum(variables: any) { + const headers = { + 'x-hasura-admin-secret': this.configSvc.get( + 'graphql.adminSecret' + ), + }; + return this.graphqlSvc.query( + this.configSvc.get('graphql.endpoint'), + '', + `query list_album($creator_id: Int!, $limit: Int = 0, $offset: Int = 20) { + default_album: albums_by_pk(id: 1) { + name + show + disable + created_at + artworks_aggregate(where: {creator_id: {_eq: $creator_id}}) { + aggregate { + count + } + } + } + albums(where: {creator_id: {_eq: $creator_id}}, limit: $limit, offset: $offset) { + name + show + disable + created_at + artworks_aggregate { + aggregate { + count + } + } + } + } + `, + 'list_album', + variables, + headers + ); + } + + insert(variables: any) { + const headers = { + 'x-hasura-admin-secret': this.configSvc.get( + 'graphql.adminSecret' + ), + }; + + return this.graphqlSvc.query( + this.configSvc.get('graphql.endpoint'), + '', + `mutation insert_albums_one($object: albums_insert_input = {}) { + insert_albums_one(object: $object) { + id + } + }`, + 'insert_albums_one', + variables, + headers + ); + } + + insertArtworks(variables: any) { + const headers = { + 'x-hasura-admin-secret': this.configSvc.get( + 'graphql.adminSecret' + ), + }; + + return this.graphqlSvc.query( + this.configSvc.get('graphql.endpoint'), + '', + `mutation insert_artworks($objects: [artworks_insert_input!] = {}) { + insert_artworks(objects: $objects) { + affected_rows + } + }`, + 'insert_artworks', + variables, + headers + ); + } + + update(variables: any) { + const headers = { + 'x-hasura-admin-secret': this.configSvc.get( + 'graphql.adminSecret' + ), + }; + + return this.graphqlSvc.query( + this.configSvc.get('graphql.endpoint'), + '', + `mutation update_albums_by_pk($id: Int!, $data: albums_set_input = {}) { + update_albums_by_pk(pk_columns: {id: $id}, _set: $data) { + updated_at + } + }`, + 'update_albums_by_pk', + variables, + headers + ); + } + + queryByPk(variables: any) { + const headers = { + 'x-hasura-admin-secret': this.configSvc.get( + 'graphql.adminSecret' + ), + }; + return this.graphqlSvc.query( + this.configSvc.get('graphql.endpoint'), + '', + `query launchpad_by_pk($id: Int!) { + launchpad_by_pk(id: $id) { + status + contract_address + fund + id + slug + launchpad_creator { + avatar_url + bio + name + pen_name + slug + wallet_address + } + launchpad_i18ns { + id + language_id + data + } + featured_images + creator_id + } + }`, + 'launchpad_by_pk', + variables, + headers + ); + } + + queryBySlug(variables: any) { + const headers = { + 'x-hasura-admin-secret': this.configSvc.get( + 'graphql.adminSecret' + ), + }; + return this.graphqlSvc.query( + this.configSvc.get('graphql.endpoint'), + '', + `query launchpad($slug: String!) { + launchpad(where: {slug: {_eq: $slug}}) { + status + contract_address + fund + id + slug + launchpad_creator { + avatar_url + bio + name + pen_name + slug + wallet_address + } + launchpad_i18ns { + id + language_id + data + } + featured_images + creator_id + } + }`, + 'launchpad', + variables, + headers + ); + } + + async insertI18n(variables: any) { + const headers = { + 'x-hasura-admin-secret': this.configSvc.get( + 'graphql.adminSecret' + ), + }; + console.log(variables); + return this.graphqlSvc.query( + this.configSvc.get('graphql.endpoint'), + '', + `mutation insert_i18n($objects: [i18n_insert_input!] = {}) { + insert_i18n(on_conflict: {constraint: i18n_launchpad_id_language_id_key, update_columns: data}, objects: $objects) { + affected_rows + } + }`, + 'insert_i18n', + variables, + headers + ); + } +} diff --git a/src/modules/album/album.module.ts b/src/modules/album/album.module.ts new file mode 100644 index 00000000..8b59475c --- /dev/null +++ b/src/modules/album/album.module.ts @@ -0,0 +1,23 @@ +import { Module } from '@nestjs/common'; +import { GraphqlModule } from '../graphql/graphql.module'; +import { AlbumGraphql } from './album.graphql'; +import { FilesModule } from '../files/files.module'; +import { JwtModule } from '@nestjs/jwt'; +import { UserWalletModule } from '../user-wallet/user-wallet.module'; +import { AlbumService } from './album.service'; +import { AlbumController } from './album.controller'; +import { CreatorModule } from '../creator/creator.module'; + +@Module({ + imports: [ + GraphqlModule, + FilesModule, + JwtModule, + UserWalletModule, + CreatorModule, + ], + providers: [AlbumService, AlbumGraphql], + controllers: [AlbumController], + exports: [], +}) +export class AlbumModule {} diff --git a/src/modules/album/album.service.ts b/src/modules/album/album.service.ts new file mode 100644 index 00000000..ac2b970b --- /dev/null +++ b/src/modules/album/album.service.ts @@ -0,0 +1,130 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +import { CreatorService } from '../creator/creator.service'; +import { FilesService } from '../files/files.service'; +import { UserWalletService } from '../user-wallet/user-wallet.service'; +import { AlbumGraphql } from './album.graphql'; +import { CreateAlbumRequestDto } from './dto/create-album-request.dto'; +import { QueryAlbumDto } from './dto/query-album-query.dto'; + +@Injectable() +export class AlbumService { + private readonly logger = new Logger(AlbumService.name); + + constructor( + private configService: ConfigService, + private albumGraphql: AlbumGraphql, + private fileService: FilesService, + private userWalletService: UserWalletService, + private creatorService: CreatorService + ) {} + + async create(data: CreateAlbumRequestDto, files: Array) { + try { + const creatorId = await this.creatorService.getCreatorIdAuthToken(); + + const { name, description, show } = data; + // insert db + const result = await this.albumGraphql.insert({ + object: { + name, + description, + show, + creator_id: creatorId, + }, + }); + + const albumId = result.data.insert_albums_one.id; + + let thumbnail_url = ''; + const artworks = []; + const s3SubFolder = + this.configService.get('aws.s3SubFolder') || 'images'; + const s3Path = `${s3SubFolder}/creators/${creatorId}/albums/${albumId}`; + + // map files + const uploadPromises = files.map((file) => { + if (file.mimetype.includes('image')) { + return this.fileService.uploadToS3( + `${s3Path}/${file.fieldname}-${file.originalname}`, + file.buffer, + file.mimetype + ); + } + + return undefined; + }); + + const uploadResult = await Promise.all(uploadPromises); + files.forEach((file, index) => { + // if have upload result + if (uploadResult[index]) { + // throw error if upload failed + if (uploadResult[index].$metadata.httpStatusCode !== 200) + throw new Error('Upload fail' + JSON.stringify(result)); + + // build uploaded url + const uploadedUrl = new URL( + `${s3Path}/${file.fieldname}-${file.originalname}`, + this.configService.get('aws.queryEndpoint') + ).href; + + switch (file.fieldname) { + case 'thumbnail': + thumbnail_url = uploadedUrl; + break; + case 'artworks': + artworks.push(uploadedUrl); + break; + default: + break; + } + } + }); + + // update + if (thumbnail_url !== '') { + const updateResult = await this.albumGraphql.update({ + id: albumId, + data: { + thumbnail_url, + }, + }); + + if (updateResult.errors) return updateResult; + } + + const artworkData = artworks.map((artworkUrl) => ({ + album_id: albumId, + url: artworkUrl, + creator_id: creatorId, + })); + // insert + const insertArtworksResult = await this.albumGraphql.insertArtworks({ + objects: artworkData, + }); + + if (insertArtworksResult.errors) return insertArtworksResult; + return result; + } catch (error) { + return { + errors: [ + { + message: error.message, + }, + ], + }; + } + } + + async getAll(query: QueryAlbumDto) { + const creatorId = await this.creatorService.getCreatorIdAuthToken(); + const { limit, offset } = query; + return this.albumGraphql.getListAlbum({ + creator_id: creatorId, + limit, + offset, + }); + } +} diff --git a/src/modules/album/dto/create-album-request.dto.ts b/src/modules/album/dto/create-album-request.dto.ts new file mode 100644 index 00000000..4bd0085a --- /dev/null +++ b/src/modules/album/dto/create-album-request.dto.ts @@ -0,0 +1,18 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; + +export class CreateAlbumRequestDto { + @ApiProperty() + name: string; + + @ApiProperty() + description: string; + + @ApiProperty({ type: 'string', format: 'binary' }) + thumbnail: Express.Multer.File; + + @ApiProperty() + show: boolean; + + @ApiProperty({ type: ['string'], format: 'binary' }) + artworks: Express.Multer.File[]; +} diff --git a/src/modules/album/dto/query-album-query.dto.ts b/src/modules/album/dto/query-album-query.dto.ts new file mode 100644 index 00000000..67d2551d --- /dev/null +++ b/src/modules/album/dto/query-album-query.dto.ts @@ -0,0 +1,9 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; + +export class QueryAlbumDto { + @ApiPropertyOptional() + limit: string; + + @ApiPropertyOptional() + offset: string; +} diff --git a/src/modules/creator/creator.module.ts b/src/modules/creator/creator.module.ts index 0ea99e59..ef97e041 100644 --- a/src/modules/creator/creator.module.ts +++ b/src/modules/creator/creator.module.ts @@ -10,5 +10,6 @@ import { CreatorGraphql } from './creator.graphql'; imports: [JwtModule, FilesModule, GraphqlModule], providers: [CreatorService, CreatorGraphql], controllers: [CreatorController], + exports: [CreatorService], }) export class CreatorModule {} diff --git a/src/modules/creator/creator.service.ts b/src/modules/creator/creator.service.ts index 79fdee5b..45e752f6 100644 --- a/src/modules/creator/creator.service.ts +++ b/src/modules/creator/creator.service.ts @@ -17,6 +17,17 @@ export class CreatorService { private creatorGraphql: CreatorGraphql ) {} + async getCreatorIdAuthToken(): Promise { + const { userId } = ContextProvider.getAuthUser(); + const result = await this.creatorGraphql.queryCreatorIdByUserId({ + id: userId, + }); + if (result.errors) return result; + + const creatorId = result.data.authorizer_users_by_pk.creator.id; + return creatorId; + } + async getCreator() { const { userId, token } = ContextProvider.getAuthUser(); const result = await this.creatorGraphql.queryCreatorIdByUserId({ @@ -24,8 +35,8 @@ export class CreatorService { }); if (result.errors) return result; - const creatorId = result.data.authorizer_users_by_pk.creator.id; - return this.get(creatorId); + const creatorId = await this.getCreatorIdAuthToken(); + return this.get(creatorId.toString()); } async get(keyword: string) { @@ -100,13 +111,7 @@ export class CreatorService { data: UpdateCreatorRequestDto, files: Array ) { - const { userId, token } = ContextProvider.getAuthUser(); - const result = await this.creatorGraphql.queryCreatorIdByUserId({ - id: userId, - }); - if (result.errors) return result; - - const creatorId = result.data.authorizer_users_by_pk.creator.id; + const creatorId = await this.getCreatorIdAuthToken(); return this.update(creatorId, data, files); } From 6be8dbfc92891e6e88557d6c19c1dee46bc940ab Mon Sep 17 00:00:00 2001 From: harisato Date: Fri, 16 Aug 2024 10:02:48 +0700 Subject: [PATCH 29/50] fix: list album api --- src/modules/album/album.graphql.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/album/album.graphql.ts b/src/modules/album/album.graphql.ts index b1b9893f..6232e74d 100644 --- a/src/modules/album/album.graphql.ts +++ b/src/modules/album/album.graphql.ts @@ -18,7 +18,7 @@ export class AlbumGraphql { return this.graphqlSvc.query( this.configSvc.get('graphql.endpoint'), '', - `query list_album($creator_id: Int!, $limit: Int = 0, $offset: Int = 20) { + `query list_album($creator_id: Int!, $limit: Int = 20, $offset: Int = 0) { default_album: albums_by_pk(id: 1) { name show From 1125ade5f452783f9e05ca66249c60a8ffe3f8f3 Mon Sep 17 00:00:00 2001 From: ThienLK Date: Mon, 12 Aug 2024 15:20:29 +0700 Subject: [PATCH 30/50] add status --- hasura/metadata/query_collections.yaml | 71 ++++++-------------------- 1 file changed, 16 insertions(+), 55 deletions(-) diff --git a/hasura/metadata/query_collections.yaml b/hasura/metadata/query_collections.yaml index c1e3a2fd..cba4f936 100644 --- a/hasura/metadata/query_collections.yaml +++ b/hasura/metadata/query_collections.yaml @@ -1229,6 +1229,21 @@ } } } + - name: Public - Get manga by creator and contest + query: | + query manga ($contest_id: Int!, $id: Int!, $limit: Int = 100, $offset: Int = 0) { + manga(where: {contest_id:{_eq:$contest_id},manga_creators:{creator_id:{_eq:$id}}}, limit: $limit, offset: $offset) { + banner + id + slug + poster + } + manga_aggregate(where: {contest_id:{_eq:$contest_id},manga_creators:{creator_id:{_eq:$id}}}, limit: $limit, offset: $offset) { + aggregate { + count + } + } + } - name: Public - Get artwork by creator and contest query: | query artwork ($contest_id: Int!, $id: Int!, $limit: Int = 100, $offset: Int = 0) { @@ -1427,64 +1442,10 @@ id } slug + status updated_at created_at } } } } - - name: Public - Get manga by creator and contest - query: | - query manga ($contest_id: Int!, $id: Int!, $limit: Int = 100, $offset: Int = 0) { - manga(where: {contest_id:{_eq:$contest_id},manga_creators:{creator_id:{_eq:$id}}}, limit: $limit, offset: $offset) { - id - slug - latest_published - manga_total_views { - views - } - manga_total_likes { - likes - } - manga_creators { - creator { - id - slug - name - pen_name - isActive - } - } - status - chapters(limit: 1, order_by: {chapter_number:desc}, where: {status:{_eq:"Published"}}) { - id - chapter_number - pushlish_date - status - updated_at - } - manga_tags(limit: 5) { - tag { - id - tag_languages { - language_id - tag_id - value - } - } - } - manga_languages { - title - description - is_main_language - language_id - } - banner - poster - } - manga_aggregate(where: {contest_id:{_eq:$contest_id},manga_creators:{creator_id:{_eq:$id}}}, limit: $limit, offset: $offset) { - aggregate { - count - } - } - } From b433724662133cbe0de12499d92fcaefca9c8365 Mon Sep 17 00:00:00 2001 From: ThienLK Date: Mon, 12 Aug 2024 15:34:28 +0700 Subject: [PATCH 31/50] edit delete chapter collection --- hasura/metadata/query_collections.yaml | 14 +++++++------- hasura/metadata/rest_endpoints.yaml | 18 +++++++++--------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/hasura/metadata/query_collections.yaml b/hasura/metadata/query_collections.yaml index cba4f936..c050a800 100644 --- a/hasura/metadata/query_collections.yaml +++ b/hasura/metadata/query_collections.yaml @@ -1290,13 +1290,6 @@ id } } - - name: Admin - Delete Chapter Collection - query: | - mutation delete_chapter_collection_by_pk ($id: Int!) { - delete_chapter_collection_by_pk(id: $id) { - id - } - } - name: Admin - Insert Chapter Collection query: | mutation MyMutation ($objects: [chapter_collection_insert_input!] = {}) { @@ -1449,3 +1442,10 @@ } } } + - name: Admin - Delete Chapter Collection + query: | + mutation delete_chapter_collection_by_pk ($id: Int!) { + delete_chapter_collection_by_pk(id: $id) { + id + } + } diff --git a/hasura/metadata/rest_endpoints.yaml b/hasura/metadata/rest_endpoints.yaml index 48dd23cc..7d5a8866 100644 --- a/hasura/metadata/rest_endpoints.yaml +++ b/hasura/metadata/rest_endpoints.yaml @@ -34,15 +34,6 @@ - GET name: Admin - Query campaign detail url: admin/campaign/:id -- comment: Delete Chapter Collection - definition: - query: - collection_name: allowed-queries - query_name: Admin - Delete Chapter Collection - methods: - - DELETE - name: Admin - Delete Chapter Collection - url: admin/chapter-collection - comment: "Insert Chapter Collection\nchapter_collection_insert_input:{\nchapter_id: Int!, \nlaunchpad_id: Int!\n}" definition: query: @@ -52,6 +43,15 @@ - POST name: Admin - Insert Chapter Collection url: admin/chapter-collection +- comment: Delete Chapter Collection + definition: + query: + collection_name: allowed-queries + query_name: Admin - Delete Chapter Collection + methods: + - DELETE + name: Admin - Delete Chapter Collection + url: admin/chapter-collection/:id - comment: "" definition: query: From 1d31157d7a303418b298bc279d8f3b1c0b97d75c Mon Sep 17 00:00:00 2001 From: ThienLK Date: Mon, 12 Aug 2024 16:10:44 +0700 Subject: [PATCH 32/50] fix manga detail --- hasura/metadata/query_collections.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/hasura/metadata/query_collections.yaml b/hasura/metadata/query_collections.yaml index c050a800..b47353b4 100644 --- a/hasura/metadata/query_collections.yaml +++ b/hasura/metadata/query_collections.yaml @@ -1360,9 +1360,16 @@ } } } + - name: Admin - Delete Chapter Collection + query: | + mutation delete_chapter_collection_by_pk ($id: Int!) { + delete_chapter_collection_by_pk(id: $id) { + id + } + } - name: Admin - Get manga detail query: | - query GetMangaReadingDetail ($id: Int = 1) { + query GetMangaReadingDetail ($id: Int = 28) { manga_by_pk(id: $id) { id poster @@ -1428,7 +1435,6 @@ slug id } - id launchpad_i18ns { data(path: "name") language_id @@ -1439,13 +1445,7 @@ updated_at created_at } + id } } } - - name: Admin - Delete Chapter Collection - query: | - mutation delete_chapter_collection_by_pk ($id: Int!) { - delete_chapter_collection_by_pk(id: $id) { - id - } - } From a3c09f5d5fa112606a0f094b3121e6bef813d8d8 Mon Sep 17 00:00:00 2001 From: ThienLK Date: Mon, 12 Aug 2024 16:35:35 +0700 Subject: [PATCH 33/50] fix admin get chapter detail --- hasura/metadata/query_collections.yaml | 96 +++++++++++--------------- 1 file changed, 39 insertions(+), 57 deletions(-) diff --git a/hasura/metadata/query_collections.yaml b/hasura/metadata/query_collections.yaml index b47353b4..39012403 100644 --- a/hasura/metadata/query_collections.yaml +++ b/hasura/metadata/query_collections.yaml @@ -1303,63 +1303,6 @@ } } } - - name: Admin - Get chapter detail - query: | - query GetChapterReadingDetail ($slug: String, $chapter_number: Int = 1, $user_id: bpchar = "") { - chapters(where: {_and:{chapter_number:{_eq:$chapter_number},manga:{_and:{slug:{_eq:$slug},_or:[{status:{_ilike:"On-going"}},{status:{_eq:"Finished"}}]}}}}) { - id - chapter_number - chapter_name - chapter_type - thumbnail_url - status - pushlish_date - chapter_languages(where: {chapter:{status:{_eq:"Published"}}}) { - language_id - detail - } - comments: social_activities_aggregate { - aggregate { - count - } - } - views - chapters_likes_aggregate { - aggregate { - count - } - } - chapters_likes(where: {user_id:{_eq:$user_id}}) { - id - created_at - user_id - chapter_id - } - chapter_collections { - id - launchpad_id - created_at - updated_at - chapter_collection { - contract_address - created_at - creator_id - id - launchpad_creator { - avatar_url - name - slug - } - slug - status - launchpad_i18ns { - data(path: "name") - language_id - } - } - } - } - } - name: Admin - Delete Chapter Collection query: | mutation delete_chapter_collection_by_pk ($id: Int!) { @@ -1449,3 +1392,42 @@ } } } + - name: Admin - Get chapter detail + query: | + query GetChapterDetail ($manga_id: Int = 18, $chapter_number: Int = 1) { + chapters(where: {_and:{chapter_number:{_eq:$chapter_number},manga:{id:{_eq:$manga_id}}}}) { + id + chapter_number + chapter_name + chapter_type + thumbnail_url + status + pushlish_date + chapter_languages { + language_id + detail + } + chapter_collections { + id + chapter_collection { + contract_address + launchpad_creator { + name + slug + id + } + launchpad_i18ns { + data(path: "name") + language_id + id + } + slug + status + updated_at + created_at + } + chapter_id + launchpad_id + } + } + } From a67bce3bf489a0915b6e18c31837275de31eda31 Mon Sep 17 00:00:00 2001 From: ThienLK Date: Tue, 13 Aug 2024 14:44:59 +0700 Subject: [PATCH 34/50] update create chapter --- hasura/metadata/query_collections.yaml | 26 +++++++------- src/modules/chapter/chapter.graphql.ts | 28 ++++++++++++++- src/modules/chapter/chapter.service.ts | 36 ++++++++++++++++++- .../chapter/dto/create-chapter-request.dto.ts | 3 ++ 4 files changed, 78 insertions(+), 15 deletions(-) diff --git a/hasura/metadata/query_collections.yaml b/hasura/metadata/query_collections.yaml index 39012403..3560254c 100644 --- a/hasura/metadata/query_collections.yaml +++ b/hasura/metadata/query_collections.yaml @@ -1290,19 +1290,6 @@ id } } - - name: Admin - Insert Chapter Collection - query: | - mutation MyMutation ($objects: [chapter_collection_insert_input!] = {}) { - insert_chapter_collection(objects: $objects, on_conflict: {constraint:chapter_collection_chapter_id_launchpad_id_key,update_columns:chapter_id}) { - affected_rows - returning { - chapter_id - created_at - id - launchpad_id - } - } - } - name: Admin - Delete Chapter Collection query: | mutation delete_chapter_collection_by_pk ($id: Int!) { @@ -1431,3 +1418,16 @@ } } } + - name: Admin - Insert Chapter Collection + query: | + mutation insert_chapter_collection ($objects: [chapter_collection_insert_input!] = {}) { + insert_chapter_collection(objects: $objects, on_conflict: {constraint:chapter_collection_chapter_id_launchpad_id_key,update_columns:chapter_id}) { + affected_rows + returning { + chapter_id + created_at + id + launchpad_id + } + } + } diff --git a/src/modules/chapter/chapter.graphql.ts b/src/modules/chapter/chapter.graphql.ts index 950fd553..a78aecba 100644 --- a/src/modules/chapter/chapter.graphql.ts +++ b/src/modules/chapter/chapter.graphql.ts @@ -15,7 +15,7 @@ export class ChapterGraphql { token, `query GetChapterInfo($id: Int!) { chapters_by_pk(id: $id) { - manga_id + chapter_id chapter_number thumbnail_url chapter_languages { @@ -125,4 +125,30 @@ export class ChapterGraphql { } ); } + + createChapterCollection(variables: any) { + const headers = { + 'x-hasura-admin-secret': this.configService.get( + 'graphql.adminSecret' + ), + }; + return this.graphqlSvc.query( + this.configService.get('graphql.endpoint'), + "", + `mutation insert_chapter_collection($objects: [chapter_collection_insert_input!] = {}) { + insert_chapter_collection(objects: $objects, on_conflict: {constraint: chapter_collection_chapter_id_launchpad_id_key, update_columns: chapter_id}) { + affected_rows + returning { + chapter_id + created_at + id + launchpad_id + } + } + }`, + 'insert_chapter_collection', + variables, + headers + ); + } } diff --git a/src/modules/chapter/chapter.service.ts b/src/modules/chapter/chapter.service.ts index 1f011976..b1741e6a 100644 --- a/src/modules/chapter/chapter.service.ts +++ b/src/modules/chapter/chapter.service.ts @@ -92,6 +92,7 @@ export class ChapterService { chapter_type, pushlish_date, status, + collection_ids, } = data; const chapter_images = plainToInstance( ChapterImage, @@ -171,7 +172,15 @@ export class ChapterService { return updateResult; } } - + const collectionIdListStr = collection_ids.toString().split(','); + let collectionIdList = Array.from(collectionIdListStr,Number) + const updateResult = await this.addChapterCollection( + chapterId, + collectionIdList + ); + if (updateResult.errors && updateResult.errors.length > 0) { + return updateResult; + } return result.data; } catch (errors) { return { @@ -403,4 +412,29 @@ export class ChapterService { }; } } + async addChapterCollection(chapterId: Number, collectionIdList: number[]) { + try { + // const { token } = ContextProvider.getAuthUser(); + const objects = []; + // update chapter collection in DB + await Promise.all( + collectionIdList.map((collectionId) => { + const o = { + chapter_id: chapterId, + launchpad_id: collectionId, + }; + objects.push(o); + }) + ); + const updateResponse = await this.chapterGraphql.createChapterCollection({ + objects, + }); + + return updateResponse; + } catch (errors) { + return { + errors, + }; + } + } } diff --git a/src/modules/chapter/dto/create-chapter-request.dto.ts b/src/modules/chapter/dto/create-chapter-request.dto.ts index 21909e53..8a97ee23 100644 --- a/src/modules/chapter/dto/create-chapter-request.dto.ts +++ b/src/modules/chapter/dto/create-chapter-request.dto.ts @@ -47,4 +47,7 @@ export class CreateChapterRequestDto { @ApiPropertyOptional({ type: ['string'], format: 'binary' }) files: Express.Multer.File[]; + + @ApiPropertyOptional({ type: [Number], example: [1] }) + collection_ids: number[]; } From c44f07d9c19e22b1905e931250ea0350bdf09735 Mon Sep 17 00:00:00 2001 From: ThienLK Date: Tue, 13 Aug 2024 17:08:12 +0700 Subject: [PATCH 35/50] add verify when view chapter --- src/modules/chapter/chapter.controller.ts | 2 +- src/modules/chapter/chapter.graphql.ts | 109 +++++++++++++++++++++- src/modules/chapter/chapter.service.ts | 65 +++++++++++-- 3 files changed, 167 insertions(+), 9 deletions(-) diff --git a/src/modules/chapter/chapter.controller.ts b/src/modules/chapter/chapter.controller.ts index a572b1e9..579deaba 100644 --- a/src/modules/chapter/chapter.controller.ts +++ b/src/modules/chapter/chapter.controller.ts @@ -76,7 +76,7 @@ export class ChapterController { @UseGuards(AuthGuard, RolesGuard) @ApiBearerAuth() - @Roles(Role.User) + @Roles(Role.User,Role.Admin) @Get(':chapterId/protected') @UseInterceptors(AuthUserInterceptor) view(@Param() data: ViewProtectedChapterRequestDto) { diff --git a/src/modules/chapter/chapter.graphql.ts b/src/modules/chapter/chapter.graphql.ts index a78aecba..928acedb 100644 --- a/src/modules/chapter/chapter.graphql.ts +++ b/src/modules/chapter/chapter.graphql.ts @@ -1,6 +1,6 @@ import { ConfigService } from '@nestjs/config'; import { GraphqlService } from '../graphql/graphql.service'; -import { Injectable } from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; @Injectable() export class ChapterGraphql { @@ -15,13 +15,18 @@ export class ChapterGraphql { token, `query GetChapterInfo($id: Int!) { chapters_by_pk(id: $id) { - chapter_id + id chapter_number thumbnail_url chapter_languages { detail language_id } + chapter_collections { + chapter_collection { + contract_address + } + } } }`, 'GetChapterInfo', @@ -151,4 +156,104 @@ export class ChapterGraphql { headers ); } + + queryErc721Tokens(token: string, network: string, variables: any) { + return this.graphqlSvc.query( + this.configService.get('horosope.endpoint'), + token, + `query QueryCw721Tokens($owner: String = "", $smart_contracts: [String!] = "") { + ${network} { + erc721_contract(where: {evm_smart_contract: {address: {_in: $smart_contracts}}}) { + evm_smart_contract { + id + } + erc721_tokens(where: {owner: {_eq: $owner}}) { + owner + token_id + } + } + } + }`, + 'QueryCw721Tokens', + variables + ); + } + + async queryUserAddress(token: string): Promise { + const result = await this.graphqlSvc.query( + this.configService.get('graphql.endpoint'), + token, + `query GetUserProfile { + authorizer_users(limit: 1) { + id + email + email_verified_at + bio + birthdate + gender + active_wallet_address: active_evm_address + wallet_address + nickname + picture + signup_methods + levels { + xp + level + user_level_chain { + id + name + punkga_config + } + } + authorizer_users_user_wallet { + address + } + user_quests_aggregate { + aggregate { + count + } + } + user_quests(order_by: {created_at:desc}, limit: 20) { + created_at + status + user_quest_rewards { + tx_hash + } + quest { + id + name + quests_campaign { + campaign_chain { + punkga_config + } + } + quests_i18n { + id + quest_id + language_id + data + i18n_language { + id + description + icon + is_main + symbol + } + } + reward + } + } + } + } + `, + 'GetUserProfile', + {} + ); + + if (result.data.authorizer_users[0]?.active_wallet_address) { + return result.data.authorizer_users[0]?.active_wallet_address; + } else { + throw new NotFoundException('wallet address not found'); + } + } } diff --git a/src/modules/chapter/chapter.service.ts b/src/modules/chapter/chapter.service.ts index b1741e6a..ddfadfc3 100644 --- a/src/modules/chapter/chapter.service.ts +++ b/src/modules/chapter/chapter.service.ts @@ -4,7 +4,7 @@ import * as _ from 'lodash'; import md5 from 'md5'; import rimraf from 'rimraf'; -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable, Logger, NotFoundException } from '@nestjs/common'; import { ContextProvider } from '../../providers/contex.provider'; import { MangaService } from '../manga/manga.service'; @@ -13,6 +13,7 @@ import { ChapterImage, CreateChapterRequestDto, } from './dto/create-chapter-request.dto'; +import { ConfigService } from '@nestjs/config'; import { UpdateChapterImage, UpdateChapterParamDto, @@ -30,7 +31,8 @@ export class ChapterService { constructor( private mangaService: MangaService, private chapterGraphql: ChapterGraphql, - private uploadChapterService: UploadChapterService + private uploadChapterService: UploadChapterService, + private configSvc: ConfigService ) {} async upload(data: UploadInputDto, file: Express.Multer.File) { @@ -173,7 +175,7 @@ export class ChapterService { } } const collectionIdListStr = collection_ids.toString().split(','); - let collectionIdList = Array.from(collectionIdListStr,Number) + let collectionIdList = Array.from(collectionIdListStr, Number); const updateResult = await this.addChapterCollection( chapterId, collectionIdList @@ -391,11 +393,24 @@ export class ChapterService { if (result.errors && result.errors.length > 0) { return result; } + const chapterInfor = await this.chapterGraphql.getChapterInfo( + token, + chapterId + ); + if (chapterInfor.errors && chapterInfor.errors.length > 0) { + return chapterInfor; + } + const chapterCollectionAddress = + chapterInfor.chapter_collections.map((c) => { + return c.chapter_collection.contract_address; + }); + const walletAddress = await this.chapterGraphql.queryUserAddress(token); + if (walletAddress === null) { + throw new NotFoundException('wallet address not found'); + } if (result.data.chapters[0].chapter_type === 'NFTs only') { - const access = await this.mangaService.getAccess( - result.data.chapters[0].manga_id - ); + const access = await this.getAccess(walletAddress, chapterCollectionAddress); this.logger.debug(`Access ${JSON.stringify(access)}`); @@ -412,6 +427,7 @@ export class ChapterService { }; } } + async addChapterCollection(chapterId: Number, collectionIdList: number[]) { try { // const { token } = ContextProvider.getAuthUser(); @@ -437,4 +453,41 @@ export class ChapterService { }; } } + + async getAccess(walletAddress: string, contractAddresses: string[]) { + try { + const network = this.configSvc.get('horosope.network'); + let nft = false; + const { token } = ContextProvider.getAuthUser(); + + // check data on horoscope + const getCw721TokenResult = await this.chapterGraphql.queryErc721Tokens( + token, + network, + { + smart_contracts: contractAddresses.map((address) => + address.toLowerCase() + ), + owner: walletAddress.toLowerCase(), + } + ); + + if ( + getCw721TokenResult.data[`${network}`].erc721_contract.length > 0 && + getCw721TokenResult.data[`${network}`].erc721_contract.find( + (contract) => contract.erc721_tokens.length > 0 + ) + ) { + nft = true; + } + + return { + nft, + }; + } catch (errors) { + return { + errors, + }; + } + } } From 97bfde85e2efbfe2524a7779b6cd4d504e2f5d29 Mon Sep 17 00:00:00 2001 From: ThienLK Date: Wed, 14 Aug 2024 11:15:25 +0700 Subject: [PATCH 36/50] add collection ids to update chapter --- src/modules/chapter/chapter.service.ts | 22 +++++++++++++++---- .../chapter/dto/update-chapter-request.dto.ts | 5 ++++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/modules/chapter/chapter.service.ts b/src/modules/chapter/chapter.service.ts index ddfadfc3..86a3e58c 100644 --- a/src/modules/chapter/chapter.service.ts +++ b/src/modules/chapter/chapter.service.ts @@ -208,6 +208,7 @@ export class ChapterService { chapter_type, pushlish_date, status, + collection_ids, } = data; // get chapter info @@ -252,6 +253,15 @@ export class ChapterService { JSON.parse(data.chapter_images) ); + const collectionIdListStr = collection_ids.toString().split(','); + let collectionIdList = Array.from(collectionIdListStr, Number); + const updateResult = await this.addChapterCollection( + chapter_id, + collectionIdList + ); + if (updateResult.errors && updateResult.errors.length > 0) { + return updateResult; + } // upload chapter languages const uploadChapterResult = await this.uploadChapterService.uploadChapterLanguagesFiles({ @@ -400,17 +410,21 @@ export class ChapterService { if (chapterInfor.errors && chapterInfor.errors.length > 0) { return chapterInfor; } - const chapterCollectionAddress = - chapterInfor.chapter_collections.map((c) => { + const chapterCollectionAddress = chapterInfor.chapter_collections.map( + (c) => { return c.chapter_collection.contract_address; - }); + } + ); const walletAddress = await this.chapterGraphql.queryUserAddress(token); if (walletAddress === null) { throw new NotFoundException('wallet address not found'); } if (result.data.chapters[0].chapter_type === 'NFTs only') { - const access = await this.getAccess(walletAddress, chapterCollectionAddress); + const access = await this.getAccess( + walletAddress, + chapterCollectionAddress + ); this.logger.debug(`Access ${JSON.stringify(access)}`); diff --git a/src/modules/chapter/dto/update-chapter-request.dto.ts b/src/modules/chapter/dto/update-chapter-request.dto.ts index 126b2fab..767c6428 100644 --- a/src/modules/chapter/dto/update-chapter-request.dto.ts +++ b/src/modules/chapter/dto/update-chapter-request.dto.ts @@ -1,4 +1,4 @@ -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsNumber, IsString } from 'class-validator'; import { ChapterStatus, ChapterType } from '../../../common/enum'; import { ChapterLanguage } from './create-chapter-request.dto'; @@ -42,6 +42,9 @@ export class UpdateChapterRequestDto { @ApiProperty({ type: ['string'], format: 'binary' }) files: Express.Multer.File[]; + + @ApiPropertyOptional({ type: [Number], example: [1] }) + collection_ids: number[]; } export class UpdateChapterParamDto { From f1b5c3c315fd7c294bd4efc1ebbaa9bcbf77689d Mon Sep 17 00:00:00 2001 From: harisato Date: Fri, 16 Aug 2024 14:03:08 +0700 Subject: [PATCH 37/50] feat: detail album api --- .../down.sql | 4 + .../up.sql | 2 + src/modules/album/album.controller.ts | 21 ++- src/modules/album/album.graphql.ts | 133 +++++------------- src/modules/album/album.service.ts | 8 ++ .../album/dto/detail-album-request.dto.ts | 6 + 6 files changed, 74 insertions(+), 100 deletions(-) create mode 100644 hasura/migrations/punkga-pg/1723778753471_alter_table_public_artworks_add_column_name/down.sql create mode 100644 hasura/migrations/punkga-pg/1723778753471_alter_table_public_artworks_add_column_name/up.sql create mode 100644 src/modules/album/dto/detail-album-request.dto.ts diff --git a/hasura/migrations/punkga-pg/1723778753471_alter_table_public_artworks_add_column_name/down.sql b/hasura/migrations/punkga-pg/1723778753471_alter_table_public_artworks_add_column_name/down.sql new file mode 100644 index 00000000..ec08fc6d --- /dev/null +++ b/hasura/migrations/punkga-pg/1723778753471_alter_table_public_artworks_add_column_name/down.sql @@ -0,0 +1,4 @@ +-- Could not auto-generate a down migration. +-- Please write an appropriate down migration for the SQL below: +-- alter table "public"."artworks" add column "name" text +-- null; diff --git a/hasura/migrations/punkga-pg/1723778753471_alter_table_public_artworks_add_column_name/up.sql b/hasura/migrations/punkga-pg/1723778753471_alter_table_public_artworks_add_column_name/up.sql new file mode 100644 index 00000000..f4322a12 --- /dev/null +++ b/hasura/migrations/punkga-pg/1723778753471_alter_table_public_artworks_add_column_name/up.sql @@ -0,0 +1,2 @@ +alter table "public"."artworks" add column "name" text + null; diff --git a/src/modules/album/album.controller.ts b/src/modules/album/album.controller.ts index 2ade2852..169e42e1 100644 --- a/src/modules/album/album.controller.ts +++ b/src/modules/album/album.controller.ts @@ -2,6 +2,7 @@ import { Body, Controller, Get, + Param, Post, Query, UploadedFiles, @@ -9,7 +10,12 @@ import { UseInterceptors, } from '@nestjs/common'; import { AnyFilesInterceptor } from '@nestjs/platform-express'; -import { ApiBearerAuth, ApiConsumes, ApiTags } from '@nestjs/swagger'; +import { + ApiBearerAuth, + ApiConsumes, + ApiOperation, + ApiTags, +} from '@nestjs/swagger'; import { AuthGuard } from '../../auth/auth.guard'; import { Role } from '../../auth/role.enum'; @@ -19,6 +25,7 @@ import { AuthUserInterceptor } from '../../interceptors/auth-user.interceptor'; import { AlbumService } from './album.service'; import { CreateAlbumRequestDto } from './dto/create-album-request.dto'; import { QueryAlbumDto } from './dto/query-album-query.dto'; +import { DetailAlbumParamDto } from './dto/detail-album-request.dto'; @Controller('album') @ApiTags('album') @@ -29,15 +36,27 @@ export class AlbumController { @UseGuards(AuthGuard, RolesGuard) @ApiBearerAuth() @Roles(Role.Creator) + @ApiOperation({ summary: 'list album - creator role' }) @UseInterceptors(AuthUserInterceptor) list(@Query() query: QueryAlbumDto) { return this.albumSvc.getAll(query); } + @Get(':id') + @UseGuards(AuthGuard, RolesGuard) + @ApiBearerAuth() + @Roles(Role.Creator) + @ApiOperation({ summary: 'album detail - creator role' }) + @UseInterceptors(AuthUserInterceptor) + detailAlbumDetail(@Param() param: DetailAlbumParamDto) { + return this.albumSvc.getDetail(param.id); + } + @UseGuards(AuthGuard, RolesGuard) @ApiBearerAuth() @Roles(Role.Creator) @Post() + @ApiOperation({ summary: 'create album - creator role' }) @ApiConsumes('multipart/form-data') @UseInterceptors(AuthUserInterceptor, AnyFilesInterceptor()) create( diff --git a/src/modules/album/album.graphql.ts b/src/modules/album/album.graphql.ts index 6232e74d..1c870a06 100644 --- a/src/modules/album/album.graphql.ts +++ b/src/modules/album/album.graphql.ts @@ -20,6 +20,7 @@ export class AlbumGraphql { '', `query list_album($creator_id: Int!, $limit: Int = 20, $offset: Int = 0) { default_album: albums_by_pk(id: 1) { + id name show disable @@ -31,6 +32,7 @@ export class AlbumGraphql { } } albums(where: {creator_id: {_eq: $creator_id}}, limit: $limit, offset: $offset) { + id name show disable @@ -49,28 +51,39 @@ export class AlbumGraphql { ); } - insert(variables: any) { + albumDetail(variables: any) { const headers = { 'x-hasura-admin-secret': this.configSvc.get( 'graphql.adminSecret' ), }; - return this.graphqlSvc.query( this.configSvc.get('graphql.endpoint'), '', - `mutation insert_albums_one($object: albums_insert_input = {}) { - insert_albums_one(object: $object) { + `query album_detail($id: Int!, $creator_id: Int!) { + albums(where: {id: {_eq: $id}, creator_id: {_eq: $creator_id}}) { id + name + description + thumbnail_url + show + disable + artworks { + id + name + url + created_at + } } - }`, - 'insert_albums_one', + } + `, + 'album_detail', variables, headers ); } - insertArtworks(variables: any) { + insert(variables: any) { const headers = { 'x-hasura-admin-secret': this.configSvc.get( 'graphql.adminSecret' @@ -80,18 +93,18 @@ export class AlbumGraphql { return this.graphqlSvc.query( this.configSvc.get('graphql.endpoint'), '', - `mutation insert_artworks($objects: [artworks_insert_input!] = {}) { - insert_artworks(objects: $objects) { - affected_rows + `mutation insert_albums_one($object: albums_insert_input = {}) { + insert_albums_one(object: $object) { + id } }`, - 'insert_artworks', + 'insert_albums_one', variables, headers ); } - update(variables: any) { + insertArtworks(variables: any) { const headers = { 'x-hasura-admin-secret': this.configSvc.get( 'graphql.adminSecret' @@ -101,111 +114,33 @@ export class AlbumGraphql { return this.graphqlSvc.query( this.configSvc.get('graphql.endpoint'), '', - `mutation update_albums_by_pk($id: Int!, $data: albums_set_input = {}) { - update_albums_by_pk(pk_columns: {id: $id}, _set: $data) { - updated_at + `mutation insert_artworks($objects: [artworks_insert_input!] = {}) { + insert_artworks(objects: $objects) { + affected_rows } }`, - 'update_albums_by_pk', + 'insert_artworks', variables, headers ); } - queryByPk(variables: any) { + update(variables: any) { const headers = { 'x-hasura-admin-secret': this.configSvc.get( 'graphql.adminSecret' ), }; - return this.graphqlSvc.query( - this.configSvc.get('graphql.endpoint'), - '', - `query launchpad_by_pk($id: Int!) { - launchpad_by_pk(id: $id) { - status - contract_address - fund - id - slug - launchpad_creator { - avatar_url - bio - name - pen_name - slug - wallet_address - } - launchpad_i18ns { - id - language_id - data - } - featured_images - creator_id - } - }`, - 'launchpad_by_pk', - variables, - headers - ); - } - queryBySlug(variables: any) { - const headers = { - 'x-hasura-admin-secret': this.configSvc.get( - 'graphql.adminSecret' - ), - }; return this.graphqlSvc.query( this.configSvc.get('graphql.endpoint'), '', - `query launchpad($slug: String!) { - launchpad(where: {slug: {_eq: $slug}}) { - status - contract_address - fund - id - slug - launchpad_creator { - avatar_url - bio - name - pen_name - slug - wallet_address - } - launchpad_i18ns { - id - language_id - data - } - featured_images - creator_id + `mutation update_albums_by_pk($id: Int!, $data: albums_set_input = {}) { + update_albums_by_pk(pk_columns: {id: $id}, _set: $data) { + updated_at } }`, - 'launchpad', - variables, - headers - ); - } - - async insertI18n(variables: any) { - const headers = { - 'x-hasura-admin-secret': this.configSvc.get( - 'graphql.adminSecret' - ), - }; - console.log(variables); - return this.graphqlSvc.query( - this.configSvc.get('graphql.endpoint'), - '', - `mutation insert_i18n($objects: [i18n_insert_input!] = {}) { - insert_i18n(on_conflict: {constraint: i18n_launchpad_id_language_id_key, update_columns: data}, objects: $objects) { - affected_rows - } - }`, - 'insert_i18n', + 'update_albums_by_pk', variables, headers ); diff --git a/src/modules/album/album.service.ts b/src/modules/album/album.service.ts index ac2b970b..60cd8966 100644 --- a/src/modules/album/album.service.ts +++ b/src/modules/album/album.service.ts @@ -127,4 +127,12 @@ export class AlbumService { offset, }); } + + async getDetail(id: number) { + const creatorId = await this.creatorService.getCreatorIdAuthToken(); + return this.albumGraphql.albumDetail({ + id, + creator_id: creatorId, + }); + } } diff --git a/src/modules/album/dto/detail-album-request.dto.ts b/src/modules/album/dto/detail-album-request.dto.ts new file mode 100644 index 00000000..492b68aa --- /dev/null +++ b/src/modules/album/dto/detail-album-request.dto.ts @@ -0,0 +1,6 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class DetailAlbumParamDto { + @ApiProperty() + id: number; +} From 36b9485997b7c8f3437dff1a093cab34f4b416a0 Mon Sep 17 00:00:00 2001 From: harisato Date: Fri, 16 Aug 2024 14:46:03 +0700 Subject: [PATCH 38/50] feat: update album api --- src/modules/album/album.controller.ts | 20 ++++ src/modules/album/album.graphql.ts | 29 +++++- src/modules/album/album.service.ts | 92 ++++++++++++++++++- .../album/dto/update-album-request.dto.ts | 20 ++++ 4 files changed, 156 insertions(+), 5 deletions(-) create mode 100644 src/modules/album/dto/update-album-request.dto.ts diff --git a/src/modules/album/album.controller.ts b/src/modules/album/album.controller.ts index 169e42e1..141c264f 100644 --- a/src/modules/album/album.controller.ts +++ b/src/modules/album/album.controller.ts @@ -4,6 +4,7 @@ import { Get, Param, Post, + Put, Query, UploadedFiles, UseGuards, @@ -26,6 +27,10 @@ import { AlbumService } from './album.service'; import { CreateAlbumRequestDto } from './dto/create-album-request.dto'; import { QueryAlbumDto } from './dto/query-album-query.dto'; import { DetailAlbumParamDto } from './dto/detail-album-request.dto'; +import { + UpdateAlbumParamDto, + UpdateAlbumRequestDto, +} from './dto/update-album-request.dto'; @Controller('album') @ApiTags('album') @@ -65,4 +70,19 @@ export class AlbumController { ) { return this.albumSvc.create(data, files); } + + @UseGuards(AuthGuard, RolesGuard) + @ApiBearerAuth() + @Roles(Role.Creator) + @Put(':id') + @ApiOperation({ summary: 'update album - creator role' }) + @ApiConsumes('multipart/form-data') + @UseInterceptors(AuthUserInterceptor, AnyFilesInterceptor()) + update( + @Param() param: UpdateAlbumParamDto, + @Body() data: UpdateAlbumRequestDto, + @UploadedFiles() files: Array + ) { + return this.albumSvc.update(param.id, data, files); + } } diff --git a/src/modules/album/album.graphql.ts b/src/modules/album/album.graphql.ts index 1c870a06..deb12cdc 100644 --- a/src/modules/album/album.graphql.ts +++ b/src/modules/album/album.graphql.ts @@ -83,6 +83,26 @@ export class AlbumGraphql { ); } + albumByPk(variables: any) { + const headers = { + 'x-hasura-admin-secret': this.configSvc.get( + 'graphql.adminSecret' + ), + }; + return this.graphqlSvc.query( + this.configSvc.get('graphql.endpoint'), + '', + `query albums_by_pk($id: Int!) { + albums_by_pk(id: $id) { + creator_id + } + }`, + 'albums_by_pk', + variables, + headers + ); + } + insert(variables: any) { const headers = { 'x-hasura-admin-secret': this.configSvc.get( @@ -135,11 +155,12 @@ export class AlbumGraphql { return this.graphqlSvc.query( this.configSvc.get('graphql.endpoint'), '', - `mutation update_albums_by_pk($id: Int!, $data: albums_set_input = {}) { - update_albums_by_pk(pk_columns: {id: $id}, _set: $data) { - updated_at + `mutation update_albums_by_pk($id: Int!, $data: albums_set_input = {}, $creator_id: Int!) { + update_albums(where: {id: {_eq: $id}, creator_id: {_eq: $creator_id}}, _set: $data) { + affected_rows } - }`, + } + `, 'update_albums_by_pk', variables, headers diff --git a/src/modules/album/album.service.ts b/src/modules/album/album.service.ts index 60cd8966..8c18000f 100644 --- a/src/modules/album/album.service.ts +++ b/src/modules/album/album.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Logger } from '@nestjs/common'; +import { ForbiddenException, Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { CreatorService } from '../creator/creator.service'; @@ -7,6 +7,7 @@ import { UserWalletService } from '../user-wallet/user-wallet.service'; import { AlbumGraphql } from './album.graphql'; import { CreateAlbumRequestDto } from './dto/create-album-request.dto'; import { QueryAlbumDto } from './dto/query-album-query.dto'; +import { UpdateAlbumRequestDto } from './dto/update-album-request.dto'; @Injectable() export class AlbumService { @@ -135,4 +136,93 @@ export class AlbumService { creator_id: creatorId, }); } + + async update( + id: number, + data: UpdateAlbumRequestDto, + files: Array + ) { + try { + const { name, description, show } = data; + const creatorId = await this.creatorService.getCreatorIdAuthToken(); + + const albumCreatorId = await this.getAlbumCreatorByAlbumPk(id); + if (albumCreatorId !== creatorId) + throw new ForbiddenException('invalid creator'); + + let thumbnail_url = ''; + + const s3SubFolder = + this.configService.get('aws.s3SubFolder') || 'images'; + const s3Path = `${s3SubFolder}/creators/${creatorId}/albums/${id}`; + + // map files + const uploadPromises = files.map((file) => { + if (file.mimetype.includes('image')) { + return this.fileService.uploadToS3( + `${s3Path}/${file.fieldname}-${file.originalname}`, + file.buffer, + file.mimetype + ); + } + + return undefined; + }); + + const uploadResult = await Promise.all(uploadPromises); + files.forEach((file, index) => { + // if have upload result + if (uploadResult[index]) { + // throw error if upload failed + if (uploadResult[index].$metadata.httpStatusCode !== 200) + throw new Error('Upload fail' + JSON.stringify(uploadResult)); + + // build uploaded url + const uploadedUrl = new URL( + `${s3Path}/${file.fieldname}-${file.originalname}`, + this.configService.get('aws.queryEndpoint') + ).href; + + switch (file.fieldname) { + case 'thumbnail': + thumbnail_url = uploadedUrl; + break; + default: + break; + } + } + }); + + const updateData = { + name, + description, + show, + }; + if (thumbnail_url !== '') updateData['thumbnail_url'] = thumbnail_url; + + // update + const updateResult = await this.albumGraphql.update({ + id, + creator_id: creatorId, + data: updateData, + }); + + return updateResult; + } catch (error) { + return { + errors: [ + { + message: error.message, + }, + ], + }; + } + } + + private async getAlbumCreatorByAlbumPk(id: number) { + const result = await this.albumGraphql.albumByPk({ + id, + }); + return result.data?.albums_by_pk?.creator_id || undefined; + } } diff --git a/src/modules/album/dto/update-album-request.dto.ts b/src/modules/album/dto/update-album-request.dto.ts new file mode 100644 index 00000000..87a36eb1 --- /dev/null +++ b/src/modules/album/dto/update-album-request.dto.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class UpdateAlbumParamDto { + @ApiProperty() + id: number; +} + +export class UpdateAlbumRequestDto { + @ApiProperty() + name: string; + + @ApiProperty() + description: string; + + @ApiProperty({ type: 'string', format: 'binary' }) + thumbnail: Express.Multer.File; + + @ApiProperty() + show: boolean; +} From d989c9a59ff92289f8dffd594b8b0582d8901201 Mon Sep 17 00:00:00 2001 From: harisato Date: Fri, 16 Aug 2024 14:56:50 +0700 Subject: [PATCH 39/50] feat: update album api --- src/modules/album/album.graphql.ts | 5 +++++ src/modules/album/album.service.ts | 16 ++++++++++++---- .../album/dto/update-album-request.dto.ts | 4 ++-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/modules/album/album.graphql.ts b/src/modules/album/album.graphql.ts index deb12cdc..0a951274 100644 --- a/src/modules/album/album.graphql.ts +++ b/src/modules/album/album.graphql.ts @@ -95,6 +95,11 @@ export class AlbumGraphql { `query albums_by_pk($id: Int!) { albums_by_pk(id: $id) { creator_id + artworks_aggregate { + aggregate { + count + } + } } }`, 'albums_by_pk', diff --git a/src/modules/album/album.service.ts b/src/modules/album/album.service.ts index 8c18000f..8c8709f8 100644 --- a/src/modules/album/album.service.ts +++ b/src/modules/album/album.service.ts @@ -143,13 +143,21 @@ export class AlbumService { files: Array ) { try { - const { name, description, show } = data; + const { name, description } = data; + let { show } = data; const creatorId = await this.creatorService.getCreatorIdAuthToken(); - const albumCreatorId = await this.getAlbumCreatorByAlbumPk(id); + const getAlbumResult = await this.getAlbumByPk(id); + if (getAlbumResult.errors) return getAlbumResult; + const albumCreatorId = getAlbumResult.data?.albums_by_pk?.creator_id; + if (albumCreatorId !== creatorId) throw new ForbiddenException('invalid creator'); + const totalArtworks = + getAlbumResult.data.albums_by_pk.artworks_aggregate.aggregate.count; + if (show === true && Number(totalArtworks) === 0) show = false; + let thumbnail_url = ''; const s3SubFolder = @@ -219,10 +227,10 @@ export class AlbumService { } } - private async getAlbumCreatorByAlbumPk(id: number) { + private async getAlbumByPk(id: number) { const result = await this.albumGraphql.albumByPk({ id, }); - return result.data?.albums_by_pk?.creator_id || undefined; + return result; } } diff --git a/src/modules/album/dto/update-album-request.dto.ts b/src/modules/album/dto/update-album-request.dto.ts index 87a36eb1..8113a60d 100644 --- a/src/modules/album/dto/update-album-request.dto.ts +++ b/src/modules/album/dto/update-album-request.dto.ts @@ -1,4 +1,4 @@ -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; export class UpdateAlbumParamDto { @ApiProperty() @@ -12,7 +12,7 @@ export class UpdateAlbumRequestDto { @ApiProperty() description: string; - @ApiProperty({ type: 'string', format: 'binary' }) + @ApiPropertyOptional({ type: 'string', format: 'binary' }) thumbnail: Express.Multer.File; @ApiProperty() From 8da16f0fefe82dd022a387ff66cd0801499fbc97 Mon Sep 17 00:00:00 2001 From: harisato Date: Fri, 16 Aug 2024 15:27:15 +0700 Subject: [PATCH 40/50] feat: delete album api --- src/modules/album/album.controller.ts | 12 +++++++++ src/modules/album/album.graphql.ts | 21 ++++++++++++++++ src/modules/album/album.service.ts | 25 +++++++++++++++++++ .../album/dto/delete-album-request.dto.ts | 6 +++++ 4 files changed, 64 insertions(+) create mode 100644 src/modules/album/dto/delete-album-request.dto.ts diff --git a/src/modules/album/album.controller.ts b/src/modules/album/album.controller.ts index 141c264f..2f7c7b79 100644 --- a/src/modules/album/album.controller.ts +++ b/src/modules/album/album.controller.ts @@ -1,6 +1,7 @@ import { Body, Controller, + Delete, Get, Param, Post, @@ -85,4 +86,15 @@ export class AlbumController { ) { return this.albumSvc.update(param.id, data, files); } + + @UseGuards(AuthGuard, RolesGuard) + @ApiBearerAuth() + @Roles(Role.Creator) + @Delete(':id') + @ApiOperation({ summary: 'delete album - creator role' }) + @ApiConsumes('multipart/form-data') + @UseInterceptors(AuthUserInterceptor, AnyFilesInterceptor()) + delete(@Param() param: UpdateAlbumParamDto) { + return this.albumSvc.delete(param.id); + } } diff --git a/src/modules/album/album.graphql.ts b/src/modules/album/album.graphql.ts index 0a951274..f6c53df3 100644 --- a/src/modules/album/album.graphql.ts +++ b/src/modules/album/album.graphql.ts @@ -129,6 +129,27 @@ export class AlbumGraphql { ); } + delete(variables: any) { + const headers = { + 'x-hasura-admin-secret': this.configSvc.get( + 'graphql.adminSecret' + ), + }; + + return this.graphqlSvc.query( + this.configSvc.get('graphql.endpoint'), + '', + `mutation delete_albums_by_pk($id: Int!) { + delete_albums_by_pk(id: $id) { + id + } + }`, + 'delete_albums_by_pk', + variables, + headers + ); + } + insertArtworks(variables: any) { const headers = { 'x-hasura-admin-secret': this.configSvc.get( diff --git a/src/modules/album/album.service.ts b/src/modules/album/album.service.ts index 8c8709f8..a02c55cc 100644 --- a/src/modules/album/album.service.ts +++ b/src/modules/album/album.service.ts @@ -227,6 +227,31 @@ export class AlbumService { } } + async delete(id: number) { + try { + const creatorId = await this.creatorService.getCreatorIdAuthToken(); + + const getAlbumResult = await this.getAlbumByPk(id); + if (getAlbumResult.errors) return getAlbumResult; + const albumCreatorId = getAlbumResult.data?.albums_by_pk?.creator_id; + + if (albumCreatorId !== creatorId) + throw new ForbiddenException('invalid creator'); + + return this.albumGraphql.delete({ + id, + }); + } catch (error) { + return { + errors: [ + { + message: error.message, + }, + ], + }; + } + } + private async getAlbumByPk(id: number) { const result = await this.albumGraphql.albumByPk({ id, diff --git a/src/modules/album/dto/delete-album-request.dto.ts b/src/modules/album/dto/delete-album-request.dto.ts new file mode 100644 index 00000000..1e662e1f --- /dev/null +++ b/src/modules/album/dto/delete-album-request.dto.ts @@ -0,0 +1,6 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class DeleteAlbumParamDto { + @ApiProperty() + id: number; +} From 3af35e016035e06bea64e97dd95583aec3f00316 Mon Sep 17 00:00:00 2001 From: harisato Date: Fri, 16 Aug 2024 16:11:15 +0700 Subject: [PATCH 41/50] feat: update & delete creator artwork --- src/modules/artwork/artwork.controller.ts | 36 +++++++++++++++- src/modules/artwork/artwork.graphql.ts | 42 +++++++++++++++++++ src/modules/artwork/artwork.module.ts | 3 +- src/modules/artwork/artwork.service.ts | 25 ++++++++++- .../artwork/dto/delete-artworks.dto.ts | 6 +++ src/modules/artwork/dto/update-artwork.dto.ts | 11 +++++ 6 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 src/modules/artwork/dto/delete-artworks.dto.ts create mode 100644 src/modules/artwork/dto/update-artwork.dto.ts diff --git a/src/modules/artwork/artwork.controller.ts b/src/modules/artwork/artwork.controller.ts index d75fe30d..8d669a10 100644 --- a/src/modules/artwork/artwork.controller.ts +++ b/src/modules/artwork/artwork.controller.ts @@ -1,6 +1,7 @@ import { Body, Controller, + Delete, Get, Param, Post, @@ -11,7 +12,12 @@ import { UseGuards, UseInterceptors, } from '@nestjs/common'; -import { ApiBearerAuth, ApiConsumes, ApiTags } from '@nestjs/swagger'; +import { + ApiBearerAuth, + ApiConsumes, + ApiOperation, + ApiTags, +} from '@nestjs/swagger'; import { AuthGuard } from '../../auth/auth.guard'; import { Role } from '../../auth/role.enum'; @@ -22,6 +28,11 @@ import { FileInterceptor } from '@nestjs/platform-express'; import { ArtworkService } from './artwork.service'; import { SetRequestTimeout } from '../../decorators/set-timeout.decorator'; import { ImportArtworkDto } from './dto/import-artwork.dto'; +import { + UpdateArtworkDto, + UpdateArtworkParamDto, +} from './dto/update-artwork.dto'; +import { DeleteArtworksDto } from './dto/delete-artworks.dto'; @Controller('artwork') @ApiTags('artwork') @@ -41,4 +52,27 @@ export class ArtworkController { ) { return this.artworkSvc.import(body, file); } + + @UseGuards(AuthGuard, RolesGuard) + @ApiBearerAuth() + @Roles(Role.Creator) + @Put(':id') + @ApiOperation({ summary: 'update artwork - creator role' }) + @UseInterceptors(AuthUserInterceptor) + update( + @Param() param: UpdateArtworkParamDto, + @Body() body: UpdateArtworkDto + ) { + return this.artworkSvc.update(param.id, body); + } + + @UseGuards(AuthGuard, RolesGuard) + @ApiBearerAuth() + @Roles(Role.Creator) + @Delete() + @ApiOperation({ summary: 'delete artworks - creator role' }) + @UseInterceptors(AuthUserInterceptor) + delete(@Body() body: DeleteArtworksDto) { + return this.artworkSvc.deleteArtworks(body); + } } diff --git a/src/modules/artwork/artwork.graphql.ts b/src/modules/artwork/artwork.graphql.ts index 63ce361c..fcd49a90 100644 --- a/src/modules/artwork/artwork.graphql.ts +++ b/src/modules/artwork/artwork.graphql.ts @@ -441,6 +441,48 @@ export class ArtworkGraphql { ); } + async updateArtwork(variables: any) { + const headers = { + 'x-hasura-admin-secret': this.configService.get( + 'graphql.adminSecret' + ), + }; + + return this.graphqlSvc.query( + this.configService.get('graphql.endpoint'), + '', + `mutation update_artworks($id: Int!, $creator_id: Int!, $data: artworks_set_input = {}) { + update_artworks(where: {id: {_eq: $id}, creator_id: {_eq: $creator_id}}, _set: $data) { + affected_rows + } + }`, + 'update_artworks', + variables, + headers + ); + } + + async deleteArtworks(variables: any) { + const headers = { + 'x-hasura-admin-secret': this.configService.get( + 'graphql.adminSecret' + ), + }; + + return this.graphqlSvc.query( + this.configService.get('graphql.endpoint'), + '', + `mutation delete_artworks($ids: [Int!], $creator_id: Int!) { + delete_artworks(where: {id: {_in: $ids}, creator_id: {_eq: $creator_id}}) { + affected_rows + } + }`, + 'delete_artworks', + variables, + headers + ); + } + async getI18n(variables: any, token: string) { const result = await this.graphqlSvc.query( this.configService.get('graphql.endpoint'), diff --git a/src/modules/artwork/artwork.module.ts b/src/modules/artwork/artwork.module.ts index 30e8530a..4acaf201 100644 --- a/src/modules/artwork/artwork.module.ts +++ b/src/modules/artwork/artwork.module.ts @@ -6,9 +6,10 @@ import { FilesModule } from '../files/files.module'; import { ArtworkService } from './artwork.service'; import { ArtworkController } from './artwork.controller'; import { ArtworkGraphql } from './artwork.graphql'; +import { CreatorModule } from '../creator/creator.module'; @Module({ - imports: [JwtModule, GraphqlModule, FilesModule], + imports: [JwtModule, GraphqlModule, FilesModule, CreatorModule], providers: [ArtworkService, ArtworkGraphql], controllers: [ArtworkController], }) diff --git a/src/modules/artwork/artwork.service.ts b/src/modules/artwork/artwork.service.ts index 13d8a8a4..7f55f7ec 100644 --- a/src/modules/artwork/artwork.service.ts +++ b/src/modules/artwork/artwork.service.ts @@ -16,6 +16,9 @@ import { FilesService } from '../files/files.service'; import { ImportArtworkDto } from './dto/import-artwork.dto'; import { ArtworkGraphql } from './artwork.graphql'; import { generateSlug } from '../manga/util'; +import { UpdateArtworkDto } from './dto/update-artwork.dto'; +import { CreatorService } from '../creator/creator.service'; +import { DeleteArtworksDto } from './dto/delete-artworks.dto'; @Injectable() export class ArtworkService implements OnModuleInit { @@ -25,7 +28,8 @@ export class ArtworkService implements OnModuleInit { constructor( private configService: ConfigService, private fileService: FilesService, - private artworkGraphql: ArtworkGraphql + private artworkGraphql: ArtworkGraphql, + private creatorService: CreatorService ) {} onModuleInit() { @@ -78,6 +82,25 @@ export class ArtworkService implements OnModuleInit { return creatorArtworks; } + async update(id: number, data: UpdateArtworkDto) { + const creatorId = await this.creatorService.getCreatorIdAuthToken(); + return this.artworkGraphql.updateArtwork({ + id, + creator_id: creatorId, + data: { + name: data.name, + }, + }); + } + + async deleteArtworks(data: DeleteArtworksDto) { + const creatorId = await this.creatorService.getCreatorIdAuthToken(); + return this.artworkGraphql.deleteArtworks({ + ids: data.ids, + creator_id: creatorId, + }); + } + private async importProcess( token: string, contest_id: number, diff --git a/src/modules/artwork/dto/delete-artworks.dto.ts b/src/modules/artwork/dto/delete-artworks.dto.ts new file mode 100644 index 00000000..dc3a59dd --- /dev/null +++ b/src/modules/artwork/dto/delete-artworks.dto.ts @@ -0,0 +1,6 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class DeleteArtworksDto { + @ApiProperty({ type: [String] }) + ids: number[]; +} diff --git a/src/modules/artwork/dto/update-artwork.dto.ts b/src/modules/artwork/dto/update-artwork.dto.ts new file mode 100644 index 00000000..e6150dee --- /dev/null +++ b/src/modules/artwork/dto/update-artwork.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class UpdateArtworkParamDto { + @ApiProperty() + id: number; +} + +export class UpdateArtworkDto { + @ApiProperty() + name: string; +} From ba0d9c24c37b7aa61ca9976e50e974ca687a3379 Mon Sep 17 00:00:00 2001 From: harisato Date: Mon, 26 Aug 2024 10:32:09 +0700 Subject: [PATCH 42/50] feat: add api verify teleuser --- .../tables/public_telegram_users.yaml | 3 ++ .../databases/punkga-pg/tables/tables.yaml | 1 + .../down.sql | 1 + .../up.sql | 17 +++++++ src/app.module.ts | 2 + src/auth/auth.guard.ts | 17 ++++++- src/modules/telegram/telegram.controller.ts | 16 +++++++ src/modules/telegram/telegram.module.ts | 13 ++++++ src/modules/telegram/telegram.service.ts | 44 +++++++++++++++++++ 9 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 hasura/metadata/databases/punkga-pg/tables/public_telegram_users.yaml create mode 100644 hasura/migrations/punkga-pg/1724642251768_create_table_public_telegram_users/down.sql create mode 100644 hasura/migrations/punkga-pg/1724642251768_create_table_public_telegram_users/up.sql create mode 100644 src/modules/telegram/telegram.controller.ts create mode 100644 src/modules/telegram/telegram.module.ts create mode 100644 src/modules/telegram/telegram.service.ts diff --git a/hasura/metadata/databases/punkga-pg/tables/public_telegram_users.yaml b/hasura/metadata/databases/punkga-pg/tables/public_telegram_users.yaml new file mode 100644 index 00000000..cbbb33e0 --- /dev/null +++ b/hasura/metadata/databases/punkga-pg/tables/public_telegram_users.yaml @@ -0,0 +1,3 @@ +table: + name: telegram_users + schema: public diff --git a/hasura/metadata/databases/punkga-pg/tables/tables.yaml b/hasura/metadata/databases/punkga-pg/tables/tables.yaml index 59a8deaf..0b0ac2b6 100644 --- a/hasura/metadata/databases/punkga-pg/tables/tables.yaml +++ b/hasura/metadata/databases/punkga-pg/tables/tables.yaml @@ -34,6 +34,7 @@ - "!include public_system_key.yaml" - "!include public_tag_languages.yaml" - "!include public_tags.yaml" +- "!include public_telegram_users.yaml" - "!include public_user_campaign.yaml" - "!include public_user_campaign_reward.yaml" - "!include public_user_level.yaml" diff --git a/hasura/migrations/punkga-pg/1724642251768_create_table_public_telegram_users/down.sql b/hasura/migrations/punkga-pg/1724642251768_create_table_public_telegram_users/down.sql new file mode 100644 index 00000000..f847a6be --- /dev/null +++ b/hasura/migrations/punkga-pg/1724642251768_create_table_public_telegram_users/down.sql @@ -0,0 +1 @@ +DROP TABLE "public"."telegram_users"; diff --git a/hasura/migrations/punkga-pg/1724642251768_create_table_public_telegram_users/up.sql b/hasura/migrations/punkga-pg/1724642251768_create_table_public_telegram_users/up.sql new file mode 100644 index 00000000..9746557a --- /dev/null +++ b/hasura/migrations/punkga-pg/1724642251768_create_table_public_telegram_users/up.sql @@ -0,0 +1,17 @@ +CREATE TABLE "public"."telegram_users" ("id" serial NOT NULL, "telegram_id" text NOT NULL, "username" text NOT NULL, "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), PRIMARY KEY ("id") ); +CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"() +RETURNS TRIGGER AS $$ +DECLARE + _new record; +BEGIN + _new := NEW; + _new."updated_at" = NOW(); + RETURN _new; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER "set_public_telegram_users_updated_at" +BEFORE UPDATE ON "public"."telegram_users" +FOR EACH ROW +EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"(); +COMMENT ON TRIGGER "set_public_telegram_users_updated_at" ON "public"."telegram_users" +IS 'trigger to set value of column "updated_at" to current timestamp on row update'; diff --git a/src/app.module.ts b/src/app.module.ts index b6a87d47..76bc10ab 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -26,6 +26,7 @@ import { ChainModule } from './modules/chain/chain.module'; import { ChainGateWayModule } from './chain-gateway/chain-gateway.module'; import { ArtworkModule } from './modules/artwork/artwork.module'; import { AlbumModule } from './modules/album/album.module'; +import { TelegramModule } from './modules/telegram/telegram.module'; @Module({ imports: [ @@ -75,6 +76,7 @@ import { AlbumModule } from './modules/album/album.module'; ChainGateWayModule, ArtworkModule, AlbumModule, + TelegramModule, ], controllers: [], providers: [ diff --git a/src/auth/auth.guard.ts b/src/auth/auth.guard.ts index d38a38eb..6187d647 100644 --- a/src/auth/auth.guard.ts +++ b/src/auth/auth.guard.ts @@ -16,7 +16,16 @@ export class AuthGuard implements CanActivate { async canActivate(context: ExecutionContext): Promise { const request = context.switchToHttp().getRequest(); - const token = this.extractTokenFromHeader(request); + const token = this.extractBrearerTokenFromHeader(request); + if (token) { + return this.bearerAuth(request); + } + } + + private async telegramAuth(request: Request) {} + + private async bearerAuth(request: Request): Promise { + const token = this.extractBrearerTokenFromHeader(request); if (!token) { throw new UnauthorizedException(); } @@ -43,8 +52,12 @@ export class AuthGuard implements CanActivate { return true; } - private extractTokenFromHeader(request: Request): string | undefined { + private extractBrearerTokenFromHeader(request: Request): string | undefined { const [type, token] = request.headers.authorization?.split(' ') ?? []; return type === 'Bearer' ? token : undefined; } + + private extractTelegramInitData(request: Request): string | undefined { + return request.headers.authorization; + } } diff --git a/src/modules/telegram/telegram.controller.ts b/src/modules/telegram/telegram.controller.ts new file mode 100644 index 00000000..6335e6d8 --- /dev/null +++ b/src/modules/telegram/telegram.controller.ts @@ -0,0 +1,16 @@ +import { Controller, Post } from '@nestjs/common'; +import { ApiOperation, ApiTags } from '@nestjs/swagger'; + +import { TelegramService } from './telegram.service'; + +@Controller('telegram') +@ApiTags('telegram') +export class TelegramController { + constructor(private readonly telegramSvc: TelegramService) {} + + @Post() + @ApiOperation({ summary: '' }) + auth() { + console.log('auth'); + } +} diff --git a/src/modules/telegram/telegram.module.ts b/src/modules/telegram/telegram.module.ts new file mode 100644 index 00000000..cca87408 --- /dev/null +++ b/src/modules/telegram/telegram.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { JwtModule } from '@nestjs/jwt'; + +import { GraphqlModule } from '../graphql/graphql.module'; +import { TelegramController } from './telegram.controller'; +import { TelegramService } from './telegram.service'; + +@Module({ + imports: [JwtModule, GraphqlModule], + providers: [TelegramService], + controllers: [TelegramController], +}) +export class TelegramModule {} diff --git a/src/modules/telegram/telegram.service.ts b/src/modules/telegram/telegram.service.ts new file mode 100644 index 00000000..280f4b57 --- /dev/null +++ b/src/modules/telegram/telegram.service.ts @@ -0,0 +1,44 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { createHmac } from 'crypto'; + +@Injectable() +export class TelegramService { + private readonly logger = new Logger(TelegramService.name); + + constructor(private configService: ConfigService) { + const result = this.verifyTelegramWebAppData(''); + console.log(result); + } + + verifyTelegramWebAppData(telegramInitData: string) { + const TELEGRAM_BOT_TOKEN = + this.configService.get('telgram.bot_token'); + // / The data is a query string, which is composed of a series of field-value pairs. + const encoded = decodeURIComponent(telegramInitData); + + // HMAC-SHA-256 signature of the bot's token with the constant string WebAppData used as a key. + const secret = createHmac('sha256', 'WebAppData').update( + TELEGRAM_BOT_TOKEN + ); + + // Data-check-string is a chain of all received fields'. + const arr = encoded.split('&'); + const hashIndex = arr.findIndex((str) => str.startsWith('hash=')); + const hash = arr.splice(hashIndex)[0].split('=')[1]; + // sorted alphabetically + arr.sort((a, b) => a.localeCompare(b)); + // in the format key= with a line feed character ('\n', 0x0A) used as separator + // e.g., 'auth_date=\nquery_id=\nuser= + const dataCheckString = arr.join('\n'); + + // The hexadecimal representation of the HMAC-SHA-256 signature of the data-check-string with the secret key + const _hash = createHmac('sha256', secret.digest()) + .update(dataCheckString) + .digest('hex'); + + // if hash are equal the data may be used on your server. + // Complex data types are represented as JSON-serialized objects. + return _hash === hash; + } +} From 458519ef9b40ba56e1fee69944a98195604f28cd Mon Sep 17 00:00:00 2001 From: harisato Date: Mon, 26 Aug 2024 10:54:18 +0700 Subject: [PATCH 43/50] fix: update creator api --- src/modules/creator/creator.graphql.ts | 16 +++++++++---- src/modules/creator/creator.service.ts | 33 +++++++++++++++----------- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/modules/creator/creator.graphql.ts b/src/modules/creator/creator.graphql.ts index ead00a6c..f2b9600d 100644 --- a/src/modules/creator/creator.graphql.ts +++ b/src/modules/creator/creator.graphql.ts @@ -132,10 +132,10 @@ export class CreatorGraphql { ); } - queryCreatorById(token: string, variables: any) { + queryCreatorById(variables: any) { return this.graphqlSvc.query( this.configSvc.get('graphql.endpoint'), - token, + '', `query QueryCreatorById($id: Int!) { creators_by_pk(id: $id) { id @@ -148,7 +148,14 @@ export class CreatorGraphql { ); } - updateCreator(token: string, variables: any) { + updateCreator(token: string, variables: any, creatorRole = false) { + const headers = creatorRole + ? { + 'x-hasura-admin-secret': this.configSvc.get( + 'graphql.adminSecret' + ), + } + : {}; return this.graphqlSvc.query( this.configSvc.get('graphql.endpoint'), token, @@ -167,7 +174,8 @@ export class CreatorGraphql { } `, 'UpdateCreator', - variables + variables, + headers ); } } diff --git a/src/modules/creator/creator.service.ts b/src/modules/creator/creator.service.ts index 45e752f6..d6560dc7 100644 --- a/src/modules/creator/creator.service.ts +++ b/src/modules/creator/creator.service.ts @@ -112,20 +112,21 @@ export class CreatorService { files: Array ) { const creatorId = await this.getCreatorIdAuthToken(); - return this.update(creatorId, data, files); + return this.update(creatorId, data, files, true); } async update( creatorId: number, data: UpdateCreatorRequestDto, - files: Array + files: Array, + creatorRole = false ) { try { const { token } = ContextProvider.getAuthUser(); const { name, socials, pen_name, bio, gender, dob, wallet_address } = data; - const result = await this.creatorGraphql.queryCreatorById(token, { + const result = await this.creatorGraphql.queryCreatorById({ id: creatorId, }); @@ -148,17 +149,21 @@ export class CreatorService { ); // update creator in DB - const updateResult = await this.creatorGraphql.updateCreator(token, { - id: creatorId, - name, - bio: bio.toString(), - socials: JSON.parse(socials), - pen_name, - gender, - dob, - avatar_url: avatarUrl, - wallet_address, - }); + const updateResult = await this.creatorGraphql.updateCreator( + token, + { + id: creatorId, + name, + bio: bio.toString(), + socials: JSON.parse(socials), + pen_name, + gender, + dob, + avatar_url: avatarUrl, + wallet_address, + }, + creatorRole + ); return updateResult; } catch (errors) { From 55dad4c603d9cd5ddb9a4dc8b8420da1eb54d2e2 Mon Sep 17 00:00:00 2001 From: harisato Date: Mon, 26 Aug 2024 15:32:26 +0700 Subject: [PATCH 44/50] feat: update guard --- .../tables/public_telegram_users.yaml | 4 + .../down.sql | 4 + .../up.sql | 2 + .../down.sql | 1 + .../up.sql | 5 + .../down.sql | 1 + .../up.sql | 1 + src/auth/auth.guard.ts | 105 +++++++++++++++++- src/auth/dto/user.dto.ts | 1 + .../interfaces/telegram-user.interface.ts | 4 + src/modules/graphql/graphql.module.ts | 3 +- src/modules/telegram/telegram.graphql.ts | 32 ++++++ src/modules/telegram/telegram.service.ts | 35 +----- 13 files changed, 162 insertions(+), 36 deletions(-) create mode 100644 hasura/migrations/punkga-pg/1724645892288_alter_table_public_telegram_users_add_column_user_id/down.sql create mode 100644 hasura/migrations/punkga-pg/1724645892288_alter_table_public_telegram_users_add_column_user_id/up.sql create mode 100644 hasura/migrations/punkga-pg/1724645916362_set_fk_public_telegram_users_user_id/down.sql create mode 100644 hasura/migrations/punkga-pg/1724645916362_set_fk_public_telegram_users_user_id/up.sql create mode 100644 hasura/migrations/punkga-pg/1724656307108_alter_table_public_telegram_users_add_unique_telegram_id/down.sql create mode 100644 hasura/migrations/punkga-pg/1724656307108_alter_table_public_telegram_users_add_unique_telegram_id/up.sql create mode 100644 src/auth/interfaces/telegram-user.interface.ts create mode 100644 src/modules/telegram/telegram.graphql.ts diff --git a/hasura/metadata/databases/punkga-pg/tables/public_telegram_users.yaml b/hasura/metadata/databases/punkga-pg/tables/public_telegram_users.yaml index cbbb33e0..215f97d6 100644 --- a/hasura/metadata/databases/punkga-pg/tables/public_telegram_users.yaml +++ b/hasura/metadata/databases/punkga-pg/tables/public_telegram_users.yaml @@ -1,3 +1,7 @@ table: name: telegram_users schema: public +object_relationships: + - name: authorizer_user + using: + foreign_key_constraint_on: user_id diff --git a/hasura/migrations/punkga-pg/1724645892288_alter_table_public_telegram_users_add_column_user_id/down.sql b/hasura/migrations/punkga-pg/1724645892288_alter_table_public_telegram_users_add_column_user_id/down.sql new file mode 100644 index 00000000..1b80d538 --- /dev/null +++ b/hasura/migrations/punkga-pg/1724645892288_alter_table_public_telegram_users_add_column_user_id/down.sql @@ -0,0 +1,4 @@ +-- Could not auto-generate a down migration. +-- Please write an appropriate down migration for the SQL below: +-- alter table "public"."telegram_users" add column "user_id" bpchar +-- null; diff --git a/hasura/migrations/punkga-pg/1724645892288_alter_table_public_telegram_users_add_column_user_id/up.sql b/hasura/migrations/punkga-pg/1724645892288_alter_table_public_telegram_users_add_column_user_id/up.sql new file mode 100644 index 00000000..06963a8a --- /dev/null +++ b/hasura/migrations/punkga-pg/1724645892288_alter_table_public_telegram_users_add_column_user_id/up.sql @@ -0,0 +1,2 @@ +alter table "public"."telegram_users" add column "user_id" bpchar + null; diff --git a/hasura/migrations/punkga-pg/1724645916362_set_fk_public_telegram_users_user_id/down.sql b/hasura/migrations/punkga-pg/1724645916362_set_fk_public_telegram_users_user_id/down.sql new file mode 100644 index 00000000..706f1482 --- /dev/null +++ b/hasura/migrations/punkga-pg/1724645916362_set_fk_public_telegram_users_user_id/down.sql @@ -0,0 +1 @@ +alter table "public"."telegram_users" drop constraint "telegram_users_user_id_fkey"; diff --git a/hasura/migrations/punkga-pg/1724645916362_set_fk_public_telegram_users_user_id/up.sql b/hasura/migrations/punkga-pg/1724645916362_set_fk_public_telegram_users_user_id/up.sql new file mode 100644 index 00000000..b2d89dd1 --- /dev/null +++ b/hasura/migrations/punkga-pg/1724645916362_set_fk_public_telegram_users_user_id/up.sql @@ -0,0 +1,5 @@ +alter table "public"."telegram_users" + add constraint "telegram_users_user_id_fkey" + foreign key ("user_id") + references "public"."authorizer_users" + ("id") on update set null on delete set null; diff --git a/hasura/migrations/punkga-pg/1724656307108_alter_table_public_telegram_users_add_unique_telegram_id/down.sql b/hasura/migrations/punkga-pg/1724656307108_alter_table_public_telegram_users_add_unique_telegram_id/down.sql new file mode 100644 index 00000000..8f1a886f --- /dev/null +++ b/hasura/migrations/punkga-pg/1724656307108_alter_table_public_telegram_users_add_unique_telegram_id/down.sql @@ -0,0 +1 @@ +alter table "public"."telegram_users" drop constraint "telegram_users_telegram_id_key"; diff --git a/hasura/migrations/punkga-pg/1724656307108_alter_table_public_telegram_users_add_unique_telegram_id/up.sql b/hasura/migrations/punkga-pg/1724656307108_alter_table_public_telegram_users_add_unique_telegram_id/up.sql new file mode 100644 index 00000000..d1569331 --- /dev/null +++ b/hasura/migrations/punkga-pg/1724656307108_alter_table_public_telegram_users_add_unique_telegram_id/up.sql @@ -0,0 +1 @@ +alter table "public"."telegram_users" add constraint "telegram_users_telegram_id_key" unique ("telegram_id"); diff --git a/src/auth/auth.guard.ts b/src/auth/auth.guard.ts index 6187d647..6ef3b502 100644 --- a/src/auth/auth.guard.ts +++ b/src/auth/auth.guard.ts @@ -4,15 +4,22 @@ import { Injectable, UnauthorizedException, } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; import { JwtService } from '@nestjs/jwt'; -// import { jwtConstants } from './constants'; +import { createHmac } from 'crypto'; import { Request } from 'express'; import { readFile } from 'fs/promises'; import * as path from 'path'; +import { ITelegramUser } from './interfaces/telegram-user.interface'; +import { GraphqlService } from '../modules/graphql/graphql.service'; @Injectable() export class AuthGuard implements CanActivate { - constructor(private jwtService: JwtService) {} + constructor( + private jwtService: JwtService, + private configService: ConfigService, + private graphqlSvc: GraphqlService + ) {} async canActivate(context: ExecutionContext): Promise { const request = context.switchToHttp().getRequest(); @@ -20,9 +27,75 @@ export class AuthGuard implements CanActivate { if (token) { return this.bearerAuth(request); } + + return this.telegramAuth(request); + } + + private async telegramAuth(request: Request) { + const telegramInitData = this.extractTelegramInitData(request); + if (!telegramInitData) { + throw new UnauthorizedException(); + } + + const user: ITelegramUser = this.verifyTelegramWebAppData(telegramInitData); + // insert user + + if (!user) { + throw new UnauthorizedException(); + } + + const insertResult = await this.insertTelegramUser({ + object: { + telegram_id: user.id, + username: user.username, + }, + }); + if (insertResult.errors) + throw new UnauthorizedException(JSON.stringify(insertResult)); + + request['user'] = { + userId: insertResult.data.insert_telegram_users_one.authorizer_user?.id, + roles: ['telegram-user'], + telegramUserId: insertResult.data.insert_telegram_users_one.id, + }; + + return true; } - private async telegramAuth(request: Request) {} + private verifyTelegramWebAppData(telegramInitData: string) { + const TELEGRAM_BOT_TOKEN = + this.configService.get('telgram.bot_token'); + // / The data is a query string, which is composed of a series of field-value pairs. + const encoded = decodeURIComponent(telegramInitData); + + // HMAC-SHA-256 signature of the bot's token with the constant string WebAppData used as a key. + const secret = createHmac('sha256', 'WebAppData').update( + TELEGRAM_BOT_TOKEN + ); + + // Data-check-string is a chain of all received fields'. + const arr = encoded.split('&'); + const hashIndex = arr.findIndex((str) => str.startsWith('hash=')); + const hash = arr.splice(hashIndex)[0].split('=')[1]; + // sorted alphabetically + arr.sort((a, b) => a.localeCompare(b)); + // in the format key= with a line feed character ('\n', 0x0A) used as separator + // e.g., 'auth_date=\nquery_id=\nuser= + const dataCheckString = arr.join('\n'); + + // The hexadecimal representation of the HMAC-SHA-256 signature of the data-check-string with the secret key + const _hash = createHmac('sha256', secret.digest()) + .update(dataCheckString) + .digest('hex'); + + // if hash are equal the data may be used on your server. + // Complex data types are represented as JSON-serialized objects. + if (_hash === hash) throw new UnauthorizedException(); + + const userIndex = arr.findIndex((str) => str.startsWith('user=')); + const user = JSON.parse(arr.splice(userIndex)[0].split('=')[1]); + return user; + } private async bearerAuth(request: Request): Promise { const token = this.extractBrearerTokenFromHeader(request); @@ -60,4 +133,30 @@ export class AuthGuard implements CanActivate { private extractTelegramInitData(request: Request): string | undefined { return request.headers.authorization; } + + private insertTelegramUser(variables: any) { + const headers = { + 'x-hasura-admin-secret': this.configService.get( + 'graphql.adminSecret' + ), + }; + + return this.graphqlSvc.query( + this.configService.get('graphql.endpoint'), + '', + `mutation insert_telegram_users_one($object: telegram_users_insert_input = {}) { + insert_telegram_users_one(object: $object, on_conflict: {constraint: telegram_users_telegram_id_key, update_columns: updated_at}) { + id + authorizer_user { + id + email + nickname + } + } + }`, + 'insert_telegram_users_one', + variables, + headers + ); + } } diff --git a/src/auth/dto/user.dto.ts b/src/auth/dto/user.dto.ts index 02422434..91edc6ac 100644 --- a/src/auth/dto/user.dto.ts +++ b/src/auth/dto/user.dto.ts @@ -2,4 +2,5 @@ export class UserDto { userId: string; roles: string[]; token: string; + telegramUserId?: string; } diff --git a/src/auth/interfaces/telegram-user.interface.ts b/src/auth/interfaces/telegram-user.interface.ts new file mode 100644 index 00000000..1580bc8b --- /dev/null +++ b/src/auth/interfaces/telegram-user.interface.ts @@ -0,0 +1,4 @@ +export interface ITelegramUser { + id: string; + username: string; +} diff --git a/src/modules/graphql/graphql.module.ts b/src/modules/graphql/graphql.module.ts index d7fa53d0..30e58b9e 100644 --- a/src/modules/graphql/graphql.module.ts +++ b/src/modules/graphql/graphql.module.ts @@ -1,6 +1,7 @@ -import { Module } from '@nestjs/common'; +import { Global, Module } from '@nestjs/common'; import { GraphqlService } from './graphql.service'; +@Global() @Module({ providers: [GraphqlService], exports: [GraphqlService], diff --git a/src/modules/telegram/telegram.graphql.ts b/src/modules/telegram/telegram.graphql.ts new file mode 100644 index 00000000..861c9763 --- /dev/null +++ b/src/modules/telegram/telegram.graphql.ts @@ -0,0 +1,32 @@ +import { ConfigService } from '@nestjs/config'; +import { GraphqlService } from '../graphql/graphql.service'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class TelegramGraphql { + constructor( + private configSvc: ConfigService, + private graphqlSvc: GraphqlService + ) {} + + insertTelegramUser(variables: any) { + const headers = { + 'x-hasura-admin-secret': this.configSvc.get( + 'graphql.adminSecret' + ), + }; + + return this.graphqlSvc.query( + this.configSvc.get('graphql.endpoint'), + '', + `mutation insert_telegram_users_one($object: telegram_users_insert_input = {}) { + insert_telegram_users_one(object: $object, on_conflict: {constraint: telegram_users_telegram_id_key, update_columns: updated_at}) { + id + } + }`, + 'insert_telegram_users_one', + variables, + headers + ); + } +} diff --git a/src/modules/telegram/telegram.service.ts b/src/modules/telegram/telegram.service.ts index 280f4b57..6d2bab37 100644 --- a/src/modules/telegram/telegram.service.ts +++ b/src/modules/telegram/telegram.service.ts @@ -7,38 +7,9 @@ export class TelegramService { private readonly logger = new Logger(TelegramService.name); constructor(private configService: ConfigService) { - const result = this.verifyTelegramWebAppData(''); - console.log(result); + // const result = this.verifyTelegramWebAppData(''); + // console.log(result); } - verifyTelegramWebAppData(telegramInitData: string) { - const TELEGRAM_BOT_TOKEN = - this.configService.get('telgram.bot_token'); - // / The data is a query string, which is composed of a series of field-value pairs. - const encoded = decodeURIComponent(telegramInitData); - - // HMAC-SHA-256 signature of the bot's token with the constant string WebAppData used as a key. - const secret = createHmac('sha256', 'WebAppData').update( - TELEGRAM_BOT_TOKEN - ); - - // Data-check-string is a chain of all received fields'. - const arr = encoded.split('&'); - const hashIndex = arr.findIndex((str) => str.startsWith('hash=')); - const hash = arr.splice(hashIndex)[0].split('=')[1]; - // sorted alphabetically - arr.sort((a, b) => a.localeCompare(b)); - // in the format key= with a line feed character ('\n', 0x0A) used as separator - // e.g., 'auth_date=\nquery_id=\nuser= - const dataCheckString = arr.join('\n'); - - // The hexadecimal representation of the HMAC-SHA-256 signature of the data-check-string with the secret key - const _hash = createHmac('sha256', secret.digest()) - .update(dataCheckString) - .digest('hex'); - - // if hash are equal the data may be used on your server. - // Complex data types are represented as JSON-serialized objects. - return _hash === hash; - } + profile() {} } From 78187032b417db222fe8411911e42f7a42d965b9 Mon Sep 17 00:00:00 2001 From: harisato Date: Mon, 26 Aug 2024 15:40:38 +0700 Subject: [PATCH 45/50] feat: update new album api --- src/modules/album/album.service.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/modules/album/album.service.ts b/src/modules/album/album.service.ts index a02c55cc..e7daa253 100644 --- a/src/modules/album/album.service.ts +++ b/src/modules/album/album.service.ts @@ -36,6 +36,8 @@ export class AlbumService { }, }); + if (result.errors) return result; + const albumId = result.data.insert_albums_one.id; let thumbnail_url = ''; @@ -88,6 +90,7 @@ export class AlbumService { if (thumbnail_url !== '') { const updateResult = await this.albumGraphql.update({ id: albumId, + creator_id: creatorId, data: { thumbnail_url, }, From 85197e0c05cbbace8a3acd75d5d771fafafc4b08 Mon Sep 17 00:00:00 2001 From: harisato Date: Mon, 26 Aug 2024 15:57:25 +0700 Subject: [PATCH 46/50] feat: update get album api --- src/modules/album/album.graphql.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/modules/album/album.graphql.ts b/src/modules/album/album.graphql.ts index f6c53df3..f7e85d9d 100644 --- a/src/modules/album/album.graphql.ts +++ b/src/modules/album/album.graphql.ts @@ -24,6 +24,7 @@ export class AlbumGraphql { name show disable + thumbnail_url created_at artworks_aggregate(where: {creator_id: {_eq: $creator_id}}) { aggregate { @@ -36,6 +37,7 @@ export class AlbumGraphql { name show disable + thumbnail_url created_at artworks_aggregate { aggregate { @@ -43,7 +45,7 @@ export class AlbumGraphql { } } } - } + } `, 'list_album', variables, From c92792a5a2b59535d8ca2e149f0f692fa9490dad Mon Sep 17 00:00:00 2001 From: harisato Date: Mon, 26 Aug 2024 16:06:43 +0700 Subject: [PATCH 47/50] feat: update get list album api --- src/modules/album/album.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/album/album.service.ts b/src/modules/album/album.service.ts index e7daa253..405d3c20 100644 --- a/src/modules/album/album.service.ts +++ b/src/modules/album/album.service.ts @@ -127,8 +127,8 @@ export class AlbumService { const { limit, offset } = query; return this.albumGraphql.getListAlbum({ creator_id: creatorId, - limit, - offset, + limit: Number(limit), + offset: Number(offset), }); } From 858d784d331b22d05d25fd7e8ae76ecf0a63ac65 Mon Sep 17 00:00:00 2001 From: harisato Date: Mon, 26 Aug 2024 17:19:02 +0700 Subject: [PATCH 48/50] feat: telegram link user --- src/auth/auth.guard.ts | 7 ++- src/auth/role.enum.ts | 1 + src/modules/telegram/dto/link-user.dto.ts | 9 +++ src/modules/telegram/telegram.controller.ts | 37 +++++++++-- src/modules/telegram/telegram.graphql.ts | 44 +++++++++++-- src/modules/telegram/telegram.module.ts | 3 +- src/modules/telegram/telegram.service.ts | 68 +++++++++++++++++++-- 7 files changed, 151 insertions(+), 18 deletions(-) create mode 100644 src/modules/telegram/dto/link-user.dto.ts diff --git a/src/auth/auth.guard.ts b/src/auth/auth.guard.ts index 6ef3b502..219d8b31 100644 --- a/src/auth/auth.guard.ts +++ b/src/auth/auth.guard.ts @@ -12,6 +12,7 @@ import { readFile } from 'fs/promises'; import * as path from 'path'; import { ITelegramUser } from './interfaces/telegram-user.interface'; import { GraphqlService } from '../modules/graphql/graphql.service'; +import { Role } from './role.enum'; @Injectable() export class AuthGuard implements CanActivate { @@ -46,7 +47,7 @@ export class AuthGuard implements CanActivate { const insertResult = await this.insertTelegramUser({ object: { - telegram_id: user.id, + telegram_id: user.id.toString(), username: user.username, }, }); @@ -55,7 +56,7 @@ export class AuthGuard implements CanActivate { request['user'] = { userId: insertResult.data.insert_telegram_users_one.authorizer_user?.id, - roles: ['telegram-user'], + roles: [Role.TelegramUser], telegramUserId: insertResult.data.insert_telegram_users_one.id, }; @@ -90,7 +91,7 @@ export class AuthGuard implements CanActivate { // if hash are equal the data may be used on your server. // Complex data types are represented as JSON-serialized objects. - if (_hash === hash) throw new UnauthorizedException(); + if (_hash !== hash) throw new UnauthorizedException(); const userIndex = arr.findIndex((str) => str.startsWith('user=')); const user = JSON.parse(arr.splice(userIndex)[0].split('=')[1]); diff --git a/src/auth/role.enum.ts b/src/auth/role.enum.ts index 9c0f0e09..6a861dff 100644 --- a/src/auth/role.enum.ts +++ b/src/auth/role.enum.ts @@ -2,4 +2,5 @@ export enum Role { User = 'user', Creator = 'creator', Admin = 'admin', + TelegramUser = 'telegram_user', } diff --git a/src/modules/telegram/dto/link-user.dto.ts b/src/modules/telegram/dto/link-user.dto.ts new file mode 100644 index 00000000..58d88f9b --- /dev/null +++ b/src/modules/telegram/dto/link-user.dto.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class LinkUserDto { + @ApiProperty() + email: string; + + @ApiProperty() + password: string; +} diff --git a/src/modules/telegram/telegram.controller.ts b/src/modules/telegram/telegram.controller.ts index 6335e6d8..d4bd7c13 100644 --- a/src/modules/telegram/telegram.controller.ts +++ b/src/modules/telegram/telegram.controller.ts @@ -1,16 +1,43 @@ -import { Controller, Post } from '@nestjs/common'; -import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { + Body, + Controller, + Get, + Post, + UseGuards, + UseInterceptors, +} from '@nestjs/common'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { TelegramService } from './telegram.service'; +import { AuthGuard } from '../../auth/auth.guard'; +import { RolesGuard } from '../../auth/role.guard'; +import { Roles } from '../../auth/roles.decorator'; +import { Role } from '../../auth/role.enum'; +import { AuthUserInterceptor } from '../../interceptors/auth-user.interceptor'; +import { LinkUserDto } from './dto/link-user.dto'; @Controller('telegram') @ApiTags('telegram') export class TelegramController { constructor(private readonly telegramSvc: TelegramService) {} - @Post() + @UseGuards(AuthGuard, RolesGuard) + @ApiBearerAuth() + @Roles(Role.TelegramUser) + @Get('profile') + @UseInterceptors(AuthUserInterceptor) @ApiOperation({ summary: '' }) - auth() { - console.log('auth'); + profile() { + return this.telegramSvc.profile(); + } + + @UseGuards(AuthGuard, RolesGuard) + @ApiBearerAuth() + @Roles(Role.TelegramUser) + @Post('link') + @UseInterceptors(AuthUserInterceptor) + @ApiOperation({ summary: '' }) + link(@Body() body: LinkUserDto) { + return this.telegramSvc.link(body.email, body.password); } } diff --git a/src/modules/telegram/telegram.graphql.ts b/src/modules/telegram/telegram.graphql.ts index 861c9763..c4e9aff4 100644 --- a/src/modules/telegram/telegram.graphql.ts +++ b/src/modules/telegram/telegram.graphql.ts @@ -9,7 +9,7 @@ export class TelegramGraphql { private graphqlSvc: GraphqlService ) {} - insertTelegramUser(variables: any) { + getTelegramUser(variables) { const headers = { 'x-hasura-admin-secret': this.configSvc.get( 'graphql.adminSecret' @@ -19,12 +19,48 @@ export class TelegramGraphql { return this.graphqlSvc.query( this.configSvc.get('graphql.endpoint'), '', - `mutation insert_telegram_users_one($object: telegram_users_insert_input = {}) { - insert_telegram_users_one(object: $object, on_conflict: {constraint: telegram_users_telegram_id_key, update_columns: updated_at}) { + `query telegram_users_by_pk($id: Int!) { + telegram_user: telegram_users_by_pk(id: $id) { id + telegram_id + username + created_at + authorizer_user { + id + nickname + email + } } }`, - 'insert_telegram_users_one', + 'telegram_users_by_pk', + variables, + headers + ); + } + + updateTelegramUser(variables: any) { + const headers = { + 'x-hasura-admin-secret': this.configSvc.get( + 'graphql.adminSecret' + ), + }; + + return this.graphqlSvc.query( + this.configSvc.get('graphql.endpoint'), + '', + `mutation update_telegram_users_by_pk($id: Int!, $user_id: bpchar!) { + telegram_user: update_telegram_users_by_pk(pk_columns: {id: $id}, _set: {user_id: $user_id}) { + id + telegram_id + username + authorizer_user { + id + nickname + email + } + } + }`, + 'update_telegram_users_by_pk', variables, headers ); diff --git a/src/modules/telegram/telegram.module.ts b/src/modules/telegram/telegram.module.ts index cca87408..e84298ef 100644 --- a/src/modules/telegram/telegram.module.ts +++ b/src/modules/telegram/telegram.module.ts @@ -4,10 +4,11 @@ import { JwtModule } from '@nestjs/jwt'; import { GraphqlModule } from '../graphql/graphql.module'; import { TelegramController } from './telegram.controller'; import { TelegramService } from './telegram.service'; +import { TelegramGraphql } from './telegram.graphql'; @Module({ imports: [JwtModule, GraphqlModule], - providers: [TelegramService], + providers: [TelegramService, TelegramGraphql], controllers: [TelegramController], }) export class TelegramModule {} diff --git a/src/modules/telegram/telegram.service.ts b/src/modules/telegram/telegram.service.ts index 6d2bab37..b7e09e77 100644 --- a/src/modules/telegram/telegram.service.ts +++ b/src/modules/telegram/telegram.service.ts @@ -1,15 +1,73 @@ -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable, Logger, UnauthorizedException } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { createHmac } from 'crypto'; +import { ContextProvider } from '../../providers/contex.provider'; +import { TelegramGraphql } from './telegram.graphql'; +import { Authorizer } from '@authorizerdev/authorizer-js'; @Injectable() export class TelegramService { private readonly logger = new Logger(TelegramService.name); - constructor(private configService: ConfigService) { - // const result = this.verifyTelegramWebAppData(''); - // console.log(result); + constructor( + private configService: ConfigService, + private telegramGraphql: TelegramGraphql + ) {} + + profile() { + const { telegramUserId } = ContextProvider.getAuthUser(); + return this.telegramGraphql.getTelegramUser({ + id: telegramUserId, + }); } - profile() {} + async link(email: string, password: string) { + const { telegramUserId } = ContextProvider.getAuthUser(); + + const query = ` + mutation userLogin($email: String!, $password: String!) { + login(params: {email: $email, password: $password}) { + user { + id + email + given_name + family_name + picture + roles + } + access_token + expires_in + message + } + } + `; + + const variables = { + email, + password, + }; + + const authRef = new Authorizer({ + redirectURL: this.configService.get('authorizer.redirectUrl'), // window.location.origin + authorizerURL: this.configService.get('authorizer.authorizerUrl'), + clientID: this.configService.get('authorizer.clientId'), // obtain your client id from authorizer dashboard + }); + + try { + const result = await authRef.graphqlQuery({ + query, + variables, + }); + + if (result.errors) return result; + + const userId = result.login.user.id; + return this.telegramGraphql.updateTelegramUser({ + id: telegramUserId, + user_id: userId, + }); + } catch (error) { + throw new UnauthorizedException(error.message); + } + } } From 2283058d7944a81a346b0971e1b0adb0be11e97c Mon Sep 17 00:00:00 2001 From: harisato Date: Tue, 27 Aug 2024 10:05:53 +0700 Subject: [PATCH 49/50] feat: telegram connect --- src/modules/telegram/telegram.controller.ts | 6 +++--- src/modules/telegram/telegram.service.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/modules/telegram/telegram.controller.ts b/src/modules/telegram/telegram.controller.ts index d4bd7c13..6000cc0e 100644 --- a/src/modules/telegram/telegram.controller.ts +++ b/src/modules/telegram/telegram.controller.ts @@ -24,11 +24,11 @@ export class TelegramController { @UseGuards(AuthGuard, RolesGuard) @ApiBearerAuth() @Roles(Role.TelegramUser) - @Get('profile') + @Post('connect') @UseInterceptors(AuthUserInterceptor) @ApiOperation({ summary: '' }) - profile() { - return this.telegramSvc.profile(); + connect() { + return this.telegramSvc.connect(); } @UseGuards(AuthGuard, RolesGuard) diff --git a/src/modules/telegram/telegram.service.ts b/src/modules/telegram/telegram.service.ts index b7e09e77..4bba632b 100644 --- a/src/modules/telegram/telegram.service.ts +++ b/src/modules/telegram/telegram.service.ts @@ -14,7 +14,7 @@ export class TelegramService { private telegramGraphql: TelegramGraphql ) {} - profile() { + connect() { const { telegramUserId } = ContextProvider.getAuthUser(); return this.telegramGraphql.getTelegramUser({ id: telegramUserId, From 1949fa2c08325dfd6d83523b33689be31c468e54 Mon Sep 17 00:00:00 2001 From: ThienLK Date: Tue, 27 Aug 2024 11:10:41 +0700 Subject: [PATCH 50/50] check collection vefore create --- src/modules/chapter/chapter.service.ts | 41 +++++++++++++++----------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/src/modules/chapter/chapter.service.ts b/src/modules/chapter/chapter.service.ts index 86a3e58c..75fa8b62 100644 --- a/src/modules/chapter/chapter.service.ts +++ b/src/modules/chapter/chapter.service.ts @@ -174,14 +174,18 @@ export class ChapterService { return updateResult; } } - const collectionIdListStr = collection_ids.toString().split(','); - let collectionIdList = Array.from(collectionIdListStr, Number); - const updateResult = await this.addChapterCollection( - chapterId, - collectionIdList - ); - if (updateResult.errors && updateResult.errors.length > 0) { - return updateResult; + if (collection_ids) { + const collectionIdListStr = collection_ids.toString().split(','); + let collectionIdList = Array.from(collectionIdListStr, Number); + if (collectionIdList.length > 0) { + const updateResult = await this.addChapterCollection( + chapterId, + collectionIdList + ); + if (updateResult.errors && updateResult.errors.length > 0) { + return updateResult; + } + } } return result.data; } catch (errors) { @@ -252,15 +256,18 @@ export class ChapterService { UpdateChapterImage, JSON.parse(data.chapter_images) ); - - const collectionIdListStr = collection_ids.toString().split(','); - let collectionIdList = Array.from(collectionIdListStr, Number); - const updateResult = await this.addChapterCollection( - chapter_id, - collectionIdList - ); - if (updateResult.errors && updateResult.errors.length > 0) { - return updateResult; + if (collection_ids) { + const collectionIdListStr = collection_ids.toString().split(','); + let collectionIdList = Array.from(collectionIdListStr, Number); + if (collectionIdList.length > 0) { + const updateResult = await this.addChapterCollection( + chapter_id, + collectionIdList + ); + if (updateResult.errors && updateResult.errors.length > 0) { + return updateResult; + } + } } // upload chapter languages const uploadChapterResult =