Skip to content

Commit

Permalink
Apillon integration
Browse files Browse the repository at this point in the history
  • Loading branch information
jvonasek committed Aug 30, 2024
1 parent 43dd7b8 commit 4ef3055
Show file tree
Hide file tree
Showing 20 changed files with 628 additions and 124 deletions.
3 changes: 2 additions & 1 deletion .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ VITE_EVM_PROVIDER_URL=https://rpc.nice.hydration.cloud
VITE_EVM_EXPLORER_URL=https://explorer.nice.hydration.cloud
VITE_EVM_NATIVE_ASSET_ID=20
VITE_MIGRATION_TRIGGER_DOMAIN="deploy-preview-1334--testnet-hydra-app.netlify.app"
VITE_MIGRATION_TARGET_DOMAIN="testnet-app.hydradx.io"
VITE_MIGRATION_TARGET_DOMAIN="testnet-app.hydradx.io"
VITE_MEMEPAD_APILLON_BUCKET_UUID="1b216a6c-704f-49c4-b5d7-6ae4a16e6f25"
3 changes: 2 additions & 1 deletion .env.production
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ VITE_EVM_PROVIDER_URL=https://rpc.hydradx.cloud
VITE_EVM_EXPLORER_URL=https://explorer.evm.hydration.cloud
VITE_EVM_NATIVE_ASSET_ID=20
VITE_MIGRATION_TRIGGER_DOMAIN="app.hydradx.io"
VITE_MIGRATION_TARGET_DOMAIN="app.hydration.net"
VITE_MIGRATION_TARGET_DOMAIN="app.hydration.net"
VITE_MEMEPAD_APILLON_BUCKET_UUID="1b216a6c-704f-49c4-b5d7-6ae4a16e6f25"
3 changes: 2 additions & 1 deletion .env.rococo
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ VITE_EVM_PROVIDER_URL=
VITE_EVM_EXPLORER_URL=
VITE_EVM_NATIVE_ASSET_ID=20
VITE_MIGRATION_TRIGGER_DOMAIN=""
VITE_MIGRATION_TARGET_DOMAIN=""
VITE_MIGRATION_TARGET_DOMAIN=""
VITE_MEMEPAD_APILLON_BUCKET_UUID="1b216a6c-704f-49c4-b5d7-6ae4a16e6f25"
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -160,25 +160,30 @@
"@storybook/react-vite": "^8.1.1",
"@storybook/test": "^8.1.1",
"@types/color": "^3.0.3",
"@types/cors": "^2.8.17",
"@types/jest": "^27.5.2",
"@types/md5": "^2.3.2",
"@types/multer": "^1.4.12",
"@types/node": "^16.11.47",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"@vitejs/plugin-react": "^4.2.1",
"babel-plugin-named-exports-order": "^0.0.2",
"cors": "^2.8.5",
"eslint": "8.50.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-no-relative-import-paths": "^1.5.2",
"eslint-plugin-prettier": "5.0.0",
"eslint-plugin-unused-imports": "^3.0.0",
"multer": "^1.4.5-lts.1",
"npm-run-all": "^4.1.5",
"prettier": "^3.3.2",
"prop-types": "^15.8.1",
"storybook": "^8.1.1",
"ts-node": "^10.9.1",
"unplugin-fonts": "^1.1.1",
"vite": "4.4.6",
"vite-plugin-api-routes": "^1.1.10",
"vite-plugin-svgr": "^4.2.0",
"vite-tsconfig-paths": "^4.2.1",
"vitest": "^1.6.0"
Expand Down
240 changes: 240 additions & 0 deletions server/api/apillon/upload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
import multer from "multer"

const APILLON_STORAGE_ENDOINT = "https://api.apillon.io/storage"

const API_KEY = "94b3fc0f-fb40-494a-8fef-c416421842ae"

Check failure

Code scanning / CodeQL

Hard-coded credentials Critical

