diff --git a/api/src/app.ts b/api/src/app.ts index fda8a96..098f11b 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -3,11 +3,13 @@ import cors from "cors"; import express, { Request, Response } from "express"; import "express-async-errors"; import path from "path"; -import templateRouter from "./routes/template"; +import templateRouterV1 from "./routes/template-v1"; +import templateRouterV2 from "./routes/template-v2"; import auditorsRouter from "./routes/auditors"; import { TemplateService } from "./services/template"; const V1 = "/v1/"; +const V2 = "/v2/"; const corsOptions = { origin: "*", @@ -26,7 +28,8 @@ const initApp = ( app.use(cors(corsOptions)); app.use(json()); app.use(urlencoded({ extended: false })); - app.use(V1, templateRouter(templateService, namesJSONFile)); + app.use(V1, templateRouterV1(templateService, namesJSONFile)); + app.use(V2, templateRouterV2(templateService, namesJSONFile)); app.use(V1, auditorsRouter(auditorsJSONFile)); app.all("*", async (req: Request, res: Response) => { diff --git a/api/src/config.ts b/api/src/config.ts index 552e573..7806fe0 100644 --- a/api/src/config.ts +++ b/api/src/config.ts @@ -22,11 +22,7 @@ export function getConfig(env) { const templateManifestFile = env.TEMPLATE_MANIFEST_FILE; - console.log("ENV: ", env); - console.log("accessApi", accessApi); - console.log("databaseMigrationPath: ", databaseMigrationPath); - - return { + const _CONFIG = { port, accessApi, dbPath, @@ -38,4 +34,6 @@ export function getConfig(env) { peers, templateManifestFile, }; + + return _CONFIG } diff --git a/api/src/migrations/20220718235815_create_templates_table.ts b/api/src/migrations/20220718235815_create_templates_table.ts index 2fad367..4cc9b4c 100644 --- a/api/src/migrations/20220718235815_create_templates_table.ts +++ b/api/src/migrations/20220718235815_create_templates_table.ts @@ -7,6 +7,8 @@ export async function up(knex: Knex): Promise { table.json("json_string"); table.text("mainnet_cadence_ast_sha3_256_hash"); table.text("testnet_cadence_ast_sha3_256_hash"); + table.text("messages_title_enUS"); + table.text("messages_description_enUS"); table.timestamps(true, true); }); } diff --git a/api/src/models/template.ts b/api/src/models/template.ts index a7fa57d..b37d0f4 100644 --- a/api/src/models/template.ts +++ b/api/src/models/template.ts @@ -5,6 +5,8 @@ class Template extends BaseModel { json_string!: string; testnet_cadence_ast_sha3_256_hash!: string; mainnet_cadence_ast_sha3_256_hash!: string; + messages_title_enUS?: string; + messages_description_enUS?: string; static get tableName() { return "templates"; diff --git a/api/src/routes/template.ts b/api/src/routes/template-v1.ts similarity index 99% rename from api/src/routes/template.ts rename to api/src/routes/template-v1.ts index c6c5335..1f15058 100644 --- a/api/src/routes/template.ts +++ b/api/src/routes/template-v1.ts @@ -168,4 +168,4 @@ function templateRouter( return router; } -export default templateRouter; +export default templateRouter; \ No newline at end of file diff --git a/api/src/routes/template-v2.ts b/api/src/routes/template-v2.ts new file mode 100644 index 0000000..3786974 --- /dev/null +++ b/api/src/routes/template-v2.ts @@ -0,0 +1,185 @@ +import express, { Request, Response, Router } from "express"; +import { body } from "express-validator"; +import { validateRequest } from "../middlewares/validate-request"; +import { TemplateService } from "../services/template"; +import { genHash } from "../utils/gen-hash"; +import { mixpanelTrack } from "../utils/mixpanel"; +import { parseCadence } from "../utils/parse-cadence"; + +function templateRouter( + templateService: TemplateService, + namesJSONFile: JSON +): Router { + const router = express.Router(); + + router.get("/templates", async (req: Request, res: Response) => { + const name = req.query.name as string; + + if (!name) { + mixpanelTrack("get_template_by_name", { + name, + status: 400, + }); + + res.status(400); + return res.send( + `GET /templates-- Required query parameter "name" not provided.` + ); + } + + let templateId: string = ""; + let _name: string = name; + while (_name !== undefined) { + let foundName = namesJSONFile[_name]; + if (foundName !== undefined) templateId = foundName; + _name = foundName; + } + + const template = await templateService.getTemplate(templateId); + + if (!template) { + mixpanelTrack("get_template_by_name", { + name, + templateId, + status: 204, + }); + + res.status(204); + return res.send( + `GET /templates/:template_id -- Did not find template for template_id=${templateId}` + ); + } + + mixpanelTrack("get_template_by_name", { + name, + templateId, + status: 200, + }); + + return res.send(template); + }); + + router.get("/templates/manifest", async (req: Request, res: Response) => { + const templateManifest = await templateService.getTemplateManifest(); + + if (!templateManifest) { + mixpanelTrack("get_template_manifest", { + status: 204, + }); + + res.status(204); + return res.send( + `GET /templates/manifest -- Did not find template manifest` + ); + } + + mixpanelTrack("get_template_manifest", { + status: 200, + }); + + return res.send(templateManifest); + }); + + router.get("/templates/:template_id", async (req: Request, res: Response) => { + const templateId = req.params.template_id; + + const template = await templateService.getTemplate(templateId); + + if (!template) { + mixpanelTrack("get_template", { + templateId, + status: 204, + }); + + res.status(204); + return res.send( + `GET /templates/:template_id -- Did not find template for template_id=${templateId}` + ); + } + + mixpanelTrack("get_template", { + templateId, + status: 200, + }); + + return res.send(template); + }); + + router.post("/templates/search/messages", async (req: Request, res: Response) => { + const page = req.body.page as number || undefined; + const range = req.body.range as number || undefined; + const searchMessagesTitleENUS = req.body.searchMessagesTitleENUS as string || undefined; + const searchMessagesDescriptionENUS = req.body.searchMessagesDescriptionENUS as string || undefined; + + try { + const templates = await templateService.searchTemplates( + page ?? 0, + range ?? 100, + searchMessagesTitleENUS, + searchMessagesDescriptionENUS + ) + return res.send(templates) + + } catch (e) { + mixpanelTrack("search_template_messages", { + page, + range, + searchMessagesTitleENUS, + searchMessagesDescriptionENUS, + status: 400, + }); + res.status(400); + return res.send("POST /templates/search/messages -- Error occurred when getting template"); + } + }) + + router.post("/templates/search/cadence", async (req: Request, res: Response) => { + const cadence_base64 = req.body.cadence_base64 as string; + const network = req.body.network as string + + let cadence = Buffer.from(cadence_base64, "base64").toString("utf8"); + let cadenceAST = await parseCadence(cadence); + + let template; + try { + template = await templateService.getTemplateByCadenceASTHash( + await genHash(cadenceAST), + network + ); + } catch (e) { + mixpanelTrack("search_template", { + cadence_ast_hash: await genHash(cadenceAST), + network, + status: 400, + }); + + res.status(400); + return res.send("POST /templates/search/cadence -- Error occurred when getting template"); + } + + if (!template) { + mixpanelTrack("search_template", { + cadence_ast_hash: await genHash(cadenceAST), + network, + status: 204, + }); + res.status(204); + return res.send( + `POST /templates/search/cadence -- Did not find template for network=${network} cadence=${cadence_base64}` + ); + } + + mixpanelTrack("search_template", { + cadence_ast_hash: await genHash(cadenceAST), + network, + found_template_id: template.id, + status: 200, + }); + + return res.send(template); + }); + + return router; +} + +export default templateRouter; diff --git a/api/src/services/template.ts b/api/src/services/template.ts index f086644..4188351 100644 --- a/api/src/services/template.ts +++ b/api/src/services/template.ts @@ -1,5 +1,6 @@ import * as fcl from "@onflow/fcl"; import fetch from "node-fetch"; +import fs from "fs"; import { Template } from "../models/template"; import { readFiles } from "../utils/read-files"; import { writeFile } from "../utils/write-file"; @@ -44,6 +45,41 @@ class TemplateService { return foundTemplateJson; } + async searchTemplates( + page: number = 0, + range: number = 100, + searchMessagesTitleENUS?: string, + searchMessagesDescriptionENUS?: string + ) { + let queryBuilder = Template.query().page(page, range); + + if (searchMessagesTitleENUS) { + queryBuilder = queryBuilder.where("messages_title_enUS", "like", `%${searchMessagesTitleENUS}%`) + } + + if (searchMessagesDescriptionENUS) { + queryBuilder = queryBuilder.where("messages_description_enUS", "like", `%${searchMessagesDescriptionENUS}%`) + } + + const foundTemplatesQueryResult = await queryBuilder + + const foundTemplates = foundTemplatesQueryResult.results.map(foundTemplate => { + let foundTemplateJson = foundTemplate?.json_string || null; + if (typeof foundTemplateJson === "string") { + foundTemplateJson = JSON.parse(foundTemplateJson); + return foundTemplateJson; + } + return null + }).filter(foundTemplate => foundTemplate !== null) + + return { + page, + range, + count: foundTemplatesQueryResult.total, + results: foundTemplates, + } + } + async getTemplateByCadenceASTHash(cadenceASTHash: string, network: string) { let foundTemplate: Template | null = null; @@ -71,17 +107,12 @@ class TemplateService { } async getTemplateManifest() { - let templateManifest; try { - templateManifest = ( - await readFiles(this.config.templateManifestFile) - ).map((file: any) => file.content)[0]; - templateManifest = JSON.parse(templateManifest); + return JSON.parse(fs.readFileSync(this.config.templateManifestFile, "utf8")) } catch (e) { console.error("Error reading manifest file"); return null; } - return templateManifest; } async seed() { @@ -186,6 +217,8 @@ class TemplateService { testnet_cadence_ast_sha3_256_hash: testnet_cadence ? await genHash(await parseCadence(testnet_cadence)) : undefined, + messages_title_enUS: parsedTemplate?.data?.messages?.title?.i18n?.["en-US"], + messages_description_enUS: parsedTemplate?.data?.messages?.description?.i18n?.["en-US"] }); templateManifest[parsedTemplate.id] = parsedTemplate;