├── .github ├── dependabot.yml └── workflows │ └── greetings.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── calculator ├── README.md ├── packages │ └── calc │ │ └── eval │ │ ├── eval.js │ │ └── package.json └── web │ ├── index.html │ └── logo.png ├── chat ├── README.md ├── chatroom_demo-1.0.0-swagger.yaml ├── lib │ └── validation.js ├── packages │ ├── chatadmin │ │ ├── create │ │ │ ├── .include │ │ │ └── index.js │ │ ├── reset │ │ │ ├── .include │ │ │ └── index.js │ │ └── who │ │ │ ├── .include │ │ │ └── index.js │ └── chatroom │ │ ├── delAllMessages │ │ ├── .include │ │ └── index.js │ │ ├── delAllUsers │ │ ├── .include │ │ └── index.js │ │ ├── delByUserId │ │ ├── .include │ │ └── index.js │ │ ├── delMessage │ │ ├── .include │ │ └── index.js │ │ ├── getMessages │ │ ├── .include │ │ └── index.js │ │ ├── getUserList │ │ ├── .include │ │ └── index.js │ │ ├── newUserId │ │ ├── .include │ │ └── index.js │ │ └── postMessage │ │ ├── .include │ │ └── index.js ├── project.yml └── web │ ├── .gitignore │ ├── .include │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json │ └── src │ ├── actions │ └── index.js │ ├── components │ ├── App.js │ ├── Form.js │ ├── Header.js │ ├── Main.js │ ├── MessageInput.js │ ├── Messages.js │ └── Sidebar.js │ ├── constants │ └── index.js │ ├── images │ ├── Nimbella-Logo.svg │ ├── userlist-active.png │ └── userlist.png │ ├── index.js │ ├── reducers │ ├── index.js │ ├── messages.js │ └── userlist.js │ ├── serviceWorker.js │ └── stylesheets │ ├── Form.css │ ├── Header.css │ ├── MessageInput.css │ ├── Messages.css │ ├── Sidebar.css │ ├── dashboard.css │ └── style.css ├── election ├── .env-template ├── .gitignore ├── README.md ├── packages │ ├── .gitignore │ └── ge2020 │ │ ├── counties │ │ ├── .include │ │ └── index.js │ │ ├── divisions │ │ ├── __mocks__ │ │ │ └── axios.js │ │ ├── __tests__ │ │ │ └── index.spec.js │ │ ├── index.js │ │ └── package.json │ │ ├── elections │ │ ├── __mocks__ │ │ │ └── axios.js │ │ ├── __tests__ │ │ │ └── index.spec.js │ │ ├── index.js │ │ └── package.json │ │ ├── exitpolls │ │ ├── .include │ │ ├── __tests__ │ │ │ ├── empty.spec.js │ │ │ ├── exists.spec.js │ │ │ └── valid.spec.js │ │ ├── index.js │ │ └── package.json │ │ ├── news │ │ ├── __mocks__ │ │ │ └── rss.js │ │ ├── __tests__ │ │ │ ├── index.spec.js │ │ │ ├── rss.spec.js │ │ │ └── sort.spec.js │ │ ├── index.js │ │ └── package.json │ │ ├── reps │ │ ├── __mocks__ │ │ │ └── axios.js │ │ ├── __tests__ │ │ │ └── index.spec.js │ │ ├── index.js │ │ └── package.json │ │ ├── resources │ │ ├── .include │ │ └── index.js │ │ ├── results │ │ ├── all-states.js │ │ ├── election-contract.js │ │ ├── index.js │ │ ├── package.json │ │ └── web3.js │ │ ├── state_counties │ │ ├── .include │ │ └── index.js │ │ ├── timeseries │ │ ├── .include │ │ └── index.js │ │ └── voterinfo │ │ ├── __mocks__ │ │ └── axios.js │ │ ├── __tests__ │ │ └── index.spec.js │ │ ├── index.js │ │ └── package.json ├── project.yml ├── raw-data │ ├── 1976-2016-president.csv │ ├── 1976-2020-president.csv │ ├── countypres_2000-2016.csv │ └── exitpoll.csv ├── transformed-data │ ├── counties.json │ ├── counties2000.json │ ├── counties2004.json │ ├── counties2008.json │ ├── counties2012.json │ ├── exitpolls.json │ ├── resources.json │ ├── state_county_wise.json │ ├── timeseries.json │ └── total.json ├── transformers │ ├── .gitignore │ ├── counties.js │ ├── package.json │ ├── state_county_wise.js │ ├── timeseries.js │ └── total.js └── web │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .include │ ├── LICENSE.md │ ├── build.sh │ ├── package.json │ ├── public │ ├── Nimbella Election API.postman_collection.json │ ├── Nimbella Election API.yaml │ ├── favicon.ico │ ├── fonts │ │ └── Archia │ │ │ ├── archia-bold-webfont.woff2 │ │ │ ├── archia-medium-webfont.woff2 │ │ │ └── archia-semibold-webfont.woff2 │ ├── icon.png │ ├── index.html │ ├── manifest.json │ ├── robots.txt │ ├── site.webmanifest │ └── sitemap.xml │ ├── resources.md │ ├── rollup.config.js │ ├── scripts │ └── project-maps.sh │ └── src │ ├── App.js │ ├── App.scss │ ├── ProgressBar.scss │ ├── SplitElectoralVotes.scss │ ├── animations.js │ ├── components │ ├── API.js │ ├── Actions.js │ ├── ActionsPanel.js │ ├── Build.js │ ├── Cell.js │ ├── Contest.js │ ├── CountyRow.js │ ├── DeltaBarGraph.js │ ├── Divisions.js │ ├── Elections.js │ ├── ExitPolls.js │ ├── Footer.js │ ├── HeaderCell.js │ ├── Home.js │ ├── Level.js │ ├── MapExplorer.js │ ├── MapLegend.js │ ├── MapSwitcher.js │ ├── MapVisualizer.js │ ├── Minigraphs.js │ ├── Navbar.js │ ├── ProgressBar.js │ ├── Representatives.js │ ├── Resources.js │ ├── Row.js │ ├── Search.js │ ├── SplitElectoralVotes.js │ ├── State.js │ ├── StateDropdown.js │ ├── StateHeader.js │ ├── StateMeta.js │ ├── StateMetaCard.js │ ├── Table.js │ ├── Timeline.js │ ├── Timer.js │ ├── Timeseries.js │ ├── TimeseriesExplorer.js │ ├── Tooltip.js │ ├── Trends.js │ ├── USAMap.js │ ├── USAState.js │ ├── Updates.js │ ├── Voter.js │ ├── loaders │ │ ├── MapVisualizer.js │ │ ├── Table.js │ │ └── Timeseries.js │ └── snippets │ │ └── TableDeltaHelper.js │ ├── constants.js │ ├── data │ ├── API_Response │ │ ├── elections.json │ │ ├── representatives.json │ │ └── voterInfo.json │ ├── historical-maps.js │ ├── historical-maps.json │ ├── resources.js │ ├── resources.json │ ├── usa-map-dimensions.js │ ├── usa-states-dimensions.json │ └── usa-states-labels.json │ ├── hooks │ ├── useIsVisible.js │ ├── useResizeObserver.js │ ├── useScript.js │ ├── useStickySWR.js │ ├── useTime.js │ └── useTimeout.js │ ├── img │ ├── 270-marker.png │ ├── Election2020ContestBanner.png │ └── Timeline.png │ ├── index.js │ ├── placeholder.js │ ├── serviceWorker.js │ ├── setupTests.js │ ├── swBuild.js │ ├── swTemplate.js │ ├── tests │ ├── components │ │ ├── Level.test.js │ │ └── Row.test.js │ ├── mapAndApiStateNames.test.js │ └── utils │ │ └── index.js │ ├── utils │ ├── commonFunctions.js │ ├── genericTable.js │ ├── loader.js │ └── noResult.js │ ├── wdyr.js │ └── workers │ └── getCounties.js ├── hello-dotnet ├── README.md ├── packages │ └── default │ │ └── hello │ │ ├── .gitignore │ │ ├── .include │ │ ├── Hello.cs │ │ ├── build.cmd │ │ └── build.sh └── project.yml ├── hello-typescript ├── .gitignore ├── README.md ├── lib │ ├── .jestrc.json │ ├── build.sh │ ├── package.json │ ├── src │ │ └── hello │ │ │ ├── Hello.ts │ │ │ └── webpack.config.js │ ├── test │ │ └── hello │ │ │ └── Hello.test.ts │ └── tsconfig.json └── packages │ └── hello-ts │ └── hello │ ├── build.sh │ └── package.json ├── images ├── ocrtutorial-44e74a5a.svg ├── ocrtutorial-5b2cba72.svg ├── ocrtutorial-cb781cec.svg ├── qrcodetutorial-d1d54d1b.svg └── visitstutorial-cc3f0c43.svg ├── jokes ├── README.md ├── commands.yaml └── packages │ └── default │ └── joke │ ├── __main__.py │ ├── build.sh │ └── requirements.txt ├── mongo-music ├── .gitignore ├── LICENSE ├── README.md ├── packages │ └── mongo-music │ │ ├── CreateAlbum │ │ ├── .include │ │ ├── MongoMusic.API │ │ │ ├── CreateAlbum.cs │ │ │ ├── Models │ │ │ │ └── Album.cs │ │ │ └── MongoMusic.API.csproj │ │ ├── build.cmd │ │ └── build.sh │ │ └── GetAlbum │ │ ├── .include │ │ ├── MongoMusic.API │ │ ├── GetAlbum.cs │ │ ├── Models │ │ │ └── Album.cs │ │ └── MongoMusic.API.csproj │ │ ├── build.cmd │ │ └── build.sh └── project.yml ├── numbers-to-words ├── .gitignore ├── README.md └── packages │ └── default │ └── n2w │ ├── .ignores │ ├── build.sh │ ├── composer.json │ └── index.php ├── ocr ├── .gitignore ├── .slack-env.template ├── README.md ├── commander │ └── ocr.js ├── packages │ ├── ocr │ │ ├── acceptImage │ │ │ ├── .include │ │ │ ├── package.json │ │ │ └── workflow.js │ │ ├── credential │ │ │ └── index.js │ │ ├── imageToText │ │ │ ├── .include │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── progress │ │ │ └── index.js │ │ └── textToSpeech │ │ │ └── index.js │ └── utils │ │ └── slack.js ├── project-slack-notification.yml ├── project.yml └── web │ ├── .gitignore │ ├── .include │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json │ └── src │ ├── actions │ └── index.js │ ├── components │ ├── App.js │ ├── FileUpload.js │ ├── Header.js │ ├── ImageDisplay.js │ ├── Result.js │ └── TextDisplay.js │ ├── images │ ├── Nimbella-Logo.svg │ ├── covert-icon.svg │ └── upload-icon.svg │ ├── index.js │ ├── serviceWorker.js │ └── stylesheets │ ├── FileUpload.css │ ├── Header.css │ ├── ImageDisplay.css │ ├── Result.css │ ├── TextDisplay.css │ └── style.css ├── printer ├── .gitignore ├── packages │ ├── admin │ │ └── flush │ │ │ └── index.js │ └── printer │ │ ├── create │ │ ├── .include │ │ ├── __mocks__ │ │ │ └── @nimbella │ │ │ │ └── sdk.js │ │ ├── package.json │ │ ├── src │ │ │ └── create.js │ │ └── test │ │ │ └── create.test.js │ │ ├── get │ │ ├── .include │ │ ├── __mocks__ │ │ │ └── @nimbella │ │ │ │ └── sdk.js │ │ ├── package.json │ │ ├── src │ │ │ └── get.js │ │ └── test │ │ │ └── get.test.js │ │ ├── list │ │ ├── .include │ │ ├── __mocks__ │ │ │ └── @nimbella │ │ │ │ └── sdk.js │ │ ├── package.json │ │ ├── src │ │ │ └── list.js │ │ └── test │ │ │ └── list.test.js │ │ ├── notify │ │ └── slack.js │ │ └── update │ │ ├── .include │ │ ├── __mocks__ │ │ └── @nimbella │ │ │ └── sdk.js │ │ ├── package.json │ │ ├── src │ │ └── update.js │ │ └── test │ │ └── update.test.js ├── project.yml └── web │ ├── .built │ ├── .gitignore │ ├── .include │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json │ └── src │ ├── actions │ └── index.js │ ├── components │ ├── App.js │ ├── FileUpload.js │ ├── Header.js │ ├── ImageDisplay.js │ └── Result.js │ ├── images │ ├── Nimbella-Logo.svg │ ├── covert-icon.svg │ └── upload-icon.svg │ ├── index.js │ ├── serviceWorker.js │ └── stylesheets │ ├── FileUpload.css │ ├── Header.css │ ├── ImageDisplay.css │ ├── Result.css │ └── style.css ├── qrcode-multi-lang ├── README.md ├── packages │ └── default │ │ ├── qr-java │ │ ├── .gitignore │ │ ├── build.cmd │ │ ├── build.gradle │ │ ├── build.sh │ │ ├── pom.xml │ │ ├── settings.gradle │ │ └── src │ │ │ └── main │ │ │ └── java │ │ │ └── qr │ │ │ └── Generate.java │ │ └── qr │ │ ├── package.json │ │ └── qr.js ├── project.yml └── web │ ├── index.html │ └── logo.png ├── qrcode ├── README.md ├── packages │ └── default │ │ └── qr │ │ ├── package.json │ │ └── qr.js └── web │ ├── index.html │ └── logo.png ├── trade ├── README.md ├── packages │ └── tradedemo │ │ ├── buyStock.js │ │ ├── getCashBalance.js │ │ ├── getPositions.js │ │ ├── getStockDescription.js │ │ ├── getStockHistory.js │ │ ├── newAccount.js │ │ └── sellStock.js ├── project.yml ├── trading_demo-1.0.0-swagger.yaml └── web │ ├── .gitignore │ ├── .include │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json │ └── src │ ├── action │ ├── numberFormat.js │ └── services.js │ ├── components │ ├── About.js │ ├── App.js │ ├── Charts.js │ ├── ErrorBoundary.js │ ├── Header.js │ ├── Sidebar.js │ ├── Summary.js │ ├── Ticker.js │ └── submit │ │ ├── Buy.js │ │ ├── Sell.js │ │ └── Submit.js │ ├── css │ ├── Charts.css │ ├── Header.css │ ├── Sidebar.css │ └── Submit.css │ ├── images │ └── Nimbella-Logo.svg │ ├── index.js │ ├── serviceWorker.js │ └── style.css └── visits ├── README.md ├── packages └── visits │ ├── counter.php │ └── info.php └── web ├── index.html └── logo.png /.github/workflows/greetings.yml: -------------------------------------------------------------------------------- 1 | name: Greetings 2 | 3 | on: [issues] 4 | 5 | jobs: 6 | greeting: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/first-interaction@v1 10 | with: 11 | repo-token: ${{ secrets.GITHUB_TOKEN }} 12 | issue-message: > 13 | Hi there! 👋🏼 14 | As you're new to this repo, we'd like to suggest that you read our [code of conduct](https://github.com/nimbella/demo-projects/blob/master/CODE_OF_CONDUCT.md) 15 | as well as our [contribution guidelines](https://github.com/nimbella/demo-projects/blob/master/CONTRIBUTING.md). 16 | Thanks a bunch for opening your first issue! 🙏 17 | pr-message: > 18 | Congratulations on opening your first pull request! We'll get back to you as soon as possible. In the meantime, please make sure you've updated the documentation to reflect your changes and have added test automation as needed. Thanks! 🙏🏼 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | node_modules 3 | package-lock.json 4 | yarn.lock 5 | __deployer__.zip 6 | .nimbella 7 | .project 8 | .settings 9 | .idea 10 | demo-projects.iml 11 | pnpm-lock.yaml 12 | vendor 13 | composer.lock 14 | -------------------------------------------------------------------------------- /calculator/README.md: -------------------------------------------------------------------------------- 1 | # Calculator Demo 2 | A simple calculator showing how to use state, nodejs packages, html, and functions. -------------------------------------------------------------------------------- /calculator/packages/calc/eval/eval.js: -------------------------------------------------------------------------------- 1 | const expeval = require('expression-eval') 2 | const nim = require('@nimbella/sdk') 3 | const key = 'counter' 4 | 5 | function main(args) { 6 | let expr = args['text'] 7 | let result = evaluate(expr) 8 | let redis = nim.redis() 9 | return redis.getAsync(key) 10 | .then(reply => { return updateAndReply(redis, asCount(reply), result) }) 11 | .catch(err => { return updateAndReply(redis, 0, result) } ) 12 | } 13 | 14 | function evaluate(exprStr) { 15 | try { 16 | let ast = expeval.parse(exprStr) 17 | return expeval.eval(ast) 18 | } catch { 19 | return "error evaluating expression" 20 | } 21 | } 22 | function asCount(s) { 23 | if (Number.isInteger(s)) { return s } 24 | let v = parseInt(s, 10) 25 | return isNaN(v) ? 0 : v 26 | } 27 | 28 | function updateAndReply(redis, count, text) { 29 | return redis.setAsync(key, count+1) 30 | .then(reply => { return { count: count, result: text } }) 31 | .catch(err => { return { count: count, result: text } }) 32 | } 33 | 34 | exports.main = main 35 | -------------------------------------------------------------------------------- /calculator/packages/calc/eval/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "calc", 3 | "version": "1.0.0", 4 | "description": "Serverless calculator demo", 5 | "main": "eval.js", 6 | "dependencies": { 7 | "expression-eval": "^2.0.0" 8 | }, 9 | "devDependencies": { 10 | "@nimbella/sdk": "^1.2.7" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /calculator/web/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimbella/demo-projects/72740cd30fdbc8acba8b13d1199b124d3345e271/calculator/web/logo.png -------------------------------------------------------------------------------- /chat/README.md: -------------------------------------------------------------------------------- 1 | # Chatroom 2 | This example demonstrates how to simply create a chat application with system managed state. 3 | -------------------------------------------------------------------------------- /chat/lib/validation.js: -------------------------------------------------------------------------------- 1 | const validation = { 2 | secretKey: 'This is a secret key', 3 | username: (s) => { 4 | if(!s) { 5 | return { result: false, message: 'Username is required' }; 6 | } 7 | if(!/^[0-9a-zA-z_.-]+$/.test(s)) { 8 | return { result: false, message: 'Username contains numbers, English letters, dashes, dot only'}; 9 | } 10 | return { result: true }; 11 | }, 12 | message: (s) => { 13 | if(!s) { 14 | return { result: false, message: 'Message is required' }; 15 | } 16 | if(/[<>]/g.test(s)) { 17 | return { result: false, message: 'Message may contains harmful content'} 18 | } 19 | return { result: true } 20 | }, 21 | admin: (p) => { 22 | if(!p) { 23 | return { result: false, message: 'Password is required' }; 24 | } 25 | if(!/^[0-9a-zA-z_.-]+$/.test(p)) { 26 | return { result: false, message: 'Password contains numbers, English letters, dashes, dot only'}; 27 | } 28 | return { result: true } 29 | } 30 | }; 31 | 32 | module.exports = validation; 33 | -------------------------------------------------------------------------------- /chat/packages/chatadmin/create/.include: -------------------------------------------------------------------------------- 1 | index.js 2 | ../../../lib/validation.js 3 | -------------------------------------------------------------------------------- /chat/packages/chatadmin/reset/.include: -------------------------------------------------------------------------------- 1 | index.js 2 | ../../../lib/validation.js 3 | -------------------------------------------------------------------------------- /chat/packages/chatadmin/reset/index.js: -------------------------------------------------------------------------------- 1 | const { redis } = require('@nimbella/sdk'); 2 | const validation = require('./validation'); 3 | 4 | const main = (params) => { 5 | const key = params.key; 6 | const pair = validation.secretKey; 7 | const adminKey = 'chat_demo_admin/'; 8 | 9 | if(!key) { 10 | return { returnCode: -1, message: 'Please provide secret key' }; 11 | } 12 | 13 | if(key === pair) { 14 | return redis().delAsync(adminKey).then(() => { 15 | return { returnCode: 0, message: 'Admin account has been reset' }; 16 | }); 17 | } else { 18 | return { returnCode: -1, message: 'The key is not matched' }; 19 | } 20 | }; 21 | 22 | exports.main = main; 23 | -------------------------------------------------------------------------------- /chat/packages/chatadmin/who/.include: -------------------------------------------------------------------------------- 1 | index.js 2 | ../../../lib/validation.js 3 | -------------------------------------------------------------------------------- /chat/packages/chatadmin/who/index.js: -------------------------------------------------------------------------------- 1 | const { redis } = require('@nimbella/sdk'); 2 | 3 | const main = () => { 4 | const adminKey = 'chat_demo_admin/'; 5 | 6 | return redis().getAsync(adminKey).then(res => { 7 | if(res) { 8 | const admin = JSON.parse(res).username; 9 | return { returnCode: 0, admin }; 10 | } 11 | return { returnCode: -1, message: "Admin is not set yet." } 12 | }) 13 | }; 14 | 15 | exports.main = main; 16 | -------------------------------------------------------------------------------- /chat/packages/chatroom/delAllMessages/.include: -------------------------------------------------------------------------------- 1 | index.js 2 | ../../../lib/validation.js 3 | -------------------------------------------------------------------------------- /chat/packages/chatroom/delAllUsers/.include: -------------------------------------------------------------------------------- 1 | index.js 2 | ../../../lib/validation.js 3 | -------------------------------------------------------------------------------- /chat/packages/chatroom/delByUserId/.include: -------------------------------------------------------------------------------- 1 | index.js 2 | ../../../lib/validation.js 3 | -------------------------------------------------------------------------------- /chat/packages/chatroom/delByUserId/index.js: -------------------------------------------------------------------------------- 1 | const { redis } = require('@nimbella/sdk'); 2 | const validation = require('./validation'); 3 | 4 | const handleDel = (username) => { 5 | const userListKey = 'chat_demo_user_list/'; 6 | return redis().sismemberAsync(userListKey, username).then(userExists => { 7 | if(userExists) { 8 | return redis().sremAsync(userListKey, username).then(() => { 9 | return { "returnCode": 0, "username": username }; 10 | }) 11 | } 12 | return { "returnCode": -1, "message": "username is not existed" }; 13 | }) 14 | }; 15 | 16 | const handleValidate = async (username) => { 17 | const usernameValidate = validation.username(username); 18 | 19 | if(!usernameValidate.result) { 20 | return { returnCode: -1, message: usernameValidate.message } 21 | } 22 | 23 | return { returnCode: 0 } 24 | }; 25 | 26 | const main = (params) => { 27 | const username = params.username; 28 | 29 | return handleValidate(username).then(res => { 30 | if(res.returnCode === 0) { 31 | return handleDel(username).then(result => { 32 | return result 33 | }); 34 | } else { 35 | return res; 36 | } 37 | }); 38 | }; 39 | 40 | exports.main = main; 41 | -------------------------------------------------------------------------------- /chat/packages/chatroom/delMessage/.include: -------------------------------------------------------------------------------- 1 | index.js 2 | ../../../lib/validation.js 3 | -------------------------------------------------------------------------------- /chat/packages/chatroom/delMessage/index.js: -------------------------------------------------------------------------------- 1 | const { redis } = require('@nimbella/sdk'); 2 | 3 | const main = (params) => { 4 | const messagePoolKey = 'chat_demo_message_pool'; 5 | return redis().lrangeAsync(messagePoolKey, 0, -1).then((list) => { 6 | return redis().lremAsync('chat_demo_message_pool', 1, JSON.stringify(params.item)).then(res => { 7 | if(res === 0) { 8 | return { returnCode: -1, message: "Message data is mismatched" } 9 | } 10 | return { returnCode: 0, timestamp: params.item.timestamp } 11 | }); 12 | }); 13 | }; 14 | 15 | exports.main = main; 16 | -------------------------------------------------------------------------------- /chat/packages/chatroom/getMessages/.include: -------------------------------------------------------------------------------- 1 | index.js 2 | ../../../lib/validation.js 3 | -------------------------------------------------------------------------------- /chat/packages/chatroom/getMessages/index.js: -------------------------------------------------------------------------------- 1 | const { redis } = require('@nimbella/sdk'); 2 | 3 | const main = (params) => { 4 | const messagePoolKey = 'chat_demo_message_pool'; 5 | 6 | return redis().llenAsync(messagePoolKey).then(total => { 7 | let limit = 10; 8 | let page = (params.page) ? parseInt(params.page) : 1; 9 | let maxPage = Math.round(total/limit); 10 | let from = (maxPage > page) ? (total - limit * page) : 0; 11 | let to = (page === 1) ? total : (total - ((page - 1) * limit) - 1); 12 | 13 | if(!params.page) { // get all message list if params is not available 14 | from = 0; 15 | to = -1; 16 | maxPage = 1; 17 | page = 1; 18 | } 19 | 20 | if(page <= maxPage && page > 0) { 21 | return redis().lrangeAsync(messagePoolKey, from, to).then(res => { 22 | const messages = res.map(i => { 23 | return JSON.parse(i); 24 | }); 25 | return { messages, returnCode: 0, total }; 26 | }) 27 | } else { 28 | return { returnCode: -1, message: 'page over the range limit', maxPage, page } 29 | } 30 | 31 | }); 32 | 33 | }; 34 | 35 | exports.main = main; 36 | -------------------------------------------------------------------------------- /chat/packages/chatroom/getUserList/.include: -------------------------------------------------------------------------------- 1 | index.js 2 | ../../../lib/validation.js 3 | -------------------------------------------------------------------------------- /chat/packages/chatroom/getUserList/index.js: -------------------------------------------------------------------------------- 1 | const { redis } = require('@nimbella/sdk'); 2 | 3 | const main = () => { 4 | const userListKey = 'chat_demo_user_list/'; 5 | 6 | return redis().smembersAsync(userListKey).then(userlist => { 7 | return { returnCode: 0, userlist }; 8 | }) 9 | }; 10 | 11 | exports.main = main; 12 | -------------------------------------------------------------------------------- /chat/packages/chatroom/newUserId/.include: -------------------------------------------------------------------------------- 1 | index.js 2 | ../../../lib/validation.js 3 | -------------------------------------------------------------------------------- /chat/packages/chatroom/newUserId/index.js: -------------------------------------------------------------------------------- 1 | const { redis } = require('@nimbella/sdk'); 2 | const validation = require('./validation'); 3 | 4 | const handleAddUser = (username) => { 5 | const userListKey = 'chat_demo_user_list/'; 6 | 7 | return redis().sismemberAsync(userListKey, username).then(userExists => { 8 | if(userExists) { 9 | return { "returnCode": -1, "message": 'username has benn taken' }; 10 | } 11 | return redis().saddAsync('chat_demo_user_list/', username).then(() => { 12 | return { "returnCode": 0, "username": username }; 13 | }) 14 | }) 15 | }; 16 | 17 | const handleValidate = async (username) => { 18 | const usernameValidate = validation.username(username); 19 | 20 | if(!usernameValidate.result) { 21 | return { returnCode: -1, message: usernameValidate.message } 22 | } 23 | 24 | return { returnCode: 0 } 25 | }; 26 | 27 | const main = (params) => { 28 | const username = params.username; 29 | 30 | return handleValidate(username).then(res => { 31 | if(res.returnCode === 0) { 32 | return handleAddUser(username).then(status => { 33 | return status 34 | }); 35 | } else { 36 | return res; 37 | } 38 | }); 39 | }; 40 | 41 | 42 | exports.main = main; 43 | -------------------------------------------------------------------------------- /chat/packages/chatroom/postMessage/.include: -------------------------------------------------------------------------------- 1 | index.js 2 | ../../../lib/validation.js 3 | -------------------------------------------------------------------------------- /chat/packages/chatroom/postMessage/index.js: -------------------------------------------------------------------------------- 1 | const { redis } = require('@nimbella/sdk'); 2 | const validation = require('./validation'); 3 | 4 | const handleAddMessage = (username, message, timestamp) => { 5 | const messagePoolKey = 'chat_demo_message_pool'; 6 | const userListKey = 'chat_demo_user_list/'; 7 | 8 | return redis().sismemberAsync(userListKey, username).then(userExists => { 9 | if(userExists) { 10 | return redis().rpushAsync(messagePoolKey, JSON.stringify({ message, timestamp, username })).then(() => { 11 | return { "returnCode": 0, username, message, timestamp }; 12 | }) 13 | } else { 14 | return { "returnCode": -1, message: 'Username is not existed' }; 15 | } 16 | }) 17 | }; 18 | 19 | const handleValidate = async (username, message) => { 20 | const usernameValidate = validation.username(username); 21 | const messageValidate = validation.message(message); 22 | 23 | if(!usernameValidate.result) { 24 | return { returnCode: -1, message: usernameValidate.message } 25 | } 26 | 27 | if(!messageValidate.result) { 28 | return { returnCode: -1, message: messageValidate.message } 29 | } 30 | return { returnCode: 0 } 31 | }; 32 | 33 | const main = (params) => { 34 | const username = params.username; 35 | const message = params.message; 36 | const timestamp = new Date(); 37 | 38 | return handleValidate(username, message).then(res => { 39 | if(res.returnCode === 0) { 40 | return handleAddMessage(username, message, timestamp).then(result => { 41 | return result; 42 | }); 43 | } else { 44 | return res; 45 | } 46 | }); 47 | }; 48 | 49 | exports.main = main; 50 | -------------------------------------------------------------------------------- /chat/project.yml: -------------------------------------------------------------------------------- 1 | bucket: 2 | strip: 1 3 | -------------------------------------------------------------------------------- /chat/web/.gitignore: -------------------------------------------------------------------------------- 1 | /*.js 2 | /*.css 3 | /*.html 4 | /*.ico 5 | /static/ 6 | /build/ 7 | /*manifest.json -------------------------------------------------------------------------------- /chat/web/.include: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /chat/web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chatdemo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "bootstrap": "^3.4.1", 7 | "jquery": "^3.3.1", 8 | "prop-types": "^15.7.2", 9 | "react": "^16.8.3", 10 | "react-dom": "^16.8.3", 11 | "react-emojione": "^5.0.1", 12 | "react-redux": "^6.0.1", 13 | "react-scripts": "^3.0.0", 14 | "redux": "^4.0.1", 15 | "redux-thunk": "^2.3.0", 16 | "typescript": "^3.3.3333" 17 | }, 18 | "scripts": { 19 | "start": "PORT=3001 react-scripts start", 20 | "build": "react-scripts build", 21 | "test": "react-scripts test", 22 | "eject": "react-scripts eject" 23 | }, 24 | "eslintConfig": { 25 | "extends": "react-app" 26 | }, 27 | "browserslist": [ 28 | ">0.2%", 29 | "not dead", 30 | "not ie <= 11", 31 | "not op_mini all" 32 | ], 33 | "devDependencies": { 34 | "redux-devtools-extension": "^2.13.8", 35 | "redux-logger": "^3.0.6" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /chat/web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimbella/demo-projects/72740cd30fdbc8acba8b13d1199b124d3345e271/chat/web/public/favicon.ico -------------------------------------------------------------------------------- /chat/web/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 15 | 16 | 25 | Nimbella Chatroom 26 | 27 | 28 | 29 |
30 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /chat/web/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": ">Nimbella Chatroom", 3 | "name": ">Nimbella Chatroom Application", 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": "#122A2E", 14 | "background_color": "#222222" 15 | } 16 | -------------------------------------------------------------------------------- /chat/web/src/components/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from 'react'; 2 | import { connect } from "react-redux"; 3 | 4 | import Header from './Header'; 5 | import Sidebar from './Sidebar'; 6 | import Main from './Main'; 7 | import Form from './Form'; 8 | 9 | class App extends Component { 10 | constructor(props) { 11 | super(props); 12 | 13 | this.state = { 14 | showForm: false, 15 | showSidebar: false 16 | } 17 | } 18 | 19 | componentDidMount() { 20 | const username = localStorage.getItem('username'); 21 | if(!username) { 22 | this.setState({ 23 | showForm: true 24 | }) 25 | } 26 | } 27 | 28 | componentWillReceiveProps(nextProps, nextContext) { 29 | const username = localStorage.getItem('username'); 30 | if(nextProps.userlist.filter(curr => curr.username === username).length) { 31 | this.setState({ showForm: false }); 32 | } else { 33 | this.setState({ showForm: true }); 34 | } 35 | }; 36 | 37 | handleCloseForm = () => { 38 | this.setState({ 39 | showForm: false 40 | }) 41 | }; 42 | 43 | handleSidebar = () => { 44 | this.setState({ showSidebar: !this.state.showSidebar }); 45 | }; 46 | 47 | render() { 48 | const { showForm, showSidebar } = this.state; 49 | return ( 50 | 51 | { showForm &&
} 52 |
53 |
54 |
55 | 56 |
57 |
58 |
59 | 60 | ); 61 | } 62 | } 63 | 64 | const mapStateToProps = (state) => { 65 | return { 66 | userlist: state.userlist 67 | } 68 | }; 69 | 70 | export default connect(mapStateToProps, null)(App); 71 | -------------------------------------------------------------------------------- /chat/web/src/components/Header.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import logo from '../images/Nimbella-Logo.svg'; 3 | import '../stylesheets/Header.css'; 4 | import PropTypes from 'prop-types'; 5 | import { connect } from "react-redux"; 6 | 7 | class Header extends Component { 8 | handleSidebar = (e) => { 9 | e.preventDefault(); 10 | this.props.fn(); 11 | }; 12 | 13 | render() { 14 | const { show, userlist } = this.props; 15 | const userNum = userlist.length; 16 | return ( 17 | 26 | ); 27 | } 28 | } 29 | 30 | Header.propTypes = { 31 | fn: PropTypes.func.isRequired, 32 | show: PropTypes.bool.isRequired 33 | }; 34 | 35 | const mapStateToProps = (state) => { 36 | return{ 37 | userlist: state.userlist, 38 | } 39 | }; 40 | 41 | export default connect(mapStateToProps, {})(Header); 42 | -------------------------------------------------------------------------------- /chat/web/src/components/Main.js: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from 'react'; 2 | 3 | import Messages from './Messages'; 4 | import MessageInput from './MessageInput'; 5 | 6 | class Main extends Component { 7 | render() { 8 | return ( 9 | 10 |
11 | 12 |
13 | 14 |
15 | ); 16 | } 17 | } 18 | 19 | export default Main; -------------------------------------------------------------------------------- /chat/web/src/components/MessageInput.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { connect } from "react-redux"; 4 | import { submitMessage } from "../actions"; 5 | 6 | import '../stylesheets/MessageInput.css'; 7 | 8 | class Message extends Component { 9 | constructor(props) { 10 | super(props); 11 | 12 | this.state = { 13 | isLoading: false 14 | } 15 | }; 16 | 17 | handleLoading = () => { 18 | this.setState({ isLoading: true }); 19 | }; 20 | 21 | handleUnloading = () => { 22 | this.setState({ isLoading: false }); 23 | }; 24 | 25 | handleEnter = (e) => { 26 | let message = this.refs.message.value; 27 | let username = localStorage.getItem('username'); 28 | if(e.key === 'Enter' && message) { 29 | this.handleLoading(); 30 | this.props.submitMessage({ message, username }) 31 | .then(res => { 32 | if(res.returnCode === 0) { 33 | this.handleUnloading(); 34 | this.refs.message.focus(); 35 | this.refs.message.value = ''; 36 | } 37 | }) 38 | .catch(err => console.log(err)); 39 | } 40 | }; 41 | 42 | render() { 43 | const { isLoading } = this.state; 44 | return ( 45 |
46 |
47 | 48 |
49 |
50 | ); 51 | } 52 | } 53 | 54 | Message.propTypes = { 55 | submitMessage: PropTypes.func.isRequired 56 | 57 | }; 58 | 59 | export default connect(null, { submitMessage })(Message); -------------------------------------------------------------------------------- /chat/web/src/constants/index.js: -------------------------------------------------------------------------------- 1 | export const SET_USERNAME = 'SET_USERNAME'; 2 | export const GET_USERLIST = 'GET_USERLIST'; 3 | export const DEL_USERNAME = 'DEL_USERNAME'; 4 | 5 | export const POST_MESSAGE = 'POST_MESSAGE'; 6 | export const GET_MESSAGES = 'GET_MESSAGES'; 7 | export const DEL_MESSAGE = 'DEL_MESSAGE'; 8 | 9 | export const GET_LOOPUSERLIST = 'GET_LOOPUSERLIST'; 10 | export const GET_LOOPMESSAGES = 'GET_LOOPMESSAGES'; 11 | -------------------------------------------------------------------------------- /chat/web/src/images/userlist-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimbella/demo-projects/72740cd30fdbc8acba8b13d1199b124d3345e271/chat/web/src/images/userlist-active.png -------------------------------------------------------------------------------- /chat/web/src/images/userlist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimbella/demo-projects/72740cd30fdbc8acba8b13d1199b124d3345e271/chat/web/src/images/userlist.png -------------------------------------------------------------------------------- /chat/web/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './components/App'; 4 | import * as serviceWorker from './serviceWorker'; 5 | 6 | import { Provider } from 'react-redux'; 7 | import { createStore, applyMiddleware } from "redux"; 8 | import rootReducer from './reducers'; 9 | import { composeWithDevTools } from "redux-devtools-extension"; 10 | import { createLogger } from 'redux-logger'; 11 | import thunk from 'redux-thunk'; 12 | 13 | import { GET_LOOPUSERLIST, GET_LOOPMESSAGES } from "./constants"; 14 | 15 | import 'bootstrap/dist/css/bootstrap.min.css'; 16 | import './stylesheets/dashboard.css'; 17 | import './stylesheets/style.css'; 18 | 19 | const logger = createLogger({ 20 | predicate: (getState, action) => action.type !== GET_LOOPMESSAGES && action.type !== GET_LOOPUSERLIST 21 | }); 22 | 23 | const store = createStore( 24 | rootReducer, 25 | composeWithDevTools( 26 | applyMiddleware(thunk, logger) 27 | ) 28 | ); 29 | 30 | ReactDOM.render(, document.getElementById('root')); 31 | 32 | // If you want your app to work offline and load faster, you can change 33 | // unregister() to register() below. Note this comes with some pitfalls. 34 | // Learn more about service workers: http://bit.ly/CRA-PWA 35 | serviceWorker.unregister(); 36 | -------------------------------------------------------------------------------- /chat/web/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from "redux"; 2 | import userlist from './userlist'; 3 | import messages from './messages' 4 | 5 | export default combineReducers({ 6 | userlist, 7 | messages 8 | }); -------------------------------------------------------------------------------- /chat/web/src/reducers/messages.js: -------------------------------------------------------------------------------- 1 | import { POST_MESSAGE, GET_MESSAGES, GET_LOOPMESSAGES, DEL_MESSAGE } from "../constants"; 2 | 3 | const messages = (state = [], action = {}) => { 4 | switch(action.type) { 5 | case POST_MESSAGE: 6 | return [ 7 | ...state, 8 | action.messageData 9 | ]; 10 | case DEL_MESSAGE: 11 | return state.filter((curr, i , arr) => { 12 | return curr.timestamp !== action.timestamp; 13 | }); 14 | case GET_MESSAGES: 15 | return action.messages; 16 | default: return state; 17 | case GET_LOOPMESSAGES: 18 | return action.messages; 19 | } 20 | }; 21 | 22 | export default messages; -------------------------------------------------------------------------------- /chat/web/src/reducers/userlist.js: -------------------------------------------------------------------------------- 1 | import { SET_USERNAME, GET_USERLIST, GET_LOOPUSERLIST, DEL_USERNAME } from "../constants"; 2 | 3 | const userlist = (state = [], action = {}) => { 4 | switch(action.type) { 5 | case SET_USERNAME: 6 | return [ 7 | ...state, 8 | action.username 9 | ]; 10 | case DEL_USERNAME: 11 | return state.filter((curr, i , arr) => { 12 | return curr.username !== action.username; 13 | }); 14 | case GET_USERLIST: 15 | return action.userlist; 16 | default: return state; 17 | case GET_LOOPUSERLIST: 18 | return action.userlist; 19 | } 20 | }; 21 | 22 | export default userlist; -------------------------------------------------------------------------------- /chat/web/src/stylesheets/Form.css: -------------------------------------------------------------------------------- 1 | .mask{ display: inline-block; width: 100%; height: 100%; background-color: rgba(4,28,33,.8); position: fixed; top: 0; left: 0; z-index: 9999; } 2 | 3 | .mask .header h3{ display: inline;} 4 | 5 | .mask .form-wrap{ background-color: white; margin-top: 5%; padding: 2% 3%; border-radius: 5px; } 6 | 7 | .form-group.one-line span { display: inline-block; } 8 | 9 | .form-group.has-error div:first-child{ border-color: #a94442; } -------------------------------------------------------------------------------- /chat/web/src/stylesheets/Header.css: -------------------------------------------------------------------------------- 1 | .navbar .logo-text{ display: inline-block; margin:0; padding: 20px 0; min-height:50px; } 2 | .navbar .navbar-brand{ padding: 0; height: 55px; } 3 | .navbar .navbar-brand:hover{ color: #55BFD1; } 4 | .navbar-nav{ list-style: none; float: right; min-height: 50px; padding: 20px; margin-right: 15px; } 5 | .navbar-nav .nav-item{ margin-left: 40px; } 6 | .navbar .list-icon{ position: relative; display: line-block; float: right;margin-top: 15px; } 7 | .navbar .list-icon::before{ content: ''; position: absolute; right: 15px; top: -12px; background: transparent url(../images/userlist.png) center center no-repeat; background-size: 50px; display: inline-block; width: 50px; height: 50px; } 8 | .navbar .list-icon:hover, .navbar .list-icon.active{ color: #55BFD1; } 9 | .navbar .list-icon:hover::before, .navbar .list-icon.active::before{ background-image: url(../images/userlist-active.png); } 10 | 11 | 12 | @media only screen and (max-width: 736px) { 13 | .navbar-nav{ padding: 12px 12px 0 0; margin: 0; float: right !important; } 14 | } 15 | 16 | @media only screen and (max-width: 414px) { 17 | .navbar-brand{ padding: 15px 5px; } 18 | .navbar-collapse{ float: left; } 19 | .navbar-collapse .nav-item{ margin-left: 10px; } 20 | } -------------------------------------------------------------------------------- /chat/web/src/stylesheets/MessageInput.css: -------------------------------------------------------------------------------- 1 | .input-wrap{ position: fixed !important; bottom: 0; background-color: white; padding: 20px; border-top: 1px solid #efefef; } 2 | .input-wrap .form-group{ margin: 0; } -------------------------------------------------------------------------------- /chat/web/src/stylesheets/Messages.css: -------------------------------------------------------------------------------- 1 | .messages-wrap { list-style: none; padding-left: 0; padding-bottom: 60px; overflow-y: auto; height: 1000px; } 2 | .messages-wrap .message{ margin: 10px auto; } 3 | 4 | .messages-wrap .message .init{ display: inline-block; margin-right: 10px; vertical-align: top; margin-top: 15px; } 5 | .messages-wrap .message .info{ display: inline-block; width: 60%; } 6 | .messages-wrap .message .info span:first-of-type{ margin-right: 10px; } 7 | .messages-wrap .message .info .message-box{ display: inline-block; background-color: #E2F6F9; border: 1px solid #CDE2E5; padding: 10px; width: 100%; border-radius: 5px; word-wrap: break-word; word-break: break-word; } 8 | 9 | .messages-wrap .message.me .info{ margin-left: 90px; position: relative; } 10 | .messages-wrap .message.me .info .del-btn{ position: absolute; right: -25px; top: 50%; cursor: pointer; transition: opacity .3s ease-in-out; opacity: 0; } 11 | .messages-wrap .message.me .info:hover .del-btn{ opacity: 0.5; } 12 | .messages-wrap .message.me .info .message-box{ background-color: #E4F9E2; border-color: #D1E5CF; } -------------------------------------------------------------------------------- /chat/web/src/stylesheets/Sidebar.css: -------------------------------------------------------------------------------- 1 | .sidebar-wrap .sidebar{ display: block; background-color: #122A2E; } 2 | .sidebar-wrap .nav-sidebar{ padding-top: 10px; padding-bottom: 20px; } 3 | .sidebar-wrap .nav-sidebar > li.nav-item{ padding-left: 20px; padding-right: 20px; margin:10px 0; } 4 | .sidebar-wrap .nav-sidebar li.nav-item span:nth-of-type(2) { margin-left: 10px; } 5 | .sidebar-wrap .nav-sidebar li.nav-item.active span:nth-of-type(2) { color: #55BFD1; } 6 | .sidebar-wrap .person{ position: fixed; bottom: 0; left: 0; text-align: center; } 7 | .sidebar-wrap .person span{ background-color: #122A2E; width: 100%; display: inline-block; padding-top: 20px; padding-bottom: 20px; } 8 | .sidebar-wrap .del-btn{ margin-top: 12px; cursor: pointer; opacity: 0; transition: opacity .3s ease-in-out; } 9 | .sidebar-wrap .nav-item:hover .del-btn{ opacity: 1; } 10 | 11 | @media only screen and (max-width: 768px) { 12 | .sidebar-wrap{ padding-top: 0; padding-bottom: 0; } 13 | .sidebar-wrap .sidebar { background-color: #122A2E; } 14 | } 15 | 16 | @media only screen and (max-width: 767px) { 17 | .sidebar-wrap{ position: fixed; z-index: 999; background-color: rgba(18,42,46, .9); padding-top: 20px; padding-bottom: 20px; } 18 | .sidebar-wrap .sidebar{ overflow-y: auto; overflow-x: hidden; background-color: transparent; } 19 | .sidebar-wrap .nav-sidebar{ padding-bottom: 0; } 20 | .sidebar-wrap .nav-sidebar > li.nav-item{ padding-left: 20px; padding-right: 0px; margin:5px 0; } 21 | } -------------------------------------------------------------------------------- /election/.env-template: -------------------------------------------------------------------------------- 1 | GOOGLE_CIVIC_API_TOKEN= 2 | INFURA_MAINNET_HTTP= 3 | -------------------------------------------------------------------------------- /election/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .nimbella 3 | coverage 4 | web/.built 5 | -------------------------------------------------------------------------------- /election/packages/.gitignore: -------------------------------------------------------------------------------- 1 | attic/ 2 | __pycache__/ 3 | virtualenv/ 4 | *.zip 5 | *.pyc 6 | *.done 7 | -------------------------------------------------------------------------------- /election/packages/ge2020/counties/.include: -------------------------------------------------------------------------------- 1 | ../../../transformed-data/counties.json 2 | ../../../transformed-data/counties2000.json 3 | ../../../transformed-data/counties2004.json 4 | ../../../transformed-data/counties2008.json 5 | ../../../transformed-data/counties2012.json 6 | index.js 7 | -------------------------------------------------------------------------------- /election/packages/ge2020/counties/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | function main(args) { 4 | 5 | const year = args.year || ''; 6 | const contents = fs.readFileSync(__dirname + `/counties${year}.json`, 'UTF-8'); 7 | 8 | return { body: JSON.parse(contents) }; 9 | 10 | } 11 | 12 | exports.main = main; 13 | -------------------------------------------------------------------------------- /election/packages/ge2020/divisions/__mocks__/axios.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get: jest.fn(() => Promise.resolve({ data: {} })) 3 | }; 4 | -------------------------------------------------------------------------------- /election/packages/ge2020/divisions/__tests__/index.spec.js: -------------------------------------------------------------------------------- 1 | const axios = require("../__mocks__/axios") 2 | const { main } = require('../index') 3 | 4 | it("fetches divisions data", async () => { 5 | // arrange 6 | const response = 7 | { 8 | "kind": "civicinfo#divisionSearchResponse", 9 | "results": [ 10 | { 11 | "ocdId": "testOcdId", 12 | "name": "testName", 13 | "aliases": [ 14 | "testAlias" 15 | ] 16 | } 17 | ] 18 | } 19 | 20 | 21 | axios.get.mockImplementationOnce(() => 22 | Promise.resolve({ data: response }), 23 | ); 24 | 25 | // act 26 | const data = await main({}) 27 | 28 | // assert 29 | expect(data).toStrictEqual({ body: response }) 30 | expect(axios.get).toHaveBeenCalledTimes(1) 31 | }); 32 | 33 | 34 | it('returns error message on failure', async () => { 35 | // arrange 36 | const errorMessage = 'Network Error'; 37 | const message = { "body": errorMessage } 38 | 39 | axios.get.mockImplementationOnce(() => 40 | Promise.reject(new Error(errorMessage)), 41 | ); 42 | 43 | // act 44 | const data = await main({}) 45 | 46 | // assert 47 | expect(data).toStrictEqual(message) 48 | }); 49 | -------------------------------------------------------------------------------- /election/packages/ge2020/divisions/index.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | const google_api_token = process.env.GOOGLE_CIVIC_API_TOKEN 3 | 4 | async function main(args) { 5 | const { query = '' } = args 6 | const path = `/civicinfo/v2/divisions?query=${encodeURI(query)}&key=${google_api_token}` 7 | 8 | return axios 9 | .get(`https://www.googleapis.com/${path}`) 10 | .then(res => ({ body: res.data })) 11 | .catch(error => ({ body: error.message })) 12 | } 13 | 14 | exports.main = main 15 | -------------------------------------------------------------------------------- /election/packages/ge2020/divisions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "divisions", 3 | "version": "1.0.0", 4 | "description": "List political divisions by their natural name or OCD ID", 5 | "main": "index.js", 6 | "devDependencies": { 7 | "jest": "^26.5.3" 8 | }, 9 | "scripts": { 10 | "test": "jest" 11 | }, 12 | "jest": { 13 | "collectCoverage": true, 14 | "coverageReporters": [ 15 | "html" 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /election/packages/ge2020/elections/__mocks__/axios.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get: jest.fn(() => Promise.resolve({ data: {} })) 3 | }; 4 | -------------------------------------------------------------------------------- /election/packages/ge2020/elections/__tests__/index.spec.js: -------------------------------------------------------------------------------- 1 | const axios = require("../__mocks__/axios") 2 | const { main } = require('../index') 3 | 4 | it("fetches election data", async () => { 5 | // arrange 6 | const response = { 7 | "elections": [ 8 | { 9 | "id": "2000", 10 | "name": "VIP Test Election", 11 | "electionDay": "2021-06-06", 12 | "ocdDivisionId": "ocd-division/country:us" 13 | }, 14 | { 15 | "id": "4955", 16 | "name": "West Virginia Presidential and State Primary Election", 17 | "electionDay": "2020-06-09", 18 | "ocdDivisionId": "ocd-division/country:us/state:wv" 19 | }, 20 | { 21 | "id": "4958", 22 | "name": "Georgia Presidential and State Primary Election", 23 | "electionDay": "2020-06-09", 24 | "ocdDivisionId": "ocd-division/country:us/state:ga" 25 | }] 26 | } 27 | 28 | axios.get.mockImplementationOnce(() => 29 | Promise.resolve({ data: response }), 30 | ); 31 | 32 | // act 33 | const data = await main() 34 | 35 | // assert 36 | expect(data).toStrictEqual({ body: response }) 37 | expect(axios.get).toHaveBeenCalledTimes(1) 38 | }); 39 | 40 | 41 | it('returns error message on failure', async () => { 42 | // arrange 43 | const errorMessage = 'Network Error'; 44 | const message = { "body": errorMessage } 45 | 46 | axios.get.mockImplementationOnce(() => 47 | Promise.reject(new Error(errorMessage)), 48 | ); 49 | 50 | // act 51 | const data = await main() 52 | 53 | // assert 54 | expect(data).toStrictEqual(message) 55 | }); 56 | -------------------------------------------------------------------------------- /election/packages/ge2020/elections/index.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | const google_api_token = process.env.GOOGLE_CIVIC_API_TOKEN 3 | 4 | const path = `https://www.googleapis.com/civicinfo/v2/elections?key=${google_api_token}` 5 | 6 | async function main(args) { 7 | return axios 8 | .get(path) 9 | .then(res => ({ body: res.data })) 10 | .catch(error => ({ body: error.message })) 11 | } 12 | 13 | module.exports = { main } 14 | -------------------------------------------------------------------------------- /election/packages/ge2020/elections/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elections", 3 | "version": "1.0.0", 4 | "description": "Upcoming elections across USA", 5 | "main": "index.js", 6 | "devDependencies": { 7 | "jest": "^26.5.3" 8 | }, 9 | "scripts": { 10 | "test": "jest" 11 | }, 12 | "jest": { 13 | "collectCoverage": true, 14 | "coverageReporters": [ 15 | "html" 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /election/packages/ge2020/exitpolls/.include: -------------------------------------------------------------------------------- 1 | ../../../transformed-data/exitpolls.json 2 | index.js 3 | -------------------------------------------------------------------------------- /election/packages/ge2020/exitpolls/__tests__/empty.spec.js: -------------------------------------------------------------------------------- 1 | const { main } = require('../index') 2 | 3 | jest.resetModules() 4 | jest.mock('fs', () => ({ 5 | readFileSync: jest.fn(() => '{}'), 6 | })); 7 | 8 | describe('main method call', () => { 9 | test('should return empty json response', () => { 10 | // act 11 | const output = main() 12 | 13 | // assert 14 | expect(output).toStrictEqual({ body: {} }); 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /election/packages/ge2020/exitpolls/__tests__/exists.spec.js: -------------------------------------------------------------------------------- 1 | 2 | const fs = require('fs') 3 | 4 | test('should check existence of data file', () => { 5 | expect(fs.existsSync('../../../transformed-data/exitpolls.json')).toBe(true) 6 | }); 7 | -------------------------------------------------------------------------------- /election/packages/ge2020/exitpolls/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const contents = fs.readFileSync(__dirname + '/exitpolls.json', 'UTF-8'); 4 | const body = JSON.parse(contents); 5 | function main(args) { 6 | return { body }; 7 | } 8 | 9 | module.exports = { main } 10 | -------------------------------------------------------------------------------- /election/packages/ge2020/exitpolls/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "test": "jest" 4 | }, 5 | "devDependencies": { 6 | "jest": "^26.5.3" 7 | }, 8 | "jest": { 9 | "testEnvironment": "node" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /election/packages/ge2020/news/__tests__/sort.spec.js: -------------------------------------------------------------------------------- 1 | const { sortByLatest } = require('../index'); 2 | 3 | describe('sort function', () => { 4 | test('it should sort by latest date', () => { 5 | const input = [ 6 | { id: 3, created: 1602569513559 }, 7 | { id: 1, created: 1602569508015 }, 8 | { id: 2, created: 1602569511570 }, 9 | ]; 10 | 11 | const output = [ 12 | { id: 3, created: 1602569513559 }, 13 | { id: 2, created: 1602569511570 }, 14 | { id: 1, created: 1602569508015 }, 15 | ]; 16 | 17 | expect(input.sort(sortByLatest('created'))).toEqual(output); 18 | }); 19 | 20 | test('it should sort by latest id', () => { 21 | const input = [ 22 | { id: 3, created: 1602569513559 }, 23 | { id: 1, created: 1602569513559 }, 24 | { id: 2, created: 1602569511570 }, 25 | ]; 26 | 27 | const output = [ 28 | { id: 3, created: 1602569513559 }, 29 | { id: 2, created: 1602569511570 }, 30 | { id: 1, created: 1602569513559 }, 31 | ]; 32 | 33 | expect(input.sort(sortByLatest('id'))).toEqual(output); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /election/packages/ge2020/news/index.js: -------------------------------------------------------------------------------- 1 | const GoogleNewsRss = require("google-news-rss") 2 | 3 | function main(args) { 4 | const googleNews = new GoogleNewsRss() 5 | return googleNews.search("us election", 50, "en", { 6 | scoring: "n", 7 | }).then((resp) => { 8 | resp.forEach(obj => obj.created = new Date(obj.pubDate).getTime()) 9 | sorted = resp.sort(sortByLatest("created")); // scoring not working 10 | return { 11 | body: sorted, 12 | } 13 | }); 14 | } 15 | 16 | function sortByLatest(property) { 17 | return function (a, b) { 18 | if (a[property] > b[property]) return -1 19 | else if (a[property] < b[property]) return 1 20 | else return 0 21 | }; 22 | } 23 | 24 | module.exports = { main, sortByLatest } 25 | -------------------------------------------------------------------------------- /election/packages/ge2020/news/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "news", 3 | "version": "1.0.0", 4 | "description": "Election Related News", 5 | "main": "index.js", 6 | "dependencies": { 7 | "google-news-rss": "^0.4.1" 8 | }, 9 | "devDependencies": { 10 | "jest": "^26.5.3", 11 | "nock": "^13.0.4" 12 | }, 13 | "scripts": { 14 | "test": "jest" 15 | }, 16 | "jest": { 17 | "collectCoverage": true, 18 | "coverageReporters": [ 19 | "html" 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /election/packages/ge2020/reps/__mocks__/axios.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get: jest.fn(() => Promise.resolve({ data: {} })) 3 | }; 4 | -------------------------------------------------------------------------------- /election/packages/ge2020/reps/index.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | const google_api_token = process.env.GOOGLE_CIVIC_API_TOKEN 3 | 4 | async function main(args) { 5 | const { ocdId = '', address = '', levels = '', roles = '', includeOffices = true, recursive = true } = args 6 | let path = `/civicinfo/v2/representatives?address=${encodeURI(address)}&includeOffices=${includeOffices}${levels ? `&levels=${levels}` : ''}${roles ? `&roles=${roles}` : ''}&key=${google_api_token}` 7 | 8 | if (ocdId) { 9 | path = `/civicinfo/v2/representatives/${encodeURI(ocdId)}?recursive=${recursive}${levels ? `&levels=${levels}` : ''}${roles ? `&roles=${roles}` : ''}&key=${google_api_token}` 10 | } 11 | 12 | return axios 13 | .get(`https://www.googleapis.com/${path}`) 14 | .then(res => ({ body: res.data })) 15 | .catch(error => ({ body: error.message })) 16 | } 17 | 18 | exports.main = main 19 | -------------------------------------------------------------------------------- /election/packages/ge2020/reps/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reps", 3 | "version": "1.0.0", 4 | "description": "Representatives for a given address", 5 | "main": "index.js", 6 | "devDependencies": { 7 | "jest": "^26.5.3" 8 | }, 9 | "scripts": { 10 | "test": "jest" 11 | }, 12 | "jest": { 13 | "collectCoverage": true, 14 | "coverageReporters": [ 15 | "html" 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /election/packages/ge2020/resources/.include: -------------------------------------------------------------------------------- 1 | ../../../transformed-data/resources.json 2 | index.js 3 | -------------------------------------------------------------------------------- /election/packages/ge2020/resources/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const contents = fs.readFileSync(__dirname + '/resources.json', 'UTF-8'); 4 | const body = JSON.parse(contents); 5 | 6 | function main(args) { 7 | 8 | return { body }; 9 | 10 | } 11 | 12 | exports.main = main; -------------------------------------------------------------------------------- /election/packages/ge2020/results/all-states.js: -------------------------------------------------------------------------------- 1 | const allStates = [ 2 | "US", 3 | "AK", 4 | "AL", 5 | "AR", 6 | "AZ", 7 | "CA", 8 | "CO", 9 | "CT", 10 | "DC", 11 | "DE", 12 | "FL", 13 | "GA", 14 | "HI", 15 | "IA", 16 | "ID", 17 | "IL", 18 | "IN", 19 | "KS", 20 | "KY", 21 | "LA", 22 | "MA", 23 | "MD", 24 | "ME", 25 | "MI", 26 | "MN", 27 | "MO", 28 | "MS", 29 | "MT", 30 | "NC", 31 | "ND", 32 | "NE", 33 | "NH", 34 | "NJ", 35 | "NM", 36 | "NV", 37 | "NY", 38 | "OH", 39 | "OK", 40 | "OR", 41 | "PA", 42 | "RI", 43 | "SC", 44 | "SD", 45 | "TN", 46 | "TX", 47 | "UT", 48 | "VA", 49 | "VT", 50 | "WA", 51 | "WI", 52 | "WV", 53 | "WY", 54 | ]; 55 | 56 | module.exports = { allStates } 57 | -------------------------------------------------------------------------------- /election/packages/ge2020/results/index.js: -------------------------------------------------------------------------------- 1 | const { allStates } = require('./all-states') 2 | const { mainnetContract } = require('./election-contract') 3 | 4 | async function main(_args) { 5 | const result = {} 6 | const fetchAllStates = 7 | allStates.map((state) => { 8 | return mainnetContract.methods 9 | .presidentialWinners(state) 10 | .call() 11 | .then((winner) => { 12 | if (winner.winner === 'Trump') 13 | result[state] = 'red' 14 | else if (winner.winner === 'Biden') 15 | result[state] = '#007bff' 16 | else 17 | result[state] = 'grey' 18 | }); 19 | }); 20 | return Promise.all(fetchAllStates).then(_res => ({ body: result })).catch(error => ({ body: error.message })); 21 | } 22 | 23 | module.exports = { main } 24 | -------------------------------------------------------------------------------- /election/packages/ge2020/results/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "results", 3 | "version": "1.0.0", 4 | "description": "Election results live", 5 | "main": "index.js", 6 | "dependencies": { 7 | "web3": "^1.3.0" 8 | }, 9 | "devDependencies": { 10 | "jest": "^26.5.3" 11 | }, 12 | "scripts": { 13 | "test": "jest" 14 | }, 15 | "jest": { 16 | "collectCoverage": true, 17 | "coverageReporters": [ 18 | "html" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /election/packages/ge2020/results/web3.js: -------------------------------------------------------------------------------- 1 | const Web3 = require("web3"); 2 | const web3 = new Web3( 3 | new Web3.providers.HttpProvider(process.env.INFURA_MAINNET_HTTP || 'https://mainnet.infura.io/v3/d390dc5f79ce4cf38314163a516084a1') 4 | ); 5 | module.exports = { web3 } 6 | -------------------------------------------------------------------------------- /election/packages/ge2020/state_counties/.include: -------------------------------------------------------------------------------- 1 | ../../../transformed-data/state_county_wise.json 2 | index.js 3 | -------------------------------------------------------------------------------- /election/packages/ge2020/state_counties/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const contents = fs.readFileSync(__dirname + '/state_county_wise.json', 'UTF-8'); 4 | const body = JSON.parse(contents); 5 | 6 | function main(args) { 7 | 8 | return { body }; 9 | } 10 | 11 | exports.main = main; -------------------------------------------------------------------------------- /election/packages/ge2020/timeseries/.include: -------------------------------------------------------------------------------- 1 | ../../../transformed-data/timeseries.json 2 | index.js 3 | -------------------------------------------------------------------------------- /election/packages/ge2020/timeseries/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const contents = fs.readFileSync(__dirname + '/timeseries.json', 'UTF-8'); 4 | const body = JSON.parse(contents); 5 | 6 | function main(args) { 7 | 8 | return { body }; 9 | } 10 | 11 | exports.main = main; -------------------------------------------------------------------------------- /election/packages/ge2020/voterinfo/__mocks__/axios.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get: jest.fn(() => Promise.resolve({ data: {} })) 3 | }; 4 | -------------------------------------------------------------------------------- /election/packages/ge2020/voterinfo/index.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | const google_api_token = process.env.GOOGLE_CIVIC_API_TOKEN 3 | 4 | async function main(args) { 5 | const { address = '', electionId = 0, officialOnly = true, returnAllAvailableData = true } = args 6 | let path = `/civicinfo/v2/voterinfo?address=${encodeURI(address)}&electionId=${electionId}&officialOnly=${officialOnly}&returnAllAvailableData=${returnAllAvailableData}&key=${google_api_token}` 7 | 8 | return axios 9 | .get(`https://www.googleapis.com/${path}`) 10 | .then(res => ({ body: res.data })) 11 | .catch(error => ({ body: error.message })) 12 | } 13 | 14 | exports.main = main 15 | -------------------------------------------------------------------------------- /election/packages/ge2020/voterinfo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "voterinfo", 3 | "version": "1.0.0", 4 | "description": "Voter information for a given address", 5 | "main": "index.js", 6 | "devDependencies": { 7 | "jest": "^26.5.3" 8 | }, 9 | "scripts": { 10 | "test": "jest" 11 | }, 12 | "jest": { 13 | "collectCoverage": true, 14 | "coverageReporters": [ 15 | "html" 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /election/project.yml: -------------------------------------------------------------------------------- 1 | bucket: 2 | strip: 1 3 | 4 | packages: 5 | - name: ge2020 6 | actions: 7 | - name: news 8 | - name: timeseries 9 | - name: counties 10 | - name: exitpolls 11 | - name: state_counties 12 | - name: resources 13 | - name: divisions 14 | - name: elections 15 | - name: reps 16 | - name: voterinfo 17 | - name: results 18 | -------------------------------------------------------------------------------- /election/transformers/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules/ 5 | npm-debug.log* 6 | -------------------------------------------------------------------------------- /election/transformers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "transformers", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "total.js", 6 | "dependencies": { 7 | }, 8 | "devDependencies": { 9 | "node-fetch": "^2.6.1", 10 | "fast-csv": "^4.3.6" 11 | }, 12 | "scripts": { 13 | "test": "echo \"Error: no test specified\" && exit 1" 14 | }, 15 | "author": "", 16 | "license": "ISC" 17 | } 18 | -------------------------------------------------------------------------------- /election/transformers/timeseries.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const csv = require('fast-csv'); 4 | 5 | const data = {} 6 | let years = {} 7 | let total = {} 8 | let meta = {} 9 | let candidates = {} 10 | 11 | let changers = { counter: 0 } 12 | 13 | fs.createReadStream(path.resolve(__dirname, '..', 'raw-data', '1976-2020-president.csv')) 14 | .pipe(csv.parse({ headers: true })) 15 | .on('error', error => console.error(error)) 16 | .on('data', row => { 17 | if (changers.counter == 0) { 18 | changers.state = row.state_po 19 | changers.year = row.year 20 | } 21 | changers.counter = changers.counter + 1 22 | 23 | if (changers.year != row.year) { 24 | years[changers.year] = { total, meta, candidates } 25 | total = {} 26 | meta = {} 27 | candidates = {} 28 | changers.year = row.year 29 | } 30 | 31 | if (changers.state != row.state_po) { 32 | years[changers.year] = { total, meta, candidates } 33 | data[changers.state] = Object.assign({}, data[changers.state]) 34 | data[changers.state].years = Object.assign(years, data[changers.state].years) 35 | years = {} 36 | total = {} 37 | meta = {} 38 | candidates = {} 39 | changers.state = row.state_po 40 | } 41 | total[(row.party_simplified || 'others').toLowerCase()] = row.candidatevotes 42 | meta['total'] = row.totalvotes 43 | candidates[(row.party_simplified || 'others').toLowerCase()] = row.candidate 44 | 45 | } 46 | 47 | ) 48 | .on('end', rowCount => { 49 | console.log(JSON.stringify(data, null, 4)) 50 | fs.writeFileSync('timeseries.json', JSON.stringify(data, null, 4)) 51 | console.log(`Parsed ${rowCount} rows`) 52 | }); 53 | -------------------------------------------------------------------------------- /election/transformers/total.js: -------------------------------------------------------------------------------- 1 | const fetch = require('node-fetch'); 2 | const fs = require('fs'); 3 | let url = "https://devchand-hfo8lwtg1e5-apigcp.nimbella.io/api/ge2020/timeseries"; 4 | 5 | let settings = { method: "Get" }; 6 | const TT = {} 7 | years = ['1976', 8 | '1980', 9 | '1984', 10 | '1988', 11 | '1992', 12 | '1996', 13 | '2000', 14 | '2004', 15 | '2008', 16 | '2012', 17 | '2016', 18 | '2020' 19 | ] 20 | const states = [ 21 | 'AK', 'AL', 'AR', 'AZ', 'CA', 'CO', 'CT', 22 | 'DC', 'DE', 'FL', 'GA', 'HI', 'IA', 'ID', 23 | 'IL', 'IN', 'KS', 'KY', 'LA', 'MA', 'MD', 24 | 'ME', 'MI', 'MN', 'MO', 'MS', 'MT', 'NC', 25 | 'ND', 'NE', 'NH', 'NJ', 'NM', 'NV', 'NY', 26 | 'OH', 'OK', 'OR', 'PA', 'RI', 'SC', 'SD', 27 | 'TN', 'TX', 'UT', 'VA', 'VT', 'WA', 'WI', 28 | 'WV', 'WY' 29 | ] 30 | fetch(url, settings) 31 | .then(res => res.json()) 32 | .then((json) => { 33 | years.forEach(year => { 34 | const counter = {democrat:0,libertarian:0,other:0,republican:0} 35 | states.forEach(state => { 36 | Object.entries(json[state].years[year].total).forEach(e => { 37 | counter[e[0]] = counter[e[0]] + Number(e[1]) 38 | TT.years = Object.assign({}, TT.years) 39 | TT.years[year] = Object.assign({}, TT.years[year]) 40 | TT.years[year][e[0]] = counter[e[0]] //Object.assign(0, TT.years[year][e[1]]) 41 | // TT.years[year][e[0]] = Number(TT.years[year][e[0]]) + Number(e[1]) 42 | 43 | TT.years[year].total = Object.assign({}, TT.years[year].total) 44 | TT.years[year].total[e[0]] = counter[e[0]] //Object.assign(0, TT.years[year].total[e[0]]) 45 | }); 46 | 47 | }); 48 | }); 49 | fs.writeFileSync(`total.json`, JSON.stringify(TT, null, 4)) 50 | }); 51 | -------------------------------------------------------------------------------- /election/web/.eslintignore: -------------------------------------------------------------------------------- 1 | build/** 2 | node_modules/** 3 | -------------------------------------------------------------------------------- /election/web/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true, 5 | }, 6 | extends: ['plugin:react/recommended', 'google', 'prettier'], 7 | globals: { 8 | Atomics: 'readonly', 9 | SharedArrayBuffer: 'readonly', 10 | }, 11 | parser: 'babel-eslint', 12 | parserOptions: { 13 | ecmaFeatures: { 14 | jsx: true, 15 | }, 16 | ecmaVersion: 2018, 17 | sourceType: 'module', 18 | }, 19 | plugins: ['react', 'prettier', 'react-hooks', 'import'], 20 | rules: { 21 | 'require-jsdoc': 0, 22 | 'prettier/prettier': 0, 23 | 'no-invalid-this': 0, 24 | 'react/prop-types': 0, 25 | quotes: [0, "single", { "avoidEscape": true }], 26 | 'react-hooks/rules-of-hooks': 'warn', 27 | 'react-hooks/exhaustive-deps': 'error', 28 | 'import/no-unresolved': [2, {commonjs: true, amd: true}], 29 | 'import/named': 2, 30 | 'import/default': 2, 31 | 'import/export': 2, 32 | 'import/order': [ 33 | 0, 34 | { 35 | groups: [ 36 | 'index', 37 | 'sibling', 38 | 'parent', 39 | 'internal', 40 | 'external', 41 | 'builtin', 42 | ], 43 | 'newlines-between': 'always', 44 | alphabetize: { 45 | order: 'asc', 46 | caseInsensitive: true, 47 | }, 48 | }, 49 | ], 50 | }, 51 | settings: { 52 | react: { 53 | version: 'detect', 54 | }, 55 | }, 56 | }; 57 | -------------------------------------------------------------------------------- /election/web/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules/ 5 | /.pnp 6 | .pnp.js 7 | 8 | #IDE file 9 | .idea 10 | .vscode 11 | 12 | # testing 13 | /coverage 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | .history 21 | .env.local 22 | .env.development.local 23 | .env.test.local 24 | .env.production.local 25 | /server/.env 26 | 27 | npm-debug.log* 28 | yarn-debug.log* 29 | yarn-error.log* 30 | 31 | # vim swap files 32 | *.swp 33 | *.swo 34 | -------------------------------------------------------------------------------- /election/web/.include: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /election/web/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jeremy Philemon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /election/web/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | npm install --production 3 | npm run build 4 | -------------------------------------------------------------------------------- /election/web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimbella/demo-projects/72740cd30fdbc8acba8b13d1199b124d3345e271/election/web/public/favicon.ico -------------------------------------------------------------------------------- /election/web/public/fonts/Archia/archia-bold-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimbella/demo-projects/72740cd30fdbc8acba8b13d1199b124d3345e271/election/web/public/fonts/Archia/archia-bold-webfont.woff2 -------------------------------------------------------------------------------- /election/web/public/fonts/Archia/archia-medium-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimbella/demo-projects/72740cd30fdbc8acba8b13d1199b124d3345e271/election/web/public/fonts/Archia/archia-medium-webfont.woff2 -------------------------------------------------------------------------------- /election/web/public/fonts/Archia/archia-semibold-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimbella/demo-projects/72740cd30fdbc8acba8b13d1199b124d3345e271/election/web/public/fonts/Archia/archia-semibold-webfont.woff2 -------------------------------------------------------------------------------- /election/web/public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimbella/demo-projects/72740cd30fdbc8acba8b13d1199b124d3345e271/election/web/public/icon.png -------------------------------------------------------------------------------- /election/web/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "ge2020usa", 3 | "name": "ge2020usa", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "48x48", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "/", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff", 15 | "orientation": "portrait" 16 | } 17 | -------------------------------------------------------------------------------- /election/web/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | Sitemap: https://www.ge2020usa.org/sitemap.xml 3 | User-agent: * 4 | Disallow: 5 | -------------------------------------------------------------------------------- /election/web/public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "General Election 2020 | USA", 3 | "short_name": "General Election 2020 | USA", 4 | "icons": [{ 5 | "src": "/android-chrome-192x192.png", 6 | "sizes": "192x192", 7 | "type": "image/png" 8 | }, { 9 | "src": "/android-chrome-512x512.png", 10 | "sizes": "512x512", 11 | "type": "image/png" 12 | }], 13 | "theme_color": "#ffffff", 14 | "background_color": "#ffffff", 15 | "display": "standalone" 16 | } 17 | -------------------------------------------------------------------------------- /election/web/public/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | https://www.ge2020usa.org/ 5 | 6 | 7 | https://www.ge2020usa.org/map/ 8 | 9 | 10 | https://www.ge2020usa.org/exitpolls/ 11 | 12 | 13 | https://www.ge2020usa.org/api/ 14 | 15 | 16 | https://www.ge2020usa.org/resources/ 17 | 18 | 19 | https://www.ge2020usa.org/state/FL 20 | 21 | 22 | -------------------------------------------------------------------------------- /election/web/rollup.config.js: -------------------------------------------------------------------------------- 1 | import copy from 'rollup-plugin-copy'; 2 | 3 | export default { 4 | input: './src/placeholder.js', 5 | plugins: [ 6 | copy({ 7 | targets: [ 8 | { 9 | src: 'public/projected_maps/*.json', 10 | dest: 'build/mini_maps', 11 | transform: (contents) => JSON.stringify(JSON.parse(contents)), 12 | }, 13 | ], 14 | }), 15 | ], 16 | }; 17 | -------------------------------------------------------------------------------- /election/web/scripts/project-maps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | WIDTH=432 4 | HEIGHT=488 5 | 6 | SCRIPT_DIR=$(dirname $0) 7 | MAPS_DIR=${SCRIPT_DIR}/../public/maps 8 | OUTPUT_DIR=${SCRIPT_DIR}/../public/projected_maps 9 | 10 | mkdir -p "${OUTPUT_DIR}" 11 | 12 | for file in ${MAPS_DIR}/*.json; do 13 | fn=$(basename ${file}) 14 | topo2geo counties=- -i "$file" | geoproject "d3.geoMercator().fitSize([${WIDTH}, ${HEIGHT}], d)" | geo2topo counties=- -o "${OUTPUT_DIR}/${fn}" 15 | done 16 | 17 | topomerge states=counties -k 'd.properties.st_nm' "${OUTPUT_DIR}/india.json" -o "${OUTPUT_DIR}/india_merged.json" 18 | 19 | mv "${OUTPUT_DIR}/india_merged.json" "${OUTPUT_DIR}/india.json" 20 | prettier --loglevel silent --write "$OUTPUT_DIR" 21 | 22 | -------------------------------------------------------------------------------- /election/web/src/ProgressBar.scss: -------------------------------------------------------------------------------- 1 | 2 | .progress-bar-labels { 3 | margin-top: 50px; 4 | width: 1000px; 5 | font-size: 20px; 6 | font-weight: bold; 7 | } 8 | 9 | .dem-label { 10 | float: left; 11 | width: 50%; 12 | color: #007bff; 13 | } 14 | 15 | .rep-label { 16 | float: left; 17 | width: 50%; 18 | text-align: right; 19 | color: red; 20 | } 21 | 22 | .num-label { 23 | font-size: 40px; 24 | font-weight: bold; 25 | } 26 | 27 | .progress-bar { 28 | position: relative; 29 | height: 18%; 30 | width: 100%; 31 | background: #d3d3d3; 32 | border-radius: 50px; 33 | border: 1px solid #333; 34 | display: inline-block; 35 | overflow: hidden; 36 | } 37 | 38 | .blank-filler { 39 | float: left; 40 | background: #d3d3d3; 41 | height: 100%; 42 | border-radius: inherit; 43 | transition: width 0.2s ease-in; 44 | color: black; 45 | font-weight: bold; 46 | padding-left: -1px; 47 | padding-right: -1px; 48 | padding-top: 5px; 49 | } 50 | 51 | .dem-filler { 52 | float: left; 53 | background: #007bff; 54 | height: 100%; 55 | min-width: 0px; 56 | border-radius: inherit; 57 | transition: width 0.2s ease-in; 58 | } 59 | 60 | .rep-filler { 61 | float: left; 62 | background: red; 63 | height: 100%; 64 | min-width: 0px; 65 | border-radius: inherit; 66 | transition: width 0.2s ease-in; 67 | } 68 | 69 | .marker { 70 | margin-left: 48.5%; 71 | } 72 | -------------------------------------------------------------------------------- /election/web/src/SplitElectoralVotes.scss: -------------------------------------------------------------------------------- 1 | .btn-group { 2 | float: left; 3 | margin-right:2%; 4 | } 5 | 6 | .btn-group button { 7 | height: 30px; 8 | width: 30px; 9 | background-color: #d3d3d3; /* Green background */ 10 | border: 1px solid black; /* Green border */ 11 | color: black; /* White text */ 12 | font-weight: bold; 13 | padding: 2px 2px; 14 | cursor: pointer; /* Pointer/hand icon */ 15 | float: left; /* Float the buttons side by side */ 16 | } 17 | 18 | .state { 19 | width: 60px !important; 20 | } 21 | 22 | /* Clear floats (clearfix hack) */ 23 | .btn-group:after { 24 | content: ""; 25 | clear: both; 26 | display: table; 27 | } 28 | 29 | /* Add a background color on hover */ 30 | .btn-group button:hover { 31 | opacity: 0.5; 32 | cursor: pointer; 33 | } 34 | 35 | .red { 36 | background-color: red !important; 37 | } 38 | 39 | .blue { 40 | background-color: #007bff !important; 41 | } 42 | -------------------------------------------------------------------------------- /election/web/src/animations.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Navbar 3 | */ 4 | 5 | export const SLIDE_IN = { 6 | position: 'absolute', 7 | transform: 'translate3d(-20rem, 0, 0)', 8 | height: '100vh', 9 | zIndex: -1, 10 | }; 11 | 12 | export const SLIDE_OUT = { 13 | position: 'absolute', 14 | transform: 'translate3d(10rem, 0, 0)', 15 | }; 16 | 17 | export const SLIDE_IN_MOBILE = { 18 | opacity: 1, 19 | position: 'absolute', 20 | height: '100vh', 21 | top: 64, 22 | zIndex: 999, 23 | }; 24 | 25 | export const SLIDE_OUT_MOBILE = { 26 | opacity: 1, 27 | position: 'absolute', 28 | height: '100vh', 29 | top: 64, 30 | zIndex: 999, 31 | }; 32 | 33 | /* 34 | * Tooltip 35 | */ 36 | 37 | export const TOOLTIP_FADE_IN = { 38 | opacity: 1, 39 | transform: 'translate3d(0, 0px, 0)', 40 | zIndex: 999, 41 | position: 'absolute', 42 | pointerEvents: 'none', 43 | }; 44 | 45 | export const TOOLTIP_FADE_OUT = { 46 | opacity: 0, 47 | transform: 'translate3d(0, 2px, 0)', 48 | zIndex: 999, 49 | position: 'absolute', 50 | pointerEvents: 'none', 51 | }; 52 | /* 53 | * Table 54 | */ 55 | 56 | export const TABLE_FADE_IN = { 57 | opacity: 1, 58 | transform: 'translate3d(0, 0px, 0)', 59 | height: 200, 60 | }; 61 | 62 | export const TABLE_FADE_OUT = { 63 | opacity: 0, 64 | transform: 'translate3d(0, 2px, 0)', 65 | height: 0, 66 | }; 67 | -------------------------------------------------------------------------------- /election/web/src/components/API.js: -------------------------------------------------------------------------------- 1 | import Footer from './Footer'; 2 | 3 | import { RedocStandalone } from 'redoc'; 4 | import useScript from '../hooks/useScript'; 5 | import React from 'react'; 6 | import { Helmet } from 'react-helmet'; 7 | import useDarkMode from 'use-dark-mode'; 8 | 9 | function API() { 10 | const darkMode = useDarkMode(false); 11 | 12 | useScript(null, `(function (p, o, s, t, m, a, n) { 13 | !p[s] && (p[s] = function () { (p[t] || (p[t] = [])).push(arguments); }) 14 | !o.getElementById(s + t) && o.getElementsByTagName("head")[0].appendChild(( 15 | (n = o.createElement("script")), 16 | (n.id = s + t), (n.async = 1), (n.src = m), n 17 | )); 18 | 19 | }(window, document, "_pm", "PostmanRunObject", "https://run.pstmn.io/button.js")); 20 | `); 21 | return ( 22 | 23 | 24 | Nimbella API - General Election 2020 USA 25 | 29 | 30 |
31 |

Nimbella API for General Election 2020 USA

32 |

33 | Look up the representatives, polling places, early vote location, 34 | candidate data, and other election official information. 35 |

36 |
37 |
38 |
39 | 40 |
41 | 42 |
43 |