filecoin-pin
Version:
Bridge IPFS content to Filecoin Onchain Cloud using familiar tools
245 lines • 9.15 kB
JavaScript
import fastify from 'fastify';
import { CID } from 'multiformats/cid';
import { FilecoinPinStore } from './filecoin-pin-store.js';
import { setupSynapse } from './synapse/service.js';
const DEFAULT_USER_INFO = {
id: 'default-user',
name: 'Default User',
};
export async function createFilecoinPinningServer(config, logger, serviceInfo) {
// Set up Synapse service
const synapseService = await setupSynapse(config, logger);
// Create our custom Filecoin pin store with Synapse service
const filecoinPinStore = new FilecoinPinStore({
config,
logger,
synapseService,
});
// Set up event handlers for monitoring
filecoinPinStore.on('pin:block:stored', (data) => {
logger.debug({
pinId: data.pinId,
userId: data.userId,
cid: data.cid.toString(),
size: data.size,
}, 'Block stored for pin');
});
filecoinPinStore.on('pin:block:missing', (data) => {
logger.warn({
pinId: data.pinId,
userId: data.userId,
cid: data.cid.toString(),
}, 'Block missing for pin');
});
filecoinPinStore.on('pin:car:completed', (data) => {
logger.info({
pinId: data.pinId,
userId: data.userId,
cid: data.cid.toString(),
blocksWritten: data.stats.blocksWritten,
totalSize: data.stats.totalSize,
missingBlocks: data.stats.missingBlocks.size,
carFilePath: data.carFilePath,
}, 'CAR file completed for pin');
});
filecoinPinStore.on('pin:failed', (data) => {
logger.error({
pinId: data.pinId,
userId: data.userId,
cid: data.cid.toString(),
error: data.error,
}, 'Pin operation failed');
});
// Create a custom Fastify server
const server = fastify({
logger: false, // We'll use our own logger
});
// Add root route for health check (no auth required)
server.get('/', async (_request, reply) => {
await reply.send({
service: serviceInfo.service,
version: serviceInfo.version,
status: 'ok',
});
});
// Add authentication hook
server.addHook('preHandler', async (request, reply) => {
// Skip auth for root health check
if (request.url === '/') {
return;
}
const authHeader = request.headers.authorization;
if (authHeader?.startsWith('Bearer ') !== true) {
await reply.code(401).send({ error: 'Missing or invalid authorization header' });
return;
}
const token = authHeader.slice(7); // Remove 'Bearer ' prefix
if (token.trim().length === 0) {
await reply.code(401).send({ error: 'Invalid access token' });
return;
}
// Add user to request context
request.user = DEFAULT_USER_INFO;
});
// Add our custom pin store to the Fastify context
server.decorate('pinStore', filecoinPinStore);
// Register custom routes that use our pin store
await server.register(async (fastify) => {
// Override the default routes with our custom implementations
await registerCustomPinRoutes(fastify, filecoinPinStore, logger);
});
await filecoinPinStore.start();
// Start listening
await server.listen({
port: config.port ?? 0, // Use random port for testing
host: config.host,
});
logger.info('Filecoin pinning service API server started');
return {
server,
pinStore: filecoinPinStore,
};
}
async function registerCustomPinRoutes(fastify, pinStore, logger) {
// POST /pins - Create a new pin
fastify.post('/pins', async (request, reply) => {
try {
const { cid, name, origins, meta } = request.body;
if (cid == null) {
await reply.code(400).send({ error: 'Missing required field: cid' });
return;
}
// Parse the CID string to CID object
let cidObject;
try {
cidObject = CID.parse(cid);
}
catch (_error) {
await reply.code(400).send({ error: `Invalid CID format: ${cid}` });
return;
}
const pinOptions = {};
if (name != null)
pinOptions.name = name;
if (origins != null)
pinOptions.origins = origins;
if (meta != null)
pinOptions.meta = meta;
if (request.user == null) {
await reply.code(401).send({ error: 'Unauthorized' });
return;
}
const result = await pinStore.pin(request.user, cidObject, pinOptions);
await reply.code(202).send({
requestid: result.id,
status: result.status,
created: new Date(result.created).toISOString(),
pin: result.pin,
delegates: [],
info: result.info,
});
}
catch (error) {
logger.error({ error }, 'Failed to create pin');
await reply.code(500).send({ error: 'Internal server error' });
}
});
// GET /pins/:requestId - Get pin status
fastify.get('/pins/:requestId', async (request, reply) => {
try {
if (request.user == null) {
await reply.code(401).send({ error: 'Unauthorized' });
return;
}
const result = await pinStore.get(request.user, request.params.requestId);
if (result == null) {
await reply.code(404).send({ error: 'Pin not found' });
return;
}
await reply.send({
requestid: result.id,
status: result.status,
created: new Date(result.created).toISOString(),
pin: result.pin,
delegates: [],
info: result.info,
});
}
catch (error) {
logger.error({ error }, 'Failed to get pin status');
await reply.code(500).send({ error: 'Internal server error' });
}
});
// GET /pins - List pins
fastify.get('/pins', async (request, reply) => {
try {
const { cid, name, status, limit } = request.query;
const limitNum = limit != null ? parseInt(limit, 10) : undefined;
const listQuery = {};
if (cid != null)
listQuery.cid = cid;
if (name != null)
listQuery.name = name;
if (status != null)
listQuery.status = status;
if (limitNum != null && !Number.isNaN(limitNum))
listQuery.limit = limitNum;
if (request.user == null) {
await reply.code(401).send({ error: 'Unauthorized' });
return;
}
const result = await pinStore.list(request.user, listQuery);
const results = result.results.map((pin) => ({
requestid: pin.id,
status: pin.status,
created: new Date(pin.created).toISOString(),
pin: pin.pin,
delegates: [],
info: pin.info,
}));
await reply.send({
count: result.count,
results,
});
}
catch (error) {
logger.error({ error }, 'Failed to list pins');
await reply.code(500).send({ error: 'Internal server error' });
}
});
// POST /pins/:requestId - Update pin (not commonly used)
fastify.post('/pins/:requestId', async (request, reply) => {
try {
const { name, origins, meta } = request.body;
const result = await pinStore.update(request.user, request.params.requestId, { name, origins, meta });
if (result == null) {
await reply.code(404).send({ error: 'Pin not found' });
return;
}
await reply.send({
requestid: result.id,
status: result.status,
created: new Date(result.created).toISOString(),
pin: result.pin,
delegates: [],
info: result.info,
});
}
catch (error) {
logger.error({ error }, 'Failed to update pin');
await reply.code(500).send({ error: 'Internal server error' });
}
});
// DELETE /pins/:requestId - Cancel/delete pin and clean up CAR file
fastify.delete('/pins/:requestId', async (request, reply) => {
try {
await pinStore.cancel(request.user, request.params.requestId);
await reply.code(202).send();
}
catch (error) {
logger.error({ error }, 'Failed to cancel pin');
await reply.code(500).send({ error: 'Internal server error' });
}
});
}
//# sourceMappingURL=filecoin-pinning-server.js.map