Skip to content

Commit

Permalink
Merge pull request #2270 from zeitgeistpm/protected-ipfs-api
Browse files Browse the repository at this point in the history
Protected IPFS api
  • Loading branch information
yornaath authored Mar 4, 2024
2 parents eff41d2 + 5a61caf commit c779641
Show file tree
Hide file tree
Showing 9 changed files with 327 additions and 58 deletions.
10 changes: 10 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ NEXT_PUBLIC_MARKET_IMAGE_MAX_KB=100
NEXT_PUBLIC_BLOCK_TIME=12
NEXT_PUBLIC_MARKET_POLL_INTERVAL_MS=120000

#IPFS
NEXT_PUBLIC_IPFS_NODE_URL=https://ipfs.zeitgeist.pm
IPFS_CLUSTER_URL=https://ipfs-cluster.zeitgeist.pm
IPFS_CLUSTER_USERNAME=xxxx
IPFS_CLUSTER_PASSWORD=xxxx

NEXT_PUBLIC_SHOW_COURT=false
NEXT_PUBLIC_SHOW_CROSS_CHAIN=true

Expand Down Expand Up @@ -62,3 +68,7 @@ NEXT_PUBLIC_SHOW_TOPICS=true
# note: should be addresses of official zeitgeist markets or trusted third party creators.
NEXT_PUBLIC_WHITELISTED_TRUSTED_CREATORS=["xxxxxx"]

# IPFS node basic auth credentials
IPFS_NODE_BASIC_AUTH_USERNAME=xxxxx
IPFS_NODE_BASIC_AUTH_PASSWORD=xxxxx

6 changes: 4 additions & 2 deletions components/create/editor/Publishing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,9 @@ export const Publishing = ({ editor, creationParams }: PublishingProps) => {
? feeDetails?.assetId
: undefined,
);
const marketId = result.saturate().unwrap().market.marketId;

const { market } = result.saturate().unwrap();
const marketId = market.marketId;

editor.published(marketId);

Expand Down Expand Up @@ -198,7 +200,7 @@ export const Publishing = ({ editor, creationParams }: PublishingProps) => {
let errorMessage = "Unknown error occurred.";

if (StorageError.is(error)) {
errorMessage = "IPFS metadata upload failed.";
errorMessage = error?.message ?? "IPFS metadata upload failed.";
}

if (isArray(error?.docs)) {
Expand Down
7 changes: 4 additions & 3 deletions lib/hooks/useSdkv2.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Context, create$, Sdk, ZeitgeistIpfs } from "@zeitgeistpm/sdk";
import { Context, create$, Sdk } from "@zeitgeistpm/sdk";
import { atom, useAtom } from "jotai";
import { endpointOptions as endpoints, graphQlEndpoint } from "lib/constants";
import { createMetadataStorage } from "lib/metadata-storage";
import { memoize } from "lodash-es";
import { useEffect, useState } from "react";
import { Subscription } from "rxjs";
import { atom, useAtom } from "jotai";
import { usePrevious } from "./usePrevious";

export type UseSdkv2 = [
Expand Down Expand Up @@ -77,7 +78,7 @@ const init = memoize(
return create$({
provider: endpoints,
indexer: graphQlEndpoint,
storage: ZeitgeistIpfs(),
storage: createMetadataStorage(),
});
},
(endpoints, graphQlEndpoint) => identify(endpoints, graphQlEndpoint) ?? "--",
Expand Down
113 changes: 113 additions & 0 deletions lib/metadata-storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { u8aToString } from "@polkadot/util";
import { u8aConcat } from "@polkadot/util/u8a";
import {
MarketMetadata,
MetadataStorage,
createStorage,
} from "@zeitgeistpm/sdk";
import { Codec, JsonCodec } from "@zeitgeistpm/utility/dist/codec";
import * as O from "@zeitgeistpm/utility/dist/option";
import * as Te from "@zeitgeistpm/utility/dist/taskeither";
import { Storage, StorageError } from "@zeitgeistpm/web3.storage";
import * as IPFSHTTPClient from "ipfs-http-client";
import { CID } from "multiformats/cid";

const node = IPFSHTTPClient.create({
url: process.env.NEXT_PUBLIC_IPFS_NODE_URL,
});

export const createMetadataStorage = (): MetadataStorage<MarketMetadata> => {
const createInnerStorage = (
codec: Codec<
string | Uint8Array,
MarketMetadata
> = JsonCodec<MarketMetadata>(),
): Storage<MarketMetadata, CID> => {
return {
put: Te.from(
async (data) => {
const response = await fetch("/api/ipfs", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});

if (response.status === 400) {
const { message } = await response.json();
throw new Error(message);
}

const { cid: cidString } = await response.json();
const cid = CID.parse(cidString);

return cid;
},
(message, error) => new StorageError(message, error),
),
hash: Te.from(
async (data) => {
const response = await fetch("/api/ipfs?only-hash=true", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});

if (response.status === 400) {
const { message } = await response.json();
throw new Error(message);
}

const { cid: cidString } = await response.json();
const cid = CID.parse(cidString);

return cid;
},
(message, error) => new StorageError(message, error),
),
get: Te.from(
async (cid) => {
const data = (await read(node, cid).unwrap())
.map((chunks) => u8aConcat(...chunks))
.unwrap();

if (data) {
const parsed = JSON.parse(u8aToString(data));
return O.option(O.some(parsed));
}

return O.option(O.none());
},
(message, error) => new StorageError(message, error),
),
withCodec: <A>() => createInnerStorage(codec) as Storage<A, CID>,
provider: node,
};
};

return createStorage(createInnerStorage());
};

/**
* Read data from a cid and parse it to a string.
*/
const read = Te.from<
O.IOption<Uint8Array[]>,
Error,
[node: IPFSHTTPClient.IPFSHTTPClient, cid: IPFSHTTPClient.CID]
>(async (node, cid) => {
let content: Uint8Array[] = [];

for await (const chunk of node.cat(cid)) {
content = [...content, chunk];
}

new Blob(content);

if (content.length === 0) return O.option(O.none());

return O.option(O.some(content));
});
2 changes: 1 addition & 1 deletion lib/state/market-creation/types/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ export const marketFormDataToExtrinsicParams = (
},
metadata: {
__meta: "markets",
description: form.description,
description: form.description ?? "",
question: form.question,
slug: form.question,
tags: form.tags,
Expand Down
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@
"@zeitgeistpm/avatara-nft-sdk": "^1.3.1",
"@zeitgeistpm/avatara-react": "^1.3.2",
"@zeitgeistpm/avatara-util": "^1.2.0",
"@zeitgeistpm/sdk": "3.1.3",
"@zeitgeistpm/utility": "3.1.0",
"@zeitgeistpm/sdk": "3.2.1",
"@zeitgeistpm/utility": "3.2.1",
"axios": "^0.21.4",
"boring-avatars": "^1.6.1",
"decimal.js": "^10.4.3",
Expand All @@ -64,11 +64,14 @@
"fuse.js": "^6.6.2",
"graphql-request": "^5.0.0",
"groq": "^3.23.4",
"ipfs-http-client": "^60.0.1",
"ipfs-utils": "^9.0.14",
"jotai": "^2.0.4",
"jotai-tanstack-query": "^0.7.0",
"lodash.merge": "^4.6.2",
"median-range": "^0.0.11",
"moment-timezone": "^0.5.43",
"multiformats": "^13.0.1",
"next": "^13.4.19",
"next-absolute-url": "^1.2.2",
"next-qrcode": "^2.5.1",
Expand Down Expand Up @@ -102,7 +105,8 @@
"uri-js": "^4.4.1",
"use-debounce": "^7.0.1",
"validatorjs": "^3.22.1",
"zod": "^3.21.4"
"zod": "^3.21.4",
"zod-validation-error": "^3.0.2"
},
"devDependencies": {
"@next/bundle-analyzer": "^12.3.1",
Expand Down
106 changes: 106 additions & 0 deletions pages/api/ipfs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { create as createIPFSClient } from "ipfs-http-client";
import type { PageConfig } from "next";
import { NextApiRequest, NextApiResponse } from "next";
import { fromZodError } from "zod-validation-error";
import { IOMarketMetadata } from "./types";

export const config: PageConfig = {
runtime: "nodejs",
};

export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
if (req.method === "POST") {
return POST(req, res);
}
}

const POST = async (req: NextApiRequest, res: NextApiResponse) => {
const node = createIPFSClient({
url: process.env.NEXT_PUBLIC_IPFS_NODE_URL,
headers: {
Authorization: `Basic ${Buffer.from(
process.env.IPFS_NODE_BASIC_AUTH_USERNAME +
":" +
process.env.IPFS_NODE_BASIC_AUTH_PASSWORD,
).toString("base64")}`,
},
});

const parsed = IOMarketMetadata.safeParse(req.body);

const onlyHash = req.query["only-hash"] === "true" ? true : false;

if (!parsed.success) {
return res.status(400).json({
message: fromZodError(parsed.error).toString(),
});
}

const metadata = {
__meta: "markets",
...parsed.data,
};

const content = JSON.stringify(metadata);
const kbSize = Buffer.byteLength(content) / 1024;

if (kbSize > 10) {
return res.status(400).json({
message: "Market metadata is too large. Please keep it under 10kb.",
});
}
try {
const { cid } = await node.add(
{ content },
{
hashAlg: "sha3-384",
pin: !onlyHash,
onlyHash,
},
);

// if (!onlyHash) {
// await node.pin.add(cid);
// }

return res.status(200).json({
message: `Market metadata ${
onlyHash ? "hashed" : "pinned"
} successfully.`,
cid: cid.toString(),
});
} catch (error) {
return res.status(500).json({
message: error.message,
});
}
};

// const DELETE = async (req: NextRequest) => {
// const { cid } = JSON.parse(await extractBody(req));

// try {
// await node.pin.rm(IPFSHTTPClient.CID.parse(cid));

// return new Response(
// JSON.stringify({
// message: `Market metadata(cid: ${cid}) unpinned successfully.`,
// }),
// {
// status: 200,
// },
// );
// } catch (error) {
// return new Response(
// JSON.stringify({
// message: error.message,
// }),
// {
// status: 500,
// },
// );
// }
// };
18 changes: 18 additions & 0 deletions pages/api/ipfs/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as z from "zod";

export const IOMarketMetadata = z.object({
question: z.string(),
description: z.optional(z.string()),
tags: z.optional(z.array(z.string())),
slug: z.optional(z.string()),
categories: z.optional(
z.array(
z.object({
name: z.string(),
ticker: z.optional(z.string()),
img: z.optional(z.string()),
color: z.optional(z.string()),
}),
),
),
});
Loading

0 comments on commit c779641

Please sign in to comment.