Commit 2d134f3a authored by Daniel Maricic's avatar Daniel Maricic
Browse files

update


Signed-off-by: Daniel Maricic's avatarDaniel Maricic <daniel@woss.io>
parent 9cf3da0e
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
module.exports = {
env: {
browser: true,
es2020: true,
node: true,
jest: true,
es6: true,
},
parser: "@typescript-eslint/parser", // Specifies the ESLint parser
extends: [
'prettier',
'eslint:recommended',
"plugin:@typescript-eslint/recommended", // Uses the recommended rules from the @typescript-eslint/eslint-plugin
"prettier/@typescript-eslint", // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
"plugin:prettier/recommended", // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.
],
parserOptions: {
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
sourceType: "module", // Allows for the use of imports
},
rules: {
"no-shadow": "warn",
"@typescript-eslint/explicit-member-accessibility": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/indent": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/camelcase": "off",
"@typescript-eslint/array-type": "off",
"@typescript-eslint/no-object-literal-type-assertion": "off",
"@typescript-eslint/no-use-before-define": "off",
complexity: ['warn', 4],
},
}
node_modules
\ No newline at end of file
node_modules
.env
{
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"trailingComma": "es5",
"arrowParens": "avoid",
"printWidth": 100,
"bracketSpacing": true
}
{
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": []
}
{
"editor.tabSize": 2,
"editor.insertSpaces": true,
"eslint.lintTask.enable": true,
"eslint.lintTask.options": "--ext .js,.jsx,.ts,.tsx,.graphql .",
"eslint.codeActionsOnSave.mode": "all",
"eslint.options": {
"fix": true
},
"eslint.format.enable": true,
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact", "graphql"],
"javascript.updateImportsOnFileMove.enabled": "always",
"git.alwaysSignOff": true,
"files.insertFinalNewline": true,
"files.trimTrailingWhitespace": true,
"files.associations": {
".gmrc": "jsonc",
".sql": "sql"
},
"[markdown]": {
"files.trimTrailingWhitespace": false
},
"editor.codeActionsOnSave": {
"source.fixAll": true,
"source.organizeImports": true,
"source.fixAll.eslint": false
},
"editor.wordWrap": "bounded",
"editor.wordWrapColumn": 100,
"editor.rulers": [100],
"cSpell.words": ["fastify"],
"todohighlight.keywords": [
{
"text": "@FUCK",
"color": "#ffffff",
"backgroundColor": "#a3104f",
"overviewRulerColor": "grey"
},
{
"text": "@TODO",
"color": "#ffffff",
"backgroundColor": "#1a30d8",
"overviewRulerColor": "grey"
}
],
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[graphql]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[plsql]": {
"editor.defaultFormatter": "bradymholt.pgformatter",
"editor.formatOnSave": true
},
"[sql]": {
"editor.defaultFormatter": "bradymholt.pgformatter",
"editor.formatOnSave": true
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
}
}
# ipfs-secure-upload
Check the docs by going to http://localhost:9876/docs
{
"name": "ipfs-secure-upload",
"version": "0.1.0",
"main": "index.js",
"main": "server.js",
"repository": "git@gitlab.com:kelp_digital/ipfs-secure-upload.git",
"author": "Daniel Maricic <daniel@woss.io>",
"license": "MIT",
"license": "GPL-3.0",
"scripts": {
"start": "ts-node-dev --rs --require dotenv/config src/index.ts",
"build": "tsc -p tsconfig.json"
},
"devDependencies": {
"@types/busboy": "^0.2.3",
"@types/node": "^14.14.35",
"@types/ramda": "^0.27.39",
"@typescript-eslint/eslint-plugin": "^4.18.0",
"@typescript-eslint/parser": "^4.18.0",
"dotenv": "^8.2.0",
"eslint": "^7.22.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-prettier": "^3.3.1",
"prettier": "^2.2.1",
"ts-node-dev": "^1.1.6",
"typescript": "^4.2.3"
},
"dependencies": {
"fastify": "^3.14.0",
"fastify-healthcheck": "^3.1.0",
"fastify-multipart": "^4.0.3",
"fastify-plugin": "^3.0.0",
"fastify-postgres": "^3.5.0",
"fastify-sensible": "^3.1.1",
"fastify-swagger": "^4.4.2",
"file-type": "^16.3.0",
"ipfs-http-client": "49.0.4",
"multiaddr": "^8.1.2",
"pg": "^8.5.1",
"ramda": "^0.27.1"
}
}
import f, { FastifyInstance } from 'fastify'
import { IncomingMessage, Server, ServerResponse } from 'http'
import { isEmpty, isNil } from 'ramda'
const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 9876
let ipfsEndpoint = process.env.IPFS_ENDPOINT
if (isEmpty(ipfsEndpoint) || isNil(ipfsEndpoint)) {
throw new Error('IPFS_ENDPOINT must be set')
} else {
ipfsEndpoint = process.env.IPFS_ENDPOINT
}
// create the app
const fastify: FastifyInstance<Server, IncomingMessage, ServerResponse> = f({
logger: true,
})
////////////////////// assign plugins
fastify.register(import('fastify-sensible'))
fastify.register(import('fastify-healthcheck'))
fastify.register(import('./plugins/ipfs/fastify'), { endpoint: ipfsEndpoint })
fastify.register(import('fastify-multipart'), {
// attachFieldsToBody: true,
sharedSchemaId: '#macula',
limits: {
fieldNameSize: 100, // Max field name size in bytes
fieldSize: 1000000, // Max field value size in bytes 1MB
fields: 10, // Max number of non-file fields
fileSize: 100 * 1000000, // For multipart forms, the max file size ( 100 is how many megabytes)
files: 2, // Max number of file fields
headerPairs: 2000, // Max number of header key=>value pairs
},
})
fastify.register(require('fastify-swagger'), {
routePrefix: '/docs',
swagger: {
info: {
title: 'IPFS upload',
description: 'Kelp IPFS upload API',
version: '0.1.0',
},
host: 'localhost:9876',
schemes: ['http'],
consumes: ['application/json'],
produces: ['application/json'],
// tags: [
// { name: 'user', description: 'User related end-points' },
// { name: 'code', description: 'Code related end-points' },
// ],
},
uiConfig: {
docExpansion: 'full',
deepLinking: true,
},
exposeRoute: true,
})
////////////////////// assign plugins
////////////////////// assign routes
fastify.register(import('./routes/ipfs/uploadFile'))
fastify.register(import('./routes/ipfs/readFile'))
////////////////////// assign routes
/**
* Home route
*/
fastify.get('/', async (request, reply) => {
reply.type('application/json').code(200).send({ hello: 'world' })
})
fastify.listen(port, (err, address) => {
if (err) {
fastify.log.error(err)
console.error(err)
process.exit(1)
}
fastify.log.info(`server listening on ${address}`)
})
import { FastifyInstance } from 'fastify'
import fp from 'fastify-plugin'
import ipfsClient, { ipfsClientReturn } from 'ipfs-http-client'
export interface FastifyIpfsOptions {
endpoint: string
}
declare module 'fastify' {
export interface FastifyInstance {
ipfsClient: ipfsClientReturn
}
}
/**
* Connect to the IPFS node
* @param fastify
* @param options
*/
async function fastifyIpfs(fastify: FastifyInstance, options: any, done: any) {
const { endpoint } = options
const ipfs = ipfsClient({
url: endpoint,
})
fastify.decorate('ipfsClient', ipfs)
done()
}
export default fp(fastifyIpfs, {
fastify: '^3.0.0',
name: 'fastify-ipfs',
})
import { FastifyInstance, RouteShorthandOptions } from 'fastify';
export default async function routes(fastify: FastifyInstance) {
const opts: RouteShorthandOptions = {
schema: {
response: {
200: {
type: 'object',
properties: {
cid: { type: 'string' },
}
}
}
},
};
fastify.get<{ Params: { cid: string } }>('/ipfs/pinByHash', opts, async (req, reply) => {
reply.type('application/json').code(200).send({ cid: req.params.cid })
})
}
import { FastifyInstance, RouteShorthandOptions } from 'fastify'
import { isNil } from 'ramda'
import fileType from '../../utils/fileType'
import itToBuff from '../../utils/itToBuff'
export default async function routes(fastify: FastifyInstance) {
const opts: RouteShorthandOptions = {
schema: {
// description: 'post some data',
// tags: ['user', 'code'],
// summary: 'qwerty',
params: {
type: 'object',
properties: {
cid: {
type: 'string',
description: 'IPFS content address.',
},
},
},
},
}
/**
* Read the CID from the IPFS. This api uses `cat` and with the process.env.IPFS_CAT_TIMEOUT = 2000 ms to get the data
*/
fastify.get<{ Params: { cid: string } }>('/ipfs/cat/:cid', opts, async (req, reply) => {
try {
const cid = req.params.cid
const { ipfsClient } = fastify
const content = await itToBuff(
ipfsClient.cat(cid, {
timeout: process.env.IPFS_CAT_TIMEOUT ? parseInt(process.env.IPFS_CAT_TIMEOUT, 10) : 2000,
})
)
// detect the file type if possible
const ft = await fileType(content)
let type = 'plain/text'
let ret: Buffer | string = ''
// check did we get the file type
if (!isNil(ft)) {
type = ft.mime
ret = content
} else {
fastify.log.info('File type is not found for the CID', cid)
const str = content.toString()
// let's try to see is the non media file JSON file
try {
// this potentially can be slow
JSON.parse(str)
type = 'application/json'
ret = str
} catch (error) {
ret = str
}
}
reply.type(type).code(200).send(ret)
} catch (error) {
reply.badRequest(error)
}
})
}
import { FastifyInstance, RouteShorthandOptions } from 'fastify';
import { createWriteStream } from 'fs';
import { pipeline } from 'stream';
import util from 'util';
const pump = util.promisify(pipeline)
export default async function routes(fastify: FastifyInstance) {
const opts: RouteShorthandOptions = {
schema: {
// response: {
// 200: {
// type: 'object',
// properties: {
// cid: { type: 'string' },
// }
// }
// }
},
};
fastify.post('/ipfs/upload_file', opts, async (req, reply) => {
if (!req.isMultipart()) {
reply.code(400).send(new Error('Request is not multipart'))
}
const part = await req.file({ limits: { files: 1 } })
await pump(part.file, createWriteStream(part.filename))
const uploaded = ({ fileName: part.filename, cid: 'bafy...' })
reply.type('application/json').code(200).send({ uploaded, body: req.body })
})
}
import 'ipfs-http-client'
import { ClientOptions } from 'ipfs-http-client/src/lib/core'
import Multiaddr from 'multiaddr'
declare module 'ipfs-http-client' {
export as namespace ipfsClient
export interface ipfsClientReturn {
cat: (
cid: string,
options: ClientOptions | URL | Multiaddr | string
) => AsyncIterable<Uint8Array>
}
}
import FileType, { FileTypeResult } from 'file-type'
/**
* Using the file-type module slice the Bufffer for first 100 bytes and try to detect the file type with mime
* @param content
* @returns FileTypeResult | undefined
*/
export default async function fileType(content: Buffer): Promise<FileTypeResult | undefined> {
return await FileType.fromBuffer(content.slice(0, 100))
}
/**
* Async Iterable to buffer.
* This method will go through the iterable ( for of ) and create the buffer
* @param iteratable
* @returns
*/
export default async function itToBuff(iteratable: AsyncIterable<Uint8Array>): Promise<Buffer> {
let content: Uint8Array[] = []
for await (const chunk of iteratable) {
content.push(chunk)
}
return Buffer.concat(content)
}
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true /* Skip type checking of declaration files. */,
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */