├── .gitignore ├── .dockerignore ├── slave_build.sh ├── public ├── favicon.ico ├── manifest.json └── index.html ├── .babelrc ├── .env.development ├── pre_local.sh ├── slave_start.sh ├── .eslintignore ├── reload.sh ├── src ├── reducers │ ├── init_ipfs.js │ └── index.js ├── components │ ├── App.test.js │ ├── Status.js │ ├── App.js │ ├── Upload.js │ └── See.js ├── actions │ └── index.js ├── containers │ ├── Footer.js │ └── Header.js ├── index.js └── registerServiceWorker.js ├── .scripts ├── pre_remote.sh └── post-receive ├── Dockerfile ├── .eslintrc.js ├── index.js ├── README.md ├── package.json └── .eslintrc /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .env 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .cube 3 | build 4 | .scripts -------------------------------------------------------------------------------- /slave_build.sh: -------------------------------------------------------------------------------- 1 | 2 | #!/bin/bash 3 | 4 | APP=$1 5 | 6 | docker build -t "$APP" . 7 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TalaikisInc/ipfs-uploader/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["import", {"libraryName": "antd", "style": true} ] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true 2 | NODE_ENV = "development" 3 | CI = true 4 | HTTPS = false 5 | -------------------------------------------------------------------------------- /pre_local.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git remote -v 4 | git remote add production ssh://root@123.123.123.123/opt/ipfs.git 5 | -------------------------------------------------------------------------------- /slave_start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | APP=$1 4 | PORT=$2 5 | 6 | docker run -it -p "$PORT:3000" --rm --name "$APP" -d "$APP" 7 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | tmp/** 2 | build/** 3 | node_modules/** 4 | contracts/** 5 | migrations/1_initial_migration.js 6 | migrations/2_deploy_contracts.js 7 | -------------------------------------------------------------------------------- /reload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | APP=ipfs 4 | PORT=3004 5 | 6 | ./slave_build.sh "$APP" 7 | docker stop "$APP" 8 | docker rm "$APP" 9 | ./slave_start.sh "$APP" "$PORT" 10 | -------------------------------------------------------------------------------- /src/reducers/init_ipfs.js: -------------------------------------------------------------------------------- 1 | export default (state = [], action) => { 2 | switch (action.type) { 3 | case 'INIT_IPFS': 4 | return action.payload 5 | default: 6 | return state 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | 3 | import IPFSReducer from './init_ipfs' 4 | 5 | const rootReducer = combineReducers({ 6 | ipfs: IPFSReducer 7 | }) 8 | 9 | export default rootReducer 10 | -------------------------------------------------------------------------------- /.scripts/pre_remote.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd /opt && \ 4 | git init --bare ipfs.git && \ 5 | git clone ipfs.git ipfs 6 | 7 | cp /root/.scripts/post-receive /opt/ipfs.git/hooks 8 | chmod ug+x /opt/ipfs.git/hooks/post-receive 9 | cp /root/.scripts/.env /opt/ipfs 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM keymetrics/pm2:latest-alpine 2 | 3 | RUN npm i -g pm2 4 | 5 | WORKDIR /var/www/app 6 | COPY ./ ./ 7 | RUN npm i 8 | 9 | ENV NODE_ENV production 10 | ENV PORT 3000 11 | 12 | EXPOSE 3000 13 | 14 | RUN npm run build 15 | 16 | CMD ["pm2-runtime", "index.js", "i", "2"] 17 | -------------------------------------------------------------------------------- /src/components/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | 5 | /* 6 | Full tests 7 | */ 8 | it('renders without crashing', () => { 9 | const div = document.createElement('div') 10 | ReactDOM.render(, div) 11 | }) 12 | -------------------------------------------------------------------------------- /src/actions/index.js: -------------------------------------------------------------------------------- 1 | import IPFS from 'ipfs-mini' 2 | 3 | export function initIPFS(payload) { 4 | const ipfs = new IPFS({ 5 | host: 'ipfs.infura.io', 6 | port: 5001, 7 | protocol: 'https' 8 | }) 9 | 10 | return { 11 | type: 'INIT_IPFS', 12 | payload: ipfs 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "IPFS Uploader", 3 | "name": "IPFS Uploader", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true 5 | }, 6 | extends: [ 7 | 'standard' 8 | ], 9 | globals: { 10 | Atomics: 'readonly', 11 | SharedArrayBuffer: 'readonly' 12 | }, 13 | parserOptions: { 14 | ecmaFeatures: { 15 | jsx: true 16 | }, 17 | ecmaVersion: 2018, 18 | sourceType: 'module' 19 | }, 20 | plugins: [ 21 | 'react' 22 | ], 23 | rules: { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/containers/Footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux' 3 | 4 | import Box from 'grommet/components/Box' 5 | import Paragraph from 'grommet/components/Paragraph' 6 | 7 | const Footer = () => ( 8 | 9 | © 2018, Talaikis Ltd. 10 | 11 | ) 12 | 13 | function mapStateToProps(state) { 14 | return { } 15 | } 16 | 17 | export default connect(mapStateToProps)(Footer) 18 | -------------------------------------------------------------------------------- /src/components/Status.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | 4 | const Status = (props) => { 5 | return ( 6 |
7 | { !props.ipfs 8 | ?
9 | Warning! IPFS isn't initiated by some reason. 10 |
11 | : null 12 | } 13 |
14 | ) 15 | } 16 | 17 | function mapStateToProps(state) { 18 | return { 19 | ipfs: state.ipfs 20 | } 21 | } 22 | 23 | export default connect(mapStateToProps)(Status) 24 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const { join } = require('path') 3 | const app = express() 4 | 5 | app.use(require('compression')()) 6 | app.use(express.static(join(__dirname, 'build'))) 7 | 8 | app.get('/*', (req, res) => { 9 | res.sendFile(join(__dirname, 'build', 'index.html')) 10 | }) 11 | 12 | const PORT = process.env.NODE_ENV === 'production' ? process.env.PORT : 3000 13 | app.listen(PORT, '0.0.0.0', (err) => { 14 | if (err) { 15 | console.log(err) 16 | } 17 | console.info(`==> listening on http://localhost:${PORT}.`) 18 | }) 19 | -------------------------------------------------------------------------------- /.scripts/post-receive: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | read_var() { 4 | VAR=$(grep $1 $2 | xargs) 5 | IFS="=" read -ra VAR <<< "$VAR" 6 | echo ${VAR[1]#*=} 7 | } 8 | 9 | APP_NAME=$(read_var APP_NAME /root/.scripts/.env) 10 | PORT=$(read_var PROD_PORT /root/.scripts/.env) 11 | 12 | echo "Triger received. Deploying..." 13 | cd /opt/ipfs 14 | git --git-dir=/opt/ipfs.git --work-tree=/opt/ipfs checkout master -f 15 | 16 | echo 'Installing' 17 | npm install 18 | 19 | echo 'Building...' 20 | PORT=$PORT npm run build 21 | 22 | echo 'Starting server.' 23 | pm2 delete $APP_NAME 24 | PORT=$PORT pm2 start npm --name $APP_NAME -- start 25 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import * as ReactDOM from 'react-dom' 3 | import { Provider } from 'react-redux' 4 | import { createStore, applyMiddleware } from 'redux' 5 | import thunk from 'redux-thunk' 6 | import reducers from './reducers' 7 | 8 | import App from './components/App' 9 | import registerServiceWorker from './registerServiceWorker' 10 | // import { unregister } from './registerServiceWorker' 11 | const createStoreWithMiddleware = applyMiddleware(thunk)(createStore) 12 | 13 | ReactDOM.render( 14 | 15 | 16 | , 17 | document.getElementById('root') 18 | ) 19 | 20 | registerServiceWorker() 21 | // unregister() 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | identiForm 4 | 5 |

6 | 7 | # IPFS Image Uploader 8 | 9 | [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) 10 | [![Dependency Status](https://david-dm.org/TalaikisInc/ipfs-uploader.svg)](https://david-dm.org/powerpiper/ipfs-uploader) 11 | [![devDependency Status](https://david-dm.org/TalaikisInc/ipfs-uploader/dev-status.svg)](https://david-dm.org/powerpiper/ipfs-uploader/?type=dev) 12 | [![Build Status](https://travis-ci.org/TalaikisInc/ipfs-uploader.svg?branch=master)](https://travis-ci.org/powerpiper/ipfs-uploader) 13 | 14 | Upload your images to Interplanetary file system (IPFS) and see them. 15 | 16 | ## Demo 17 | 18 | [Visit](https://ipfs.talaikis.com/upload) 19 | -------------------------------------------------------------------------------- /src/containers/Header.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connect } from 'react-redux' 3 | import { Redirect } from 'react-router' 4 | 5 | import Heading from 'grommet/components/Heading' 6 | import Tabs from 'grommet/components/Tabs' 7 | import Box from 'grommet/components/Box' 8 | import Tab from 'grommet/components/Tab' 9 | 10 | import * as actions from '../actions' 11 | 12 | class Header extends Component { 13 | render () { 14 | return ( 15 | 16 | IPFS Image Uploader 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ) 27 | } 28 | } 29 | 30 | function mapStateToProps(state) { 31 | return { } 32 | } 33 | 34 | export default connect(mapStateToProps, actions)(Header) 35 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | IPFS Uploader 10 | 11 | 12 | 18 | 19 | 20 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ipfs-uploader", 3 | "version": "0.2.0", 4 | "private": false, 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/TalaikisINc/ipfs-uploader.git" 8 | }, 9 | "author": "Tadas Talaikis ", 10 | "license": "GPL-3.0", 11 | "bugs": { 12 | "url": "https://github.com/TalaikisINc/ipfs-uploader/issues" 13 | }, 14 | "dependencies": { 15 | "compression": "^1.7.4", 16 | "express": "^4.17.1", 17 | "grommet": "^1.13.0", 18 | "grommet-css": "^1.6.0", 19 | "ipfs-mini": "^1.1.5", 20 | "react": "^16.9.0", 21 | "react-dom": "^16.9.0", 22 | "react-ga": "^2.6.0", 23 | "react-redux": "^7.1.1", 24 | "react-router-dom": "^4.3.1", 25 | "react-scripts": "3.1.1", 26 | "redux": "^4.0.4", 27 | "redux-thunk": "^2.3.0" 28 | }, 29 | "scripts": { 30 | "start": "react-scripts start", 31 | "build": "react-scripts build", 32 | "eject": "react-scripts eject", 33 | "analyze": "source-map-explorer build/static/js/main.*", 34 | "cypress:open": "cypress open", 35 | "cypress:run": "cypress run --record", 36 | "test": "jest" 37 | }, 38 | "devDependencies": { 39 | "babel-plugin-import": "^1.12.1", 40 | "eslint": "^6.3.0", 41 | "eslint-config-standard": "^14.1.0", 42 | "eslint-plugin-import": "^2.18.2", 43 | "eslint-plugin-node": "^9.2.0", 44 | "eslint-plugin-promise": "^4.2.1", 45 | "eslint-plugin-react": "^7.14.3", 46 | "eslint-plugin-standard": "^4.0.1", 47 | "husky": "^3.0.4", 48 | "jest": "^24.9.0", 49 | "source-map-explorer": "^2.0.1" 50 | }, 51 | "browserslist": { 52 | "production": [ 53 | ">0.2%", 54 | "not dead", 55 | "not op_mini all" 56 | ] 57 | }, 58 | "husky": { 59 | "hooks": { 60 | "pre-commit": "npm run build", 61 | "pre-push": "npm run build" 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/components/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connect } from 'react-redux' 3 | import { BrowserRouter, Route } from 'react-router-dom' 4 | import ReactGA from 'react-ga' 5 | 6 | import '../../node_modules/grommet-css' 7 | import App from 'grommet/components/App' 8 | import Box from 'grommet/components/Box' 9 | 10 | import * as actions from '../actions' 11 | import Header from '../containers/Header' 12 | import Footer from '../containers/Footer' 13 | import Status from './Status' 14 | import See from './See' 15 | import Upload from './Upload' 16 | 17 | class _App extends Component { 18 | initGA () { 19 | ReactGA.initialize(process.env.GA_TRACKING_ID) 20 | // console.log('Initialized') 21 | } 22 | 23 | logPageView () { 24 | ReactGA.set({ page: window.location.pathname }) 25 | ReactGA.pageview(window.location.pathname) 26 | // console.log(`Logged: ${window.location.pathname}`) 27 | } 28 | 29 | componentDidMount () { 30 | this.props.initIPFS() 31 | if (process.env.NODE_ENV === 'production') { 32 | if (!window.GA_INITIALIZED) { 33 | this.initGA() 34 | window.GA_INITIALIZED = true 35 | } 36 | this.logPageView() 37 | } 38 | } 39 | 40 | render() { 41 | return ( 42 | 43 |
44 | 45 |
46 | 47 | 48 | 49 |
50 | 51 | 52 | 53 | 54 |
55 |
56 |
57 |
58 |
59 | ) 60 | } 61 | } 62 | 63 | function mapStateToProps(state) { 64 | return { 65 | ipfs: state.ipfs 66 | } 67 | } 68 | 69 | export default connect(mapStateToProps, actions)(_App) 70 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | const isLocalhost = Boolean( 2 | window.location.hostname === 'localhost' || 3 | // [::1] is the IPv6 localhost address. 4 | window.location.hostname === '[::1]' || 5 | // 127.0.0.1/8 is considered localhost for IPv4. 6 | window.location.hostname.match( 7 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 8 | ) 9 | ) 10 | 11 | export default function register() { 12 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 13 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location) 14 | if (publicUrl.origin !== window.location.origin) { 15 | return 16 | } 17 | 18 | window.addEventListener('load', () => { 19 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js` 20 | 21 | if (isLocalhost) { 22 | checkValidServiceWorker(swUrl) 23 | 24 | navigator.serviceWorker.ready.then(() => { 25 | console.log( 26 | 'This web app is being served cache-first by a service ' + 27 | 'worker. To learn more, visit https://goo.gl/SC7cgQ' 28 | ) 29 | }) 30 | } else { 31 | registerValidSW(swUrl) 32 | } 33 | }) 34 | } 35 | } 36 | 37 | function registerValidSW(swUrl) { 38 | navigator.serviceWorker 39 | .register(swUrl) 40 | .then(registration => { 41 | registration.onupdatefound = () => { 42 | const installingWorker = registration.installing 43 | installingWorker.onstatechange = () => { 44 | if (installingWorker.state === 'installed') { 45 | if (navigator.serviceWorker.controller) { 46 | console.log('New content is available; please refresh.') 47 | } else { 48 | console.log('Content is cached for offline use.') 49 | } 50 | } 51 | } 52 | } 53 | }) 54 | .catch(error => { 55 | console.error('Error during service worker registration:', error) 56 | }) 57 | } 58 | 59 | function checkValidServiceWorker(swUrl) { 60 | fetch(swUrl) 61 | .then(response => { 62 | if ( 63 | response.status === 404 || 64 | response.headers.get('content-type').indexOf('javascript') === -1 65 | ) { 66 | navigator.serviceWorker.ready.then(registration => { 67 | registration.unregister().then(() => { 68 | window.location.reload() 69 | }) 70 | }) 71 | } else { 72 | registerValidSW(swUrl) 73 | } 74 | }) 75 | .catch(() => { 76 | console.log( 77 | 'No internet connection found. App is running in offline mode.' 78 | ) 79 | }) 80 | } 81 | 82 | export function unregister() { 83 | if ('serviceWorker' in navigator) { 84 | navigator.serviceWorker.ready.then(registration => { 85 | registration.unregister() 86 | }) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/components/Upload.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connect } from 'react-redux' 3 | 4 | import Toast from 'grommet/components/Toast' 5 | import Heading from 'grommet/components/Heading' 6 | import Box from 'grommet/components/Box' 7 | import Button from 'grommet/components/Button' 8 | import Label from 'grommet/components/Label' 9 | import Form from 'grommet/components/Form' 10 | 11 | class Put extends Component { 12 | constructor(props) { 13 | super(props) 14 | 15 | this.state = { 16 | hash: '', 17 | success: '', 18 | failure: '', 19 | modalOpen: false, 20 | document: '', 21 | loading: false 22 | } 23 | 24 | this.handleUploadFile = this.handleUploadFile.bind(this) 25 | this.handleSubmit = this.handleSubmit.bind(this) 26 | } 27 | 28 | handleUploadFile(event) { 29 | const data = event.target.files[0] 30 | const name = event.target.name 31 | if (data.type.match('image/*')) { 32 | const reader = new FileReader() 33 | reader.onload = (function(theFile) { 34 | return function(e) { 35 | this.setState({ 36 | [name]: e.target.result 37 | }) 38 | }.bind(this) 39 | }.bind(this))(data) 40 | reader.readAsDataURL(data) 41 | } else { 42 | this.setState({ 43 | modalOpen: true, 44 | failure: `We can accept only image files.` 45 | }) 46 | } 47 | } 48 | 49 | handleSubmit(event) { 50 | event.preventDefault() 51 | 52 | this.setState({ 53 | loading: true 54 | }) 55 | 56 | if (this.state.document !== '') { 57 | this.props.ipfs.addJSON(this.state.document, async (err, _hash) => { 58 | if (err) { 59 | this.setState({ 60 | failure: `Error occured: ${err.message}` 61 | }) 62 | } else { 63 | this.setState({ 64 | modalOpen: true, 65 | hash: _hash, 66 | success: `Success! Your hash: ${_hash}` 67 | }) 68 | } 69 | }) 70 | } else { 71 | this.setState({ 72 | modalOpen: true, 73 | failure: `You need an image.` 74 | }) 75 | } 76 | 77 | this.setState({ 78 | loading: false 79 | }) 80 | } 81 | 82 | render() { 83 | return ( 84 | 85 | Upload image to Interplanetary File System (IPFS) 86 | 87 |
88 | 89 | 90 | 91 | 92 | 93 | { this.state.loading ? 'Loading...' :