diff --git a/src/adapters/contentpagesservicelocator.ts b/src/adapters/contentpagesservicelocator.ts new file mode 100644 index 0000000..0dc2cc1 --- /dev/null +++ b/src/adapters/contentpagesservicelocator.ts @@ -0,0 +1,13 @@ +import { ContentPagesDto } from "src/contentPages/dto/contentPages.dto"; + +export interface IServicelocator { + createContentPage(request: any, createContentPage: ContentPagesDto); + getContentPageData(contentPageSlug: string, request: any); + getContentPagesSet(request: any, limit?: number, offset?: number); + updateContentPageData( + contentPageId: string, + request: any, + contentPageData: ContentPagesDto + ); + deleteContentPage(contentPageId: string, request: any); +} diff --git a/src/adapters/esamwad/contentPages.adapter.ts b/src/adapters/esamwad/contentPages.adapter.ts new file mode 100644 index 0000000..70b8dfc --- /dev/null +++ b/src/adapters/esamwad/contentPages.adapter.ts @@ -0,0 +1,252 @@ +import { Injectable } from "@nestjs/common"; +import { HttpService } from "@nestjs/axios"; +import { IServicelocator } from "../contentpagesservicelocator"; +import { ContentPagesDto } from "src/contentPages/dto/contentPages.dto"; +import { SuccessResponse } from "src/success-response"; + +export const ESamwadContentPagesToken = "ESamwadContentPages"; + +@Injectable() +export class ContentPagesEsamwadService implements IServicelocator { + constructor(private httpService: HttpService) { } + baseURL = process.env.HASURAURL; + adminSecret = process.env.ADMINSECRET; + + + //to create a content page + public async createContentPage(request: any, contentPageData: ContentPagesDto) { + var axios = require("axios"); + const blocksData = contentPageData.blocks.map(({ blockType, blockData }, idx) => ({ block_index: idx, block_type: blockType, block_data: blockData })); + var data = { + query: `mutation create_content_page($_slug: String, $_status: String,$_author: String, $_title: String, $_data: [blocks_insert_input!]!, $_created_by: String, $_modified_by: String) { + insert_content_pages_one(object: {slug: $_slug, status: $_status, author: $_author, title: $_title, blocks: {data: $_data}, created_by: $_created_by, modified_by: $_modified_by}) { + id + } + } + `, + variables: { + _slug: contentPageData?.urlSlug, + _title: contentPageData?.title, + _status: contentPageData?.status, + _data: blocksData, + _author: contentPageData?.author, + _created_by: "eyJhbGciOiJSUzI1NiIsInR5cCIgO", + _modified_by: "eyJhbGciOiJSUzI1NiIsInR5cCIgO" + } + }; + var config = { + method: "post", + url: this.baseURL, + headers: { + "x-hasura-admin-secret": this.adminSecret, + "Content-Type": "application/json", + }, + data: data, + }; + + const responseData = await axios(config); + const response = responseData.data; + let final = { + ...response, + result: { + ContentPage: { osid: response.data.insert_content_pages_one.id }, + }, + }; + + return new SuccessResponse({ + statusCode: 200, + message: "ok.", + data: final, + }); + } + //to get details of a given content page + public async getContentPageData(contentPageSlug: string, request: any) { + var axios = require('axios'); + var data = { + query: `query getContentPageData($_slug: String!) { + content_pages(where: {slug: {_eq: $_slug}}) { + modified_at + slug + title + status + author + id + blocks(order_by: {block_index: asc}) { + block_data + block_index + block_type + id + } + } + } + `, + variables: { + _slug: contentPageSlug, + } + } + + var config = { + method: "post", + url: this.baseURL, + headers: { + "x-hasura-admin-secret": this.adminSecret, + "Content-Type": "application/json", + }, + data: data, + }; + + const responseData = await axios(config); + const response = responseData.data.data.content_pages; + + const responsedata = response.map( + (item: any) => new ContentPagesDto(item) + ); + + let x = new SuccessResponse({ + statusCode: 200, + message: "OK", + data: responsedata, + }); + return x; + } + + //to fetch content pages with given limit and offset + public async getContentPagesSet(request: any, limit?: number, offset?: number) { + var axios = require('axios'); + var data = { + query: `query getContentPagesSet($_limit: Int, $_offset: Int) { + content_pages(limit: $_limit, offset: $_offset, order_by: {modified_at: desc}) { + id + modified_at + slug + status + title + } + content_pages_aggregate(limit: $_limit, offset: $_offset) { + aggregate { + count + } + } + }`, + variables: { + _limit: limit, + _offset: offset, + } + } + + var config = { + method: "post", + url: this.baseURL, + headers: { + "x-hasura-admin-secret": this.adminSecret, + "Content-Type": "application/json", + }, + data: data, + }; + + const responseData = await axios(config); + const response = responseData.data.data.content_pages; + + const responsedata = response.map( + (item: any) => new ContentPagesDto(item) + ); + + let x = new SuccessResponse({ + statusCode: 200, + message: "OK", + data: { + count: responseData.data.data.content_pages_aggregate.aggregate.count, + data: responsedata + }, + }); + return x; + } + + //to update a content page data + public async updateContentPageData( + contentPageId: string, + request: any, + contentPageData: ContentPagesDto + ) { + var axios = require("axios"); + const pageId = parseInt(contentPageId); + const blocksData = contentPageData.blocks.map(({blockType, blockData }, idx) => ({ block_index: idx, block_type: blockType, block_data: blockData, content_page_id: contentPageId })); + + var updateData = { + query: `mutation update_content_page_data($_id: Int!, $_author: String, $_modified_at: timestamptz, $_modified_by: String, $_slug: String, $_title: String, $_status: String, $_data: [blocks_insert_input!] = {}) { + update_content_pages_by_pk(pk_columns: {id: $_id}, _set: {author: $_author, modified_at: $_modified_at, modified_by: $_modified_by, slug: $_slug, title: $_title, status: $_status}) { + id + slug + } + delete_blocks(where: {content_page_id: {_eq: $_id}}) { + affected_rows + } + insert_blocks(objects: $_data) { + affected_rows + } + } + `, + variables: { + _id: pageId, + _slug: contentPageData?.urlSlug, + _author: contentPageData?.author, + _status: contentPageData?.status, + _title: contentPageData?.title, + _modified_at: new Date().toISOString(), + _modified_by: "329uinfnf84f4jf94f", + _data: blocksData + }, + }; + var updateConfig = { + method: "post", + url: this.baseURL, + headers: { + "x-hasura-admin-secret": this.adminSecret, + "Content-Type": "application/json", + }, + data: updateData, + }; + + const responseData = await axios(updateConfig); + const response = responseData.data; + + return new SuccessResponse({ + statusCode: 200, + message: "ok.", + data: response, + }); + } + + //to delete a content page with given id + public async deleteContentPage(contentPageId: string, request: any) { + var axios = require("axios"); + var data = { + query: `mutation delete_content_page($_id: Int!) { + delete_content_pages_by_pk(id: $_id) { + id + } + }`, + variables: { + _id: parseInt(contentPageId) + }, + }; + var config = { + method: "post", + url: this.baseURL, + headers: { + "x-hasura-admin-secret": this.adminSecret, + "Content-Type": "application/json", + }, + data: data, + }; + + const responseData = await axios(config); + const response = responseData.data.data; + let x = new SuccessResponse({ + statusCode: 200, + message: "Deleted content page successfully", + data: response, + }); + return x; + } +} \ No newline at end of file diff --git a/src/app.module.ts b/src/app.module.ts index d7c16c6..d85171c 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -27,6 +27,7 @@ import { MonitorTrackingModule } from "./monitorTracking/monitorTracking.module" import { CourseModule } from "./course/course.module"; import { CourseTrackingModule } from "./courseTracking/courseTracking.module"; import { AnnouncementsModule } from "./announcements/announcements.module"; +import { ContentPagesModule } from './contentPages/contentPages.module'; import { RoleModule } from "./role/role.module"; import { WorkHistoryModule } from "./adapters/workHistory/workHistory.module"; @@ -66,4 +67,4 @@ import { WorkHistoryModule } from "./adapters/workHistory/workHistory.module"; controllers: [AppController], providers: [AppService], }) -export class AppModule {} +export class AppModule { } diff --git a/src/contentPages/content-pages.controller.spec.ts b/src/contentPages/content-pages.controller.spec.ts new file mode 100644 index 0000000..df82645 --- /dev/null +++ b/src/contentPages/content-pages.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ContentPagesController } from './contentPages.controller'; + +describe('ContentPagesController', () => { + let controller: ContentPagesController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [ContentPagesController], + }).compile(); + + controller = module.get(ContentPagesController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/contentPages/contentPages.controller.ts b/src/contentPages/contentPages.controller.ts new file mode 100644 index 0000000..e17371e --- /dev/null +++ b/src/contentPages/contentPages.controller.ts @@ -0,0 +1,117 @@ +import { + Body, + CacheInterceptor, + ClassSerializerInterceptor, + Controller, + Delete, + Get, + Inject, + Param, + Post, + Put, + Query, + Req, + Request, + SerializeOptions, + UseInterceptors, +} from "@nestjs/common"; +import { FileInterceptor } from "@nestjs/platform-express"; +import { + ApiBasicAuth, + ApiBody, + ApiConsumes, + ApiCreatedResponse, + ApiForbiddenResponse, + ApiOkResponse, +} from "@nestjs/swagger"; +import { IServicelocator } from "src/adapters/contentpagesservicelocator"; +import { + ContentPagesEsamwadService, + ESamwadContentPagesToken, +} from "src/adapters/esamwad/contentPages.adapter"; +import { ContentPagesDto } from "./dto/contentPages.dto"; + +@Controller('contentPages') +export class ContentPagesController { + constructor(private hasuraService: ContentPagesEsamwadService, + @Inject(ESamwadContentPagesToken) private eSamwadProvider: IServicelocator) { } + + + //to create a content page with blockdata + @Post() + @ApiConsumes("multipart/form-data") + @ApiBasicAuth("access-token") + @ApiCreatedResponse({ + description: "Content page created", + }) + @ApiBody({ type: ContentPagesDto }) + @ApiForbiddenResponse({ description: "Forbidden" }) + public async createContentPage( + @Req() request: Request, + @Body() contentPageData: ContentPagesDto + ) { + return this.eSamwadProvider.createContentPage(request, contentPageData); + } + + //to retreive details about a given content page + @Get("/:slug") + @UseInterceptors(ClassSerializerInterceptor, CacheInterceptor) + @ApiBasicAuth("access-token") + @ApiCreatedResponse({ description: "Retreived details about content page" }) + @ApiForbiddenResponse({ description: "Forbidden" }) + public async getContentPageData( + @Param("slug") contentPageSlug: string, + @Req() request: Request + ) { + return this.eSamwadProvider.getContentPageData(contentPageSlug, request); + } + + @Get("") + @UseInterceptors(ClassSerializerInterceptor, CacheInterceptor) + @ApiBasicAuth("access-token") + @ApiCreatedResponse({ description: "Fetched content pages set" }) + @ApiForbiddenResponse({ description: "Forbidden" }) + public async getContentPagesSet( + @Param("limit") limit: number, + @Param("offset") offset: number, + @Req() request: Request + ) { + return this.eSamwadProvider.getContentPagesSet(request, limit, offset); + } + + //to update content page with given id + @Put("/:id") + @ApiConsumes("multipart/form-data") + @ApiBasicAuth("access-token") + @ApiCreatedResponse({ + description: "Content page data has been updated successfully.", + }) + @ApiBody({ type: ContentPagesDto }) + @ApiForbiddenResponse({ description: "Forbidden" }) + @UseInterceptors(ClassSerializerInterceptor) + public async updateContentPageData( + @Param("id") contentPageId: string, + @Req() request: Request, + @Body() contentPageData: any + ) { + const updatedData = JSON.parse(contentPageData?.data); + return this.eSamwadProvider.updateContentPageData( + contentPageId, + request, + updatedData + ); + } + + //to delete content page + @Delete("/:id") + @UseInterceptors(ClassSerializerInterceptor, CacheInterceptor) + @ApiBasicAuth("access-token") + @ApiOkResponse({ description: "Deleted the content page successfully" }) + public async deleteContentPage( + @Param("id") contentPageId: string, + @Req() request: Request + ) { + return this.eSamwadProvider.deleteContentPage(contentPageId, request); + } + +} diff --git a/src/contentPages/contentPages.module.ts b/src/contentPages/contentPages.module.ts new file mode 100644 index 0000000..fc6250e --- /dev/null +++ b/src/contentPages/contentPages.module.ts @@ -0,0 +1,27 @@ +import { CacheModule, Module } from "@nestjs/common"; +import { HttpModule } from "@nestjs/axios"; +import { ScheduleModule } from "@nestjs/schedule"; +import { ContentPagesController } from './contentPages.controller'; +import { + ContentPagesEsamwadService, + ESamwadContentPagesToken, +} from "src/adapters/esamwad/contentPages.adapter"; + +const ttl = process.env.TTL as never; +@Module({ + imports: [ + HttpModule, + CacheModule.register({ + ttl: ttl, + }), + ScheduleModule.forRoot(), + ], + providers: [ + ContentPagesEsamwadService, { + provide: ESamwadContentPagesToken, + useClass: ContentPagesEsamwadService + } + ], + controllers: [ContentPagesController], +}) +export class ContentPagesModule { } diff --git a/src/contentPages/dto/block.dto.ts b/src/contentPages/dto/block.dto.ts new file mode 100644 index 0000000..14439c3 --- /dev/null +++ b/src/contentPages/dto/block.dto.ts @@ -0,0 +1,44 @@ +import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; +import { Expose } from "class-transformer"; + +export class BlockDto { + + @ApiProperty({ + type: String, + description: "The id of the block", + default: "", + }) + @Expose() + blockId: string; + + @ApiProperty({ + type: String, + description: "Type of the block", + default: "", + }) + @Expose() + blockType: string; + + @ApiProperty({ + type: Object, + description: "Data within the block", + default: {}, + }) + @Expose() + blockData: object; + + @ApiProperty({ + type: Number, + description: "Index of the block for relative ordering of blocks (higher index denotes lower position on the page", + default: 0, + }) + @Expose() + blockIndex: number; + + constructor(obj: any) { + this.blockId = obj?.id ?? ""; + this.blockType = obj?.block_type ?? ""; + this.blockData = obj?.block_data ?? {}; + this.blockIndex = obj?.block_index??0; + } +} \ No newline at end of file diff --git a/src/contentPages/dto/contentPages.dto.ts b/src/contentPages/dto/contentPages.dto.ts new file mode 100644 index 0000000..0e85125 --- /dev/null +++ b/src/contentPages/dto/contentPages.dto.ts @@ -0,0 +1,72 @@ +import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; +import { Expose } from "class-transformer"; +import { BlockDto } from "./block.dto"; + +export class ContentPagesDto { + + @ApiProperty({ + type: String, + description: "The id of the content-page ", + default: "", + }) + @Expose() + contentPageId: string; + + @ApiProperty({ + type: String, + description: "The slug of the content-page ", + default: "", + }) + @Expose() + urlSlug: string; + + @ApiProperty({ + type: String, + description: "The title of content page", + default: "", + }) + @Expose() + title: string; + + @ApiProperty({ + type: String, + description: "Date modified for content page", + default: "", + }) + @Expose() + dateModified: string; + + @ApiProperty({ + type: String, + description: "The status of the page-draft or published", + default: "", + }) + @Expose() + status: string; + + @ApiProperty({ + type: String, + description: "The author of the page", + default: "", + }) + @Expose() + author: string; + + @ApiProperty({ + type: Array, + description: "The blocks for the content page", + default: [] + }) + @Expose() + blocks: Array; + + constructor(obj: any) { + this.contentPageId = obj?.id ?? "", + this.author = obj?.author ?? "", + this.urlSlug = obj?.slug ?? "", + this.title = obj?.title ?? "", + this.dateModified = obj?.modified_at ?? "", + this.status = obj?.status ?? "", + this.blocks = obj?.blocks?.map((val:any) => (new BlockDto(val))) ?? [] + } +} \ No newline at end of file