The hard-coded value "94b3fc0f-fb40-494a-8fef-c416421842ae" is used as
authorization header
.
const API_SECRET = "6wlRtA8BxQId"

Check failure

Code scanning / CodeQL

Hard-coded credentials Critical

The hard-coded value "6wlRtA8BxQId" is used as
authorization header
.
const CREDENTIALS = `${API_KEY}:${API_SECRET}`

const storage = multer.memoryStorage()
const upload = multer({
storage: storage,
limits: { fileSize: 5 * 1024 * 1024 },
})

const toObject = (data: any) => {
return JSON.parse(JSON.stringify(data))
}

async function callAPI(
method: "POST" | "GET",
url: string,
data: any = null,
): Promise<any> {
const options: RequestInit = {
method,
headers: {
Authorization: `Basic ${btoa(CREDENTIALS)}`,
"Content-Type": "application/json",
},
}

if (method === "POST" && data) {
options.body = JSON.stringify(data)
} else if (method === "GET" && data) {
const params = new URLSearchParams(data).toString()
url = `${url}?${params}`
}

try {
const response = await fetch(url, options)
const statusCode = response.status
const result = await response.json()

switch (statusCode) {
case 200:
case 201:
// Success or creation successful
break
case 400:
return toObject({
error: "Bad request. Check the request data and try again.",
})
case 401:
return toObject({
error: "Unauthorized. Invalid API key or API key secret.",
})
case 403:
return toObject({
error:
"Forbidden. Insufficient permissions or unauthorized access to record.",
})
case 404:
return toObject({
error: "Path not found. Invalid endpoint or resource.",
})
case 422:
return toObject({
error: "Data validation failed. Invalid or missing fields.",
})
case 500:
return toObject({
error: "Internal server error. Please try again later.",
})
default:
return toObject({ error: `Received HTTP code ${statusCode}` })
}

return toObject({
id: result.id ?? null,
status: statusCode,
data: result.data ?? null,
})
} catch (error) {
return toObject({ error: error.message })
}
}

type FileDataInput = { fileName: string; contentType: string; file: Buffer }

type FileResponse = {
path?: string
fileName: string
contentType: string
fileUuid: string
}

// Upload to bucket
async function uploadToBucket(
fileData: FileDataInput[],
bucketUuid: string,
wrapWithDirectory = false,
directoryPath = "",
) {
const responseJson: {
status: string
message: string
data: FileResponse[]
} = {
status: "",
message: "",
data: [],
}

const files = fileData.map((data) => ({
fileName: data.fileName,
contentType: data.contentType,
}))

if (files.length === 0) {
responseJson.status = "error"
responseJson.message = "No files to upload."
return responseJson
}

const url = `${APILLON_STORAGE_ENDOINT}/buckets/${bucketUuid}/upload`
const postData = { files }

const response = await callAPI("POST", url, postData)

if (response.error) {
responseJson.status = "error"
responseJson.message = `API error: ${response.error}`
return responseJson
}

if (response.data?.sessionUuid) {
const sessionUuid = response.data.sessionUuid
const uploadUrls = response.data.files

const uploadedFiles: {
path?: string
fileName: string
contentType: string
fileUuid: string
}[] = []

// Perform file uploads
for (const [index, data] of fileData.entries()) {
const { url, ...fileProps } = uploadUrls[index]
const file = data.file

try {
await fetch(url, {
method: "PUT",
body: file,
})

uploadedFiles.push(fileProps)
} catch (error) {
responseJson.status = "error"
responseJson.message = `File upload error: ${error.message}`
return responseJson
}
}

await endUploadSession(
sessionUuid,
bucketUuid,
wrapWithDirectory,
directoryPath,
)

responseJson.status = "success"
responseJson.message = "Files uploaded successfully"
responseJson.data = uploadedFiles
} else {
responseJson.status = "error"
responseJson.message = "Failed to start upload session."
}

return responseJson
}

