diff --git a/src/documentation.js b/src/documentation.js deleted file mode 100644 index 195d0fd..0000000 --- a/src/documentation.js +++ /dev/null @@ -1,367 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.docs = void 0; -exports.docs = `openapi: 3.0.3 -info: - title: AMBRodeo API - description: API documentation for AMBRodeo API - version: "1.0.0" -components: - headers: - SignatureHeader: - description: Signature for secret message. - schema: - type: string - AddressHeader: - description: Wallet address header used for authentication. - schema: - type: string - parameters: - GlobalSignatureHeader: - name: Signature - in: header - required: true - description: Signature for secret message. - schema: - type: string - GlobalAddressHeader: - name: Address - in: header - required: true - description: Wallet address header. - schema: - type: string - -paths: - /: - get: - summary: Root endpoint - responses: - "200": - description: Empty JSON response - content: - application/json: - schema: - type: object - /upload: - post: - summary: Upload a file - parameters: - - $ref: '#/components/parameters/GlobalSignatureHeader' - - $ref: '#/components/parameters/GlobalAddressHeader' - requestBody: - content: - multipart/form-data: - schema: - type: object - properties: - file: - type: string - format: binary - responses: - "200": - description: File uploaded successfully - content: - application/json: - schema: - type: object - properties: - success: - type: boolean - message: - type: string - filename: - type: string - "400": - description: No file uploaded - content: - application/json: - schema: - type: object - properties: - error: - type: string - /api/user: - post: - summary: Add or update a user - parameters: - - $ref: '#/components/parameters/GlobalSignatureHeader' - - $ref: '#/components/parameters/GlobalAddressHeader' - requestBody: - content: - application/json: - schema: - type: object - properties: - userName: - type: string - image: - type: string - responses: - "200": - description: User added or updated - "400": - description: Invalid JSON payload - content: - application/json: - schema: - type: object - properties: - error: - type: string - "500": - description: Server error - content: - application/json: - schema: - type: object - properties: - error: - type: string - get: - summary: Get user by address - parameters: - - $ref: '#/components/parameters/GlobalAddressHeader' - responses: - "200": - description: User data - content: - application/json: - schema: - type: object - properties: - address: - type: string - userName: - type: string - image: - type: string - "500": - description: Server error - content: - application/json: - schema: - type: object - properties: - error: - type: string - /api/message: - post: - summary: Add a message - parameters: - - $ref: '#/components/parameters/GlobalSignatureHeader' - - $ref: '#/components/parameters/GlobalAddressHeader' - requestBody: - content: - application/json: - schema: - type: object - properties: - tokenAddress: - type: string - message: - type: string - responses: - "200": - description: Message added successfully - "400": - description: Invalid JSON payload or missing required fields - content: - application/json: - schema: - type: object - properties: - error: - type: string - "500": - description: Server error - content: - application/json: - schema: - type: object - properties: - error: - type: string - /api/like: - post: - summary: Add or remove a like for a token - parameters: - - $ref: '#/components/parameters/GlobalSignatureHeader' - - $ref: '#/components/parameters/GlobalAddressHeader' - requestBody: - content: - application/json: - schema: - type: object - properties: - tokenAddress: - type: string - like: - type: boolean - responses: - "200": - description: Like added or removed successfully - "400": - description: Invalid JSON payload or missing required fields - content: - application/json: - schema: - type: object - properties: - error: - type: string - "500": - description: Server error - content: - application/json: - schema: - type: object - properties: - error: - type: string - /api/messages: - get: - summary: Get messages for a specific token - parameters: - - name: tokenAddress - in: query - required: true - schema: - type: string - - name: skip - in: query - required: false - schema: - type: integer - - name: limit - in: query - required: false - schema: - type: integer - responses: - "200": - description: List of messages - content: - application/json: - schema: - type: array - items: - type: object - properties: - address: - type: string - tokenAddress: - type: string - message: - type: string - timestamp: - type: string - format: date-time - "500": - description: Server error - content: - application/json: - schema: - type: object - properties: - error: - type: string - /api/token: - get: - summary: Get token by address - parameters: - - name: tokenAddress - in: query - required: true - schema: - type: string - responses: - "200": - description: Token data - content: - application/json: - schema: - type: object - properties: - tokenAddress: - type: string - like: - type: integer - timestamp: - type: string - format: date-time - "500": - description: Server error - content: - application/json: - schema: - type: object - properties: - error: - type: string - /api/likes: - get: - summary: Get likes for a user - parameters: - - $ref: '#/components/parameters/GlobalAddressHeader' - - name: skip - in: query - required: false - schema: - type: integer - - name: limit - in: query - required: false - schema: - type: integer - responses: - "200": - description: List of likes - content: - application/json: - schema: - type: array - items: - type: object - properties: - address: - type: string - tokenAddress: - type: string - timestamp: - type: string - format: date-time - "500": - description: Server error - content: - application/json: - schema: - type: object - properties: - error: - type: string - /api/secret: - get: - summary: Get a secret for the user address - parameters: - - $ref: '#/components/parameters/GlobalAddressHeader' - responses: - "200": - description: Secret generated - content: - application/json: - schema: - type: object - properties: - secret: - type: string - "500": - description: Server error - content: - application/json: - schema: - type: object - properties: - error: - type: string -`; diff --git a/src/init.js b/src/init.js deleted file mode 100644 index f27e981..0000000 --- a/src/init.js +++ /dev/null @@ -1,57 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.getEnv = getEnv; -exports.initFastify = initFastify; -const dotenv = __importStar(require("dotenv")); -const fastify_1 = __importDefault(require("fastify")); -dotenv.config(); -function getEnv() { - const HOST = process.env.HOST || "localhost"; - const PORT = Number(process.env.PORT || 3000); - const DATABASE_URL = process.env.DATABASE_URL || "mongodb://localhost:27017/ambrodeo"; - const SUBGRAPHS_ENDPOINT = process.env.SUBGRAPHS_ENDPOINT || ""; - const UPLOAD_DIR = process.env.UPLOAD_DIR || __dirname; - return { HOST, PORT, DATABASE_URL, SUBGRAPHS_ENDPOINT, UPLOAD_DIR }; -} -function initFastify() { - return (0, fastify_1.default)({ - bodyLimit: 5000000, - logger: true, - }); -} diff --git a/src/server.js b/src/server.js deleted file mode 100644 index 492e251..0000000 --- a/src/server.js +++ /dev/null @@ -1,312 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const multipart_1 = __importDefault(require("@fastify/multipart")); -const mongodb_1 = __importDefault(require("@fastify/mongodb")); -const axios_1 = __importDefault(require("axios")); -const crypto_1 = __importDefault(require("crypto")); -const ethers_1 = __importDefault(require("ethers")); -const fs_1 = __importDefault(require("fs")); -const path_1 = __importDefault(require("path")); -const js_yaml_1 = __importDefault(require("js-yaml")); -const swagger_1 = __importDefault(require("@fastify/swagger")); -const swagger_ui_1 = __importDefault(require("@fastify/swagger-ui")); -const init_1 = require("./init"); -const documentation_1 = require("./documentation"); -const query = `query GetToken($tokenAddress: ID!) {token(id: $tokenAddress) {id}}`; -const { HOST, PORT, DATABASE_URL, SUBGRAPHS_ENDPOINT, UPLOAD_DIR } = (0, init_1.getEnv)(); -const fastify = (0, init_1.initFastify)(); -const mapSecret = new Map(); -const startServer = async () => { - try { - const openApiSpec = js_yaml_1.default.load(documentation_1.docs); - fastify.register(swagger_1.default, { - openapi: openApiSpec, - }); - fastify.register(swagger_ui_1.default, { - routePrefix: "/docs", - uiConfig: { - docExpansion: "full", - deepLinking: false, - }, - }); - fastify.register(mongodb_1.default, { - forceClose: true, - url: DATABASE_URL, - }); - fastify.register(multipart_1.default); - fastify.addHook("preHandler", async (request, reply) => { - if (request.method != "POST") { - return; - } - try { - const { address, signature } = request.headers; - const secret = mapSecret.get(address); - if (!signature || !address || !secret) { - return reply.status(400).send({ - error: "Missing address or signature in headers", - }); - } - const recoveredAddress = ethers_1.default.verifyMessage(secret, signature); - if (recoveredAddress.toLowerCase() !== address.toLowerCase()) { - return reply.status(401).send({ error: "Invalid signature" }); - } - } - catch (error) { - request.log.error(error); - return reply.status(400).send({ error: error }); - } - }); - fastify.get("/", index); - fastify.post("/upload", uploadFile); - fastify.post("/api/user", addOrUpdateUser); - fastify.post("/api/message", addMessage); - fastify.post("/api/like", addOrDeleteLike); - fastify.get("/api/user", getUser); - fastify.get("/api/messages", getMessages); - fastify.get("/api/token", getToken); - fastify.get("/api/likes", getUserLikes); - fastify.get("/api/secret", getSecret); - await fastify.listen({ host: HOST, port: PORT }); - console.log(`Server running on http://${HOST}:${PORT}`); - } - catch (error) { - fastify.log.error(error); - process.exit(1); - } -}; -async function checkTokenExist(request, tokenAddress) { - try { - if ((await request.server.mongo.db - ?.collection("token") - .findOne({ tokenAddress })) != null) - return true; - const response = await axios_1.default.post(SUBGRAPHS_ENDPOINT, { query, variables: { tokenAddress } }, { - headers: { "Content-Type": "application/json" }, - }); - if (tokenAddress == response.data.data.token.id) { - await request.server.mongo.db?.collection("token").updateOne({ tokenAddress }, { - tokenAddress, - like: 0, - timestamp: new Date(), - }, { upsert: true }); - return true; - } - } - catch (error) { - request.log.error(error); - return false; - } -} -async function index(request, reply) { - return reply.send({}); -} -async function addMessage(request, reply) { - try { - const data = request.body; - if (!data || typeof data !== "object") { - return reply.status(400).send({ error: "Invalid JSON payload" }); - } - const { address } = request.headers; - let { tokenAddress, message } = data; - tokenAddress = tokenAddress.toLowerCase(); - if (!ethers_1.default.isAddress(tokenAddress)) - throw new Error("Invalid address"); - if (!checkTokenExist(request, tokenAddress)) - return reply.status(404).send({ token: "Token not found" }); - await request.server.mongo.db?.collection("massage").insertOne({ - address, - tokenAddress, - message: message, - timestamp: new Date(), - }); - } - catch (error) { - request.log.error(error); - return reply.status(500).send({ error: error.message }); - } - return reply.send({}); -} -async function addOrUpdateUser(request, reply) { - try { - const data = request.body; - if (!data || typeof data !== "object") { - return reply.status(400).send({ error: "Invalid JSON payload" }); - } - const { address } = request.headers; - const { userName, image } = data; - await request.server.mongo.db?.collection("user").updateOne({ address }, { - address, - userName, - image, - timestamp: new Date(), - }, { upsert: true }); - } - catch (error) { - request.log.error(error); - return reply - .status(500) - .send({ error: error.message }); - } - return reply.send({}); -} -async function addOrDeleteLike(request, reply) { - try { - const data = request.body; - if (!data || typeof data !== "object") { - return reply.status(400).send({ error: "Invalid JSON payload" }); - } - const { address } = request.headers; - let { tokenAddress, like } = data; - tokenAddress = tokenAddress.toLowerCase(); - if (!ethers_1.default.isAddress(tokenAddress)) - throw new Error("Invalid address"); - if (!checkTokenExist(request, tokenAddress)) - return reply.status(404).send({ token: "Token not found" }); - if (like) { - const result = await request.server.mongo.db - ?.collection("like") - .updateOne({ address, tokenAddress }, { - address, - tokenAddress, - timestamp: new Date(), - }, { upsert: true }); - if (result.upsertedCount == 1) { - await request.server.mongo.db - ?.collection("token") - .updateOne({ tokenAddress }, { $inc: { like: 1 } }); - } - } - else { - const result = await request.server.mongo.db - ?.collection("token") - .deleteOne({ address, tokenAddress }); - if (result.deletedCount == 1) { - await request.server.mongo.db - ?.collection("token") - .updateOne({ tokenAddress }, { $inc: { like: -1 } }); - } - } - } - catch (error) { - request.log.error(error); - return reply.status(500).send({ error: error.message }); - } - return reply.send({}); -} -async function getUser(request, reply) { - try { - const { address } = request.headers; - const user = await request.server.mongo.db - ?.collection("user") - .findOne({ address }, { projection: { _id: 0 } }); - return reply.send(user); - } - catch (error) { - request.log.error(error); - return reply.status(500).send({ error: error.message }); - } -} -async function getMessages(request, reply) { - try { - const { tokenAddress, skip, limit } = request.query; - const messages = await request.server.mongo.db - ?.collection("message") - .find({ tokenAddress }, { projection: { _id: 0 } }) - .sort({ timestamp: -1 }) - .skip(skip) - .limit(limit) - .toArray(); - return reply.send(messages); - } - catch (error) { - request.log.error(error); - return reply.status(500).send({ error: error.message }); - } -} -async function getToken(request, reply) { - try { - const { tokenAddress } = request.query; - const token = await request.server.mongo.db - ?.collection("token") - .findOne({ tokenAddress }, { projection: { _id: 0 } }); - return reply.send(token); - } - catch (error) { - request.log.error(error); - return reply.status(500).send({ error: error.message }); - } -} -async function getUserLikes(request, reply) { - try { - const { address } = request.headers; - const { skip, limit } = request.query; - const likes = await request.server.mongo.db - ?.collection("like") - .find({ address }, { projection: { _id: 0 } }) - .sort({ timestamp: -1 }) - .skip(skip) - .limit(limit) - .toArray(); - return reply.send(likes); - } - catch (error) { - request.log.error(error); - return reply.status(500).send({ error: error.message }); - } -} -async function getSecret(request, reply) { - try { - let { address } = request.headers; - address = address.toLowerCase(); - console.log("----------------"); - console.log(address); - if (!ethers_1.default.isAddress(address)) - throw new Error("Invalid address"); - const secret = "AMBRodeo authorization secret: "; - secret.concat(crypto_1.default - .createHash("sha256") - .update(crypto_1.default.getRandomValues(new Uint8Array(32))) - .digest("hex")); - mapSecret.set(address, secret); - reply.send({ secret: secret }); - } - catch (error) { - request.log.error(error); - return reply.status(500).send({ error: error.message }); - } -} -async function uploadFile(request, reply) { - try { - let { address } = request.headers; - address = address.toLowerCase(); - const data = await request.file(); - if (!data) { - reply.code(400).send({ error: "No file uploaded" }); - return; - } - const uploadDir = path_1.default.join(UPLOAD_DIR, "files", address); - if (!fs_1.default.existsSync(uploadDir)) { - fs_1.default.mkdirSync(uploadDir, { recursive: true }); - } - const filePath = path_1.default.join(uploadDir, data.filename); - await new Promise((resolve, reject) => { - const writeStream = fs_1.default.createWriteStream(filePath); - data.file.pipe(writeStream); - writeStream.on("finish", resolve); - writeStream.on("error", reject); - }); - reply.send({ - success: true, - message: "File uploaded", - filename: data.filename, - }); - } - catch (error) { - request.log.error(error); - return reply.status(500).send({ error: error.message }); - } -} -startServer(); diff --git a/src/server.ts b/src/server.ts index 7f70235..c6d613b 100644 --- a/src/server.ts +++ b/src/server.ts @@ -12,6 +12,7 @@ import swaggerUi from "@fastify/swagger-ui"; import { OpenAPIV3 } from "openapi-types"; import { getEnv, initFastify } from "./init"; import { docs } from "./documentation"; +import cors from "@fastify/cors"; const query = `query GetToken($tokenAddress: ID!) {token(id: $tokenAddress) {id}}`; const { HOST, PORT, DATABASE_URL, SUBGRAPHS_ENDPOINT, UPLOAD_DIR } = getEnv(); @@ -22,6 +23,10 @@ const startServer = async () => { try { const openApiSpec = yaml.load(docs) as OpenAPIV3.Document; + fastify.register(cors, { + origin: "*", + }); + fastify.register(swagger, { openapi: openApiSpec, });