diff --git a/db/migrations/1717941249030-Data.js b/db/migrations/1717941249030-Data.js new file mode 100644 index 0000000..cf4ffa6 --- /dev/null +++ b/db/migrations/1717941249030-Data.js @@ -0,0 +1,11 @@ +module.exports = class Data1717941249030 { + name = 'Data1717941249030' + + async up(db) { + await db.query(`ALTER TABLE "project" ADD "description_html" text`) + } + + async down(db) { + await db.query(`ALTER TABLE "project" DROP COLUMN "description_html"`) + } +} diff --git a/package-lock.json b/package-lock.json index 46c7fe7..4ddebc4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "ethers": "^6.12.1", "node-cron": "^3.0.3", "pg": "^8.11.5", + "showdown": "^2.1.0", "type-graphql": "^1.2.0-rc.1", "typeorm": "^0.3.20", "zod": "^3.23.8" @@ -27,6 +28,7 @@ "@types/jest": "^29.5.12", "@types/node": "^20.11.17", "@types/node-cron": "^3.0.11", + "@types/showdown": "^2.0.6", "jest": "^29.7.0", "prettier": "^3.3.0", "ts-jest": "^29.1.2", @@ -4369,6 +4371,12 @@ "@types/node": "*" } }, + "node_modules/@types/showdown": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/showdown/-/showdown-2.0.6.tgz", + "integrity": "sha512-pTvD/0CIeqe4x23+YJWlX2gArHa8G0J0Oh6GKaVXV7TAeickpkkZiNOgFcFcmLQ5lB/K0qBJL1FtRYltBfbGCQ==", + "dev": true + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -11351,6 +11359,29 @@ "node": ">=8" } }, + "node_modules/showdown": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/showdown/-/showdown-2.1.0.tgz", + "integrity": "sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==", + "dependencies": { + "commander": "^9.0.0" + }, + "bin": { + "showdown": "bin/showdown.js" + }, + "funding": { + "type": "individual", + "url": "https://www.paypal.me/tiviesantos" + } + }, + "node_modules/showdown/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/side-channel": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz", diff --git a/package.json b/package.json index 6d3fc96..d1ef37c 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "ethers": "^6.12.1", "node-cron": "^3.0.3", "pg": "^8.11.5", + "showdown": "^2.1.0", "type-graphql": "^1.2.0-rc.1", "typeorm": "^0.3.20", "zod": "^3.23.8" @@ -33,6 +34,7 @@ "@types/jest": "^29.5.12", "@types/node": "^20.11.17", "@types/node-cron": "^3.0.11", + "@types/showdown": "^2.0.6", "jest": "^29.7.0", "prettier": "^3.3.0", "ts-jest": "^29.1.2", diff --git a/schema.graphql b/schema.graphql index 63e17fb..8412066 100644 --- a/schema.graphql +++ b/schema.graphql @@ -51,6 +51,8 @@ type Project @entity { title: String "Description of the project" description: String + "Html format of description" + descriptionHtml: String "Total attests with value True" totalVouches: Int! "Total attests with value False" diff --git a/src/features/import-projects/gitcoin/constants.ts b/src/features/import-projects/gitcoin/constants.ts index a90cc2a..5571b94 100644 --- a/src/features/import-projects/gitcoin/constants.ts +++ b/src/features/import-projects/gitcoin/constants.ts @@ -13,4 +13,5 @@ export const gitcoinSourceConfig: SourceConfig = { descriptionField: "description", imageField: "image", urlField: "url", + descriptionHtmlField: "descriptionHtml", }; diff --git a/src/features/import-projects/gitcoin/helpers.ts b/src/features/import-projects/gitcoin/helpers.ts index 1d2d6e2..a4b502a 100644 --- a/src/features/import-projects/gitcoin/helpers.ts +++ b/src/features/import-projects/gitcoin/helpers.ts @@ -1,6 +1,7 @@ import type { GitcoinProjectInfo } from "./type"; import { updateOrCreateProject } from "../helpers"; import { IPFS_GATEWAY, gitcoinSourceConfig } from "./constants"; +import Showdown from "showdown"; const generateGitcoinUrl = (project: GitcoinProjectInfo) => { const application = project.applications[0]; @@ -21,19 +22,24 @@ const convertIpfsHashToHttps = (hash: string) => { return `${IPFS_GATEWAY}/${hash}`; }; +const converter = new Showdown.Converter(); export const processProjectsBatch = async ( projectsBatch: GitcoinProjectInfo[] ) => { for (const project of projectsBatch) { if (project.metadata?.type !== "project") continue; + const description = project.metadata?.description; const processedProject = { id: project.id, title: project.name || project.metadata?.title, - description: project.metadata?.description, + description, url: generateGitcoinUrl(project), image: project.metadata?.bannerImg ? convertIpfsHashToHttps(project.metadata?.bannerImg) : "", + descriptionHtml: description + ? converter.makeHtml(description) + : undefined, }; await updateOrCreateProject(processedProject, gitcoinSourceConfig); } diff --git a/src/features/import-projects/helpers.ts b/src/features/import-projects/helpers.ts index 20722ea..543f6a6 100644 --- a/src/features/import-projects/helpers.ts +++ b/src/features/import-projects/helpers.ts @@ -11,6 +11,7 @@ export const updateOrCreateProject = async ( idField, titleField, descriptionField, + descriptionHtmlField, urlField, imageField, } = sourConfig; @@ -32,20 +33,28 @@ export const updateOrCreateProject = async ( .where("project.id = :id", { id }) .getOne(); + const title = project[titleField]; + const description = project[descriptionField]; + const url = project[urlField]; + const image = project[imageField]; + const descriptionHtml = descriptionHtmlField && project[descriptionHtmlField]; + if (existingProject) { const isUpdated = - existingProject.title !== project[titleField] || - existingProject.description !== project[descriptionField] || - existingProject.url !== project[urlField] || - existingProject.image !== project[imageField]; + existingProject.title !== title || + existingProject.description !== description || + existingProject.url !== url || + existingProject.image !== image || + existingProject.descriptionHtml !== descriptionHtml; if (isUpdated) { const updatedProject = new Project({ ...existingProject, - title: project[titleField], - description: project[descriptionField], - image: project[imageField], - url: project[urlField], + title, + description, + image, + url, + descriptionHtml, lastUpdatedTimestamp: new Date(), imported: true, }); @@ -64,12 +73,13 @@ export const updateOrCreateProject = async ( } else { const newProject = new Project({ id, - title: project[titleField], - description: project[descriptionField], - image: project[imageField], - url: project[urlField], - projectId: projectId, - source: source, + title, + description, + image, + url, + descriptionHtml, + projectId, + source, totalVouches: 0, totalFlags: 0, totalAttests: 0, diff --git a/src/features/import-projects/types.ts b/src/features/import-projects/types.ts index ccdbb74..9444c0f 100644 --- a/src/features/import-projects/types.ts +++ b/src/features/import-projects/types.ts @@ -3,6 +3,7 @@ interface SourceConfig { idField: string; titleField: string; descriptionField: string; + descriptionHtmlField?: string; urlField: string; imageField: string; } diff --git a/src/model/generated/project.model.ts b/src/model/generated/project.model.ts index 74e898b..8f092c3 100644 --- a/src/model/generated/project.model.ts +++ b/src/model/generated/project.model.ts @@ -40,6 +40,12 @@ export class Project { @Column_("text", {nullable: true}) description!: string | undefined | null + /** + * Html format of description + */ + @Column_("text", {nullable: true}) + descriptionHtml!: string | undefined | null + /** * Total attests with value True */