async function endUploadSession(
sessionUuid: string,
bucketUuid: string,
wrapWithDirectory = false,
directoryPath = "",
) {
const url = `${APILLON_STORAGE_ENDOINT}/buckets/${bucketUuid}/upload/${sessionUuid}/end`
const data = {
wrapWithDirectory,
directoryPath,
}

return callAPI("POST", url, data)
}

export const POST = async (req, res) => {
upload.single("file")(req, res, async (err) => {
if (err) {
return res.status(500).send({ status: "error", message: err.message })
}

const bucketUuid = req.body.bucketUuid

if (!bucketUuid) {
return res.status(400).send({
status: "error",
message: "Missing required field: bucketUuid",
})
}

const fileArr = req.file
? [
{
fileName: req.body.fileName || req.file.originalname,
contentType: req.file.mimetype,
file: req.file.buffer,
},
]
: []

if (!fileArr.length) {
return res.status(400).send({
status: "error",
message: "No files to upload",
})
}

const response = await uploadToBucket(fileArr, bucketUuid)

storage._removeFile(req, req.file, (err) => {
if (err) {
return res.status(500).send({ status: "error", message: err.message })
}
res.send(response)
})
})
}
3 changes: 3 additions & 0 deletions server/api/foo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const GET = async (req, res) => {
res.send({ status: "success", message: "Hello from the foo endpoint" })
}
20 changes: 20 additions & 0 deletions server/configure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import cors from "cors"
import { HandlerHook } from "vite-plugin-api-routes/model"

const allowedOrigins = ["https://app.hydration.net", "https://app.hydradx.io"]

const corsOptions = {
origin: (origin, callback) => {
callback(null, true)
return
if (allowedOrigins.indexOf(origin) !== -1) {
callback(null, true)
} else {
callback(new Error("Access denied"))
}
},
}

export const handlerBefore: HandlerHook = (handler) => {
handler.use(cors(corsOptions))
}
12 changes: 12 additions & 0 deletions src/api/external/assethub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ export const assethubNativeToken = assethub.assetsData.get(
"dot",
) as ParachainAssetsData

// TEMP CHOPSTICKS SETUP
if (window.location.hostname === "localhost") {
//@ts-ignore
assethub.ws = "ws://172.25.126.217:8000"
const hydradx = chainsMap.get("hydradx") as Parachain
//@ts-ignore
hydradx.ws = "ws://172.25.126.217:8001"
const polkadot = chainsMap.get("polkadot") as Parachain
//@ts-ignore
polkadot.ws = "ws://172.25.126.217:8002"
}

export const getAssetHubAssets = async (api: ApiPromise) => {
try {
const [dataRaw, assetsRaw] = await Promise.all([
Expand Down
20 changes: 17 additions & 3 deletions src/components/FileUploader/FileUploader.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export const SContainer = styled.div<{ error?: boolean }>`
${({ error }) => (error ? theme.colors.red400 : theme.colors.basic600)};
border-radius: ${theme.borderRadius.default}px;
&:hover {
border-color: ${theme.colors.brightBlue300};
}
&.drag-over {
border-color: ${theme.colors.brightBlue300};
background-color: ${theme.colors.darkBlue401};
Expand Down Expand Up @@ -46,16 +50,26 @@ export const SUploadPreview = styled.div`
`

export const SClearButton = styled.button`
background-color: ${theme.colors.basic800};
border: none;
position: absolute;
top: 4px;
right: 4px;
display: inline-flex;
justify-content: center;
align-items: center;
width: 24px;
height: 24px;
padding: 4px;
color: ${theme.colors.basic400};
cursor: pointer;
background-color: ${theme.colors.basic800};
border: none;
border-radius: ${theme.borderRadius.default}px;
cursor: pointer;
:hover,
:focus {
background-color: ${theme.colors.basic700};
Expand Down
Loading

0 comments on commit 4ef3055

Please sign in to comment.