Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
264 changes: 254 additions & 10 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"@types/express": "^5.0.3",
"@types/multer": "^1.4.13",
"@types/node": "^24.0.0",
"@types/swagger-jsdoc": "^6.0.4",
"@types/swagger-ui-express": "^4.1.8",
"eslint": "^9.28.0",
"globals": "^16.2.0",
"husky": "^9.1.7",
Expand Down Expand Up @@ -54,7 +56,9 @@
"multer": "^2.0.1",
"multiformats": "^13.3.7",
"pino": "^9.7.0",
"pino-http": "^10.5.0"
"pino-http": "^10.5.0",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.1"
},
"lint-staged": {
"*.(ts|js)": [
Expand Down
37 changes: 37 additions & 0 deletions src/api/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,43 @@ import { helia } from '../helia.js'

const router = Router()

/**
* @openapi
* /api/debug/autopeering:
* get:
* tags: [Debug]
* summary: Attempt to auto-peer with known nodes
* description: Attempts to dial and peer with a list of known nodes. Returns the list of nodes that were successfully peered with.
* responses:
* 200:
* description: Auto-peering operation completed
* content:
* application/json:
* schema:
* type: object
* properties:
* peeredSuccessfullyTo:
* type: array
* items:
* type: string
* description: List of node names successfully peered with.
* example:
* peeredSuccessfullyTo:
* - "NodeA"
* - "NodeB"
* 500:
* description: Internal server error during auto-peering
* content:
* application/json:
* schema:
* type: object
* properties:
* error:
* type: string
* description: Error message detailing why the operation failed.
* example:
* error: "Internal Server Error. See logs."
*/
router.get('/autopeering', async (req, res) => {
try {
const nodes = getNodesList([helia.libp2p.peerId.toString()])
Expand Down
118 changes: 118 additions & 0 deletions src/api/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,71 @@ import { downloadFile, FileNotFoundError, getFileStats } from '../utils/file.js'

const router = Router()

/**
* @openapi
* /api/file/upload:
* post:
* tags: [File]
* summary: Upload files to IPFS
* description: Uploads one or more files, adds them to IPFS, and returns their CIDs. Files are also pinned if not already.
* requestBody:
* required: true
* content:
* multipart/form-data:
* schema:
* type: object
* properties:
* files:
* type: array
* items:
* type: string
* format: binary
* description: Files to be uploaded.
* responses:
* 200:
* description: Files uploaded and pinned successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* filesNames:
* type: array
* items:
* type: string
* description: List of uploaded file names.
* cids:
* type: array
* items:
* type: string
* description: Corresponding CIDs of the uploaded files.
* example:
* filesNames:
* - "example.txt"
* - "image.png"
* cids:
* - "bafybeihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku"
* - "bafybeia6enmtx4fwdl2whnuyr5gy66jjz5rb2cq6g4r2vsmilxv5gpkh7a"
* 400:
* description: Bad request – No files or file count exceeds limit
* content:
* application/json:
* schema:
* type: object
* properties:
* error:
* type: string
* description: Error message describing the issue.
* examples:
* noFile:
* summary: No files uploaded
* value:
* error: "No file uploaded"
* fileLimitExceeded:
* summary: Too many files uploaded
* value:
* error: "File limit exceeded. Max 5 allowed."
*/
router.post('/upload', multerStorage.array('files'), async (req, res) => {
if (!req.files) {
res.status(400).send({
Expand Down Expand Up @@ -60,6 +125,58 @@ router.post('/upload', multerStorage.array('files'), async (req, res) => {
}
})

/**
* @openapi
* /api/file/{cid}:
* get:
* tags: [File]
* summary: Download file by CID
* description: Streams a file from IPFS corresponding to the given CID. The response is a binary stream with appropriate headers.
* parameters:
* - in: path
* name: cid
* required: true
* schema:
* type: string
* description: Content Identifier (CID) of the file to download.
* responses:
* 200:
* description: File stream starts successfully
* content:
* application/octet-stream:
* schema:
* type: string
* format: binary
* 408:
* description: File not found or failed to stream
* content:
* application/json:
* schema:
* type: object
* properties:
* error:
* type: string
* examples:
* notFound:
* summary: File not found
* value:
* error: "Cannot find requested CID. Request timed out."
* streamError:
* summary: Stream error
* value:
* error: "Failed to stream file"
* 500:
* description: Internal server error
* content:
* application/json:
* schema:
* type: object
* properties:
* error:
* type: string
* example:
* error: "Internal Server Error. See logs."
*/
router.get('/:cid', async (req, res) => {
try {
const cid = CID.parse(req.params.cid)
Expand All @@ -85,6 +202,7 @@ router.get('/:cid', async (req, res) => {

stream.pipe(res)
} catch (error) {
// TODO: Handle invalid CID, "type": "SyntaxError",
if (error instanceof FileNotFoundError) {
res.status(408).send({
error: error.message
Expand Down
153 changes: 153 additions & 0 deletions src/api/helia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,41 @@ import { logger } from '../utils/logger.js'

const router = Router()

// TODO: This should be totally updated

/**
* @openapi
* /api/helia/pins:
* get:
* tags: [Helia]
* summary: List all pinned CIDs in Helia
* description: Retrieves a list of all pinned content identifiers (CIDs) managed by Helia.
* responses:
* 200:
* description: List of pinned CIDs retrieved successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* pins:
* type: array
* items:
* type: object
* properties:
* cid:
* type: string
* description: The CID of the pinned content.
* type:
* type: string
* description: The type of pin (e.g., recursive, direct).
* example:
* pins:
* - cid: "QmXf123abc..."
* type: "recursive"
* - cid: "QmYz456def..."
* type: "direct"
*/
router.get('/pins', async (req, res) => {
const pins: Pin[] = []

Expand All @@ -19,6 +54,49 @@ router.get('/pins', async (req, res) => {
})
})

/**
* @openapi
* /api/helia/pin/{cid}:
* post:
* tags: [Helia]
* summary: Pin a CID in Helia
* description: Pins the specified CID to ensure its content is retained locally.
* parameters:
* - in: path
* name: cid
* required: true
* schema:
* type: string
* description: The CID to pin.
* responses:
* 200:
* description: CID pinned successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* pinned:
* type: boolean
* description: Indicates if the CID was pinned.
* cid:
* type: string
* description: The pinned CID.
* example:
* pinned: true
* cid: "QmXf123abc..."
* 500:
* description: Error pinning the CID
* content:
* application/json:
* schema:
* type: object
* properties:
* error:
* type: string
* example:
* error: "Failed to pin CID due to internal error"
*/
router.post('/pin/:cid', async (req, res) => {
const cid = CID.parse(req.params.cid)

Expand All @@ -40,6 +118,38 @@ router.post('/pin/:cid', async (req, res) => {
})
})

/**
* @openapi
* /api/helia/pins/isPinned/{cid}:
* get:
* tags: [Helia]
* summary: Check if a CID is pinned in Helia
* description: Returns whether the specified CID is currently pinned.
* parameters:
* - in: path
* name: cid
* required: true
* schema:
* type: string
* description: The CID to check.
* responses:
* 200:
* description: Pin status retrieved successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* cid:
* type: string
* description: The CID checked.
* isPinned:
* type: boolean
* description: True if the CID is pinned, false otherwise.
* example:
* cid: "QmXf123abc..."
* isPinned: true
*/
router.get('/pins/isPinned/:cid', async (req, res) => {
const cid = CID.parse(req.params.cid)

Expand All @@ -51,6 +161,49 @@ router.get('/pins/isPinned/:cid', async (req, res) => {
})
})

/**
* @openapi
* /api/helia/routing/findProviders/{cid}:
* get:
* tags: [Helia]
* summary: Find providers for a given CID
* description: Retrieves a list of peer IDs that provide the specified CID.
* parameters:
* - in: path
* name: cid
* required: true
* schema:
* type: string
* description: The CID to find providers for.
* responses:
* 200:
* description: List of providers retrieved successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* providers:
* type: array
* items:
* type: string
* description: Array of peer IDs that provide the CID.
* example:
* providers:
* - "12D3KooWKavDi49t6qZFuPqMPeehxNqHdDdbqqdvVVv7YasEYppm"
* - "12D3KooWXu8TtyGbfC6KSH82VXkEG2UVsy8vcz5Qk5aY8JDbAqfFo"
* 400:
* description: Error finding providers for the CID
* content:
* application/json:
* schema:
* type: object
* properties:
* error:
* type: string
* example:
* error: "Invalid CID format"
*/
router.get('/routing/findProviders/:cid', async (req, res) => {
try {
const cid = CID.parse(req.params.cid)
Expand Down
Loading