├── Crawlers
├── news_sites
│ ├── __init__.py
│ ├── dbs
│ │ └── default.db
│ ├── spiders
│ │ ├── __init__.py
│ │ ├── hotnews.py
│ │ ├── hiru.py
│ │ ├── hirufm.py
│ │ ├── shaagossip.py
│ │ ├── neth.py
│ │ ├── adaDeranaSinhala.py
│ │ ├── rg.py
│ │ ├── adaDerana.py
│ │ ├── lankahit.py
│ │ ├── nfsinhala.py
│ │ ├── am.py
│ │ ├── roar.py
│ │ ├── bizDerana.py
│ │ ├── thepapare.py
│ │ ├── lankadeepa.py
│ │ ├── economynext.py
│ │ ├── ft.py
│ │ ├── reporter.py
│ │ ├── dailymirror.py
│ │ ├── rm.py
│ │ ├── ctoday.py
│ │ └── nf.py
│ └── items.py
├── dbs
│ └── default.db
├── .vscode
│ └── settings.json
├── requirements.txt
├── crawl_all.bash
└── scrapy.cfg
├── fact-bounty-flask
├── tests
│ ├── __init__.py
│ ├── fake_db.py
│ ├── test_admin.py
│ └── test_crawler.py
├── boot.sh
├── api
│ ├── __init__.py
│ ├── admin
│ │ ├── __init__.py
│ │ ├── views.py
│ │ └── controller.py
│ ├── user
│ │ ├── __init__.py
│ │ └── views.py
│ ├── util
│ │ ├── __init__.py
│ │ ├── views.py
│ │ └── controller.py
│ ├── crawler
│ │ ├── __init__.py
│ │ ├── utils.py
│ │ └── views.py
│ ├── stories
│ │ ├── __init__.py
│ │ ├── views.py
│ │ └── model.py
│ ├── data-dev.sqlite
│ ├── database.py
│ ├── errors.py
│ ├── helpers.py
│ └── extensions.py
├── migrations
│ ├── README
│ ├── script.py.mako
│ ├── versions
│ │ ├── 8be924798f34_.py
│ │ └── d2fc5d7ff47e_.py
│ └── alembic.ini
├── app.yaml
├── docker.txt
├── prod.txt
├── .flake8
├── docs
│ ├── stories
│ │ ├── get_all.yml
│ │ ├── get_range.yml
│ │ ├── get_by_id.yml
│ │ ├── load_user_vote.yml
│ │ └── change_vote_count.yml
│ └── users
│ │ ├── token_refresh.yml
│ │ ├── logout_access.yml
│ │ ├── logout_refresh.yml
│ │ ├── login.yml
│ │ ├── register.yml
│ │ └── oauth.yml
├── .vscode
│ └── settings.json
├── .idea
│ ├── encodings.xml
│ ├── vcs.xml
│ ├── modules.xml
│ ├── misc.xml
│ └── fact-bounty-flask.iml
├── .pre-commit-config.yaml
├── Dockerfile
├── app.py
├── .gcloudignore
└── requirements.txt
├── fact-bounty-client
├── jarvis
│ └── .gitignore
├── src
│ ├── components
│ │ ├── TweetList
│ │ │ ├── styles.sass
│ │ │ ├── index.js
│ │ │ └── TweetList.jsx
│ │ ├── OAuthContainer
│ │ │ ├── OAuthContainer.style.js
│ │ │ ├── index.js
│ │ │ ├── OAuthContainer.jsx
│ │ │ ├── FacebookContainer.js
│ │ │ ├── index.scss
│ │ │ └── GoogleContainer.js
│ │ ├── Toast
│ │ │ ├── index.js
│ │ │ └── Toast.jsx
│ │ ├── Footer
│ │ │ ├── index.js
│ │ │ ├── style.sass
│ │ │ └── Footer.jsx
│ │ ├── NavBar
│ │ │ ├── index.js
│ │ │ ├── styles.sass
│ │ │ ├── NavBar.style.js
│ │ │ └── Links
│ │ │ │ └── PublicLinks.js
│ │ ├── PostItem
│ │ │ ├── index.js
│ │ │ └── style.sass
│ │ ├── VotesBar
│ │ │ ├── index.js
│ │ │ ├── style.sass
│ │ │ └── VotesBar.jsx
│ │ ├── PostsList
│ │ │ ├── index.js
│ │ │ └── style.sass
│ │ ├── TweetItem
│ │ │ ├── index.js
│ │ │ └── style.sass
│ │ ├── PrivateRoute
│ │ │ ├── index.js
│ │ │ └── PrivateRoute.jsx
│ │ ├── VoteButtons
│ │ │ ├── index.js
│ │ │ ├── style.sass
│ │ │ └── VoteButtons.jsx
│ │ ├── AsyncViewWrapper
│ │ │ ├── style.sass
│ │ │ ├── index.js
│ │ │ └── AsyncViewWrapper.jsx
│ │ ├── ContactUsForm
│ │ │ ├── index.js
│ │ │ └── style.sass
│ │ ├── SwipeableDrawer
│ │ │ ├── SwipeableDrawer.style.js
│ │ │ ├── index.js
│ │ │ ├── Links
│ │ │ │ ├── PublicLinks.js
│ │ │ │ └── PrivateLinks.js
│ │ │ └── SwipeableDrawer.jsx
│ │ └── DashboardSideNav
│ │ │ ├── index.js
│ │ │ ├── style.sass
│ │ │ └── DashboardSideNav.jsx
│ ├── pages
│ │ ├── Login
│ │ │ ├── index.js
│ │ │ └── Login.style.js
│ │ ├── Posts
│ │ │ ├── index.js
│ │ │ ├── style.sass
│ │ │ └── Posts.jsx
│ │ ├── Landing
│ │ │ ├── index.js
│ │ │ └── style.sass
│ │ ├── Search
│ │ │ ├── index.js
│ │ │ └── style.sass
│ │ ├── Tweets
│ │ │ ├── index.js
│ │ │ ├── style.sass
│ │ │ └── Tweets.jsx
│ │ ├── Register
│ │ │ ├── index.js
│ │ │ └── Register.style.js
│ │ ├── notFound
│ │ │ ├── index.jsx
│ │ │ ├── index.css
│ │ │ └── NotFound.jsx
│ │ ├── Dashboard
│ │ │ ├── index.js
│ │ │ ├── style.sass
│ │ │ └── Dashboard.jsx
│ │ ├── TweetSearch
│ │ │ ├── index.js
│ │ │ └── style.sass
│ │ ├── TwitterGraph
│ │ │ ├── index.js
│ │ │ └── styles.sass
│ │ ├── KibanaDashboard
│ │ │ ├── index.js
│ │ │ ├── style.sass
│ │ │ └── Search.jsx
│ │ ├── ResetPassword
│ │ │ ├── index.js
│ │ │ └── ResetPassword.style.js
│ │ ├── ForgotPassword
│ │ │ ├── index.js
│ │ │ └── ForgotPassword.style.js
│ │ └── PostDetailView
│ │ │ ├── index.js
│ │ │ └── style.sass
│ ├── assets
│ │ ├── img
│ │ │ ├── patch1.png
│ │ │ ├── patch2.png
│ │ │ ├── patch3.png
│ │ │ ├── headerImg.png
│ │ │ ├── placeholder.png
│ │ │ └── placeholderWide.png
│ │ └── logos
│ │ │ ├── factbountyLogo.png
│ │ │ └── factbountyLogoWhite.png
│ ├── constants
│ │ ├── ApiConstants.js
│ │ └── KibanaConstants.js
│ ├── redux
│ │ ├── actions
│ │ │ ├── errorActions.js
│ │ │ ├── successActions.js
│ │ │ ├── contactUsActions.js
│ │ │ ├── actionTypes.js
│ │ │ └── twitterActions.js
│ │ ├── reducers
│ │ │ ├── errorReducers.js
│ │ │ ├── successReducers.js
│ │ │ ├── index.js
│ │ │ ├── authReducers.js
│ │ │ ├── contactUsReducers.js
│ │ │ └── twitterReducers.js
│ │ └── store
│ │ │ └── index.js
│ ├── styles
│ │ ├── style.sass
│ │ ├── fonts.sass
│ │ ├── global-styles.sass
│ │ ├── variables.sass
│ │ └── theme.js
│ ├── App.test.js
│ ├── services
│ │ ├── ContactUsService.js
│ │ ├── PostsService.js
│ │ └── AuthService.js
│ ├── index.js
│ ├── helpers
│ │ ├── AuthTokenHelper.js
│ │ └── ApiBuilder.js
│ ├── App.js
│ └── AppRouter.js
├── prettier.config.js
├── public
│ ├── favicon.ico
│ ├── manifest.json
│ └── static
│ │ ├── config.js
│ │ └── css
│ │ └── widget.css
├── .env.example
├── .idea
│ ├── encodings.xml
│ ├── watcherTasks.xml
│ ├── codeStyles
│ │ ├── codeStyleConfig.xml
│ │ └── Project.xml
│ ├── misc.xml
│ ├── vcs.xml
│ ├── inspectionProfiles
│ │ └── Project_Default.xml
│ ├── modules.xml
│ └── fact-bounty-client.iml
├── .dockerignore
├── Dockerfile
├── .vscode
│ └── launch.json
├── .eslintrc.js
└── package.json
├── .flake8
├── .idea
├── encodings.xml
├── vcs.xml
├── modules.xml
├── misc.xml
└── fact-Bounty.iml
├── .dockerignore
├── .vscode
├── settings.json
└── launch.json
├── pyproject.toml
├── db
├── dump
│ ├── users.json
│ └── stories.json
├── add_es.py
└── create_index.py
├── .dependabot
└── config.yml
├── codefresh.yml
├── .travis.yml
├── .remarkrc.js
├── .github
├── PULL_REQUEST_TEMPLATE.md
└── ISSUE_TEMPLATE.md
├── OAuthdSetup.md
└── docker-compose.yml
/Crawlers/news_sites/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/fact-bounty-flask/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/fact-bounty-client/jarvis/.gitignore:
--------------------------------------------------------------------------------
1 | config.local.js
2 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/TweetList/styles.sass:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/fact-bounty-flask/boot.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | exec flask run
3 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/OAuthContainer/OAuthContainer.style.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/fact-bounty-flask/api/__init__.py:
--------------------------------------------------------------------------------
1 | """Main application package."""
2 |
--------------------------------------------------------------------------------
/fact-bounty-flask/migrations/README:
--------------------------------------------------------------------------------
1 | Generic single-database configuration.
--------------------------------------------------------------------------------
/fact-bounty-flask/api/admin/__init__.py:
--------------------------------------------------------------------------------
1 | from . import views # noqa: F401
2 |
--------------------------------------------------------------------------------
/fact-bounty-flask/api/user/__init__.py:
--------------------------------------------------------------------------------
1 | from . import views # noqa: F401
2 |
--------------------------------------------------------------------------------
/fact-bounty-flask/api/util/__init__.py:
--------------------------------------------------------------------------------
1 | from . import views # noqa: F401
2 |
--------------------------------------------------------------------------------
/fact-bounty-flask/api/crawler/__init__.py:
--------------------------------------------------------------------------------
1 | from . import views # noqa: F401
2 |
--------------------------------------------------------------------------------
/fact-bounty-flask/api/stories/__init__.py:
--------------------------------------------------------------------------------
1 | from . import views # noqa: F401
2 |
--------------------------------------------------------------------------------
/fact-bounty-flask/app.yaml:
--------------------------------------------------------------------------------
1 | runtime: python37
2 | entrypoint: gunicorn -b :$PORT app:app
--------------------------------------------------------------------------------
/fact-bounty-flask/docker.txt:
--------------------------------------------------------------------------------
1 | -r requirements.txt
2 | gunicorn==19.7.1
3 | pymysql==0.9.3
4 |
--------------------------------------------------------------------------------
/fact-bounty-flask/tests/fake_db.py:
--------------------------------------------------------------------------------
1 | import tempfile
2 |
3 | db_fd, db_path = tempfile.mkstemp()
--------------------------------------------------------------------------------
/Crawlers/dbs/default.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scorelab/fact-bounty/HEAD/Crawlers/dbs/default.db
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/Login/index.js:
--------------------------------------------------------------------------------
1 | import Login from './Login'
2 | export default Login
3 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/Posts/index.js:
--------------------------------------------------------------------------------
1 | import Posts from './Posts'
2 | export default Posts
3 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/Toast/index.js:
--------------------------------------------------------------------------------
1 | import Toast from './Toast'
2 | export default Toast
3 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/Landing/index.js:
--------------------------------------------------------------------------------
1 | import Landing from './Landing'
2 | export default Landing
3 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/Search/index.js:
--------------------------------------------------------------------------------
1 | import Search from './Search'
2 | export default Search
3 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/Tweets/index.js:
--------------------------------------------------------------------------------
1 | import Tweets from './Tweets'
2 | export default Tweets
3 |
--------------------------------------------------------------------------------
/fact-bounty-client/prettier.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | semi: false,
3 | singleQuote: true
4 | }
5 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/Footer/index.js:
--------------------------------------------------------------------------------
1 | import Footer from './Footer'
2 | export default Footer
3 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/NavBar/index.js:
--------------------------------------------------------------------------------
1 | import NavBar from './NavBar'
2 | export default NavBar
3 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/Register/index.js:
--------------------------------------------------------------------------------
1 | import Register from './Register'
2 | export default Register
3 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/notFound/index.jsx:
--------------------------------------------------------------------------------
1 | import NotFound from './NotFound'
2 | export default NotFound
3 |
--------------------------------------------------------------------------------
/fact-bounty-flask/prod.txt:
--------------------------------------------------------------------------------
1 | -r requirements.txt
2 | gunicorn==19.7.1
3 | pymysql==0.9.3
4 | certifi==2019.3.9
5 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/PostItem/index.js:
--------------------------------------------------------------------------------
1 | import PostItem from './PostItem'
2 | export default PostItem
3 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/VotesBar/index.js:
--------------------------------------------------------------------------------
1 | import VotesBar from './VotesBar'
2 | export default VotesBar
3 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/Dashboard/index.js:
--------------------------------------------------------------------------------
1 | import Dashboard from './Dashboard'
2 | export default Dashboard
3 |
--------------------------------------------------------------------------------
/Crawlers/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "python.linting.pylintEnabled": true,
3 | "python.linting.enabled": true
4 | }
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/PostsList/index.js:
--------------------------------------------------------------------------------
1 | import PostsList from './PostsList'
2 | export default PostsList
3 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/TweetItem/index.js:
--------------------------------------------------------------------------------
1 | import TweetItem from './TweetItem'
2 | export default TweetItem
3 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/TweetList/index.js:
--------------------------------------------------------------------------------
1 | import TweetList from './TweetList'
2 | export default TweetList
3 |
--------------------------------------------------------------------------------
/Crawlers/news_sites/dbs/default.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scorelab/fact-bounty/HEAD/Crawlers/news_sites/dbs/default.db
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/TweetSearch/index.js:
--------------------------------------------------------------------------------
1 | import TweetSearch from './TweetSearch'
2 | export default TweetSearch
3 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/TwitterGraph/index.js:
--------------------------------------------------------------------------------
1 | import TwitterGraph from './TwitterGraph'
2 | export default TwitterGraph
3 |
--------------------------------------------------------------------------------
/fact-bounty-client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scorelab/fact-bounty/HEAD/fact-bounty-client/public/favicon.ico
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/PrivateRoute/index.js:
--------------------------------------------------------------------------------
1 | import PrivateRoute from './PrivateRoute'
2 | export default PrivateRoute
3 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/VoteButtons/index.js:
--------------------------------------------------------------------------------
1 | import VoteButtons from './VoteButtons'
2 | export default VoteButtons
3 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/KibanaDashboard/index.js:
--------------------------------------------------------------------------------
1 | import KibanaDashboard from './Search'
2 | export default KibanaDashboard
3 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/ResetPassword/index.js:
--------------------------------------------------------------------------------
1 | import ResetPassword from './ResetPassword'
2 | export default ResetPassword
3 |
--------------------------------------------------------------------------------
/fact-bounty-flask/api/data-dev.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scorelab/fact-bounty/HEAD/fact-bounty-flask/api/data-dev.sqlite
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/AsyncViewWrapper/style.sass:
--------------------------------------------------------------------------------
1 | .async-view-wrapper-container
2 | .loader
3 | text-align: center
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/ContactUsForm/index.js:
--------------------------------------------------------------------------------
1 | import ContactUsForm from './ContactUsForm'
2 | export default ContactUsForm
3 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/ForgotPassword/index.js:
--------------------------------------------------------------------------------
1 | import ForgotPassword from './ForgotPassword'
2 | export default ForgotPassword
3 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/PostDetailView/index.js:
--------------------------------------------------------------------------------
1 | import PostDetailView from './PostDetailView'
2 | export default PostDetailView
3 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/OAuthContainer/index.js:
--------------------------------------------------------------------------------
1 | import OAuthContainer from './OAuthContainer'
2 | export default OAuthContainer
3 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/SwipeableDrawer/SwipeableDrawer.style.js:
--------------------------------------------------------------------------------
1 | export default {
2 | list: {
3 | width: 250
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | ignore = E203, E266, E501, W503, F403, F401
3 | max-line-length = 79
4 | max-complexity = 18
5 | select = B,C,E,F,W,T4,B9
6 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/assets/img/patch1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scorelab/fact-bounty/HEAD/fact-bounty-client/src/assets/img/patch1.png
--------------------------------------------------------------------------------
/fact-bounty-client/src/assets/img/patch2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scorelab/fact-bounty/HEAD/fact-bounty-client/src/assets/img/patch2.png
--------------------------------------------------------------------------------
/fact-bounty-client/src/assets/img/patch3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scorelab/fact-bounty/HEAD/fact-bounty-client/src/assets/img/patch3.png
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/SwipeableDrawer/index.js:
--------------------------------------------------------------------------------
1 | import SwipeableDrawer from './SwipeableDrawer'
2 | export default SwipeableDrawer
3 |
--------------------------------------------------------------------------------
/Crawlers/requirements.txt:
--------------------------------------------------------------------------------
1 | attrs==19.2.0
2 | scrapy==1.6.0
3 | elasticsearch==6.2.0
4 | scrapyd==1.2.0
5 | Twisted==18.9.0
6 | python-dateutil==2.8.0
7 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/assets/img/headerImg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scorelab/fact-bounty/HEAD/fact-bounty-client/src/assets/img/headerImg.png
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/AsyncViewWrapper/index.js:
--------------------------------------------------------------------------------
1 | import AsyncViewWrapper from './AsyncViewWrapper'
2 | export default AsyncViewWrapper
3 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/DashboardSideNav/index.js:
--------------------------------------------------------------------------------
1 | import DashboardSideNav from './DashboardSideNav'
2 | export default DashboardSideNav
3 |
--------------------------------------------------------------------------------
/Crawlers/crawl_all.bash:
--------------------------------------------------------------------------------
1 | #!/usr/bin/sh
2 | source venv/bin/activate
3 | for crawler in $(scrapy list)
4 | do
5 | scrapy crawl $crawler
6 | done
7 |
--------------------------------------------------------------------------------
/fact-bounty-client/.env.example:
--------------------------------------------------------------------------------
1 | REACT_APP_GOOGLE_CLIENT_ID=""
2 | REACT_APP_FACEBOOK_CLIENT_ID=""
3 | REACT_APP_OAUTHD_KEY=""
4 | REACT_APP_OAUTHD_URL=""
5 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/assets/img/placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scorelab/fact-bounty/HEAD/fact-bounty-client/src/assets/img/placeholder.png
--------------------------------------------------------------------------------
/fact-bounty-client/src/assets/img/placeholderWide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scorelab/fact-bounty/HEAD/fact-bounty-client/src/assets/img/placeholderWide.png
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/assets/logos/factbountyLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scorelab/fact-bounty/HEAD/fact-bounty-client/src/assets/logos/factbountyLogo.png
--------------------------------------------------------------------------------
/fact-bounty-flask/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | ignore = E203, E266, E501, W503, F403, F401
3 | max-line-length = 79
4 | max-complexity = 18
5 | select = B,C,E,F,W,T4,B9
6 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/assets/logos/factbountyLogoWhite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scorelab/fact-bounty/HEAD/fact-bounty-client/src/assets/logos/factbountyLogoWhite.png
--------------------------------------------------------------------------------
/fact-bounty-flask/docs/stories/get_all.yml:
--------------------------------------------------------------------------------
1 | Retrieve stories
2 | ---
3 | tags:
4 | - Stories
5 | responses:
6 | 200:
7 | description: JSON object with all stories.
8 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/Dashboard/style.sass:
--------------------------------------------------------------------------------
1 | @import '../../styles/variables.sass'
2 |
3 | .dashboard-container
4 | .left-section
5 | border-right: 1px solid $grey
--------------------------------------------------------------------------------
/fact-bounty-flask/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "python.pythonPath": "venv/bin/python",
3 | "python.linting.pylintEnabled": true,
4 | "python.linting.enabled": true
5 | }
--------------------------------------------------------------------------------
/fact-bounty-client/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fact-bounty-flask/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fact-bounty-client/.idea/watcherTasks.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | Dockerfile*
4 | docker-compose*
5 | .dockerignore
6 | .git
7 | .gitignore
8 | .env
9 | */bin
10 | */obj
11 | README.md
12 | LICENSE
13 | .vscode
--------------------------------------------------------------------------------
/fact-bounty-client/src/constants/ApiConstants.js:
--------------------------------------------------------------------------------
1 | const API_URL_LOCAL = 'http://127.0.0.1:5000'
2 | const API_URL_PROD = ''
3 |
4 | export const APIConstantsDev = {
5 | API_URL: API_URL_LOCAL
6 | }
7 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/fact-bounty-client/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Crawlers/news_sites/spiders/__init__.py:
--------------------------------------------------------------------------------
1 | # This package will contain the spiders of your Scrapy project
2 | #
3 | # Please refer to the documentation for information on how to create and manage
4 | # your spiders.
5 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/redux/actions/errorActions.js:
--------------------------------------------------------------------------------
1 | import { UPDATE_ERRORS } from './actionTypes'
2 |
3 | export const updateError = payload => dispatch =>
4 | dispatch({ type: UPDATE_ERRORS, payload })
5 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/redux/actions/successActions.js:
--------------------------------------------------------------------------------
1 | import { UPDATE_SUCCESS } from './actionTypes'
2 |
3 | export const updateSuccess = payload => dispatch =>
4 | dispatch({ type: UPDATE_SUCCESS, payload })
5 |
--------------------------------------------------------------------------------
/fact-bounty-client/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | Dockerfile*
4 | docker-compose*
5 | .dockerignore
6 | .git
7 | .gitignore
8 | .env
9 | */bin
10 | */obj
11 | README.md
12 | LICENSE
13 | .vscode
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "python.pythonPath": "fact-bounty-flask/venv/bin/python",
3 | "python.linting.pylintEnabled": false,
4 | "python.linting.flake8Enabled": true,
5 | "python.linting.enabled": true
6 | }
--------------------------------------------------------------------------------
/fact-bounty-client/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/fact-bounty-client/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/PostsList/style.sass:
--------------------------------------------------------------------------------
1 | .post-list-wrapper
2 | .postLayout
3 | display: grid
4 | grid-template-rows: auto
5 | min-height: calc(100vh - 64px)
6 | .loader
7 | text-align: center
8 |
9 |
--------------------------------------------------------------------------------
/fact-bounty-flask/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/styles/style.sass:
--------------------------------------------------------------------------------
1 | //Third Party Libs
2 | @import '~bootstrap/scss/bootstrap.scss'
3 | @import '~font-awesome/css/font-awesome.min.css'
4 | @import 'variables.sass'
5 | @import 'fonts.sass'
6 | @import 'global-styles.sass'
7 |
--------------------------------------------------------------------------------
/fact-bounty-flask/api/admin/views.py:
--------------------------------------------------------------------------------
1 | from flask import Blueprint
2 | from .controller import adminController
3 |
4 | adminprint = Blueprint("admin", __name__)
5 |
6 | adminprint.add_url_rule(
7 | "/system", view_func=adminController["system"], methods=["GET"]
8 | )
9 |
--------------------------------------------------------------------------------
/fact-bounty-flask/api/database.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """Database module, including the SQLAlchemy database object \
3 | and DB-related utilities."""
4 | from .extensions import db
5 |
6 | # Alias common SQLAlchemy names
7 | Column = db.Column
8 | Model = db.Model
9 |
--------------------------------------------------------------------------------
/fact-bounty-flask/docs/stories/get_range.yml:
--------------------------------------------------------------------------------
1 | Retrieve stories in range
2 | ---
3 | tags:
4 | - Stories
5 | parameters:
6 | - in: page
7 | name: page
8 | type: integer
9 | responses:
10 | 200:
11 | description: JSON object with range of stories.
12 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/Footer/style.sass:
--------------------------------------------------------------------------------
1 | @import '../../styles/variables.sass'
2 |
3 | .footer-container
4 | background-color: $primary_color
5 | margin-top: 80px
6 | padding: 30px
7 | text-align: center
8 | color: #fff
9 | .logo
10 | width: 200px
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.black]
2 | line-length = 79
3 | include = '\.pyi?$'
4 | exclude = '''
5 | /(
6 | \.git
7 | | \.hg
8 | | \.mypy_cache
9 | | \.tox
10 | | \.venv
11 | | _build
12 | | buck-out
13 | | build
14 | | migrations
15 | | dist
16 | | venv
17 | )/
18 | '''
19 |
--------------------------------------------------------------------------------
/fact-bounty-flask/api/util/views.py:
--------------------------------------------------------------------------------
1 | from flask import Blueprint
2 | from .controller import utilController
3 |
4 | utilprint = Blueprint('util', __name__)
5 |
6 | utilprint.add_url_rule(
7 | '/contact_us',
8 | view_func=utilController['contact_us'],
9 | methods=['POST']
10 | )
11 |
--------------------------------------------------------------------------------
/fact-bounty-flask/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/ambv/black
3 | rev: stable
4 | hooks:
5 | - id: black
6 | language_version: python3.6
7 | - repo: https://github.com/pre-commit/pre-commit-hooks
8 | rev: v1.2.3
9 | hooks:
10 | - id: flake8
11 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import App from './App'
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div')
7 | ReactDOM.render(, div)
8 | ReactDOM.unmountComponentAtNode(div)
9 | })
10 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/NavBar/styles.sass:
--------------------------------------------------------------------------------
1 | .nav-bar-container
2 | background-color: #ffffff !important
3 | @media #{'only screen and (max-width: 960px)'}
4 | padding: 10px
5 | padding: 10px 60px 10px 60px
6 | .logo-container
7 | flex-grow: 1
8 | img
9 | width: 200px
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/fact-bounty-client/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/fact-bounty-client/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/fact-bounty-flask/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/fact-bounty-flask/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/fact-bounty-flask/docs/users/token_refresh.yml:
--------------------------------------------------------------------------------
1 | Token refresh
2 | ---
3 | tags:
4 | - User
5 | produces:
6 | - application/json
7 | parameters:
8 | - in: header
9 | name: Authorization
10 | description: an authorization header
11 | required: true
12 | type: string
13 | responses:
14 | 200:
15 | description: Token refreshed successfully.
16 |
--------------------------------------------------------------------------------
/Crawlers/scrapy.cfg:
--------------------------------------------------------------------------------
1 | # Automatically created by: scrapy startproject
2 | #
3 | # For more information about the [deploy] section see:
4 | # https://scrapyd.readthedocs.io/en/latest/deploy.html
5 |
6 | [settings]
7 | default = news_sites.settings
8 |
9 | [scrapyd]
10 | bind_address = 0.0.0.0
11 |
12 | [deploy]
13 | #url = http://localhost:6800/
14 | project = news_sites
15 |
--------------------------------------------------------------------------------
/db/dump/users.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "_id": {
4 | "$oid": "5c47e646d0084e14fba25711"
5 | },
6 | "name": "Oshan Mudannayake",
7 | "email": "oshan.ivantha@gmail.com",
8 | "password": "$2a$10$zPYmQ44u7IuJrH4iabccWu8A0lCZiZwG1WcBq4sBbUJCPEGFUv6EK",
9 | "date": {
10 | "$date": "2019-01-23T03:57:58.659Z"
11 | },
12 | "__v": 0
13 | }
14 | ]
15 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/DashboardSideNav/style.sass:
--------------------------------------------------------------------------------
1 | @import '../../styles/variables.sass'
2 |
3 | .dashboard-side-nav-container
4 | width: 100%
5 | .links
6 | list-style: none
7 | .link
8 | color: black
9 | text-decoration: none
10 | font-size: 20px
11 | .link:hover
12 | font-weight: bold
13 |
--------------------------------------------------------------------------------
/fact-bounty-client/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:8
2 |
3 | RUN mkdir -p /usr/fact-bounty/fact-bounty-client
4 | WORKDIR /usr/fact-bounty/fact-bounty-client
5 |
6 | ENV PATH /usr/app/node_modules/.bin:$PATH
7 |
8 |
9 | COPY package.json /usr/fact-bounty/fact-bounty-client
10 | RUN npm install --silent
11 | COPY . /usr/fact-bounty/fact-bounty-client
12 |
13 | EXPOSE 3000
14 | CMD ["npm", "start"]
--------------------------------------------------------------------------------
/fact-bounty-flask/api/crawler/utils.py:
--------------------------------------------------------------------------------
1 | from flask import jsonify, current_app
2 |
3 |
4 | def cron_crawlers():
5 | scrapyd = current_app.scrapy
6 |
7 | spiders = scrapyd.list_spiders('default')
8 |
9 | tasks = []
10 | for spider in spiders:
11 | tasks.append(scrapyd.schedule('default', spider))
12 |
13 | return jsonify({'tasks_data': tasks, 'status': 'started'})
14 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/styles/fonts.sass:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Open+Sans:400,600,700|Roboto:400,500,700')
2 | @import 'variables.sass'
3 |
4 | body
5 | font-family: 'Montserrat',sans-serif
6 | color: $text-color
7 | font-weight: 400
8 | font-size: 16px
9 | h1,h2,h3,h4,h5
10 | font-family: 'Montserrat',sans-serif
11 | font-weight: 400
12 |
13 |
--------------------------------------------------------------------------------
/fact-bounty-client/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
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 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/Posts/style.sass:
--------------------------------------------------------------------------------
1 | .post-wrapper
2 | .header
3 | padding: 20px 0px 10px 0px
4 | display: flex
5 | justify-content: space-between
6 | align-items: center
7 | input
8 | width: 300px;
9 | box-shadow: 1px 1px 15px rgba(0, 0, 0, .1)
10 | border: none
11 | border-radius: 3px
12 | padding: 10px
13 | .hr
14 | margin-bottom: 40px
15 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/Tweets/style.sass:
--------------------------------------------------------------------------------
1 | .post-wrapper
2 | .header
3 | padding: 20px 0px 10px 0px
4 | display: flex
5 | justify-content: space-between
6 | align-items: center
7 | input
8 | width: 300px;
9 | box-shadow: 1px 1px 15px rgba(0, 0, 0, .1)
10 | border: none
11 | border-radius: 3px
12 | padding: 10px
13 | .hr
14 | margin-bottom: 40px
15 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/KibanaDashboard/style.sass:
--------------------------------------------------------------------------------
1 | .kibana-dashboard-wrapper
2 | .header
3 | padding: 20px 0px 10px 0px
4 | display: flex
5 | justify-content: space-between
6 | align-items: center
7 | input
8 | width: 300px;
9 | box-shadow: 1px 1px 15px rgba(0, 0, 0, .1)
10 | border: none
11 | border-radius: 3px
12 | padding: 10px
13 | .hr
14 | margin-bottom: 40px
--------------------------------------------------------------------------------
/fact-bounty-flask/docs/users/logout_access.yml:
--------------------------------------------------------------------------------
1 | User logout
2 | ---
3 | tags:
4 | - User
5 | produces:
6 | - application/json
7 | parameters:
8 | - in: header
9 | name: Authorization
10 | description: an authorization header
11 | required: true
12 | type: string
13 | responses:
14 | 200:
15 | description: Access token has been revoked.
16 | 500:
17 | description: Something went wrong!
18 |
--------------------------------------------------------------------------------
/fact-bounty-flask/docs/users/logout_refresh.yml:
--------------------------------------------------------------------------------
1 | User logout
2 | ---
3 | tags:
4 | - User
5 | produces:
6 | - application/json
7 | parameters:
8 | - in: header
9 | name: Authorization
10 | description: an authorization header
11 | required: true
12 | type: string
13 | responses:
14 | 200:
15 | description: Refresh token has been revoked.
16 | 500:
17 | description: Something went wrong!
18 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/redux/reducers/errorReducers.js:
--------------------------------------------------------------------------------
1 | import { GET_ERRORS, UPDATE_ERRORS } from '../actions/actionTypes'
2 |
3 | const initialState = {}
4 |
5 | export default function(state = initialState, action) {
6 | switch (action.type) {
7 | case GET_ERRORS:
8 | return action.payload
9 |
10 | case UPDATE_ERRORS:
11 | return action.payload
12 |
13 | default:
14 | return state
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/redux/reducers/successReducers.js:
--------------------------------------------------------------------------------
1 | import { GET_SUCCESS, UPDATE_SUCCESS } from '../actions/actionTypes'
2 |
3 | const initialState = {}
4 |
5 | export default function(state = initialState, action) {
6 | switch (action.type) {
7 | case GET_SUCCESS:
8 | return action.payload
9 |
10 | case UPDATE_SUCCESS:
11 | return action.payload
12 |
13 | default:
14 | return state
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.dependabot/config.yml:
--------------------------------------------------------------------------------
1 | version: 1
2 |
3 | update_configs:
4 | - allowed_updates:
5 | - match:
6 | update_type: "security"
7 | package_manager: "javascript"
8 | directory: "/fact-bounty-client"
9 | update_schedule: "weekly"
10 |
11 | - allowed_updates:
12 | - match:
13 | update_type: "security"
14 | package_manager: "python"
15 | directory: "/fact-bounty-flask"
16 | update_schedule: "weekly"
17 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/services/ContactUsService.js:
--------------------------------------------------------------------------------
1 | import ApiBuilder from '../helpers/ApiBuilder'
2 |
3 | /**
4 | *
5 | * POST : contactUsSubmit
6 | *
7 | */
8 | const contactUsSubmit = (name, email, phone, subject, message) => {
9 | return ApiBuilder.API.post(`api/utils/contact_us`, {
10 | name,
11 | email,
12 | phone,
13 | subject,
14 | message
15 | })
16 | }
17 |
18 | export default {
19 | contactUsSubmit
20 | }
21 |
--------------------------------------------------------------------------------
/db/add_es.py:
--------------------------------------------------------------------------------
1 | from elasticsearch import Elasticsearch
2 | import json
3 | import os
4 |
5 | ELASTICSEARCH_URL = '127.0.0.1'
6 |
7 | es = Elasticsearch(
8 | [ELASTICSEARCH_URL],
9 | port=9200
10 | )
11 |
12 | with open(os.path.join(os.path.dirname(__file__),'dump/stories.json'), 'r') as read:
13 | data = json.load(read)
14 | i = 1
15 | for row in data:
16 | es.index(index='factbounty', doc_type='story', id=i, body=row)
17 | i = i + 1
18 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/Footer/Footer.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import factbountyLogoWhite from '../../assets/logos/factbountyLogoWhite.png'
3 | import './style.sass'
4 |
5 | class Footer extends Component {
6 | render() {
7 | return (
8 |
9 |

10 |
11 | )
12 | }
13 | }
14 |
15 | export default Footer
16 |
--------------------------------------------------------------------------------
/fact-bounty-flask/api/crawler/views.py:
--------------------------------------------------------------------------------
1 | from flask import Blueprint
2 | from .controller import crawlerController
3 |
4 | blueprint = Blueprint('crawler', __name__)
5 |
6 |
7 | blueprint.add_url_rule(
8 | '/cron_job',
9 | view_func=crawlerController['setcronjob'],
10 | methods=['GET', 'POST', 'PUT', 'DELETE']
11 | )
12 |
13 | blueprint.add_url_rule(
14 | '/crawl_live',
15 | view_func=crawlerController['crawlbydate'],
16 | methods=['GET']
17 | )
18 |
--------------------------------------------------------------------------------
/fact-bounty-flask/docs/users/login.yml:
--------------------------------------------------------------------------------
1 | User login
2 | ---
3 | tags:
4 | - User
5 | consumes:
6 | - "application/json"
7 | parameters:
8 | - in: "body"
9 | name: "body"
10 | schema:
11 | type: "object"
12 | properties:
13 | email:
14 | type: string
15 | example: abcd@xyz.com
16 | password:
17 | type: string
18 | example: asdf1234
19 | responses:
20 | 200:
21 | description: User successfully logged in.
22 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/redux/store/index.js:
--------------------------------------------------------------------------------
1 | import { applyMiddleware, compose, createStore } from 'redux'
2 | import thunk from 'redux-thunk'
3 | import rootReducer from '../reducers'
4 |
5 | const initialState = {}
6 | const middleware = [thunk]
7 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
8 | const store = createStore(
9 | rootReducer,
10 | initialState,
11 | composeEnhancers(applyMiddleware(...middleware))
12 | )
13 |
14 | export default store
15 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [{
7 | "type": "chrome",
8 | "request": "launch",
9 | "name": "Launch Chrome against localhost",
10 | "url": "http://localhost:3000",
11 | "webRoot": "${workspaceFolder}"
12 | }]
13 | }
--------------------------------------------------------------------------------
/fact-bounty-client/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [{
7 | "type": "chrome",
8 | "request": "launch",
9 | "name": "Launch Chrome against localhost",
10 | "url": "http://localhost:3000",
11 | "webRoot": "${workspaceFolder}"
12 | }]
13 | }
--------------------------------------------------------------------------------
/fact-bounty-client/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import './styles/style.sass'
4 | import App from './App'
5 | import * as serviceWorker from './serviceWorker'
6 |
7 | ReactDOM.render(, document.getElementById('root'))
8 |
9 | // If you want your app to work offline and load faster, you can change
10 | // unregister() to register() below. Note this comes with some pitfalls.
11 | // Learn more about service workers: http://bit.ly/CRA-PWA
12 | serviceWorker.unregister()
13 |
--------------------------------------------------------------------------------
/fact-bounty-client/.idea/fact-bounty-client.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/NavBar/NavBar.style.js:
--------------------------------------------------------------------------------
1 | export default theme => ({
2 | root: {
3 | flexGrow: 1
4 | },
5 | grow: {
6 | flexGrow: 1
7 | },
8 | menuButton: {
9 | marginLeft: -12,
10 | marginRight: 20,
11 | [theme.breakpoints.up('md')]: {
12 | display: 'none'
13 | }
14 | },
15 | linkButtonsContainer: {
16 | [theme.breakpoints.down('sm')]: {
17 | display: 'none'
18 | }
19 | },
20 | navBar: {
21 | backgroundColor: '#ffffff',
22 | boxShadow: 'none'
23 | }
24 | })
25 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/styles/global-styles.sass:
--------------------------------------------------------------------------------
1 | html
2 | scroll-behavior: smooth
3 |
4 | body
5 | margin: 0
6 | padding: 0
7 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
8 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
9 | sans-serif
10 | -webkit-font-smoothing: antialiased
11 | -moz-osx-font-smoothing: grayscale
12 | overflow-x: hidden
13 |
14 | code
15 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New'
16 | monospace
17 |
--------------------------------------------------------------------------------
/codefresh.yml:
--------------------------------------------------------------------------------
1 | version: "1.0"
2 | stages:
3 | - "clone"
4 | - "build"
5 | - "integration"
6 | - "push"
7 | steps:
8 | main_clone:
9 | type: "git-clone"
10 | description: "Cloning main repository..."
11 | repo: "scorelab/fact-Bounty"
12 | revision: "${{CF_BRANCH}}"
13 | stage: "clone"
14 | build:
15 | title: "Building Docker Image"
16 | type: "build"
17 | image_name: "scorelab/fact-Bounty"
18 | tag: "${{CF_BRANCH_TAG_NORMALIZED}}"
19 | dockerfile: "fact-bounty-flask/Dockerfile"
20 | stage: "build"
21 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # Config file for automatic testing at travis-ci.org
2 | language: python
3 | env:
4 | - FLASK_APP=app.py FLASK_CONFIG=testing ELASTIC_SEARCH_URL=127.0.0.1
5 | python:
6 | - 3.5
7 | - 3.6
8 | services:
9 | - elasticsearch
10 | install:
11 | - pip install -r fact-bounty-flask/requirements.txt
12 | - pip install -r Crawlers/requirements.txt
13 | before_script:
14 | - sleep 10
15 | - cd Crawlers
16 | - scrapyd > logfile_scrapy &
17 | - cd ..
18 | - python db/add_es.py
19 | script:
20 | - cd fact-bounty-flask
21 | - flask test
22 |
--------------------------------------------------------------------------------
/fact-bounty-flask/api/errors.py:
--------------------------------------------------------------------------------
1 | from flask import jsonify
2 |
3 |
4 | def bad_request(message):
5 | response = jsonify({'error': 'bad request', 'message': message})
6 | response.status_code = 400
7 | return response
8 |
9 |
10 | def unauthorized(message):
11 | response = jsonify({'error': 'unauthorized', 'message': message})
12 | response.status_code = 401
13 | return response
14 |
15 |
16 | def forbidden(message):
17 | response = jsonify({'error': 'forbidden', 'message': message})
18 | response.status_code = 403
19 | return response
20 |
--------------------------------------------------------------------------------
/fact-bounty-flask/Dockerfile:
--------------------------------------------------------------------------------
1 | from python:3.6-alpine
2 |
3 | RUN apk add --no-cache --virtual .pynacl_deps build-base python3-dev libffi-dev && pip3 install --upgrade pip
4 |
5 | ENV FLASK_APP app.py
6 | ENV FLASK_CONFIG docker
7 |
8 | RUN mkdir -p /usr/fact-bounty/fact-bounty-flask
9 | WORKDIR /usr/fact-bounty/fact-bounty-flask
10 |
11 | COPY requirements.txt requirements.txt
12 | COPY docker.txt docker.txt
13 | RUN pip install -r docker.txt
14 | COPY . .
15 |
16 |
17 | RUN dos2unix boot.sh
18 | RUN chmod +x boot.sh
19 |
20 | EXPOSE 5000
21 |
22 | ENTRYPOINT [ "./boot.sh" ]
23 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/styles/variables.sass:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Open+Sans:400,600,700|Roboto:400,500,700');
2 |
3 | //colors
4 | $primary_color: #1564c0
5 | $secondary_color: #FDA907
6 | $primary_color_dark: #265daf
7 | $light-blue: #2699FB
8 | $light-blue-o: rgba(38, 153, 251, 0.1)
9 | $grey: #d4d4d4
10 | $light_grey: #e8ecf2
11 | $light-grey: #F4F6F8
12 | $dark-grey: #8C96A9
13 | $text-color: #485460
14 | $green: #05C46B
15 | $red: #E01E5A
16 | $orange: #FFC048
17 | $true-color: #009688
18 | $fake-color: #f4511e
19 | $mix-color: #1564c0
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/redux/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 | import authReducer from './authReducers'
3 | import errorReducer from './errorReducers'
4 | import postReducer from './postReducers'
5 | import contactUsReducer from './contactUsReducers'
6 | import twitterReducer from './twitterReducers'
7 | import successReducers from './successReducers'
8 |
9 | export default combineReducers({
10 | auth: authReducer,
11 | errors: errorReducer,
12 | success: successReducers,
13 | posts: postReducer,
14 | contactUs: contactUsReducer,
15 | tweets: twitterReducer
16 | })
17 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/Posts/Posts.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, Fragment } from 'react'
2 | import './style.sass'
3 | import PostsList from '../../components/PostsList'
4 |
5 | class Posts extends Component {
6 | render() {
7 | return (
8 |
9 |
10 |
11 |
Posts
12 |
13 |
14 |
15 |
16 |
17 | )
18 | }
19 | }
20 |
21 | Posts.propTypes = {}
22 |
23 | export default Posts
24 |
--------------------------------------------------------------------------------
/.remarkrc.js:
--------------------------------------------------------------------------------
1 | // .remarkrc.js
2 | exports.plugins = [
3 | require('remark-frontmatter'),
4 | [
5 | require('remark-retext'),
6 | require('unified')().use({
7 | plugins: [
8 | require('retext-english'),
9 | require('retext-syntax-urls'),
10 | [require('retext-sentence-spacing'), { preferred: 1 }],
11 | require('retext-repeated-words'),
12 | require('retext-usage'),
13 | ],
14 | }),
15 | ],
16 | require('remark-preset-lint-consistent'),
17 | require('remark-preset-lint-recommended'),
18 | require('remark-preset-lint-markdown-style-guide'),
19 | ];
--------------------------------------------------------------------------------
/fact-bounty-flask/app.py:
--------------------------------------------------------------------------------
1 | import os
2 | from dotenv import load_dotenv
3 | from flask import render_template
4 |
5 | from api.app import create_app
6 | from api.extensions import db
7 |
8 | dotenv_path = os.path.join(os.path.dirname(__file__), ".env")
9 | if os.path.exists(dotenv_path):
10 | load_dotenv(dotenv_path)
11 |
12 | app = create_app(os.getenv("FLASK_CONFIG") or "default")
13 |
14 |
15 | @app.route("/", defaults={"path": ""})
16 | @app.route("/")
17 | def default_route(path):
18 | return render_template('index.html')
19 |
20 |
21 | if __name__ == "__main__":
22 | app.run(host="0.0.0.0", port=5000)
23 |
--------------------------------------------------------------------------------
/fact-bounty-flask/migrations/script.py.mako:
--------------------------------------------------------------------------------
1 | """${message}
2 |
3 | Revision ID: ${up_revision}
4 | Revises: ${down_revision | comma,n}
5 | Create Date: ${create_date}
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 | ${imports if imports else ""}
11 |
12 | # revision identifiers, used by Alembic.
13 | revision = ${repr(up_revision)}
14 | down_revision = ${repr(down_revision)}
15 | branch_labels = ${repr(branch_labels)}
16 | depends_on = ${repr(depends_on)}
17 |
18 |
19 | def upgrade():
20 | ${upgrades if upgrades else "pass"}
21 |
22 |
23 | def downgrade():
24 | ${downgrades if downgrades else "pass"}
25 |
--------------------------------------------------------------------------------
/fact-bounty-flask/api/helpers.py:
--------------------------------------------------------------------------------
1 | from threading import Thread
2 | from flask import current_app
3 | from flask_mail import Message
4 | from api.extensions import mail
5 |
6 |
7 | def send_async_email(app, msg):
8 | with app.app_context():
9 | mail.send(msg)
10 |
11 |
12 | def send_email(to, subject, body):
13 | app = current_app._get_current_object()
14 | msg = Message(app.config['FACTBOUNTY_MAIL_SUBJECT_PREFIX'] + subject,
15 | sender=app.config['FACTBOUNTY_MAIL_SENDER'], recipients=[to])
16 | msg.body = body
17 | thr = Thread(target=send_async_email, args=[app, msg])
18 | thr.start()
19 | return thr
20 |
--------------------------------------------------------------------------------
/fact-bounty-flask/.gcloudignore:
--------------------------------------------------------------------------------
1 | # This file specifies files that are *not* uploaded to Google Cloud Platform
2 | # using gcloud. It follows the same syntax as .gitignore, with the addition of
3 | # "#!include" directives (which insert the entries of the given .gitignore-style
4 | # file at that point).
5 | #
6 | # For more information, run:
7 | # $ gcloud topic gcloudignore
8 | #
9 | .gcloudignore
10 | # If you would like to upload your .git directory, .gitignore file or files
11 | # from your .gitignore file, remove the corresponding line
12 | # below:
13 | .git
14 | .gitignore
15 |
16 | # Python pycache:
17 | __pycache__/
18 | # Ignored by the build system
19 | /setup.cfg
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/ContactUsForm/style.sass:
--------------------------------------------------------------------------------
1 | @import '../../styles/variables.sass'
2 |
3 | .contact-us-form-container
4 | margin: 150px 0px 0px 0px
5 | display:flex
6 | flex-direction:column
7 | text-align: center
8 | form
9 | display: flex
10 | flex-direction: column
11 | justify-content: center
12 | margin: 30px 10px 20px 10px
13 | input, textarea
14 | z-index: 1
15 | box-shadow: 1px 1px 15px rgba(0, 0, 0, .1)
16 | border: none
17 | border-radius: 3px
18 | padding: 10px
19 | margin: 20px 15% 0px 15%
20 | resize: none
21 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/notFound/index.css:
--------------------------------------------------------------------------------
1 | .notFoundContainer {
2 | display: flex;
3 | justify-content: center;
4 | background: linear-gradient(to bottom, #1d858a 0%, #1baeb8 4%, #13d4e2 100%);
5 | }
6 |
7 | .notFoundContainer div {
8 | margin: 4em;
9 | }
10 |
11 | .notFoundImg {
12 | height: 20%;
13 | width: 20%;
14 | }
15 |
16 | .notFoundSpan {
17 | color: #ffe600;
18 | font-size: 3.5em;
19 | margin: 20px;
20 | }
21 |
22 | .notFoundSpan1 {
23 | color: #ffe600;
24 | font-size: 2em;
25 | margin: 20px;
26 | }
27 |
28 | .notFoundSpan2 {
29 | color: #ffe600;
30 | font-size: 1.3em;
31 | margin: 20px;
32 | }
33 |
--------------------------------------------------------------------------------
/.idea/fact-Bounty.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/styles/theme.js:
--------------------------------------------------------------------------------
1 | import { createMuiTheme } from '@material-ui/core/styles'
2 |
3 | const theme = createMuiTheme({
4 | palette: {
5 | primary: {
6 | main: '#1564c0'
7 | },
8 | secondary: {
9 | main: '#FDA907'
10 | }
11 | },
12 | typography: {
13 | fontFamily: [
14 | '-apple-system',
15 | 'BlinkMacSystemFont',
16 | 'Segoe UI',
17 | 'Roboto',
18 | 'Helvetica Neue',
19 | 'Arial',
20 | 'sans-serif',
21 | 'Apple Color Emoji',
22 | 'Segoe UI Emoji',
23 | 'Segoe UI Symbol'
24 | ].join(','),
25 | useNextVariants: true
26 | }
27 | })
28 |
29 | export default theme
30 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/AsyncViewWrapper/AsyncViewWrapper.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import CircularProgress from '@material-ui/core/CircularProgress'
4 | import './style.sass'
5 |
6 | const AsyncViewWrapper = ({ loading, children }) => {
7 | return (
8 |
9 | {loading ? (
10 |
11 |
12 |
13 | ) : (
14 | children
15 | )}
16 |
17 | )
18 | }
19 |
20 | AsyncViewWrapper.propTypes = {
21 | loading: PropTypes.bool,
22 | children: PropTypes.node
23 | }
24 |
25 | export default AsyncViewWrapper
26 |
--------------------------------------------------------------------------------
/Crawlers/news_sites/items.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Define here the models for your scraped items
4 | #
5 | # See documentation in:
6 | # https://doc.scrapy.org/en/latest/topics/items.html
7 |
8 | import scrapy
9 |
10 |
11 | class NewsSitesItem(scrapy.Item):
12 | # define the fields for your item here like:
13 | # name = scrapy.Field()
14 | title = scrapy.Field()
15 | author = scrapy.Field()
16 | content = scrapy.Field()
17 | date = scrapy.Field()
18 | imageLink = scrapy.Field()
19 | source = scrapy.Field()
20 | approved_count = scrapy.Field()
21 | fake_count = scrapy.Field()
22 | mixedvote_count = scrapy.Field()
23 | news_url = scrapy.Field()
24 |
--------------------------------------------------------------------------------
/fact-bounty-flask/docs/stories/get_by_id.yml:
--------------------------------------------------------------------------------
1 | Update vote-count
2 | ---
3 | tags:
4 | - Stories
5 | produces:
6 | - "application/json"
7 | parameters:
8 | - in: path
9 | name: id
10 | responses:
11 | 200:
12 | description: JSON object containing success message
13 | schema:
14 | type: object
15 | properties:
16 | message:
17 | type: string
18 | example: Story fetched successfully
19 | 404:
20 | description: JSON object containing error message
21 | schema:
22 | type: object
23 | properties:
24 | message:
25 | type: string
26 | example: Please provide correct story id.
27 |
28 |
29 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/OAuthContainer/OAuthContainer.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import PropTypes from 'prop-types'
3 | import FacebookContainer from './FacebookContainer'
4 | import GoogleContainer from './GoogleContainer'
5 | import './index.scss'
6 |
7 | class OAuthContainer extends Component {
8 | render() {
9 | return (
10 |
11 |
12 |
13 |
14 | )
15 | }
16 | }
17 |
18 | OAuthContainer.propTypes = {
19 | button_type: PropTypes.string
20 | }
21 |
22 | export default OAuthContainer
23 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/notFound/NotFound.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import spider from '../spider.svg'
3 | import '../styles/index.css'
4 |
5 | const NotFound = () => {
6 | return (
7 |
8 |
9 |

10 |
11 | 404
12 |
13 | We have crawled everywhere,
14 |
15 | but that seems to be lost.
16 |
17 |
18 |
19 | )
20 | }
21 |
22 | export default NotFound
23 |
--------------------------------------------------------------------------------
/fact-bounty-client/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/redux/reducers/authReducers.js:
--------------------------------------------------------------------------------
1 | import { SET_CURRENT_USER, USER_LOADING } from '../actions/actionTypes'
2 |
3 | // eslint-disable-next-line prettier/prettier
4 | const isEmpty = require('is-empty')
5 | const initialState = {
6 | isAuthenticated: false,
7 | user: {},
8 | loading: false
9 | }
10 |
11 | export default function(state = initialState, action) {
12 | switch (action.type) {
13 | case SET_CURRENT_USER:
14 | return {
15 | ...state,
16 | isAuthenticated: !isEmpty(action.payload),
17 | user: action.payload
18 | }
19 | case USER_LOADING:
20 | return {
21 | ...state,
22 | loading: true
23 | }
24 | default:
25 | return state
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/fact-bounty-flask/.idea/fact-bounty-flask.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | Please provide enough information so that others can review your pull request:
2 |
3 |
4 |
5 | Explain the **details** for making this change. What existing problem does the pull request solve?
6 |
7 |
8 |
9 | **Test plan (required)**
10 |
11 | Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes UI.
12 |
13 |
14 | **Code formatting**
15 |
16 |
17 | **Closing issues**
18 |
19 | Put `closes #XXXX` in your comment and commit message to auto-close the issue that your PR fixes (if such).
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/PrivateRoute/PrivateRoute.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Redirect, Route } from 'react-router-dom'
3 | import { connect } from 'react-redux'
4 | import PropTypes from 'prop-types'
5 |
6 | const PrivateRoute = ({ component: Component, auth, ...rest }) => (
7 |
10 | auth.isAuthenticated === true ? (
11 |
12 | ) : (
13 |
14 | )
15 | }
16 | />
17 | )
18 |
19 | PrivateRoute.propTypes = {
20 | auth: PropTypes.object.isRequired,
21 | component: PropTypes.any
22 | }
23 |
24 | const mapStateToProps = state => ({
25 | auth: state.auth
26 | })
27 |
28 | export default connect(mapStateToProps)(PrivateRoute)
29 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/PostDetailView/style.sass:
--------------------------------------------------------------------------------
1 | @import '../../styles/variables.sass'
2 |
3 | .post-detail-view-wrapper
4 | .header
5 | display: flex
6 | margin-top: 20px
7 | justify-content: space-between
8 | align-items: center
9 | .left
10 | flex: 3.5
11 | .right
12 | text-align: right
13 | flex: 1
14 | .info
15 | label
16 | margin: 3px
17 | a
18 | margin-left: 5px
19 | .hr
20 | margin-bottom: 20px
21 | .image
22 | text-align: center
23 | margin-bottom: 20px
24 | position: relative
25 | img
26 | width: 100%
27 | .content
28 | text-align: justify
29 | margin-bottom: 40px
30 | .votes-bar-section
31 | margin: 20px 0px 20px 0px
32 | .vote-buttons-container
33 | opacity: 1
34 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/redux/actions/contactUsActions.js:
--------------------------------------------------------------------------------
1 | import {
2 | START_CONTACT_FORM_SUBMIT,
3 | CONTACT_FORM_SUBMIT_SUCCESS,
4 | CONTACT_FORM_SUBMIT_FAIL
5 | } from './actionTypes'
6 | import ContactUsService from '../../services/ContactUsService'
7 |
8 | export const submitForm = (
9 | name,
10 | email,
11 | phone,
12 | subject,
13 | message
14 | ) => dispatch => {
15 | dispatch({ type: START_CONTACT_FORM_SUBMIT })
16 | ContactUsService.contactUsSubmit(name, email, phone, subject, message)
17 | .then(res => {
18 | console.log(res)
19 | dispatch({ type: CONTACT_FORM_SUBMIT_SUCCESS })
20 | })
21 | .catch(err => {
22 | dispatch({ type: CONTACT_FORM_SUBMIT_FAIL, payload: err })
23 | console.error('Server response invalid:', err)
24 | })
25 | }
26 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/Search/style.sass:
--------------------------------------------------------------------------------
1 | .search-wrapper
2 | .header
3 | padding: 20px 0px 10px 0px
4 | display: flex
5 | justify-content: space-between
6 | align-items: center
7 | input
8 | width: 300px;
9 | box-shadow: 1px 1px 15px rgba(0, 0, 0, .1)
10 | border: none
11 | border-radius: 3px
12 | padding: 10px
13 | .hr
14 | margin-bottom: 40px
15 | .search-form-container
16 | text-align: center
17 | form
18 | display: flex
19 | flex-direction: row
20 | input
21 | z-index: 1
22 | box-shadow: 1px 1px 15px rgba(0, 0, 0, .1)
23 | border: none
24 | border-radius: 3px
25 | padding: 12px
26 | resize: none
27 | width: 500px
28 | margin-right: 15px
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/Tweets/Tweets.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, Fragment } from 'react'
2 | import './style.sass'
3 | import TweetList from '../../components/TweetList'
4 |
5 | class Tweets extends Component {
6 | render() {
7 | return (
8 |
9 |
10 |
11 |
Tweets
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | )
21 | }
22 | }
23 |
24 | Tweets.propTypes = {}
25 |
26 | export default Tweets
27 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/TweetSearch/style.sass:
--------------------------------------------------------------------------------
1 | .search-wrapper
2 | .header
3 | padding: 20px 0px 10px 0px
4 | display: flex
5 | justify-content: space-between
6 | align-items: center
7 | input
8 | width: 300px;
9 | box-shadow: 1px 1px 15px rgba(0, 0, 0, .1)
10 | border: none
11 | border-radius: 3px
12 | padding: 10px
13 | .hr
14 | margin-bottom: 40px
15 | .search-form-container
16 | text-align: center
17 | form
18 | display: flex
19 | flex-direction: row
20 | input
21 | z-index: 1
22 | box-shadow: 1px 1px 15px rgba(0, 0, 0, .1)
23 | border: none
24 | border-radius: 3px
25 | padding: 12px
26 | resize: none
27 | width: 500px
28 | margin-right: 15px
--------------------------------------------------------------------------------
/fact-bounty-client/src/redux/reducers/contactUsReducers.js:
--------------------------------------------------------------------------------
1 | import {
2 | START_CONTACT_FORM_SUBMIT,
3 | CONTACT_FORM_SUBMIT_SUCCESS,
4 | CONTACT_FORM_SUBMIT_FAIL
5 | } from '../actions/actionTypes'
6 |
7 | const initialState = {
8 | loading: false,
9 | error: null
10 | }
11 |
12 | export default function(state = initialState, action) {
13 | switch (action.type) {
14 | case START_CONTACT_FORM_SUBMIT:
15 | return {
16 | ...state,
17 | loading: true
18 | }
19 | case CONTACT_FORM_SUBMIT_SUCCESS:
20 | return {
21 | ...state,
22 | loading: false
23 | }
24 | case CONTACT_FORM_SUBMIT_FAIL:
25 | return {
26 | ...state,
27 | loading: false,
28 | error: action.payload
29 | }
30 | default:
31 | return state
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/fact-bounty-flask/migrations/versions/8be924798f34_.py:
--------------------------------------------------------------------------------
1 | """empty message
2 |
3 | Revision ID: 8be924798f34
4 | Revises: 28906f0ff936
5 | Create Date: 2020-03-09 13:46:18.009784
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 |
11 |
12 | # revision identifiers, used by Alembic.
13 | revision = "8be924798f34"
14 | down_revision = "28906f0ff936"
15 | branch_labels = None
16 | depends_on = None
17 |
18 |
19 | def upgrade():
20 | # ### commands auto generated by Alembic - please adjust! ###
21 | op.add_column(
22 | "user", sa.Column("role", sa.String(length=10), nullable=True)
23 | )
24 | # ### end Alembic commands ###
25 |
26 |
27 | def downgrade():
28 | # ### commands auto generated by Alembic - please adjust! ###
29 | op.drop_column("user", "role")
30 | # ### end Alembic commands ###
31 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/VotesBar/style.sass:
--------------------------------------------------------------------------------
1 | @import '../../styles/variables.sass'
2 |
3 | .votes-bar-container
4 | .vote-status
5 | height: 18px
6 | transition: 0.2s ease
7 | display: flex
8 | position: relative
9 | .votes
10 | display: flex
11 | justify-content: center
12 | align-items: center
13 | transition: 0.2s ease
14 | opacity: 1
15 | .true-votes
16 | background-color: $true-color
17 | .fake-votes
18 | background-color: $fake-color
19 | .mix-votes
20 | background-color: $mix-color
21 | .vote-value
22 | font-size: 0.8rem
23 | font-weight: 600
24 | color: #fafafa
25 | opacity: 1
26 | transition: 0.2s ease
27 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/KibanaDashboard/Search.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, Fragment } from 'react'
2 | import {
3 | KIBANA_DEV_DASHBOARD,
4 | KIBANA_PROD_DASHBOARD
5 | } from '../../constants/KibanaConstants'
6 | import './style.sass'
7 |
8 | class KibanaDashboard extends Component {
9 | render() {
10 | return (
11 |
12 |
13 |
14 |
Visualizations
15 |
16 |
17 |
23 |
24 |
25 | )
26 | }
27 | }
28 |
29 | KibanaDashboard.propTypes = {}
30 |
31 | export default KibanaDashboard
32 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/redux/reducers/twitterReducers.js:
--------------------------------------------------------------------------------
1 | import { LOADING_TWEETS, FETCH_TWEETS } from '../actions/actionTypes'
2 |
3 | const initialState = {
4 | loadingTweets: false,
5 | items: {
6 | adaderana: [],
7 | lankacnews: [],
8 | vigasapuwath: [],
9 | mawbimaonline: [],
10 | search: []
11 | }
12 | }
13 |
14 | export default function(state = initialState, action) {
15 | switch (action.type) {
16 | case LOADING_TWEETS: {
17 | return {
18 | ...state,
19 | loadingTweets: true
20 | }
21 | }
22 | case FETCH_TWEETS: {
23 | return Object.assign({}, state, {
24 | items: {
25 | ...state.items,
26 | [action.user]: action.payload
27 | },
28 | loadingTweets: false
29 | })
30 | }
31 | default: {
32 | return state
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/fact-bounty-client/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | commonjs: true,
5 | es6: true
6 | },
7 | extends: [
8 | 'eslint:recommended',
9 | 'plugin:react/recommended',
10 | 'react-app',
11 | 'plugin:prettier/recommended',
12 | 'prettier/react'
13 | ],
14 | parser: 'babel-eslint',
15 | parserOptions: {
16 | ecmaVersion: 2018,
17 | sourceType: 'module'
18 | },
19 | rules: {
20 | 'no-unused-vars': [
21 | 'off',
22 | { vars: 'all', args: 'after-used', ignoreRestSiblings: false }
23 | ],
24 | 'no-console': 'off',
25 | 'prettier/prettier': ['warn', { semi: false, singleQuote: true }]
26 | },
27 | settings: {
28 | react: {
29 | createClass: 'createReactClass',
30 | pragma: 'React',
31 | version: 'detect',
32 | flowVersion: '0.53'
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/db/create_index.py:
--------------------------------------------------------------------------------
1 | """
2 | Create ElasicSearch index
3 | """
4 | from elasticsearch import Elasticsearch
5 |
6 | es = Elasticsearch("http://localhost:9200")
7 | index_name = "test5"
8 |
9 | # Setting mappings for index
10 | mapping = {
11 | "mappings": {
12 | "stories": {
13 | "properties": {
14 | "date": {"type": "date"},
15 | "author": {"type": "keyword"},
16 | "title": {"type": "keyword"},
17 | "content": {"type": "keyword"},
18 | "imageLink": {"type": "keyword"},
19 | "source": {"type": "keyword"},
20 | "approved_count": {"type": "keyword"},
21 | "fake_count": {"type": "integer"},
22 | "mixedvote_count": {"type": "integer"},
23 | }
24 | }
25 | }
26 | }
27 |
28 |
29 | es.index(index=index_name, body=mapping)
30 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/TwitterGraph/styles.sass:
--------------------------------------------------------------------------------
1 | .check-box-list
2 | display: flex
3 | justify-content: center
4 |
5 | .radio-container
6 | margin-right: 17px
7 |
8 | .show-label
9 | padding-right: 12px
10 |
11 | .search-form
12 | display: flex
13 | justify-content: center
14 | align-items: center
15 |
16 | .twitter-container
17 | display: flex
18 | margin: 32px 32px
19 |
20 | .timeline
21 | width: 30%
22 |
23 | .chart
24 | width: 100% !important
25 | height: 80vh !important
26 | margin: 0 auto
27 |
28 | .twitter-graph
29 | width: 60%
30 | border: solid rgba(0,0,0,0.1) 1px
31 |
32 | .button-column
33 | width: 10%
34 | padding: 5px 3px
35 | text-align: center
36 | display: flex
37 | flex-direction: column
38 | align-items: center
39 |
40 | .btn
41 | max-width: 50px
42 | margin-bottom: 7px
43 |
44 |
45 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/Login/Login.style.js:
--------------------------------------------------------------------------------
1 | export default theme => ({
2 | main: {
3 | width: 'auto',
4 | display: 'block', // Fix IE 11 issue.
5 | marginLeft: theme.spacing.unit * 3,
6 | marginRight: theme.spacing.unit * 3,
7 | [theme.breakpoints.up(400 + theme.spacing.unit * 3 * 2)]: {
8 | width: 400,
9 | marginLeft: 'auto',
10 | marginRight: 'auto'
11 | }
12 | },
13 | paper: {
14 | marginTop: theme.spacing.unit * 12,
15 | display: 'flex',
16 | flexDirection: 'column',
17 | alignItems: 'center',
18 | padding: `${theme.spacing.unit * 2}px ${theme.spacing.unit * 3}px ${theme
19 | .spacing.unit * 3}px`
20 | },
21 | avatar: {
22 | margin: theme.spacing.unit,
23 | backgroundColor: theme.palette.secondary.main
24 | },
25 | form: {
26 | width: '100%', // Fix IE 11 issue.
27 | marginTop: theme.spacing.unit
28 | },
29 | submit: {
30 | marginTop: theme.spacing.unit * 3
31 | }
32 | })
33 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/Register/Register.style.js:
--------------------------------------------------------------------------------
1 | export default theme => ({
2 | main: {
3 | width: 'auto',
4 | display: 'block', // Fix IE 11 issue.
5 | marginLeft: theme.spacing.unit * 3,
6 | marginRight: theme.spacing.unit * 3,
7 | [theme.breakpoints.up(400 + theme.spacing.unit * 3 * 2)]: {
8 | width: 400,
9 | marginLeft: 'auto',
10 | marginRight: 'auto'
11 | }
12 | },
13 | paper: {
14 | marginTop: theme.spacing.unit * 12,
15 | display: 'flex',
16 | flexDirection: 'column',
17 | alignItems: 'center',
18 | padding: `${theme.spacing.unit * 2}px ${theme.spacing.unit * 3}px ${theme
19 | .spacing.unit * 3}px`
20 | },
21 | avatar: {
22 | margin: theme.spacing.unit,
23 | backgroundColor: theme.palette.secondary.main
24 | },
25 | form: {
26 | width: '100%', // Fix IE 11 issue.
27 | marginTop: theme.spacing.unit
28 | },
29 | submit: {
30 | marginTop: theme.spacing.unit * 3
31 | }
32 | })
33 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/ResetPassword/ResetPassword.style.js:
--------------------------------------------------------------------------------
1 | export default theme => ({
2 | main: {
3 | width: 'auto',
4 | display: 'block', // Fix IE 11 issue.
5 | marginLeft: theme.spacing.unit * 3,
6 | marginRight: theme.spacing.unit * 3,
7 | [theme.breakpoints.up(400 + theme.spacing.unit * 3 * 2)]: {
8 | width: 400,
9 | marginLeft: 'auto',
10 | marginRight: 'auto'
11 | }
12 | },
13 | paper: {
14 | marginTop: theme.spacing.unit * 12,
15 | display: 'flex',
16 | flexDirection: 'column',
17 | alignItems: 'center',
18 | padding: `${theme.spacing.unit * 2}px ${theme.spacing.unit * 3}px ${theme
19 | .spacing.unit * 3}px`
20 | },
21 | avatar: {
22 | margin: theme.spacing.unit,
23 | backgroundColor: theme.palette.secondary.main
24 | },
25 | form: {
26 | width: '100%', // Fix IE 11 issue.
27 | marginTop: theme.spacing.unit
28 | },
29 | submit: {
30 | marginTop: theme.spacing.unit * 3
31 | }
32 | })
33 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/ForgotPassword/ForgotPassword.style.js:
--------------------------------------------------------------------------------
1 | export default theme => ({
2 | main: {
3 | width: 'auto',
4 | display: 'block', // Fix IE 11 issue.
5 | marginLeft: theme.spacing.unit * 3,
6 | marginRight: theme.spacing.unit * 3,
7 | [theme.breakpoints.up(400 + theme.spacing.unit * 3 * 2)]: {
8 | width: 400,
9 | marginLeft: 'auto',
10 | marginRight: 'auto'
11 | }
12 | },
13 | paper: {
14 | marginTop: theme.spacing.unit * 12,
15 | display: 'flex',
16 | flexDirection: 'column',
17 | alignItems: 'center',
18 | padding: `${theme.spacing.unit * 2}px ${theme.spacing.unit * 3}px ${theme
19 | .spacing.unit * 3}px`
20 | },
21 | avatar: {
22 | margin: theme.spacing.unit,
23 | backgroundColor: theme.palette.secondary.main
24 | },
25 | form: {
26 | width: '100%', // Fix IE 11 issue.
27 | marginTop: theme.spacing.unit
28 | },
29 | submit: {
30 | marginTop: theme.spacing.unit * 3
31 | }
32 | })
33 |
--------------------------------------------------------------------------------
/fact-bounty-flask/migrations/alembic.ini:
--------------------------------------------------------------------------------
1 | # A generic, single database configuration.
2 |
3 | [alembic]
4 | # template used to generate migration files
5 | # file_template = %%(rev)s_%%(slug)s
6 |
7 | # set to 'true' to run the environment during
8 | # the 'revision' command, regardless of autogenerate
9 | # revision_environment = false
10 |
11 |
12 | # Logging configuration
13 | [loggers]
14 | keys = root,sqlalchemy,alembic
15 |
16 | [handlers]
17 | keys = console
18 |
19 | [formatters]
20 | keys = generic
21 |
22 | [logger_root]
23 | level = WARN
24 | handlers = console
25 | qualname =
26 |
27 | [logger_sqlalchemy]
28 | level = WARN
29 | handlers =
30 | qualname = sqlalchemy.engine
31 |
32 | [logger_alembic]
33 | level = INFO
34 | handlers =
35 | qualname = alembic
36 |
37 | [handler_console]
38 | class = StreamHandler
39 | args = (sys.stderr,)
40 | level = NOTSET
41 | formatter = generic
42 |
43 | [formatter_generic]
44 | format = %(levelname)-5.5s [%(name)s] %(message)s
45 | datefmt = %H:%M:%S
46 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/helpers/AuthTokenHelper.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | export const setAuthToken = token => {
4 | if (token) {
5 | // Apply authorization token to every request if logged in
6 | axios.defaults.headers.common['Authorization'] = token
7 | } else {
8 | // Delete auth header
9 | delete axios.defaults.headers.common['Authorization']
10 | }
11 | }
12 |
13 | export const saveAllTokens = tokens => {
14 | if (
15 | tokens &&
16 | tokens.access_token &&
17 | tokens.refresh_token &&
18 | tokens.user_details
19 | ) {
20 | //Setting the tokens to local storage
21 | localStorage.setItem('access_token', tokens.access_token)
22 | localStorage.setItem('refresh_token', tokens.refresh_token)
23 | localStorage.setItem('user_details', JSON.stringify(tokens.user_details))
24 | }
25 | }
26 |
27 | export const saveAcessToken = token => {
28 | if (token) {
29 | //Setting the token to local storage
30 | localStorage.setItem('access_token', token)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/constants/KibanaConstants.js:
--------------------------------------------------------------------------------
1 | export const KIBANA_DEV_DASHBOARD = `http://localhost:5601/app/kibana#/dashboard/3cc76620-87b2-11e9-91e3-f1ca30d1e0d1?embed=true&_g=()&_a=(description:'',filters:!(),fullScreenMode:!f,options:(darkTheme:!f,hidePanelTitles:!f,useMargins:!t),panels:!((embeddableConfig:(vis:(legendOpen:!t)),gridData:(h:25,i:'1',w:24,x:0,y:0),id:'1f021770-87b2-11e9-91e3-f1ca30d1e0d1',panelIndex:'1',type:visualization,version:'6.8.0'),(embeddableConfig:(),gridData:(h:15,i:'2',w:24,x:0,y:25),id:'69cad0b0-8863-11e9-91e3-f1ca30d1e0d1',panelIndex:'2',type:visualization,version:'6.8.0'),(embeddableConfig:(vis:(legendOpen:!f)),gridData:(h:19,i:'3',w:24,x:24,y:15),id:b6eec800-8864-11e9-91e3-f1ca30d1e0d1,panelIndex:'3',type:visualization,version:'6.8.0'),(embeddableConfig:(),gridData:(h:15,i:'4',w:24,x:24,y:0),id:f70ab2b0-8877-11e9-91e3-f1ca30d1e0d1,panelIndex:'4',type:visualization,version:'6.8.0')),query:(language:lucene,query:''),timeRestore:!f,title:'Dashboard+One',viewMode:edit)`
2 | export const KIBANA_PROD_DASHBOARD = ``
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # Prerequisites
2 |
3 | Please answer the following questions for yourself before submitting an issue. **YOU MAY DELETE THE PREREQUISITES SECTION.**
4 |
5 | - [ ] I am running the latest version
6 | - [ ] I checked to make sure that this issue has not already been filed
7 |
8 | # Expected Behavior
9 |
10 | Please describe the behavior you are expecting
11 |
12 | # Current Behavior
13 |
14 | What is the current behavior?
15 |
16 | # Failure Information (for bugs)
17 |
18 | Please help provide information about the failure if this is a bug. If it is not a bug, please remove the rest of this template.
19 |
20 | ## Steps to Reproduce
21 |
22 | Please provide detailed steps for reproducing the issue.
23 |
24 | 1. step 1
25 | 2. step 2
26 | 3. you get it...
27 |
28 | ## Context
29 |
30 | Please provide any relevant information about your setup. This is important in case the issue is not reproducible except for under certain conditions.
31 |
32 |
33 | ## Failure Logs
34 |
35 | Please include any relevant log snippets or files here.
--------------------------------------------------------------------------------
/fact-bounty-flask/requirements.txt:
--------------------------------------------------------------------------------
1 | alembic==1.0.8
2 | apscheduler==3.6.0
3 | astroid==2.2.3
4 | bcrypt==3.1.6
5 | cffi==1.12.2
6 | Click==7.0
7 | coverage==4.5.2
8 | elasticsearch==6.2.0
9 | entrypoints==0.3
10 | flake8==3.7.7
11 | Flask==1.0.2
12 | Flask-Bcrypt==0.7.1
13 | Flask-Cors==3.0.7
14 | Flask-HTTPAuth==3.2.4
15 | Flask-JWT-Extended==3.19.0
16 | Flask-Login==0.4.1
17 | Flask-Mail==0.9.1
18 | Flask-Migrate==2.4.0
19 | Flask-PageDown==0.2.2
20 | Flask-SQLAlchemy==2.3.2
21 | flasgger==0.9.2
22 | isort==4.3.10
23 | itsdangerous==1.1.0
24 | Jinja2==2.10
25 | lazy-object-proxy==1.3.1
26 | livereload==2.6.0
27 | Mako==1.0.7
28 | MarkupSafe==1.1.1
29 | mccabe==0.6.1
30 | passlib==1.7.1
31 | pre-commit==1.17.0
32 | psutil==5.6.6
33 | pycodestyle==2.5.0
34 | pycparser==2.19
35 | pyflakes==2.1.1
36 | PyJWT==1.7.1
37 | python-dateutil==2.8.0
38 | python-dotenv==0.10.1
39 | python-editor==1.0.4
40 | python-scrapyd-api==2.1.2
41 | six==1.12.0
42 | SQLAlchemy==1.2.18
43 | tornado==6.0
44 | tweepy==3.8.0
45 | typed-ast==1.2.0
46 | urllib3==1.22
47 | Werkzeug==0.14.1
48 | wrapt==1.11.1
49 | WTForms==2.2.1
50 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/DashboardSideNav/DashboardSideNav.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 | import './style.sass'
4 |
5 | const DashboardSideNav = () => {
6 | return (
7 |
8 |
9 | -
10 |
11 | Twitter
12 |
13 |
14 |
15 | -
16 |
17 | Search
18 |
19 |
20 |
21 | -
22 |
23 | Search Tweets
24 |
25 |
26 |
27 | -
28 |
29 | Visualizations
30 |
31 |
32 |
33 |
34 |
35 | )
36 | }
37 |
38 | DashboardSideNav.propTypes = {}
39 | export default DashboardSideNav
40 |
--------------------------------------------------------------------------------
/fact-bounty-flask/migrations/versions/d2fc5d7ff47e_.py:
--------------------------------------------------------------------------------
1 | """empty message
2 |
3 | Revision ID: d2fc5d7ff47e
4 | Revises:
5 | Create Date: 2019-07-02 18:28:34.801535
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 |
11 |
12 | # revision identifiers, used by Alembic.
13 | revision = "d2fc5d7ff47e"
14 | down_revision = None
15 | branch_labels = None
16 | depends_on = None
17 |
18 |
19 | def upgrade():
20 | # ### commands auto generated by Alembic - please adjust! ###
21 | op.create_table(
22 | "vote",
23 | sa.Column("id", sa.Integer(), nullable=False),
24 | sa.Column("story_id", sa.String(length=128), nullable=True),
25 | sa.Column("user_id", sa.Integer(), nullable=True),
26 | sa.Column("value", sa.Integer(), nullable=True),
27 | sa.ForeignKeyConstraint(["user_id"], ["user.id"]),
28 | sa.PrimaryKeyConstraint("id"),
29 | )
30 | # ### end Alembic commands ###
31 |
32 |
33 | def downgrade():
34 | # ### commands auto generated by Alembic - please adjust! ###
35 | op.drop_table("vote")
36 | # ### end Alembic commands ###
37 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/services/PostsService.js:
--------------------------------------------------------------------------------
1 | import ApiBuilder from '../helpers/ApiBuilder'
2 |
3 | /**
4 | *
5 | * GET : fetchPosts
6 | *
7 | */
8 | const fetchPosts = page => {
9 | return ApiBuilder.API.get(`/api/stories/get-range/${page}`)
10 | }
11 |
12 | /**
13 | *
14 | * GET : fetchPostById
15 | *
16 | */
17 | const fetchPostById = postId => {
18 | return ApiBuilder.API.get(`/api/stories/get/${postId}`)
19 | }
20 |
21 | /**
22 | *
23 | * POST : changeVoteCount
24 | *
25 | */
26 | const changeVoteCount = (story_id, vote_value) => {
27 | return ApiBuilder.API.post(`/api/stories/change-vote-count`, {
28 | story_id,
29 | vote_value
30 | })
31 | }
32 |
33 | /**
34 | *
35 | * POST : loadUserVotes
36 | *
37 | */
38 | const loadUserVotes = () => {
39 | return ApiBuilder.API.post(`/api/stories/load-user-votes`, {})
40 | }
41 |
42 | /**
43 | *
44 | * GET : getPostsFromKeyword
45 | *
46 | */
47 | const getPostsFromKeyword = keyword => {
48 | return ApiBuilder.API.get(`/api/stories/search/${keyword}`)
49 | }
50 |
51 | export default {
52 | fetchPosts,
53 | fetchPostById,
54 | changeVoteCount,
55 | loadUserVotes,
56 | getPostsFromKeyword
57 | }
58 |
--------------------------------------------------------------------------------
/fact-bounty-flask/docs/stories/load_user_vote.yml:
--------------------------------------------------------------------------------
1 | Update vote-count
2 | ---
3 | tags:
4 | - Stories
5 | consumes:
6 | - "application/json"
7 | parameters:
8 | - in: header
9 | name: Authorization
10 | description: an authorization header
11 | required: true
12 | type: string
13 | responses:
14 | 200:
15 | description: JSON object containing success message and user votes
16 | schema:
17 | type: object
18 | properties:
19 | message:
20 | type: string
21 | example: Retrieved user votes successfully
22 | user_votes:
23 | schema:
24 | type: array
25 | items:
26 | type: object
27 | properties:
28 | story_id:
29 | type: string
30 | value:
31 | type: integer
32 |
33 | 500:
34 | description: Something went wrong
35 | schema:
36 | type: object
37 | properties:
38 | message:
39 | type: string
40 | example: Something went wrong
41 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/redux/actions/actionTypes.js:
--------------------------------------------------------------------------------
1 | export const GET_ERRORS = 'GET_ERRORS'
2 | export const UPDATE_ERRORS = 'UPDATE_ERRORS'
3 | export const USER_LOADING = 'USER_LOADING'
4 | export const SET_CURRENT_USER = 'SET_CURRENT_USER'
5 | export const GET_SUCCESS = 'GET_SUCCESS'
6 | export const UPDATE_SUCCESS = 'UPDATE_SUCCESS'
7 |
8 | export const LOADING_POSTS = 'LOADING_POSTS'
9 | export const FETCH_POSTS = 'FETCH_POSTS'
10 | export const FETCH_POST_BY_ID = 'FETCH_POST_BY_ID'
11 | export const INCREMENT_PAGE = 'INCREMENT_PAGE'
12 | export const VOTE_COMPLETE = 'VOTE_COMPLETE'
13 | export const LOADING_USER_VOTES = 'LOADING_USER_VOTES'
14 | export const SET_USER_VOTES = 'SET_USER_VOTES'
15 | export const UPDATE_USER_VOTES = 'UPDATE_USER_VOTES'
16 | export const UPDATE_POST_VOTES = 'UPDATE_POST_VOTES'
17 | export const START_POSTS_SEARCH = 'START_POSTS_SEARCH'
18 | export const SET_POSTS_ON_SEARCH = 'SET_POSTS_ON_SEARCH'
19 |
20 | export const START_CONTACT_FORM_SUBMIT = 'START_CONTACT_FORM_SUBMIT'
21 | export const CONTACT_FORM_SUBMIT_SUCCESS = 'CONTACT_FORM_SUBMIT_SUCCESS'
22 | export const CONTACT_FORM_SUBMIT_FAIL = 'CONTACT_FORM_SUBMIT_FAIL'
23 |
24 | export const FETCH_TWEETS = 'FETCH_TWEETS'
25 | export const LOADING_TWEETS = 'LOADING_TWEETS'
26 |
--------------------------------------------------------------------------------
/fact-bounty-flask/tests/test_admin.py:
--------------------------------------------------------------------------------
1 | import os
2 | import unittest
3 | import tempfile
4 | import json
5 | import sys
6 | from app import app
7 | from fake_db import db_fd, db_path
8 |
9 | sys.path.append(os.path.join(sys.path[0], "../../"))
10 | FLASKR = app
11 |
12 |
13 | class Test_Admin(unittest.TestCase):
14 | @classmethod
15 | def setUpClass(self):
16 | self.db_fd, self.db_path = db_fd, db_path
17 | FLASKR.config["SQLALCHEMY_DATABASE_URI"] = 'sqlite:///' + self.db_path
18 | FLASKR.testing = True
19 | self.app = FLASKR.test_client()
20 |
21 | @classmethod
22 | def tearDownClass(self):
23 | # os.close(self.db_fd)
24 | # os.unlink(self.db_path)
25 | pass
26 |
27 | def test_fetch_system_panel_200(self):
28 | """Get System Panel Information"""
29 | response = self.app.get("/api/admin/system")
30 | res = response.data.decode("ASCII")
31 | res = json.loads(res)
32 | self.assertEqual(response.status_code, 200)
33 | self.assertEqual(res["message"], "Data fetched successfully!")
34 | self.assertTrue(isinstance(res["data"], (dict)))
35 |
36 |
37 | if __name__ == "__main__":
38 | unittest.main()
39 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import MuiThemeProvider from '@material-ui/core/styles/MuiThemeProvider'
3 | import jwt_decode from 'jwt-decode'
4 | import { Provider } from 'react-redux'
5 | import AppRouter from './AppRouter'
6 |
7 | import { setAuthToken } from './helpers/AuthTokenHelper'
8 | import { setCurrentUser } from './redux/actions/authActions'
9 | import store from './redux/store'
10 | import theme from './styles/theme'
11 |
12 | // Check for token to keep user logged in
13 | if (
14 | localStorage.access_token &&
15 | localStorage.refresh_token &&
16 | localStorage.user_details
17 | ) {
18 | setAuthToken(localStorage.access_token)
19 | // Decode token and get user info and exp
20 | const decoded = jwt_decode(localStorage.access_token)
21 | // Set user and isAuthenticated
22 | store.dispatch(
23 | setCurrentUser({
24 | id: decoded.identity,
25 | ...JSON.parse(localStorage.user_details)
26 | })
27 | )
28 | }
29 |
30 | class App extends Component {
31 | render() {
32 | return (
33 |
34 |
35 |
36 |
37 |
38 | )
39 | }
40 | }
41 |
42 | export default App
43 |
--------------------------------------------------------------------------------
/OAuthdSetup.md:
--------------------------------------------------------------------------------
1 | ### Setting up oauthd daemon locally
2 |
3 | This service is installed separately and will get integrated with the project by updating the environment variables in `.env` file by updating `REACT_APP_OAUTHD_KEY` and `REACT_APP_OAUTHD_URL`
4 |
5 | Refer [OAuth official page](https://oauth.net/getting-started/) to understand the usecase and setup process of OAuth.
6 | If you're already familiar with the OAuth or you just wnat to integrate without getting into much details you continue following the below mentioned steps to setup OAuth.
7 |
8 | Step 0: Open a new terminal.
9 |
10 | Step 1: Install redis-server using :
11 | `npm install redis`
12 |
13 | Step 2: Starting the redis server
14 | `redis-server --daemonize yes`
15 |
16 | Step 3: Installing ouathd
17 | `npm install -g oauthd`
18 |
19 | Step 4: (in fact bounty folder)
20 | `cd fact-bounty-client`
21 |
22 | Step 5: `oauthd init`
23 |
24 | Step 6: Give your folder a name
25 |
26 | Step 7: `cd FOLDER_NAME`
27 |
28 | Step 8: `oauthd start`
29 |
30 | your screen should look like this when visiting the localhost:6284
31 |
32 | 
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/fact-bounty-flask/docs/stories/change_vote_count.yml:
--------------------------------------------------------------------------------
1 | Update vote-count
2 | ---
3 | tags:
4 | - Stories
5 | consumes:
6 | - application/json
7 | produces:
8 | - application/json
9 | parameters:
10 | - in: header
11 | name: Authorization
12 | description: an authorization header
13 | required: true
14 | type: string
15 | - in: body
16 | name: body
17 | type: object
18 | responses:
19 | 200:
20 | description: JSON object containing success message
21 | schema:
22 | type: object
23 | properties:
24 | message:
25 | type: string
26 | example: Vote created successfully
27 | 201:
28 | description: JSON object containing success message
29 | schema:
30 | type: object
31 | properties:
32 | message:
33 | type: string
34 | example: Vote updated successfully
35 | 404:
36 | description: JSON object containing error message
37 | schema:
38 | type: object
39 | properties:
40 | message:
41 | type: string
42 | example: Please provide all the required fields
43 |
44 | 500:
45 | description: Something went wrong
46 | schema:
47 | type: object
48 | properties:
49 | message:
50 | type: string
51 | example: Something went wrong
52 |
53 |
--------------------------------------------------------------------------------
/fact-bounty-client/public/static/config.js:
--------------------------------------------------------------------------------
1 | var configuration = (function() {
2 | var obj = {}
3 |
4 | var mashape_key = '3298980fe4msh6cad2540efad0b6p11d7a7jsn9f110b5fdf9e'
5 |
6 | obj.articles_url = 'https://localhost:8080/articles'
7 | obj.articles_headers = {
8 | 'X-Mashape-Key': mashape_key,
9 | Accept: 'application/json'
10 | }
11 |
12 | obj.timeline_url = 'https://localhost:8080/timeline'
13 | obj.timeline_headers = {
14 | 'X-Mashape-Key': mashape_key,
15 | Accept: 'application/json'
16 | }
17 |
18 | obj.network_url = 'https://localhost:8080/network'
19 | obj.network_headers = {
20 | 'X-Mashape-Key': mashape_key,
21 | Accept: 'application/json'
22 | }
23 |
24 | obj.top_articles_url = 'https://localhost:8080/top-articles'
25 | obj.top_articles_headers = { 'X-Mashape-Key': mashape_key }
26 |
27 | obj.top_users_url = 'https://localhost:8080/top-users'
28 | obj.top_users_headers = { 'X-Mashape-Key': mashape_key }
29 |
30 | obj.botometer_url = 'https://example.com'
31 | obj.botometer_headers = {
32 | 'X-Mashape-Key': '--KEY--',
33 | Accept: 'application/json'
34 | }
35 |
36 | obj.botcache_url = 'https://example.com/api/scores'
37 | obj.feedback_url = 'https://example.com/api/feedback'
38 |
39 | obj.twitter_key = 'znlzy-T1dNp-G-NrjmwhxsiY-tg'
40 |
41 | obj.botcache_chunk_sizes = [
42 | // 50,
43 | // 200,
44 | 2000
45 | ]
46 |
47 | return obj
48 | })()
49 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/NavBar/Links/PublicLinks.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react'
2 | import Button from '@material-ui/core/Button'
3 | import { Link } from 'react-router-dom'
4 |
5 | const PublicLinks = () => (
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
27 |
28 |
29 |
32 |
33 |
34 |
35 | )
36 |
37 | const styles = {
38 | link: {
39 | color: 'black',
40 | textDecoration: 'none',
41 | margin: 5
42 | },
43 | btnContainer: {
44 | marginLeft: 10
45 | }
46 | }
47 |
48 | export default PublicLinks
49 |
--------------------------------------------------------------------------------
/fact-bounty-flask/api/stories/views.py:
--------------------------------------------------------------------------------
1 | from flask import Blueprint
2 | from .controller import storyController
3 |
4 | storyprint = Blueprint("stories", __name__)
5 |
6 |
7 | storyprint.add_url_rule(
8 | "/all", view_func=storyController["allstories"], methods=["GET"]
9 | )
10 |
11 | storyprint.add_url_rule(
12 | "/get-range/",
13 | view_func=storyController["getrange"],
14 | methods=["GET"],
15 | )
16 |
17 | storyprint.add_url_rule(
18 | "/get/", view_func=storyController["getstory"], methods=["GET"]
19 | )
20 |
21 | storyprint.add_url_rule(
22 | "/search/",
23 | view_func=storyController["search"],
24 | methods=["GET"],
25 | )
26 |
27 | storyprint.add_url_rule(
28 | "/load-user-votes",
29 | view_func=storyController["loaduservotes"],
30 | methods=["POST"],
31 | )
32 |
33 | storyprint.add_url_rule(
34 | "/change-vote-count",
35 | view_func=storyController["changevote"],
36 | methods=["POST"],
37 | )
38 |
39 | storyprint.add_url_rule(
40 | "/stories/load-user-comments",
41 | view_func=storyController["loadusercomments"],
42 | methods=["POST"],
43 | )
44 |
45 | storyprint.add_url_rule(
46 | "/stories/change-comment",
47 | view_func=storyController["changecomment"],
48 | methods=["POST"],
49 | )
50 |
51 | storyprint.add_url_rule(
52 | "/stories/delete-comment",
53 | view_func=storyController["deletecomment"],
54 | methods=["DELETE"],
55 | )
56 |
57 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/SwipeableDrawer/Links/PublicLinks.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Divider from '@material-ui/core/Divider'
3 | import { Link } from 'react-router-dom'
4 | import List from '@material-ui/core/List'
5 | import ListItem from '@material-ui/core/ListItem'
6 |
7 | const PublicLinks = () => (
8 |
9 |
10 |
11 | Home
12 |
13 |
14 |
15 |
16 |
17 | Posts
18 |
19 |
20 |
21 |
22 |
23 | About
24 |
25 |
26 |
27 |
28 |
29 | Contact Us
30 |
31 |
32 |
33 |
34 |
35 | Sign Up
36 |
37 |
38 |
39 |
40 |
41 | Login
42 |
43 |
44 |
45 |
46 | )
47 |
48 | const styles = {
49 | link: {
50 | color: 'black',
51 | textDecoration: 'none',
52 | padding: 10,
53 | display: 'flex',
54 | flex: 1
55 | }
56 | }
57 |
58 | export default PublicLinks
59 |
--------------------------------------------------------------------------------
/fact-bounty-flask/api/user/views.py:
--------------------------------------------------------------------------------
1 | from flask import Blueprint
2 | from .controller import userController
3 |
4 | userprint = Blueprint("user", __name__)
5 |
6 | userprint.add_url_rule(
7 | "/login", view_func=userController["login"], methods=["POST"]
8 | )
9 |
10 | userprint.add_url_rule(
11 | "/register", view_func=userController["register"], methods=["POST"]
12 | )
13 | userprint.add_url_rule(
14 | "/forgot_password",
15 | view_func=userController["forgot_password"],
16 | methods=["POST"],
17 | )
18 |
19 | userprint.add_url_rule(
20 | "/auth_verification_token",
21 | view_func=userController["auth_verification_token"],
22 | methods=["POST"],
23 | )
24 |
25 | userprint.add_url_rule(
26 | "/reset_password",
27 | view_func=userController["reset_password"],
28 | methods=["POST"],
29 | )
30 |
31 | userprint.add_url_rule(
32 | "/oauth", view_func=userController["auth"], methods=["POST"]
33 | )
34 |
35 | userprint.add_url_rule(
36 | "/logout_access",
37 | view_func=userController["logout_access"],
38 | methods=["POST"],
39 | )
40 |
41 | userprint.add_url_rule(
42 | "/logout_refresh",
43 | view_func=userController["logout_refresh"],
44 | methods=["POST"],
45 | )
46 |
47 | userprint.add_url_rule(
48 | "/token_refresh",
49 | view_func=userController["token_refresh"],
50 | methods=["POST"],
51 | )
52 |
53 | userprint.add_url_rule(
54 | "/profile",
55 | view_func=userController["profile"],
56 | methods=["GET", "POST"]
57 | )
58 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/Toast/Toast.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { withStyles } from '@material-ui/core/styles'
4 | import Snackbar from '@material-ui/core/Snackbar'
5 | import IconButton from '@material-ui/core/IconButton'
6 | import CloseIcon from '@material-ui/icons/Close'
7 |
8 | const styles = theme => ({
9 | close: {
10 | padding: theme.spacing.unit / 2
11 | }
12 | })
13 |
14 | class Toast extends React.Component {
15 | render() {
16 | const { classes, message, isOpen, handleClose } = this.props
17 | return (
18 | {message}}
30 | action={[
31 |
38 |
39 |
40 | ]}
41 | />
42 | )
43 | }
44 | }
45 |
46 | Toast.propTypes = {
47 | classes: PropTypes.object.isRequired,
48 | message: PropTypes.string.isRequired,
49 | isOpen: PropTypes.bool.isRequired,
50 | handleClose: PropTypes.func.isRequired
51 | }
52 |
53 | export default withStyles(styles)(Toast)
54 |
--------------------------------------------------------------------------------
/fact-bounty-flask/api/admin/controller.py:
--------------------------------------------------------------------------------
1 | import psutil
2 | import os
3 | import time
4 | from flask import make_response, jsonify
5 | from flask.views import MethodView
6 |
7 | PERIOD = 1 # 1 sec
8 |
9 |
10 | class SystemPanel(MethodView):
11 | def get(self):
12 |
13 | io_data_start = psutil.net_io_counters()
14 |
15 | time.sleep(PERIOD)
16 |
17 | cpu_data = psutil.cpu_percent(interval=None)
18 | ram_data = psutil.virtual_memory()
19 | disk_data = psutil.disk_usage("/")
20 | # user_data = psutil.users()
21 | io_data = psutil.net_io_counters()
22 |
23 | data = {
24 | "cpu": {"percent": cpu_data},
25 | "ram": {
26 | "percent": ram_data.percent,
27 | "total": ram_data.total >> 20,
28 | "used": ram_data.total - ram_data.available >> 20,
29 | },
30 | "disk": {
31 | "total": disk_data.total >> 30,
32 | "used": disk_data.used >> 30,
33 | },
34 | "io": {
35 | "sent_bytes_sec": (
36 | io_data.bytes_sent - io_data_start.bytes_sent
37 | ),
38 | "received_bytes_sec": (
39 | io_data.bytes_recv - io_data_start.bytes_recv
40 | ),
41 | },
42 | }
43 |
44 | response = {"message": "Data fetched successfully!", "data": data}
45 | return make_response(jsonify(response)), 200
46 |
47 |
48 | adminController = {"system": SystemPanel.as_view("system_panel")}
49 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 |
3 | services:
4 | flask_server:
5 | build: ./fact-bounty-flask
6 | ports:
7 | - 5000:5000
8 | restart: always
9 | links:
10 | - mysql:dbserver
11 |
12 | client:
13 | build: ./fact-bounty-client
14 | ports:
15 | - 3000:3000
16 | environment:
17 | - NODE_ENV=development
18 |
19 | mysql:
20 | image: "mysql/mysql-server:5.7"
21 | restart: always
22 |
23 | elasticsearch:
24 | image: docker.elastic.co/elasticsearch/elasticsearch:6.2.4
25 | container_name: elasticsearch
26 | environment:
27 | - cluster.name=docker-cluster
28 | - bootstrap.memory_lock=true
29 | - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
30 | ulimits:
31 | memlock:
32 | soft: -1
33 | hard: -1
34 | volumes:
35 | - esdata1:/usr/share/elasticsearch/data
36 | ports:
37 | - 9200:9200
38 | networks:
39 | - esnet
40 |
41 | elasticsearch2:
42 | image: docker.elastic.co/elasticsearch/elasticsearch:6.2.4
43 | container_name: elasticsearch2
44 | environment:
45 | - cluster.name=docker-cluster
46 | - bootstrap.memory_lock=true
47 | - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
48 | - "discovery.zen.ping.unicast.hosts=elasticsearch"
49 | ulimits:
50 | memlock:
51 | soft: -1
52 | hard: -1
53 | volumes:
54 | - esdata2:/usr/share/elasticsearch/data
55 | networks:
56 | - esnet
57 |
58 | volumes:
59 | esdata1:
60 | driver: local
61 | esdata2:
62 | driver: local
63 |
64 | networks:
65 | esnet:
66 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/AppRouter.js:
--------------------------------------------------------------------------------
1 | import React, { Component, Fragment } from 'react'
2 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
3 |
4 | //components
5 | import NavBar from './components/NavBar'
6 | import PrivateRoute from './components/PrivateRoute'
7 |
8 | //pages
9 | import Landing from './pages/Landing'
10 | import Register from './pages/Register'
11 | import Login from './pages/Login'
12 | import Dashboard from './pages/Dashboard'
13 | import Posts from './pages/Posts'
14 | import PostDetailView from './pages/PostDetailView'
15 | import Tweets from './pages/Tweets'
16 | import ForgotPassword from './pages/ForgotPassword'
17 | import ResetPassword from './pages/ResetPassword'
18 | class AppRouter extends Component {
19 | render() {
20 | return (
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | )
40 | }
41 | }
42 |
43 | export default AppRouter
44 |
--------------------------------------------------------------------------------
/fact-bounty-flask/api/util/controller.py:
--------------------------------------------------------------------------------
1 | from flask.views import MethodView
2 | from flask import make_response, request, jsonify, current_app
3 | from api.helpers import send_email
4 |
5 |
6 | class ContactUs(MethodView):
7 | """This class-based view handles user complaints
8 | send through contact us form request
9 | """
10 |
11 | def post(self):
12 | try:
13 | # get the request data
14 | data = request.get_json(silent=True)
15 |
16 | # extract data from dict
17 | phone = data['phone']
18 | email = data['email']
19 | subject = data['subject']
20 | message = data['message']
21 |
22 | # compose mail body
23 | body = """
24 | From: <%s>
25 | %s
26 | Contact: %s
27 | """ % (email, message, phone)
28 |
29 | # send mail to admin
30 | send_email(current_app.config['FACTBOUNTY_ADMIN'], subject, body)
31 |
32 | response = {
33 | 'message': 'Form submitted Successfully!',
34 | }
35 | # return a response notifying the user that form submitted
36 | # successfully
37 | return make_response(jsonify(response)), 201
38 | except Exception as e:
39 | # An error occured, therefore return a string message containing
40 | # the error
41 | response = {
42 | 'message': str(e)
43 | }
44 | return make_response(jsonify(response)), 404
45 |
46 |
47 | utilController = {
48 | 'contact_us': ContactUs.as_view('contactus')
49 | }
50 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/TweetList/TweetList.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { connect } from 'react-redux'
3 | import { fetchTweets } from '../../redux/actions/twitterActions'
4 | import PropTypes from 'prop-types'
5 | import AsyncViewWrapper from '../AsyncViewWrapper'
6 | import TweetItem from '../TweetItem'
7 |
8 | class TweetList extends Component {
9 | componentDidMount() {
10 | this.loadTweets()
11 | }
12 |
13 | loadTweets() {
14 | const tweets = this.props.tweets[this.props.user]
15 | if (tweets.length === 0) {
16 | this.props.fetchTweets(this.props.limit, this.props.user)
17 | }
18 | }
19 |
20 | renderTweetList() {
21 | const tweets = this.props.tweets[this.props.user]
22 | if (tweets) {
23 | return tweets.map((item, index) => {
24 | return
25 | })
26 | }
27 | }
28 |
29 | render() {
30 | const { loading } = this.props
31 | return (
32 |
33 | {this.renderTweetList()}
34 |
35 | )
36 | }
37 | }
38 |
39 | TweetList.propTypes = {
40 | fetchTweets: PropTypes.func,
41 | tweets: PropTypes.object,
42 | loading: PropTypes.bool,
43 | limit: PropTypes.number,
44 | user: PropTypes.string
45 | }
46 |
47 | const mapStateToProps = state => ({
48 | tweets: state.tweets.items,
49 | loading: state.tweets.loadingTweets
50 | })
51 |
52 | const mapDispatchToProps = dispatch => ({
53 | fetchTweets: (limit, url) => dispatch(fetchTweets(limit, url))
54 | })
55 |
56 | export default connect(
57 | mapStateToProps,
58 | mapDispatchToProps
59 | )(TweetList)
60 |
--------------------------------------------------------------------------------
/Crawlers/news_sites/spiders/hotnews.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import scrapy
3 | import dateutil.parser as dparser
4 |
5 | from news_sites.items import NewsSitesItem
6 |
7 |
8 | class LankaHotNewsSpider(scrapy.Spider):
9 | name = "hotnews"
10 | allowed_domains = ["lankahotnews.net"]
11 | start_urls = ["https://www.lankahotnews.net/"]
12 |
13 | def __init__(self, date=None, *args, **kwargs):
14 | super(LankaHotNewsSpider, self).__init__(*args, **kwargs)
15 |
16 | if date is not None:
17 | self.dateToMatch = dparser.parse(date).date()
18 | else:
19 | self.dateToMatch = None
20 |
21 | def parse(self, response):
22 | for news_url in response.css('.entry-title a::attr("href")').extract():
23 | yield response.follow(news_url, callback=self.parse_article)
24 |
25 | # next_page = response.css('.active+ li a::attr("href")').extract_first()
26 | # if next_page is not None:
27 | # yield response.follow(next_page, callback=self.parse)
28 |
29 | def parse_article(self, response):
30 | item = NewsSitesItem()
31 |
32 | item["author"] = "https://www.lankahotnews.net/"
33 | item["title"] = response.css(".entry-title a::text").extract_first()
34 | date = ""
35 |
36 | # don't add news if we are using dateToMatch and date of news
37 | if self.dateToMatch is not None and self.dateToMatch != date:
38 | return
39 |
40 | img_link = response.css(".separator img::attr(src)").extract()
41 |
42 | item["imageLink"] = img_link
43 | item["source"] = "https://www.lankahotnews.net/"
44 | item["news_url"] = response.url
45 |
46 | yield item
47 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/SwipeableDrawer/Links/PrivateLinks.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Link } from 'react-router-dom'
4 | import List from '@material-ui/core/List'
5 | import ListItem from '@material-ui/core/ListItem'
6 | import Divider from '@material-ui/core/Divider'
7 |
8 | const PrivateLinks = ({ handleLogout }) => (
9 |
10 |
11 |
12 | Home
13 |
14 |
15 |
16 |
17 |
18 | Posts
19 |
20 |
21 |
22 |
23 |
24 | About
25 |
26 |
27 |
28 |
29 |
30 | Contact Us
31 |
32 |
33 |
34 |
35 |
36 | Dashboard
37 |
38 |
39 |
40 |
41 | Logout
42 |
43 |
44 |
45 | )
46 |
47 | const styles = {
48 | link: {
49 | color: 'black',
50 | textDecoration: 'none',
51 | padding: 10,
52 | display: 'flex',
53 | flex: 1
54 | },
55 | logoutBtn: {
56 | color: 'black',
57 | textDecoration: 'none',
58 | padding: 24,
59 | display: 'flex',
60 | flex: 1
61 | }
62 | }
63 |
64 | PrivateLinks.propTypes = {
65 | handleLogout: PropTypes.func.isRequired
66 | }
67 |
68 | export default PrivateLinks
69 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/TweetItem/style.sass:
--------------------------------------------------------------------------------
1 | @import '../../styles/variables.sass'
2 |
3 | .post-item-wrapper
4 | margin: 0px 0px 30px 0px
5 | .post-container
6 | overflow: hidden
7 | display: flex
8 | .btn-container
9 | text-align: right
10 | .image
11 | margin: 10px
12 | .post-img
13 | margin: 15px
14 | border-radius: 100%
15 | width: 120px
16 | height: 120px
17 | margin: auto
18 | .details
19 | width: 100%;
20 | padding: 12px 22px 2px 22px
21 | .title
22 | font-size: 1.2rem
23 | font-weight: 500
24 | margin-bottom: 5px
25 | .date
26 | font-size: 0.8rem
27 | opacity: 0.7
28 | margin-bottom: 12px
29 | .content
30 | text-align: justify
31 | margin-bottom: 15px
32 | line-height: 1.3
33 | font-size: 14px
34 | a
35 | text-decoration: none
36 | color: #424242
37 | .read-more
38 | text-decoration: none
39 | color: #9E9E9E
40 | font-size: 0.7rem
41 | text-align: right
42 | position: absolute
43 | bottom: 4px
44 | right: 6px
45 | font-weight: 500
46 | cursor: pointer
47 | .read-more:hover
48 | color: #795548
49 | .hover-container
50 | transition: 0.2s ease
51 | position: relative
52 | .votes-bar-container
53 | .vote-status
54 | height: 4px
55 | .votes
56 | opacity: 0
57 | .vote-value
58 | opacity: 0
59 | .hover-container:hover
60 | .vote-buttons-container
61 | opacity: 1
62 | .votes-bar-container
63 | .vote-status
64 | height: 18px
65 | .votes
66 | opacity: 1
67 | .vote-value
68 | opacity: 1
69 |
70 |
--------------------------------------------------------------------------------
/fact-bounty-flask/docs/users/register.yml:
--------------------------------------------------------------------------------
1 | User registration via form
2 | ---
3 | tags:
4 | - User
5 | consumes:
6 | - "application/json"
7 | produces:
8 | - "application/json"
9 | parameters:
10 | - in: body
11 | name: body
12 | schema:
13 | type: object
14 | properties:
15 | name:
16 | type: string
17 | example: xyz
18 | email:
19 | type: string
20 | example: xyz@gmail.com
21 | password:
22 | type: string
23 | example: password123
24 | password2:
25 | type: string
26 | example: password123
27 | responses:
28 | 201:
29 | description: JSON object containing success message
30 | schema:
31 | type: object
32 | properties:
33 | message:
34 | type: string
35 | example: You registered successfully. Please log in.
36 | 202:
37 | description: JSON object containing success message
38 | schema:
39 | type: object
40 | properties:
41 | message:
42 | type: string
43 | example: User already exists. Please login.
44 | 401:
45 | description: Passwords do not match
46 | schema:
47 | type: object
48 | properties:
49 | message:
50 | type: string
51 | example: Both passwords does not match
52 | 404:
53 | description: Fields are missing
54 | schema:
55 | type: object
56 | properties:
57 | message:
58 | type: string
59 | example: Please provide all the required fields.
60 | 500:
61 | description: Something went wrong
62 | schema:
63 | type: object
64 | properties:
65 | message:
66 | type: string
67 | example: Something went wrong
68 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/VoteButtons/style.sass:
--------------------------------------------------------------------------------
1 | @import '../../styles/variables.sass'
2 |
3 |
4 | .vote-buttons-container
5 | opacity: 0
6 | transition: 0.2s ease
7 | position: absolute
8 | top: 10px
9 | right: 10px
10 | float: right
11 | .buttons
12 | float: right
13 | // true
14 | .trueBtn
15 | display: flex
16 | .trueIcon
17 | position: absolute
18 | right: 0px
19 | color: $grey
20 | .trueLbl
21 | opacity: 0
22 | .trueBtn:hover
23 | .trueIcon
24 | color: $true-color
25 | .trueLbl
26 | color: $true-color
27 | opacity: 1
28 | .trueBtnSelected
29 | display: flex
30 | .trueIcon
31 | color: $true-color
32 | .trueLbl
33 | color: $true-color
34 |
35 | // fake
36 | .fakeBtn
37 | display: flex
38 | .fakeIcon
39 | position: absolute
40 | right: 0px
41 | color: $grey
42 | .fakeLbl
43 | opacity: 0
44 | .fakeBtn:hover
45 | .fakeIcon
46 | color: $fake-color
47 | .fakeLbl
48 | color: $fake-color
49 | opacity: 1
50 | .fakeBtnSelected
51 | display: flex
52 | .fakeIcon
53 | color: $fake-color
54 | .fakeLbl
55 | color: $fake-color
56 |
57 | // mix
58 | .mixBtn
59 | display: flex
60 | .mixIcon
61 | position: absolute
62 | right: 0px
63 | color: $grey
64 | .mixLbl
65 | opacity: 0
66 | .mixBtn:hover
67 | .mixIcon
68 | color: $mix-color
69 | .mixLbl
70 | color: $mix-color
71 | opacity: 1
72 | .mixBtnSelected
73 | display: flex
74 | .mixIcon
75 | color: $mix-color
76 | .mixLbl
77 | color: $mix-color
78 |
79 | .vote_count
80 | margin-top: 10px
81 | color: $dark-grey
82 | font-size: 14px
--------------------------------------------------------------------------------
/fact-bounty-client/src/services/AuthService.js:
--------------------------------------------------------------------------------
1 | import ApiBuilder from '../helpers/ApiBuilder'
2 |
3 | /**
4 | *
5 | * POST : loginUser
6 | *
7 | */
8 | const loginUser = userData => {
9 | return ApiBuilder.API.post(`/api/users/login`, userData)
10 | }
11 |
12 | /**
13 | *
14 | * POST : registerUser
15 | *
16 | */
17 | const registerUser = userData => {
18 | return ApiBuilder.API.post(`/api/users/register`, userData)
19 | }
20 | /**
21 | *
22 | * POST : forgotPassword
23 | *
24 | */
25 | const forgotPassword = userData => {
26 | return ApiBuilder.API.post(`/api/users/forgot_password`, userData)
27 | }
28 | /**
29 | *
30 | * POST : authVerificationToken
31 | *
32 | */
33 | const authVerificationToken = userData => {
34 | return ApiBuilder.API.post(`/api/users/auth_verification_token`, userData)
35 | }
36 | /**
37 | *
38 | * POST : resetPassword
39 | *
40 | */
41 | const resetPassword = userData => {
42 | return ApiBuilder.API.post(`/api/users/reset_password`, userData)
43 | }
44 | /**
45 | *
46 | * POST : OauthUser
47 | *
48 | */
49 | const OauthUser = creds => {
50 | return ApiBuilder.API.post(`/api/users/oauth`, creds)
51 | }
52 |
53 | /**
54 | *
55 | * POST : tokenRefresh
56 | *
57 | */
58 | const tokenRefresh = () => {
59 | return ApiBuilder.API.post(`/api/users/token_refresh`, {})
60 | }
61 |
62 | /**
63 | *
64 | * POST : revokeAccessToken
65 | *
66 | */
67 | const revokeAccessToken = () => {
68 | return ApiBuilder.API.post(`/api/users/logout_access`, {})
69 | }
70 |
71 | /**
72 | *
73 | * POST : revokeRefreshToken
74 | *
75 | */
76 | const revokeRefreshToken = () => {
77 | return ApiBuilder.API.post(`/api/users/logout_refresh`, {})
78 | }
79 | export default {
80 | loginUser,
81 | registerUser,
82 | forgotPassword,
83 | authVerificationToken,
84 | resetPassword,
85 | OauthUser,
86 | tokenRefresh,
87 | revokeAccessToken,
88 | revokeRefreshToken
89 | }
90 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/PostItem/style.sass:
--------------------------------------------------------------------------------
1 | @import '../../styles/variables.sass'
2 |
3 | @media only screen and (max-width: 600px)
4 | .post-container
5 | flex-direction: column
6 |
7 | .post-item-wrapper
8 | margin: 0px 0px 30px 0px
9 | .post-container
10 | overflow: hidden
11 | display: flex
12 | .btn-container
13 | text-align: right
14 | .image
15 | margin: 10px
16 | width: 100%
17 | .post-img
18 | margin: 15px
19 | border-radius: 100%
20 | width: 120px
21 | height: 120px
22 | margin: auto
23 | .details
24 | width: 100%;
25 | padding: 12px 22px 2px 22px
26 | .title
27 | font-size: 1.2rem
28 | font-weight: 500
29 | margin-bottom: 5px
30 | .date
31 | font-size: 0.8rem
32 | opacity: 0.7
33 | margin-bottom: 12px
34 | .content
35 | text-align: justify
36 | margin-bottom: 15px
37 | line-height: 1.3
38 | font-size: 18px
39 | a
40 | text-decoration: none
41 | color: #424242
42 | .read-more
43 | text-decoration: none
44 | color: #9E9E9E
45 | font-size: 0.7rem
46 | text-align: right
47 | position: absolute
48 | bottom: 4px
49 | right: 6px
50 | font-weight: 500
51 | cursor: pointer
52 | .read-more:hover
53 | color: #795548
54 | .hover-container
55 | transition: 0.2s ease
56 | position: relative
57 | .votes-bar-container
58 | .vote-status
59 | height: 4px
60 | .votes
61 | opacity: 0
62 | .vote-value
63 | opacity: 0
64 | .hover-container:hover
65 | .vote-buttons-container
66 | opacity: 1
67 | .votes-bar-container
68 | .vote-status
69 | height: 25vh
70 | .votes
71 | opacity: 1
72 | .vote-value
73 | opacity: 1
74 |
75 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/redux/actions/twitterActions.js:
--------------------------------------------------------------------------------
1 | import { FETCH_TWEETS, LOADING_TWEETS } from './actionTypes'
2 | import Twitter from '../../helpers/twitter'
3 |
4 | export const fetchTweets = (limit, user) => dispatch => {
5 | dispatch({
6 | type: LOADING_TWEETS
7 | })
8 | const twitter = Twitter()
9 | twitter
10 | .getUserTweets(limit, user)
11 | .then(tweets => {
12 | let tweetItems = []
13 | tweets.map(tweet => {
14 | const item = {
15 | content: tweet.text,
16 | date: tweet.created_at,
17 | title: tweet.user.name,
18 | _id: tweet.id_str,
19 | approved_count: 0,
20 | mixedvote_count: 0,
21 | fake_count: 0,
22 | user: tweet.user.screen_name
23 | }
24 | tweetItems.push(item)
25 | })
26 | dispatch({
27 | type: FETCH_TWEETS,
28 | payload: tweetItems,
29 | user: user
30 | })
31 | })
32 | .catch(err => {
33 | console.error('Invalid response:', err)
34 | })
35 | }
36 |
37 | export const getTweetsFromKeyword = (query, limit) => dispatch => {
38 | dispatch({
39 | type: LOADING_TWEETS
40 | })
41 | const twitter = Twitter()
42 | twitter
43 | .searchForTweets(query, limit)
44 | .then(tweets => {
45 | let tweetItems = []
46 | tweets.map(tweet => {
47 | const item = {
48 | content: tweet.text,
49 | date: tweet.created_at,
50 | title: tweet.user.name,
51 | _id: tweet.id_str,
52 | approved_count: 0,
53 | mixedvote_count: 0,
54 | fake_count: 0,
55 | user: tweet.user.screen_name
56 | }
57 | tweetItems.push(item)
58 | })
59 | dispatch({
60 | type: FETCH_TWEETS,
61 | payload: tweetItems,
62 | user: 'search'
63 | })
64 | })
65 | .catch(err => {
66 | console.error('Invalid response:', err)
67 | })
68 | }
69 |
--------------------------------------------------------------------------------
/fact-bounty-flask/api/stories/model.py:
--------------------------------------------------------------------------------
1 | from api.database import Column, Model, db
2 | from datetime import datetime
3 |
4 |
5 | class Vote(Model):
6 | """
7 | This model holds information about a votes casted by user on stories
8 | """
9 |
10 | __tablename__ = "vote"
11 |
12 | id = Column(db.Integer, primary_key=True)
13 | story_id = Column(db.String(128))
14 | user_id = Column(db.Integer, db.ForeignKey("user.id"))
15 | value = Column(db.Integer)
16 |
17 | def __init__(self, story_id, user_id, value):
18 | self.story_id = story_id
19 | self.user_id = user_id
20 | self.value = value
21 |
22 | @classmethod
23 | def fetch_user_votes(cls, user_id):
24 | return cls.query.filter_by(user_id=user_id).all()
25 |
26 | def save(self):
27 | """
28 | Save a user to the database.
29 | This includes creating a new user and editing one.
30 | """
31 | db.session.add(self)
32 | db.session.commit()
33 |
34 | class Comment(Model):
35 | """
36 | This model represents a comment on a given note
37 | """
38 |
39 | __tablename__ = "comment"
40 |
41 | id = Column(db.Integer, primary_key=True)
42 | story_id = Column(db.String(128))
43 | user_id = Column(db.Integer, db.ForeignKey("user.id"))
44 | content = Column(db.Integer)
45 |
46 | def __init__(self, story_id, user_id, content):
47 | self.story_id = story_id
48 | self.user_id = user_id
49 | self.content = content
50 |
51 | @classmethod
52 | def fetch_user_comments(cls, user_id):
53 | return cls.query.filter_by(user_id=user_id).all()
54 |
55 | @classmethod
56 | def fetch_post_comments(cls, post_id):
57 | return cls.query.filter_by(post_id=post_id).all()
58 |
59 | def save(self):
60 | """
61 | Save a comment to the database.
62 | """
63 | db.session.add(self)
64 | db.session.commit()
65 |
--------------------------------------------------------------------------------
/Crawlers/news_sites/spiders/hiru.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import scrapy
3 | import dateutil.parser as dparser
4 |
5 | from news_sites.items import NewsSitesItem
6 |
7 |
8 | class HiruNewsSpider(scrapy.Spider):
9 | name = "hirunews"
10 | allowed_domains = ["hirunews.lk"]
11 | start_urls = ["http://www.hirunews.lk/sinhala/local-news.php"]
12 |
13 | def __init__(self, date=None, *args, **kwargs):
14 | super(HiruNewsSpider, self).__init__(*args, **kwargs)
15 |
16 | if date is not None:
17 | self.dateToMatch = dparser.parse(date).date()
18 | else:
19 | self.dateToMatch = None
20 |
21 | def parse(self, response):
22 | for news_url in response.css('.lts-cntp a::attr("href")').extract():
23 | yield response.follow(news_url, callback=self.parse_article)
24 |
25 | # next_page = response.css('.active+ li a::attr("href")').extract_first()
26 | # if next_page is not None:
27 | # yield response.follow(next_page, callback=self.parse)
28 |
29 | def parse_article(self, response):
30 | item = NewsSitesItem()
31 |
32 | item["author"] = "hirunews.lk"
33 | item["title"] = response.css(".lts-cntp2::text").extract_first()
34 | date = response.css(".time::text").extract_first()
35 | if date is None:
36 | return
37 |
38 | date = date.replace("\r", "")
39 | date = date.replace("\t", "")
40 | date = date.replace("\n", "")
41 | date = dparser.parse(date, fuzzy=True).date()
42 |
43 | # don't add news if we are using dateToMatch and date of news
44 | if self.dateToMatch is not None and self.dateToMatch != date:
45 | return
46 |
47 | img_link = response.css(".latest-pic img::attr(src)").extract()
48 |
49 | item["date"] = date.strftime("%d %B, %Y")
50 | item["imageLink"] = img_link
51 | item["source"] = "http://www.hirunews.lk/sinhala"
52 | item["news_url"] = response.url
53 |
54 | yield item
55 |
--------------------------------------------------------------------------------
/Crawlers/news_sites/spiders/hirufm.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import scrapy
3 | import dateutil.parser as dparser
4 |
5 | from news_sites.items import NewsSitesItem
6 |
7 |
8 | class HiruFmSpider(scrapy.Spider):
9 | name = "hirufm"
10 | allowed_domains = ["gossip.hirufm.lk"]
11 | start_urls = ["http://gossip.hirufm.lk/gossipnews/"]
12 |
13 | def __init__(self, date=None, *args, **kwargs):
14 | super(HiruFmSpider, self).__init__(*args, **kwargs)
15 |
16 | if date is not None:
17 | self.dateToMatch = dparser.parse(date).date()
18 | else:
19 | self.dateToMatch = None
20 |
21 | def parse(self, response):
22 | for news_url in response.css('.nwsheading a::attr("href")').extract():
23 | yield response.follow(news_url, callback=self.parse_article)
24 |
25 | # next_page = response.css('.active+ li a::attr("href")').extract_first()
26 | # if next_page is not None:
27 | # yield response.follow(next_page, callback=self.parse)
28 |
29 | def parse_article(self, response):
30 | item = NewsSitesItem()
31 |
32 | item["author"] = "gossip.hirufm.lk"
33 | item["title"] = response.css(".nwsheading a::text").extract_first()
34 | date = response.css(".datecalendar div::text").extract()[1]
35 | if date is None:
36 | return
37 |
38 | date = date.replace("\r", "")
39 | date = date.replace("\t", "")
40 | date = date.replace("\n", "")
41 | date = dparser.parse(date, fuzzy=True).date()
42 |
43 | # don't add news if we are using dateToMatch and date of news
44 | if self.dateToMatch is not None and self.dateToMatch != date:
45 | return
46 |
47 | img_link = response.css(".bdtop img::attr(src)").extract()
48 |
49 | item["date"] = date.strftime("%d %B, %Y")
50 | item["imageLink"] = img_link
51 | item["source"] = "http://gossip.hirufm.lk"
52 | item["news_url"] = response.url
53 |
54 | yield item
55 |
--------------------------------------------------------------------------------
/db/dump/stories.json:
--------------------------------------------------------------------------------
1 | [{
2 | "title": "A",
3 | "author": "A author",
4 | "content": "A content",
5 | "imageLink": "A url",
6 | "source": "A source",
7 | "approved_count": 1,
8 | "fake_count": 1,
9 | "mixedvote_count": 0,
10 | "date": "2019-01-01T00:00:00.000Z"
11 | },
12 | {
13 | "title": "B",
14 | "author": "B author",
15 | "content": "B content",
16 | "imageLink": "B url",
17 | "source": "B source",
18 | "approved_count": 2,
19 | "fake_count": 8,
20 | "mixedvote_count": 1,
21 | "date" : "2019-01-02T00:00:00.000Z"
22 | },
23 | {
24 | "title": "C",
25 | "author": "C author",
26 | "content": "C content",
27 | "imageLink": "C url",
28 | "source": "C source",
29 | "approved_count": 3,
30 | "fake_count": 2,
31 | "mixedvote_count": 0,
32 | "date" : "2019-01-03T00:00:00.000Z"
33 | },
34 | {
35 | "title": "D",
36 | "author": "D author",
37 | "content": "D content",
38 | "imageLink": "D url",
39 | "source": "D source",
40 | "approved_count": 4,
41 | "fake_count": 4,
42 | "mixedvote_count": 0,
43 | "date" : "2019-01-04T00:00:00.000Z"
44 | },
45 | {
46 | "title": "E",
47 | "author": "E author",
48 | "content": "E content",
49 | "imageLink": "E url",
50 | "source": "E source",
51 | "approved_count": 5,
52 | "fake_count": 5,
53 | "mixedvote_count": 6,
54 | "date": "2019-01-05T00:00:00.000Z"
55 | },
56 | {
57 | "title": "F",
58 | "author": "F author",
59 | "content": "F content",
60 | "imageLink": "F url",
61 | "source": "F source",
62 | "approved_count": 6,
63 | "fake_count": 6,
64 | "mixedvote_count": 2,
65 | "date": "2019-01-06T00:00:00.000Z"
66 | },
67 | {
68 | "title": "G",
69 | "author": "G author",
70 | "content": "G content",
71 | "imageLink": "G url",
72 | "source": "G source",
73 | "approved_count": 7,
74 | "fake_count": 7,
75 | "mixedvote_count": 0,
76 | "date" : "2019-01-07T00:00:00.000Z"
77 | }
78 | ]
--------------------------------------------------------------------------------
/Crawlers/news_sites/spiders/shaagossip.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import scrapy
3 | import dateutil.parser as dparser
4 |
5 | from news_sites.items import NewsSitesItem
6 |
7 |
8 | class ShaaGossipSpider(scrapy.Spider):
9 | name = "shaagossip"
10 | allowed_domains = ["gossip.shaafm.lk"]
11 | start_urls = ["http://gossip.shaafm.lk/gossipnews/"]
12 |
13 | def __init__(self, date=None, *args, **kwargs):
14 | super(ShaaGossipSpider, self).__init__(*args, **kwargs)
15 |
16 | if date is not None:
17 | self.dateToMatch = dparser.parse(date).date()
18 | else:
19 | self.dateToMatch = None
20 |
21 | def parse(self, response):
22 | for news_url in response.css('.nwsheading a::attr("href")').extract():
23 | yield response.follow(news_url, callback=self.parse_article)
24 |
25 | # next_page = response.css('.active+ li a::attr("href")').extract_first()
26 | # if next_page is not None:
27 | # yield response.follow(next_page, callback=self.parse)
28 |
29 | def parse_article(self, response):
30 | item = NewsSitesItem()
31 |
32 | item["author"] = "http://gossip.shaafm.lk"
33 | item["title"] = response.css(".nwsheading a::text").extract_first()
34 | date = response.css(".datecalendar div::text").extract_first()
35 | if date is None:
36 | return
37 |
38 | date = date.replace("\r", "")
39 | date = date.replace("\t", "")
40 | date = date.replace("\n", "")
41 | date = dparser.parse(date, fuzzy=True).date()
42 |
43 | # don't add news if we are using dateToMatch and date of news
44 | if self.dateToMatch is not None and self.dateToMatch != date:
45 | return
46 |
47 | img_link = response.css(".bdtop img::attr(src)").extract()
48 |
49 | item["date"] = date.strftime("%d %B")
50 | item["imageLink"] = img_link
51 | item["source"] = "http://gossip.shaafm.lk"
52 | item["news_url"] = response.url
53 |
54 | yield item
55 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/OAuthContainer/FacebookContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import FacebookLogin from 'react-facebook-login'
3 | import { FaFacebookSquare } from 'react-icons/fa'
4 | import { connect } from 'react-redux'
5 | import PropTypes from 'prop-types'
6 | import { OauthUser } from '../../redux/actions/authActions'
7 | import './index.scss'
8 |
9 | class FacebookContainer extends React.Component {
10 | constructor(props, context) {
11 | super(props, context)
12 | this.state = {
13 | status: 'unknown',
14 | errors: null
15 | }
16 | }
17 |
18 | responseFacebook = res => {
19 | if (res.status !== 'unknown') {
20 | const creds = {
21 | type: 'facebook',
22 | name: res.name,
23 | email: res.email
24 | }
25 | this.props.OauthUser(creds)
26 | }
27 | }
28 |
29 | render() {
30 | return (
31 |
52 |
53 |
54 | }
55 | />
56 | )
57 | }
58 | }
59 |
60 | FacebookContainer.propTypes = {
61 | OauthUser: PropTypes.func.isRequired,
62 | button_type: PropTypes.string
63 | }
64 |
65 | const mapStateToProps = null
66 |
67 | const mapDispatchToProps = {
68 | OauthUser
69 | }
70 |
71 | export default connect(
72 | mapStateToProps,
73 | mapDispatchToProps
74 | )(FacebookContainer)
75 |
--------------------------------------------------------------------------------
/Crawlers/news_sites/spiders/neth.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import scrapy
3 | import dateutil.parser as dparser
4 |
5 | from news_sites.items import NewsSitesItem
6 |
7 |
8 | class NethGossipSpider(scrapy.Spider):
9 | name = "nethgossip"
10 | allowed_domains = ["nethgossip.lk"]
11 | start_urls = ["http://nethgossip.lk/category/9"]
12 |
13 | def __init__(self, date=None, *args, **kwargs):
14 | super(NethGossipSpider, self).__init__(*args, **kwargs)
15 |
16 | if date is not None:
17 | self.dateToMatch = dparser.parse(date).date()
18 | else:
19 | self.dateToMatch = None
20 |
21 | def parse(self, response):
22 | for news_url in response.css('.col-sm-9 a::attr("href")').extract():
23 | yield response.follow(news_url, callback=self.parse_article)
24 |
25 | # next_page = response.css('.active+ li a::attr("href")').extract_first()
26 | # if next_page is not None:
27 | # yield response.follow(next_page, callback=self.parse)
28 |
29 | def parse_article(self, response):
30 | item = NewsSitesItem()
31 |
32 | item["author"] = "http://nethgossip.lk"
33 | item["title"] = response.css(".entry-title::text").extract_first()
34 | date = response.css(
35 | ".td-post-date .td-module-date::text"
36 | ).extract_first()
37 | if date is None:
38 | return
39 |
40 | date = date.replace("\r", "")
41 | date = date.replace("\t", "")
42 | date = date.replace("\n", "")
43 | date = dparser.parse(date, fuzzy=True).date()
44 |
45 | # don't add news if we are using dateToMatch and date of news
46 | if self.dateToMatch is not None and self.dateToMatch != date:
47 | return
48 |
49 | img_link = response.css(".col-sm-3 img::attr(src)").extract()
50 |
51 | item["date"] = date.strftime("%d %B, %Y")
52 | item["imageLink"] = img_link
53 | item["source"] = "http://nethnews.lk/"
54 | item["news_url"] = response.url
55 |
56 | yield item
57 |
--------------------------------------------------------------------------------
/fact-bounty-client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fact-bounty-client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@material-ui/core": "^3.9.0",
7 | "@material-ui/icons": "^3.0.2",
8 | "axios": "^0.19.0",
9 | "bootstrap": "^4.3.1",
10 | "classnames": "^2.2.6",
11 | "font-awesome": "^4.7.0",
12 | "is-empty": "^1.2.0",
13 | "jwt-decode": "^2.2.0",
14 | "moment": "^2.24.0",
15 | "node-sass": "^4.12.0",
16 | "prettier": "^1.16.4",
17 | "react": "^16.7.0",
18 | "react-dom": "^16.7.0",
19 | "react-facebook-login": "^4.1.1",
20 | "react-google-login": "^5.0.2",
21 | "react-icons": "^3.5.0",
22 | "react-infinite-scroller": "^1.2.4",
23 | "react-lines-ellipsis": "^0.14.0",
24 | "react-redux": "^6.0.0",
25 | "react-router-dom": "^4.3.1",
26 | "react-scripts": "^2.1.8",
27 | "react-swipeable-views": "^0.13.3",
28 | "recompose": "^0.30.0",
29 | "redux": "^4.0.1",
30 | "redux-form": "^8.1.0",
31 | "redux-thunk": "^2.3.0",
32 | "typeface-roboto": "0.0.54"
33 | },
34 | "scripts": {
35 | "start": "react-scripts start",
36 | "build": "react-scripts build",
37 | "postbuild": "mv -f build ../fact-bounty-flask/",
38 | "test": "react-scripts test",
39 | "eject": "react-scripts eject",
40 | "lint": "eslint src/**/*.js",
41 | "format": "prettier --write \"src/**/*.{js,jsx,json}\""
42 | },
43 | "proxy": "http://localhost:5000",
44 | "browserslist": [
45 | ">0.2%",
46 | "not dead",
47 | "not ie <= 11",
48 | "not op_mini all"
49 | ],
50 | "devDependencies": {
51 | "babel-eslint": "9.0.0",
52 | "eslint-config-prettier": "^4.1.0",
53 | "eslint-plugin-prettier": "^3.0.1",
54 | "husky": "^1.3.1",
55 | "lint-staged": "^8.1.5"
56 | },
57 | "husky": {
58 | "hooks": {
59 | "pre-commit": "lint-staged",
60 | "pre-push": "lint-staged"
61 | }
62 | },
63 | "lint-staged": {
64 | "*.{js, jsx}": [
65 | "prettier --write",
66 | "eslint",
67 | "git add"
68 | ]
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Crawlers/news_sites/spiders/adaDeranaSinhala.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import scrapy
3 | import dateutil.parser as dparser
4 |
5 | from news_sites.items import NewsSitesItem
6 |
7 |
8 | class adaDeranaSinhalaSpider(scrapy.Spider):
9 | name = "adaderanasl"
10 | allowed_domains = ["sinhala.adaderana.lk"]
11 | start_urls = ["http://sinhala.adaderana.lk/morehotnews.php"]
12 |
13 | def __init__(self, date=None, *args, **kwargs):
14 | super(adaDeranaSinhalaSpider, self).__init__(*args, **kwargs)
15 | if date is not None:
16 | self.dateToMatch = dparser.parse(date).date()
17 | else:
18 | self.dateToMatch = None
19 |
20 | def parse(self, response):
21 | for news_url in response.css('.story-text a::attr("href")').extract():
22 | yield response.follow(news_url, callback=self.parse_article)
23 |
24 | # next_page = response.css('.active+ li a::attr("href")').extract_first()
25 | # if next_page is not None:
26 | # yield response.follow(next_page, callback=self.parse)
27 |
28 | def parse_article(self, response):
29 | item = NewsSitesItem()
30 |
31 | item["author"] = "http://sinhala.adaderana.lk"
32 | item["title"] = response.css(".news-heading::text").extract_first()
33 | date = response.css(".news-datestamp::text").extract_first()
34 | if date is None:
35 | return
36 |
37 | date = date.replace("\r", "")
38 | date = date.replace("\t", "")
39 | date = date.replace("\n", "")
40 | date = dparser.parse(date, fuzzy=True).date()
41 |
42 | # don't add news if we are using dateToMatch and date of news
43 | if self.dateToMatch is not None and self.dateToMatch != date:
44 | return
45 |
46 | img_link = response.css(".news-banner img::attr(src)").extract()
47 | item["date"] = date.strftime("%d %B, %Y")
48 | item["imageLink"] = img_link
49 | item["source"] = "http://sinhala.adaderana.lk"
50 | item["news_url"] = response.url
51 |
52 | yield item
53 |
--------------------------------------------------------------------------------
/Crawlers/news_sites/spiders/rg.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import scrapy
3 | import dateutil.parser as dparser
4 |
5 | from news_sites.items import NewsSitesItem
6 |
7 |
8 | class RateGossipSpider(scrapy.Spider):
9 | name = "rategossip"
10 | allowed_domains = ["rategossip.com"]
11 | start_urls = ["http://www.rategossip.com/all-page.html"]
12 |
13 | def __init__(self, date=None, *args, **kwargs):
14 | super(RateGossipSpider, self).__init__(*args, **kwargs)
15 |
16 | if date is not None:
17 | self.dateToMatch = dparser.parse(date).date()
18 | else:
19 | self.dateToMatch = None
20 |
21 | def parse(self, response):
22 | for news_url in response.css(
23 | '.mnbtmwhite2title a::attr("href")'
24 | ).extract():
25 | yield response.follow(news_url, callback=self.parse_article)
26 |
27 | # next_page = response.css('.active+ li a::attr("href")').extract_first()
28 | # if next_page is not None:
29 | # yield response.follow(next_page, callback=self.parse)
30 |
31 | def parse_article(self, response):
32 | item = NewsSitesItem()
33 |
34 | item["author"] = "http://www.rategossip.com"
35 | item["title"] = response.css(".artititle a::text").extract_first()
36 | date = response.css(".artidate::text").extract_first()
37 | if date is None:
38 | return
39 |
40 | date = date.replace("\r", "")
41 | date = date.replace("\t", "")
42 | date = date.replace("\n", "")
43 | date = dparser.parse(date, fuzzy=True).date()
44 |
45 | # don't add news if we are using dateToMatch and date of news
46 | if self.dateToMatch is not None and self.dateToMatch != date:
47 | return
48 |
49 | img_link = response.css(".tpbtmimg2 img::attr(src)").extract()
50 |
51 | item["date"] = date.strftime("%d %B, %Y")
52 | item["imageLink"] = img_link
53 | item["source"] = "http://www.rategossip.com"
54 | item["news_url"] = response.url
55 |
56 | yield item
57 |
--------------------------------------------------------------------------------
/fact-bounty-flask/docs/users/oauth.yml:
--------------------------------------------------------------------------------
1 | User registration via OAuth like facebook, google, etc.
2 | ---
3 | tags:
4 | - User
5 | consumes:
6 | - "application/json"
7 | produces:
8 | - "application/json"
9 | parameters:
10 | - in: body
11 | name: body
12 | schema:
13 | type: object
14 | properties:
15 | name:
16 | type: string
17 | example: xyz
18 | email:
19 | type: string
20 | example: xyz@gmail.com
21 | type:
22 | type: string
23 | example: admin
24 | responses:
25 | 201:
26 | description: (for new user) JSON object containing success message, access token and refresh token.
27 | schema:
28 | type: object
29 | properties:
30 | message:
31 | type: string
32 | example: You logged in successfully.
33 | access_token:
34 | type: string
35 | example: hash-generated-by-jwt
36 | refresh_token:
37 | type: string
38 | example: hash-generated-by-jwt
39 | 202:
40 | description: (for existing user) JSON object containing success message, access token and refresh token.
41 | schema:
42 | type: object
43 | properties:
44 | message:
45 | type: string
46 | example: You logged in successfully.
47 | access_token:
48 | type: string
49 | example: hash-generated-by-jwt
50 | refresh_token:
51 | type: string
52 | example: hash-generated-by-jwt
53 |
54 | 404:
55 | description: Fields are missing
56 | schema:
57 | type: object
58 | properties:
59 | message:
60 | type: string
61 | example: Please provide all the required fields.
62 | 500:
63 | description: Something went wrong
64 | schema:
65 | type: object
66 | properties:
67 | message:
68 | type: string
69 | example: Something went wrong
70 |
--------------------------------------------------------------------------------
/Crawlers/news_sites/spiders/adaDerana.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import scrapy
3 | import dateutil.parser as dparser
4 |
5 | from news_sites.items import NewsSitesItem
6 |
7 |
8 | class adaDeranaSpider(scrapy.Spider):
9 | name = "ada"
10 | allowed_domains = ["adaderana.lk"]
11 | start_urls = [
12 | "http://www.adaderana.lk/hot-news",
13 | "http://www.adaderana.lk/sports-news",
14 | "http://www.adaderana.lk/entertainment-news",
15 | "http://www.adaderana.lk/technology-news",
16 | ]
17 |
18 | def __init__(self, date=None, *args, **kwargs):
19 | super(adaDeranaSpider, self).__init__(*args, **kwargs)
20 | if date is not None:
21 | self.dateToMatch = dparser.parse(date, fuzzy=True).date()
22 | else:
23 | self.dateToMatch = None
24 |
25 | def parse(self, response):
26 | for news_url in response.css('.hidden-xs a::attr("href")').extract():
27 | yield response.follow(news_url, callback=self.parse_article)
28 |
29 | def parse_article(self, response):
30 | item = NewsSitesItem()
31 |
32 | item["author"] = "http://www.adaderana.lk"
33 | item["title"] = response.css("h1::text").extract_first()
34 | date = response.css(".news-datestamp::text").extract_first()
35 | if date is None:
36 | return
37 |
38 | date = date.replace("\r", "")
39 | date = date.replace("\t", "")
40 | date = date.replace("\n", "")
41 | date = dparser.parse(date, fuzzy=True).date()
42 |
43 | # don't add news if we are using dateToMatch and date of news
44 | if self.dateToMatch is not None and self.dateToMatch != date:
45 | return
46 |
47 | item["date"] = date.strftime("%d %B, %Y")
48 | item["imageLink"] = response.css(
49 | ".news-banner .img-responsive::attr(src)"
50 | ).extract_first()
51 | item["source"] = "http://www.adaderana.lk"
52 | item["content"] = "\n".join(
53 | response.css(".news-content p::text").extract()
54 | )
55 | item["news_url"] = response.url
56 |
57 | yield item
58 |
--------------------------------------------------------------------------------
/Crawlers/news_sites/spiders/lankahit.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import scrapy
3 | import dateutil.parser as dparser
4 |
5 | from news_sites.items import NewsSitesItem
6 |
7 |
8 | class LankaHitGossipSpider(scrapy.Spider):
9 | name = "hitgossip"
10 | allowed_domains = ["sinhala.lankahitgossip.com"]
11 | start_urls = [
12 | "http://sinhala.lankahitgossip.com/category/hit-gossip-news/"
13 | ]
14 |
15 | def __init__(self, date=None, *args, **kwargs):
16 | super(LankaHitGossipSpider, self).__init__(*args, **kwargs)
17 |
18 | if date is not None:
19 | self.dateToMatch = dparser.parse(date).date()
20 | else:
21 | self.dateToMatch = None
22 |
23 | def parse(self, response):
24 | for news_url in response.css('h2 a::attr("href")').extract():
25 | yield response.follow(news_url, callback=self.parse_article)
26 |
27 | # next_page = response.css('.active+ li a::attr("href")').extract_first()
28 | # if next_page is not None:
29 | # yield response.follow(next_page, callback=self.parse)
30 |
31 | def parse_article(self, response):
32 | item = NewsSitesItem()
33 |
34 | item["author"] = "http://sinhala.lankahitgossip.com"
35 | item["title"] = response.css(".posttitle::text").extract_first()
36 | date = response.css("#datemeta_l::text").extract_first()
37 | if date is None:
38 | return
39 |
40 | date = date.replace("\r", "")
41 | date = date.replace("\t", "")
42 | date = date.replace("\n", "")
43 | date = dparser.parse(date, fuzzy=True).date()
44 |
45 | # don't add news if we are using dateToMatch and date of news
46 | if self.dateToMatch is not None and self.dateToMatch != date:
47 | return
48 |
49 | img_link = response.css(
50 | ".wp-block-image img::attr(src)"
51 | ).extract_first()
52 |
53 | item["date"] = date.strftime("%d %B, %Y")
54 | item["imageLink"] = img_link
55 | item["source"] = "http://sinhala.lankahitgossip.com"
56 | item["news_url"] = response.url
57 |
58 | yield item
59 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/OAuthContainer/index.scss:
--------------------------------------------------------------------------------
1 | .facebookIconLogin,
2 | .googleIconLogin {
3 | width: 60px;
4 | height: 60px;
5 | background-color: #f6f6f6;
6 | text-align: center;
7 | border-radius: 100%;
8 | color: black;
9 | border: 1px solid rgba(90, 88, 88, 0.12);
10 | padding: 6%;
11 | }
12 |
13 | .facebookIconSignup {
14 | width: 120px;
15 | height: 40px;
16 | background-image: url('/icons/facebook.svg');
17 | background-size: contain;
18 | content: 'Facebook Login';
19 | background-repeat: no-repeat;
20 | background-color: #fff;
21 | text-align: center;
22 | border-radius: 2px;
23 | color: black;
24 | border: 1px solid #cccccc;
25 | padding: 4%;
26 | }
27 |
28 | .facebookIconSignup::after {
29 | content: 'facebook';
30 | }
31 |
32 | .googleIconSignup::before {
33 | display: none;
34 | content: 'google';
35 | }
36 |
37 | .googleIconSignup {
38 | width: 120px;
39 | height: 40px;
40 | background-image: url('/icons/gmail.svg');
41 | /* content: 'Facebook Login'; */
42 | background-size: contain;
43 | background-repeat: no-repeat;
44 | background-color: #fff;
45 | text-align: center;
46 | border-radius: 2px;
47 | color: black;
48 | border: 1px solid #cccccc;
49 | padding: 4%;
50 | }
51 |
52 | .facebookIconSignup:focus,
53 | .googleIconSignup:focus {
54 | background-color: #fff;
55 | }
56 |
57 | .socialIconsLogin {
58 | /* justify-content: space-evenly; */
59 | display: flex;
60 | /* padding: 9% 6%; */
61 | }
62 |
63 | .socialIconsSignup {
64 | align-items: center;
65 | display: flex;
66 | flex-direction: column;
67 | padding: 7px;
68 | width: 100%;
69 | }
70 |
71 | .googleSignin{
72 | width: 15rem;
73 | align-items: center;
74 | justify-content: center
75 | }
76 |
77 | .fbauth {
78 | background-color: #4c69ba;
79 | box-shadow: rgba(0, 0, 0, 0.24) 0px 2px 2px 0px, rgba(0, 0, 0, 0.24) 0px 0px 1px 0px;
80 | display:flex;
81 | align-items: center;
82 | justify-content: center;
83 | color: white;
84 | border: 0;
85 | border-radius:2px;
86 | padding: 6px 6px;
87 | font-size: 14px;
88 | width: 15rem;
89 | cursor: pointer;
90 | margin-bottom: 15px;
91 | }
--------------------------------------------------------------------------------
/fact-bounty-flask/tests/test_crawler.py:
--------------------------------------------------------------------------------
1 | import os
2 | import unittest
3 | import tempfile
4 | import json
5 | import sys
6 | from app import app
7 | from fake_db import db_fd, db_path
8 |
9 | sys.path.append(os.path.join(sys.path[0], "../../"))
10 | FLASKR = app
11 |
12 | TEST_MONTH = '04'
13 | TEST_DAY = '15'
14 |
15 |
16 | class Test_Crawler(unittest.TestCase):
17 | @classmethod
18 | def setUpClass(self):
19 | self.db_fd, self.db_path = db_fd, db_path
20 | FLASKR.config["SQLALCHEMY_DATABASE_URI"] = 'sqlite:///' + self.db_path
21 | FLASKR.testing = True
22 | self.app = FLASKR.test_client()
23 | res = self.app.post(
24 | "/api/crawler/cron_job",
25 | data=json.dumps(
26 | dict(
27 | month=TEST_MONTH,
28 | day=TEST_DAY
29 | )
30 | ),
31 | content_type="application/json",
32 | follow_redirects=True,
33 | )
34 | res = res.data.decode("ASCII")
35 | res = json.loads(res)
36 |
37 | @classmethod
38 | def tearDownClass(self):
39 | # os.close(self.db_fd)
40 | # os.unlink(self.db_path)
41 | pass
42 |
43 | def test_fetch_all_jobs_200(self):
44 | """Fetch all jobs"""
45 | response = self.app.get("/api/crawler/cron_job")
46 | res = response.data.decode("ASCII")
47 | res = json.loads(res)
48 | self.assertEqual(response.status_code, 200)
49 | self.assertEqual(res["message"], "Jobs fetched successfully")
50 | self.assertTrue(isinstance(res["jobs"], (list)))
51 |
52 | def test_fetch_crawl_200(self):
53 | """Fetch all tasks and start crawling"""
54 | response = self.app.get("/api/crawler/crawl_live")
55 | res = response.data.decode("ASCII")
56 | res = json.loads(res)
57 | self.assertEqual(response.status_code, 200)
58 | self.assertEqual(res["message"], "Started Crawling today news")
59 | self.assertTrue(isinstance(res["tasks"], (list)))
60 |
61 |
62 | if __name__ == "__main__":
63 | unittest.main()
64 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/OAuthContainer/GoogleContainer.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, Component } from 'react'
2 | import { connect } from 'react-redux'
3 | import PropTypes from 'prop-types'
4 | import { GoogleLogin } from 'react-google-login'
5 | import { OauthUser } from '../../redux/actions/authActions'
6 | import Toast from '../Toast'
7 |
8 | class GoogleContainer extends Component {
9 | constructor(props, context) {
10 | super(props, context)
11 | this.state = {
12 | status: 'unknown',
13 | errors: null,
14 | openToast: false
15 | }
16 | }
17 |
18 | responseGoogle = res => {
19 | const creds = {
20 | type: 'google',
21 | name: res.profileObj.name,
22 | email: res.profileObj.email
23 | }
24 | this.props.OauthUser(creds)
25 | }
26 |
27 | errors = err => {
28 | this.setState({ errors: err, openToast: true })
29 | }
30 |
31 | closeToast = () => {
32 | this.setState({ openToast: false })
33 | }
34 |
35 | render() {
36 | const { openToast } = this.state
37 | return (
38 |
39 | {openToast && (
40 |
46 | )}
47 |
62 |
63 | )
64 | }
65 | }
66 |
67 | GoogleContainer.propTypes = {
68 | OauthUser: PropTypes.func.isRequired,
69 | button_type: PropTypes.string
70 | }
71 |
72 | const mapStateToProps = null
73 |
74 | const mapDispatchToProps = {
75 | OauthUser
76 | }
77 |
78 | export default connect(
79 | mapStateToProps,
80 | mapDispatchToProps
81 | )(GoogleContainer)
82 |
--------------------------------------------------------------------------------
/Crawlers/news_sites/spiders/nfsinhala.py:
--------------------------------------------------------------------------------
1 | import scrapy
2 | import dateutil.parser as dparser
3 |
4 | from ..items import NewsSitesItem
5 |
6 |
7 | class NewsFirstSinhalaSpider(scrapy.Spider):
8 | name = "nfsl"
9 | allowed_domains = ["newsfirst.lk"]
10 | start_urls = ["https://www.newsfirst.lk/sinhala/category/local/"]
11 |
12 | def __init__(self, date=None, *args, **kwargs):
13 | super(NewsFirstSinhalaSpider, self).__init__(*args, **kwargs)
14 |
15 | if date is not None:
16 | self.dateToMatch = dparser.parse(date, fuzzy=True).date()
17 | else:
18 | self.dateToMatch = None
19 |
20 | def parse(self, response):
21 | for news_url in response.css(
22 | ".news-lf-section a::attr(href)"
23 | ).extract():
24 | yield response.follow(news_url, callback=self.parse_article)
25 |
26 | # next_page = response.css('.next::attr(href)').extract_first()
27 | # if next_page is not None:
28 | # yield response.follow(next_page, callback=self.parse)
29 |
30 | def parse_article(self, response):
31 | item = NewsSitesItem()
32 |
33 | author = response.css(".artical-new-byline::text").extract_first()
34 | title = response.css(".artical-hd-out::text").extract_first()
35 | item["author"] = " ".join(author.split())
36 | item["title"] = " ".join(title.split())
37 | if (
38 | response.css(".artical-new-byline::text").extract()
39 | and response.css(".artical-new-byline::text").extract()[1]
40 | ):
41 | date = response.css(".artical-new-byline::text").extract()[1]
42 | date = dparser.parse(date, fuzzy=True).date()
43 | # don't add news if we are using dateToMatch and date of news
44 | if self.dateToMatch is not None and self.dateToMatch != date:
45 | return
46 | item["date"] = date.strftime("%d %B, %Y")
47 | else:
48 | return
49 | item["imageLink"] = response.css(
50 | ".main-news-block-artical .img-responsive::attr(src)"
51 | ).extract_first()
52 | item["source"] = "https://www.newsfirst.lk/sinhala"
53 | item["news_url"] = response.url
54 |
55 | yield item
56 |
--------------------------------------------------------------------------------
/fact-bounty-flask/api/extensions.py:
--------------------------------------------------------------------------------
1 | from flask_jwt_extended import JWTManager
2 | from flask_login import LoginManager
3 | from flask_migrate import Migrate
4 | from flask_mail import Mail
5 | from flask_pagedown import PageDown
6 | from flask_sqlalchemy import SQLAlchemy
7 | from flask import jsonify, make_response
8 | from api import user
9 |
10 | db = SQLAlchemy()
11 | mail = Mail()
12 | pagedown = PageDown()
13 |
14 | login_manager = LoginManager()
15 | login_manager.login_view = "auth.login"
16 | migrate = Migrate()
17 |
18 | jwt = JWTManager()
19 |
20 |
21 | # This method will check if a token is blacklisted, and will be called automatically when blacklist is enabled
22 | @jwt.token_in_blacklist_loader
23 | def check_if_token_is_blacklist(decypted_token):
24 | jti = decypted_token["jti"]
25 | return user.model.RevokedToken.is_jti_blacklisted(jti)
26 |
27 |
28 | # The following callbacks are used for customizing jwt response/error messages.
29 | @jwt.expired_token_loader
30 | def expired_token_callback():
31 | response = {"message": "The token has expired.", "error": "token_expired"}
32 | return make_response(jsonify(response)), 401
33 |
34 |
35 | @jwt.invalid_token_loader
36 | def invalid_token_callback(error):
37 | # we have to keep the argument here, since it's passed in by the caller internally
38 | response = {
39 | "message": "Signature verification failed.",
40 | "error": "invalid_token",
41 | }
42 | return make_response(jsonify(response)), 401
43 |
44 |
45 | @jwt.unauthorized_loader
46 | def missing_token_callback(error):
47 | response = {
48 | "message": "Request does not contain an access token.",
49 | "error": "authorization_required",
50 | }
51 | return make_response(jsonify(response)), 401
52 |
53 |
54 | @jwt.needs_fresh_token_loader
55 | def token_not_fresh_callback():
56 | response = {
57 | "message": "The token is not fresh.",
58 | "error": "fresh_token_required",
59 | }
60 | return make_response(jsonify(response)), 401
61 |
62 |
63 | @jwt.revoked_token_loader
64 | def revoked_token_callback():
65 | response = {
66 | "message": "The token has been revoked.",
67 | "error": "token_revoked",
68 | }
69 | return make_response(jsonify(response)), 401
70 |
--------------------------------------------------------------------------------
/Crawlers/news_sites/spiders/am.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import scrapy
3 | import dateutil.parser as dparser
4 |
5 | from news_sites.items import NewsSitesItem
6 |
7 |
8 | class amSpider(scrapy.Spider):
9 | name = "am"
10 | allowed_domains = ["am.lk"]
11 | start_urls = [
12 | "http://am.lk/local-news",
13 | "http://am.lk/si-international",
14 | "http://am.lk/si-economy",
15 | "http://am.lk/sports",
16 | ]
17 |
18 | def __init__(self, date=None, *args, **kwargs):
19 | super(amSpider, self).__init__(*args, **kwargs)
20 | if date is not None:
21 | self.dateToMatch = dparser.parse(date).date()
22 | else:
23 | self.dateToMatch = None
24 |
25 | def parse(self, response):
26 | for news_url in response.css(
27 | '.catItemTitle a ::attr("href")'
28 | ).extract():
29 | yield response.follow(news_url, callback=self.parse_article)
30 |
31 | next_page = response.css('.next::attr("href")').extract_first()
32 | if next_page is not None:
33 | yield response.follow(next_page, callback=self.parse)
34 |
35 | def parse_article(self, response):
36 | item = NewsSitesItem()
37 |
38 | item["author"] = "http://am.lk"
39 | item["title"] = response.css(".itemTitle::text").extract_first()
40 | date = response.css(".itemDateCreated::text").extract()[1]
41 | if date is None:
42 | return
43 |
44 | date = date.replace("\r", "")
45 | date = date.replace("\t", "")
46 | date = date.replace("\n", "")
47 | date = dparser.parse(date, fuzzy=True).date()
48 |
49 | # don't add news if we are using dateToMatch and date of news
50 | if self.dateToMatch is not None and self.dateToMatch != date:
51 | return
52 |
53 | img_link = response.css("#k2Container img::attr(src)").extract_first()
54 | if img_link:
55 | img_link = "http://am.lk" + img_link
56 |
57 | item["date"] = date.strftime("%d %B, %Y")
58 | item["imageLink"] = img_link
59 | item["source"] = "http://am.lk"
60 | item["content"] = " \n ".join(
61 | response.css("#k2Container p::text").extract()
62 | )
63 | item["news_url"] = response.url
64 |
65 | yield item
66 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/Dashboard/Dashboard.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { Switch } from 'react-router-dom'
3 | import PropTypes from 'prop-types'
4 | import './style.sass'
5 | import PrivateRoute from '../../components/PrivateRoute'
6 |
7 | //Pages in the dashboard
8 | import TwitterGraph from '../TwitterGraph'
9 | import DashboardSideNav from '../../components/DashboardSideNav'
10 | import Search from '../Search'
11 | import TweetSearch from '../TweetSearch'
12 | import KibanaDashboard from '../KibanaDashboard'
13 | import PostDetailView from '../../pages/PostDetailView'
14 |
15 | class Dashboard extends Component {
16 | render() {
17 | const { match } = this.props
18 |
19 | const Welcome = () => (
20 |
21 |
22 | Welcome to your Dashboard!
23 |
24 |
25 | )
26 |
27 | return (
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
42 |
47 |
52 |
57 |
62 |
63 |
64 |
65 |
66 | )
67 | }
68 | }
69 |
70 | Dashboard.propTypes = {
71 | match: PropTypes.object
72 | }
73 |
74 | const mapStateToProps = state => ({})
75 |
76 | export default Dashboard
77 |
--------------------------------------------------------------------------------
/Crawlers/news_sites/spiders/roar.py:
--------------------------------------------------------------------------------
1 | import scrapy
2 | import dateutil.parser as dparser
3 |
4 | from ..items import NewsSitesItem
5 |
6 |
7 | class RoarSpider(scrapy.Spider):
8 | name = "roar"
9 | allowed_domains = ["roar.lk"]
10 | start_urls = [
11 | "https://roar.media/english/life/",
12 | "https://roar.media/english/tech/",
13 | ]
14 |
15 | def __init__(self, date=None, *args, **kwargs):
16 | super(RoarSpider, self).__init__(*args, **kwargs)
17 |
18 | if date is not None:
19 | self.dateToMatch = dparser.parse(date, fuzzy=True).date()
20 | else:
21 | self.dateToMatch = None
22 |
23 | def parse(self, response):
24 | # extract news urls from news section
25 | temp = response.css(".withGrid > a::attr(href)").extract()
26 |
27 | # remove duplicate urls
28 | news_urls = []
29 | [news_urls.append(x) for x in temp if x not in news_urls]
30 |
31 | for news_url in news_urls:
32 | yield response.follow(news_url, callback=self.parse_article)
33 |
34 | # next_page = response.css('.fa-angle-double-right').xpath('../@href').extract_first()
35 | # if next_page is not None:
36 | # yield response.follow(next_page, callback=self.parse)
37 |
38 | def parse_article(self, response):
39 | item = NewsSitesItem()
40 |
41 | item["author"] = response.css("#articleAuthor::text").extract_first()
42 | item["title"] = response.css(".title::text").extract_first()
43 | date = response.css("#articleDate::text").extract_first()
44 | if date is None:
45 | return
46 |
47 | date = date.replace("\r", "")
48 | date = date.replace("\t", "")
49 | date = date.replace("\n", "")
50 | date = dparser.parse(date, fuzzy=True).date()
51 |
52 | # don't add news if we are using dateToMatch and date of news
53 | if self.dateToMatch is not None and self.dateToMatch != date:
54 | return
55 |
56 | item["date"] = date.strftime("%d %B, %Y")
57 | item["imageLink"] = None
58 | item["source"] = "https://roar.media"
59 | item["content"] = "\n".join(
60 | response.css(
61 | "#article-body h2 , .inner-article-body > p::text"
62 | ).extract()
63 | )
64 | item["news_url"] = response.url
65 |
66 | yield item
67 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/VotesBar/VotesBar.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import './style.sass'
4 |
5 | const VotesBar = ({ approved_count, mixedvote_count, fake_count }) => {
6 | const displayVote = (value, totalVotes) => {
7 | return (value / totalVotes) * 100 > 3
8 | ? Math.round((value / totalVotes) * 1000) / 10 + '%'
9 | : ''
10 | }
11 |
12 | const findHighestVotesColor = (
13 | approved_count,
14 | mixedvote_count,
15 | fake_count
16 | ) => {
17 | let value
18 | if (approved_count >= fake_count) {
19 | if (approved_count >= mixedvote_count) {
20 | value = '#009688'
21 | } else {
22 | value = '#1564c0'
23 | }
24 | } else {
25 | if (fake_count >= mixedvote_count) {
26 | value = '#f4511e'
27 | } else {
28 | value = '#1564c0'
29 | }
30 | }
31 | return value
32 | }
33 |
34 | const totalVotes = approved_count + fake_count + mixedvote_count
35 | const highestVotesColor = findHighestVotesColor(
36 | approved_count,
37 | mixedvote_count,
38 | fake_count
39 | )
40 |
41 | return (
42 |
43 |
47 |
51 |
52 | {displayVote(approved_count, totalVotes)}
53 |
54 |
55 |
59 |
60 | {displayVote(fake_count, totalVotes)}
61 |
62 |
63 |
67 |
68 | {displayVote(mixedvote_count, totalVotes)}
69 |
70 |
71 |
72 |
73 | )
74 | }
75 |
76 | VotesBar.propTypes = {
77 | approved_count: PropTypes.number,
78 | mixedvote_count: PropTypes.number,
79 | fake_count: PropTypes.number
80 | }
81 |
82 | export default VotesBar
83 |
--------------------------------------------------------------------------------
/Crawlers/news_sites/spiders/bizDerana.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import scrapy
3 | from scrapy.http import Request
4 | import dateutil.parser as dparser
5 |
6 | from ..items import NewsSitesItem
7 |
8 |
9 | class BizadaDeranaSpider(scrapy.Spider):
10 | name = "bizada"
11 | allowed_domains = ["adaderana.lk"]
12 | start_urls = [
13 | "http://bizenglish.adaderana.lk/category/top-news/",
14 | "http://bizenglish.adaderana.lk/category/news-2/",
15 | "http://bizenglish.adaderana.lk/category/analysis/",
16 | "http://bizenglish.adaderana.lk/category/features/",
17 | ]
18 |
19 | def __init__(self, date=None, *args, **kwargs):
20 | super(BizadaDeranaSpider, self).__init__(*args, **kwargs)
21 | if date is not None:
22 | self.dateToMatch = dparser.parse(date, fuzzy=True).date()
23 | else:
24 | self.dateToMatch = None
25 |
26 | def parse(self, response):
27 | for news_url in response.css(
28 | '.business-image .wp-post-image , h3 a::attr("href")'
29 | ).extract():
30 | yield response.follow(news_url, callback=self.parse_article)
31 |
32 | next_page = response.css(
33 | 'ul.pager a[title*=next]::attr("href")'
34 | ).extract_first()
35 | if next_page is not None:
36 | yield response.follow(next_page, callback=self.parse)
37 |
38 | def parse_article(self, response):
39 | item = NewsSitesItem()
40 |
41 | item["author"] = "bizada"
42 | item["title"] = response.css(".news-header h3::text").extract_first()
43 | date = response.css(".news-date::text").extract_first()
44 | if date is None:
45 | return
46 |
47 | date = date.replace("\r", "")
48 | date = date.replace("\t", "")
49 | date = date.replace("\n", "")
50 | date = dparser.parse(date, fuzzy=True).date()
51 |
52 | # don't add news if we are using dateToMatch and date of news
53 | if self.dateToMatch is not None and self.dateToMatch != date:
54 | return
55 |
56 | item["date"] = date.strftime("%d %B, %Y")
57 | item["imageLink"] = response.css(
58 | ".wp-post-image::attr(src)"
59 | ).extract_first()
60 | item["source"] = response.url
61 | item["content"] = " \n ".join(
62 | response.css(".news-text::text").extract()
63 | )
64 | item["news_url"] = response.url
65 |
66 | yield item
67 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/VoteButtons/VoteButtons.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { Icon } from '@material-ui/core'
3 | import { Redirect } from 'react-router-dom'
4 | import PropTypes from 'prop-types'
5 | import './style.sass'
6 |
7 | class VoteButtons extends Component {
8 | constructor(props) {
9 | super(props)
10 | this.state = {
11 | redirect: false
12 | }
13 | }
14 |
15 | handleClick = vote_value => {
16 | const {
17 | loading,
18 | isAuthenticated,
19 | userVote,
20 | post,
21 | changeVoteCount
22 | } = this.props
23 |
24 | if (!isAuthenticated) {
25 | this.setState({ redirect: true })
26 | return
27 | }
28 | if (!loading) {
29 | changeVoteCount(post._id, vote_value, userVote)
30 | }
31 | }
32 |
33 | render() {
34 | const { post, userVote } = this.props
35 | const totalVotes =
36 | post.approved_count + post.fake_count + post.mixedvote_count
37 | if (this.state.redirect) {
38 | return
39 | }
40 | return (
41 |
42 |
43 |
this.handleClick(1)}
46 | >
47 | check_circle
48 |
49 |
50 |
this.handleClick(-1)}
53 | >
54 | cancel
55 |
56 |
57 |
this.handleClick(0)}
60 | >
61 | report_problems
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | )
70 | }
71 | }
72 |
73 | VoteButtons.propTypes = {
74 | post: PropTypes.object,
75 | loading: PropTypes.bool,
76 | isAuthenticated: PropTypes.bool,
77 | userVote: PropTypes.number,
78 | changeVoteCount: PropTypes.func
79 | }
80 |
81 | export default VoteButtons
82 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/pages/Landing/style.sass:
--------------------------------------------------------------------------------
1 | @import '../../styles/variables.sass'
2 |
3 | .landing-container
4 | .patch-wrapper-2
5 | @media #{'only screen and (max-width: 1200px)'}
6 | display: none
7 | width: 600px
8 | height: auto
9 | position: absolute
10 | right: -150px
11 | top: 600px
12 | img
13 | overflow-x: hidden;
14 | width: 100%
15 | opacity: 0.5
16 | .patch-wrapper-3
17 | @media #{'only screen and (max-width: 1200px)'}
18 | display: none
19 | width: 600px
20 | height: auto
21 | position: absolute
22 | left: -200px
23 | top: 1700px
24 | img
25 | width: 140%
26 | opacity: 0.5
27 | .divider
28 | margin: 80px 0px 0px 0px
29 | .header
30 | margin: 30px 0px 0px 0px
31 | .left-section
32 | padding-left: 30px
33 | display: flex
34 | flex-direction: column
35 | justify-content: center
36 | text-align: left
37 | .right-section
38 | display: flex
39 | flex-direction: column
40 | justify-content: center
41 | .header-img
42 | width: 100%
43 | @media #{'only screen and (max-width: 800px)'}
44 | display: none
45 | .about
46 | margin: 80px 0px 0px 0px
47 | .about-block
48 | margin: 50px 5px 0px 5px
49 | label
50 | background-color: $primary_color
51 | width: 22px
52 | height: 22px
53 | border-radius: 44px
54 | color: #ffffff
55 | text-align: center
56 | margin-right: 10px
57 | font-size: 14px
58 | h3
59 | margin: 10px 0px 0px 0px
60 | p
61 | margin: 10px 0px 0px 0px
62 | .recent-posts
63 | margin: 80px 0px 0px 0px
64 | h1
65 | margin-bottom: 30px
66 | .view-all-btn
67 | z-index: 99999
68 | display: flex
69 | label
70 | font-size: 24px
71 | margin-right: 5px
72 | .tab-container
73 | padding: 24px 0 0 0
74 | .view-all-btn
75 | z-index: 99999
76 | display: flex
77 | label
78 | font-size: 24px
79 | margin-right: 5px
80 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/components/SwipeableDrawer/SwipeableDrawer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { connect } from 'react-redux'
3 | import PropTypes from 'prop-types'
4 | import { withStyles } from '@material-ui/core/styles'
5 | import Drawer from '@material-ui/core/Drawer'
6 | import PublicLinks from './Links/PublicLinks'
7 | import PrivateLinks from './Links/PrivateLinks'
8 | import Toast from '../Toast'
9 | import { logoutUser } from '../../redux/actions/authActions'
10 | import styles from './SwipeableDrawer.style'
11 |
12 | class SwipeableDrawer extends React.Component {
13 | // constructor
14 | constructor(props) {
15 | super(props)
16 | this.state = {
17 | toastIsOpen: false
18 | }
19 | }
20 |
21 | // Show Toast method
22 | showToast = () => {
23 | this.setState({ toastIsOpen: true })
24 | }
25 |
26 | // close Toast method
27 | closeToast = (event, reason) => {
28 | if (reason === 'clickaway') {
29 | return
30 | }
31 | this.setState({ toastIsOpen: false })
32 | }
33 |
34 | // handle user logout
35 | handleLogout = () => {
36 | this.showToast()
37 | this.props.logoutUser()
38 | }
39 |
40 | render() {
41 | const { classes, isOpen, toggleDrawer, auth } = this.props
42 | const { toastIsOpen } = this.state
43 | return (
44 |
45 |
50 |
51 |
57 |
58 | {auth.isAuthenticated ? (
59 |
60 | ) : (
61 |
62 | )}
63 |
64 |
65 |
66 |
67 | )
68 | }
69 | }
70 |
71 | SwipeableDrawer.propTypes = {
72 | classes: PropTypes.object.isRequired,
73 | isOpen: PropTypes.bool.isRequired,
74 | toggleDrawer: PropTypes.func,
75 | auth: PropTypes.object,
76 | logoutUser: PropTypes.func,
77 | toastIsOpen: PropTypes.bool
78 | }
79 |
80 | const SwipeableDrawerWithStyles = withStyles(styles)(SwipeableDrawer)
81 |
82 | const mapStateToProps = state => ({
83 | auth: state.auth
84 | })
85 |
86 | export default connect(
87 | mapStateToProps,
88 | { logoutUser }
89 | )(SwipeableDrawerWithStyles)
90 |
--------------------------------------------------------------------------------
/Crawlers/news_sites/spiders/thepapare.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import scrapy
3 | import dateutil.parser as dparser
4 |
5 | from news_sites.items import NewsSitesItem
6 |
7 |
8 | class ThePapareSpider(scrapy.Spider):
9 | name = "thepapare"
10 | allowed_domains = ["thepapare.com"]
11 | start_urls = ["http://www.thepapare.com/latest-news"]
12 |
13 | def __init__(self, date=None, *args, **kwargs):
14 | super(ThePapareSpider, self).__init__(*args, **kwargs)
15 |
16 | if date is not None:
17 | self.dateToMatch = dparser.parse(date, fuzzy=True).date()
18 | else:
19 | self.dateToMatch = None
20 |
21 | def parse(self, response):
22 | # extract news urls from news section
23 | temp = response.css(
24 | ".td_module_10 .td-module-title a::attr(href)"
25 | ).extract()
26 |
27 | # remove duplicate urls
28 | news_urls = []
29 | [news_urls.append(x) for x in temp if x not in news_urls]
30 |
31 | for news_url in news_urls:
32 | yield response.follow(news_url, callback=self.parse_article)
33 |
34 | # next_page = response.css('.fa-angle-double-right').xpath('../@href').extract_first()
35 | # if next_page is not None:
36 | # yield response.follow(next_page, callback=self.parse)
37 |
38 | def parse_article(self, response):
39 | item = NewsSitesItem()
40 |
41 | item["author"] = response.css(
42 | ".td-post-author-name a::text"
43 | ).extract_first()
44 | item["title"] = response.css(
45 | ".td-post-title .entry-title::text"
46 | ).extract_first()
47 | date = response.css(
48 | ".td-post-title .td-module-date::text"
49 | ).extract_first()
50 | if date is None:
51 | return
52 |
53 | date = date.replace("\r", "")
54 | date = date.replace("\t", "")
55 | date = date.replace("\n", "")
56 | date = dparser.parse(date, fuzzy=True).date()
57 |
58 | # don't add news if we are using dateToMatch and date of news
59 | if self.dateToMatch is not None and self.dateToMatch != date:
60 | return
61 |
62 | item["date"] = date.strftime("%d %B, %Y")
63 | item["imageLink"] = response.css(
64 | ".td-modal-image .td-animation-stack-type0-1::attr(src)"
65 | ).extract_first()
66 | item["source"] = "http://www.thepapare.com"
67 | item["content"] = "\n".join(
68 | response.css(".td-post-content p::text").extract()
69 | )
70 | item["news_url"] = response.url
71 |
72 | yield item
73 |
--------------------------------------------------------------------------------
/Crawlers/news_sites/spiders/lankadeepa.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import scrapy
3 | import dateutil.parser as dparser
4 |
5 | from ..items import NewsSitesItem
6 |
7 |
8 | class LankadeepaSpider(scrapy.Spider):
9 | name = "lankadeepa"
10 | allowed_domains = ["lankadeepa.lk"]
11 | start_urls = ["http://www.lankadeepa.lk/latest_news/1"]
12 |
13 | def __init__(self, date=None, *args, **kwargs):
14 | super(LankadeepaSpider, self).__init__(*args, **kwargs)
15 |
16 | if date is not None:
17 | self.dateToMatch = dparser.parse(date, fuzzy=True).date()
18 | else:
19 | self.dateToMatch = None
20 |
21 | def parse(self, response):
22 |
23 | # fetch news urls from each page
24 | news_urls = response.css(".simple-thumb")
25 |
26 | # iterate in news_urls
27 | for news_url in news_urls:
28 | # fetch url to main news page
29 | url = news_url.css('.news-title::attr("href")').extract_first()
30 |
31 | # fetch image url from main page as no image is present in article
32 | img_url = news_url.css(".thubs-img::attr(src)").extract_first()
33 |
34 | # follow the url of news article and fetch news data from it
35 | yield response.follow(
36 | url, callback=self.parse_article, meta={"img_url": img_url}
37 | )
38 |
39 | next_page = response.css('.page-numbers::attr("href")').extract_first()
40 | if next_page is not None:
41 | yield response.follow(next_page, callback=self.parse)
42 |
43 | def parse_article(self, response):
44 | item = NewsSitesItem()
45 |
46 | item["author"] = "http://www.lankadeepa.lk"
47 | item["title"] = response.css(".post-title::text").extract_first()
48 | # extract date component and generalize it
49 | date = response.css(".post-date::text").extract_first()
50 | if date is None:
51 | return
52 |
53 | date = date.replace("\r", "")
54 | date = date.replace("\t", "")
55 | date = date.replace("\n", "")
56 | date = dparser.parse(date, fuzzy=True).date()
57 |
58 | # don't add news if we are using dateToMatch and date of news
59 | if self.dateToMatch is not None and self.dateToMatch != date:
60 | return
61 |
62 | item["date"] = date.strftime("%d %B, %Y")
63 | item["imageLink"] = response.meta.get("img_url")
64 | item["source"] = "http://www.lankadeepa.lk"
65 | item["content"] = " \n ".join(
66 | response.css(".post-content p::text").extract()
67 | )
68 | item["news_url"] = response.url
69 |
70 | yield item
71 |
--------------------------------------------------------------------------------
/Crawlers/news_sites/spiders/economynext.py:
--------------------------------------------------------------------------------
1 | import scrapy
2 | import dateutil.parser as dparser
3 |
4 | from ..items import NewsSitesItem
5 |
6 |
7 | class EconomyNextSpider(scrapy.Spider):
8 | name = "economynext"
9 | allowed_domains = ["economynext.com"]
10 | start_urls = ["http://www.economynext.com/"]
11 |
12 | def __init__(self, date=None, *args, **kwargs):
13 | super(EconomyNextSpider, self).__init__(*args, **kwargs)
14 | if date is not None:
15 | self.dateToMatch = dparser.parse(date, fuzzy=True).date()
16 | else:
17 | self.dateToMatch = None
18 |
19 | def parse(self, response):
20 | categories = response.css(
21 | ".sub-menu div:nth-child(1) a::attr(href)"
22 | ).extract()
23 | for category in categories:
24 | yield response.follow(category, callback=self.parse_category)
25 |
26 | def parse_category(self, response):
27 | # extract all news articles urls
28 | temp = response.css(
29 | ".related-block a:nth-child(1)::attr(href)"
30 | ).extract()
31 |
32 | # remove duplicates
33 | news_urls = []
34 | [news_urls.append(x) for x in temp if x not in news_urls]
35 |
36 | # crawl article from each news page
37 | for news_url in news_urls:
38 | yield response.follow(news_url, callback=self.parse_article)
39 |
40 | # proceed to next page
41 | next_page = response.css(".next::attr(href)").extract_first()
42 | if next_page is not None:
43 | yield response.follow(next_page, callback=self.parse)
44 |
45 | def parse_article(self, response):
46 | item = NewsSitesItem()
47 |
48 | item["author"] = "EconomyNext"
49 | item["title"] = response.css(".article-title::text").extract_first()
50 | date = response.css(".article-title+ h2::text").extract_first()
51 | if date is None:
52 | return
53 |
54 | date = date.replace("\r", "")
55 | date = date.replace("\t", "")
56 | date = date.replace("\n", "")
57 | date = dparser.parse(date, fuzzy=True).date()
58 |
59 | # don't add news if we are using dateToMatch and date of news
60 | if self.dateToMatch is not None and self.dateToMatch != date:
61 | return
62 |
63 | item["date"] = date.strftime("%d %B, %Y")
64 | item["imageLink"] = response.css(
65 | ".article-photo .set-image-border::attr(src)"
66 | ).extract_first()
67 | item["source"] = "https://economynext.com/"
68 | item["content"] = response.css("p::text").extract_first()
69 | item["news_url"] = response.url
70 |
71 | yield item
72 |
--------------------------------------------------------------------------------
/fact-bounty-client/src/helpers/ApiBuilder.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import { APIConstantsDev } from '../constants/ApiConstants'
3 | import AuthService from '../services/AuthService'
4 | import { setAuthToken, saveAcessToken } from './AuthTokenHelper'
5 |
6 | const API = axios.create({
7 | baseURL: APIConstantsDev.API_URL,
8 | timeout: 20000,
9 | headers: { 'Access-Control-Allow-Origin': '*' }
10 | })
11 |
12 | /**
13 | * API request interceptor
14 | */
15 | API.interceptors.request.use(
16 | request => {
17 | if (
18 | request.url.includes('token_refresh') ||
19 | request.url.includes('logout_refresh')
20 | ) {
21 | request.headers.Authorization = `Bearer ${localStorage.refresh_token}`
22 | } else {
23 | request.headers.Authorization = `Bearer ${localStorage.access_token}`
24 | }
25 | return request
26 | },
27 | error => {
28 | return Promise.reject(error)
29 | }
30 | )
31 |
32 | /**
33 | * API response interceptor
34 | */
35 | let fetchingAccessToken = false
36 | let subscribers = []
37 |
38 | const onAccessTokenFetched = access_token => {
39 | subscribers = subscribers.filter(callback => callback(access_token))
40 | }
41 |
42 | const addSubscriber = callback => {
43 | subscribers.push(callback)
44 | }
45 |
46 | API.interceptors.response.use(
47 | response => response,
48 | // handling errors
49 | error => {
50 | const { config: originalRequest, response } = error
51 | const status = response ? response.status : 500
52 |
53 | console.log(status)
54 | switch (status) {
55 | //handing 401 erros
56 | case 401: {
57 | if (!fetchingAccessToken) {
58 | fetchingAccessToken = true
59 |
60 | AuthService.tokenRefresh()
61 | .then(res => {
62 | const { access_token } = res.data
63 | console.log(res)
64 | fetchingAccessToken = false
65 | //update header
66 | setAuthToken(access_token)
67 | //saving new access_token
68 | saveAcessToken(access_token)
69 | //invoking callback
70 | onAccessTokenFetched(access_token)
71 | })
72 | .catch(err => {
73 | console.log(err)
74 | })
75 | }
76 | const retryOriginalRequest = new Promise(resolve => {
77 | addSubscriber(access_token => {
78 | originalRequest.headers.Authorization = access_token
79 | resolve(axios(originalRequest))
80 | })
81 | })
82 | return retryOriginalRequest
83 | }
84 | default: {
85 | return Promise.reject(error)
86 | }
87 | }
88 | }
89 | )
90 |
91 | export default {
92 | API
93 | }
94 |
--------------------------------------------------------------------------------
/Crawlers/news_sites/spiders/ft.py:
--------------------------------------------------------------------------------
1 | import scrapy
2 | import dateutil.parser as dparser
3 |
4 | from ..items import NewsSitesItem
5 |
6 |
7 | class FtSpider(scrapy.Spider):
8 | name = "ft"
9 | allowed_domains = ["ft.lk"]
10 | start_urls = ["http://www.ft.lk"]
11 |
12 | def __init__(self, date=None, *args, **kwargs):
13 | super(FtSpider, self).__init__(*args, **kwargs)
14 |
15 | if date is not None:
16 | self.dateToMatch = dparser.parse(date, fuzzy=True).date()
17 | else:
18 | self.dateToMatch = None
19 |
20 | def parse(self, response):
21 | temp = response.css(".bottom-text::attr(href)").extract()
22 |
23 | # remove duplicate categories
24 | categories = []
25 | [categories.append(x) for x in temp if x not in categories]
26 |
27 | for category in categories:
28 | yield response.follow(category, callback=self.parse_category)
29 |
30 | def parse_category(self, response):
31 | # extract all news articles urls
32 | temp = response.css(".cat-header").xpath("../@href").extract()
33 |
34 | # remove duplicates
35 | news_urls = []
36 | [news_urls.append(x) for x in temp if x not in news_urls]
37 |
38 | # crawl article from each news page
39 | for news_url in news_urls:
40 | yield response.follow(news_url, callback=self.parse_article)
41 |
42 | # proceed to next page
43 | next_page = response.css(".active+ li a::attr(href)").extract_first()
44 | if next_page is not None:
45 | yield response.follow(next_page, callback=self.parse)
46 |
47 | def parse_article(self, response):
48 | item = NewsSitesItem()
49 |
50 | item["author"] = "FT"
51 | item["title"] = response.css(".inner-header::text").extract_first()
52 | date = "".join(response.css(".inner-other::text").extract())
53 | if date is None:
54 | return
55 |
56 | date = date.replace("\r", "")
57 | date = date.replace("\t", "")
58 | date = date.replace("\n", "")
59 | date = dparser.parse(date, fuzzy=True).date()
60 |
61 | # don't add news if we are using dateToMatch and date of news
62 | if self.dateToMatch is not None and self.dateToMatch != date:
63 | return
64 |
65 | item["date"] = date.strftime("%d %B, %Y")
66 | item["imageLink"] = response.css(
67 | ".inner-ft-text img::attr(src)"
68 | ).extract_first()
69 | item["source"] = "http://ft.lk/"
70 | item["content"] = " \n ".join(
71 | response.css(".inner-ft-text p::text").extract()
72 | )
73 | item["news_url"] = response.url
74 |
75 | yield item
76 |
--------------------------------------------------------------------------------
/Crawlers/news_sites/spiders/reporter.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import scrapy
3 | import dateutil.parser as dparser
4 |
5 | from ..items import NewsSitesItem
6 |
7 |
8 | class ReporterSpider(scrapy.Spider):
9 | name = "reporter"
10 | allowed_domains = ["reporter.lk"]
11 | start_urls = ["http://www.reporter.lk/"]
12 |
13 | def __init__(self, date=None, *args, **kwargs):
14 | super(ReporterSpider, self).__init__(*args, **kwargs)
15 |
16 | if date is not None:
17 | self.dateToMatch = dparser.parse(date, fuzzy=True).date()
18 | else:
19 | self.dateToMatch = None
20 |
21 | def parse(self, response):
22 | categories = response.css("#Label2 a::attr(href)").extract()
23 | for category in categories:
24 | yield response.follow(category, callback=self.parse_category)
25 |
26 | def parse_category(self, response):
27 | # extract all news articles urls
28 | temp = response.css(".entry-title a::attr(href)").extract()
29 |
30 | # remove duplicates
31 | news_urls = []
32 | [news_urls.append(x) for x in temp if x not in news_urls]
33 |
34 | # crawl article from each news page
35 | for news_url in news_urls:
36 | yield response.follow(news_url, callback=self.parse_article)
37 |
38 | # proceed to next page
39 | # .fa-angle-double-right
40 | next_page = response.css(".fa-angle-double-right")
41 | next_page = response.xpath("../@href").extract_first()
42 | if next_page is not None:
43 | yield response.follow(next_page, callback=self.parse)
44 |
45 | def parse_article(self, response):
46 | item = NewsSitesItem()
47 |
48 | item["author"] = "http://www.reporter.lk/"
49 | item["title"] = response.css(".entry-title::text").extract_first()
50 | date = response.css(".timeago::text").extract_first()
51 | if date is None:
52 | return
53 |
54 | date = date.replace("\r", "")
55 | date = date.replace("\t", "")
56 | date = date.replace("\n", "")
57 | date = dparser.parse(date, fuzzy=True).date()
58 |
59 | # don't add news if we are using dateToMatch and date of news
60 | if self.dateToMatch is not None and self.dateToMatch != date:
61 | return
62 |
63 | item["date"] = date.strftime("%d %B, %Y")
64 | item["imageLink"] = response.css(
65 | "#Blog1 img::attr(src)"
66 | ).extract_first()
67 | item["source"] = "http://www.reporter.lk/"
68 | item["content"] = " \n ".join(
69 | response.css(".entry-content::text").extract()
70 | )
71 | item["news_url"] = response.url
72 |
73 | yield item
74 |
--------------------------------------------------------------------------------
/Crawlers/news_sites/spiders/dailymirror.py:
--------------------------------------------------------------------------------
1 | import scrapy
2 | import dateutil.parser as dparser
3 |
4 | from ..items import NewsSitesItem
5 |
6 |
7 | class DailymirrorSpider(scrapy.Spider):
8 | name = "dailymirror"
9 | start_urls = ["http://www.dailymirror.lk/"]
10 |
11 | def __init__(self, date=None, *args, **kwargs):
12 | super(DailymirrorSpider, self).__init__(*args, **kwargs)
13 |
14 | if date is not None:
15 | self.dateToMatch = dparser.parse(date, fuzzy=True).date()
16 | else:
17 | self.dateToMatch = None
18 |
19 | def parse(self, response):
20 | categories = response.xpath(
21 | '//*[contains(concat( " ", @class, " " ), concat( " ", "hrbo", " " ))]'
22 | )
23 | categories = categories.xpath("../@href").extract()
24 | for category in categories:
25 | try:
26 | int(category.split("/")[-1])
27 | except ValueError:
28 | continue
29 |
30 | yield response.follow(category, callback=self.parse_category)
31 |
32 | def parse_category(self, response):
33 | news_urls = response.css(".cat-hd-tx")
34 | news_urls = news_urls.xpath("../@href").extract()
35 | for news_url in news_urls:
36 | yield response.follow(news_url, callback=self.parse_article)
37 |
38 | next_page = response.css('.page-numbers::attr("href")').extract_first()
39 | if next_page is not None:
40 | yield response.follow(next_page, callback=self.parse_category)
41 |
42 | def parse_article(self, response):
43 | item = NewsSitesItem()
44 |
45 | author = (
46 | response.css("strong::text")
47 | .extract_first()
48 | .replace("(", "")
49 | .replace(")", "")
50 | )
51 | item["author"] = author
52 | item["title"] = response.css(".innerheader::text").extract_first()
53 | # extract date component and generalize it
54 | date = response.css(".col-12 .gtime::text").extract_first()
55 | if date is None:
56 | return
57 |
58 | date = date.replace("\r", "")
59 | date = date.replace("\t", "")
60 | date = date.replace("\n", "")
61 | date = dparser.parse(date, fuzzy=True).date()
62 |
63 | # don't add news if we are using dateToMatch and date of news
64 | if self.dateToMatch is not None and self.dateToMatch != date:
65 | return
66 |
67 | item["date"] = date.strftime("%d %B, %Y")
68 | item["imageLink"] = None
69 | item["source"] = "http://www.dailymirror.lk"
70 | item["content"] = "\n".join(
71 | response.css(".inner-content p::text").extract()
72 | )
73 | item["news_url"] = response.url
74 |
75 | yield item
76 |
--------------------------------------------------------------------------------
/Crawlers/news_sites/spiders/rm.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import scrapy
3 | import dateutil.parser as dparser
4 |
5 | from ..items import NewsSitesItem
6 |
7 |
8 | class ReadmelkSpider(scrapy.Spider):
9 | name = "readmelk"
10 | allowed_domains = ["readme.lk"]
11 | start_urls = ["http://www.readme.lk/"]
12 |
13 | def __init__(self, date=None, *args, **kwargs):
14 | super(ReadmelkSpider, self).__init__(*args, **kwargs)
15 |
16 | if date is not None:
17 | self.dateToMatch = dparser.parse(date, fuzzy=True).date()
18 | else:
19 | self.dateToMatch = None
20 |
21 | def parse(self, response):
22 | categories = response.css("#menu-main-menu-1 a::attr(href)").extract()
23 | for category in categories:
24 | yield response.follow(category, callback=self.parse_category)
25 |
26 | def parse_category(self, response):
27 | # extract all news articles urls
28 | temp = response.css(".td-module-title a::attr(href)").extract()
29 |
30 | # remove duplicates
31 | news_urls = []
32 | [news_urls.append(x) for x in temp if x not in news_urls]
33 |
34 | # crawl article from each news page
35 | for news_url in news_urls:
36 | yield response.follow(news_url, callback=self.parse_article)
37 |
38 | # proceed to next page
39 | # next_page = response.css('.next::attr(href)').extract_first()
40 | # if next_page is not None:
41 | # yield response.follow(next_page, callback=self.parse)
42 |
43 | def parse_article(self, response):
44 | item = NewsSitesItem()
45 |
46 | item["author"] = response.css(".td-post-title a::text").extract_first()
47 | item["title"] = response.css(
48 | ".td-post-title .entry-title::text"
49 | ).extract_first()
50 | date = response.css(
51 | ".td-post-date-no-dot .td-module-date::text"
52 | ).extract_first()
53 | if date is None:
54 | return
55 |
56 | date = date.replace("\r", "")
57 | date = date.replace("\t", "")
58 | date = date.replace("\n", "")
59 | date = dparser.parse(date, fuzzy=True).date()
60 |
61 | # don't add news if we are using dateToMatch and date of news
62 | if self.dateToMatch is not None and self.dateToMatch != date:
63 | return
64 |
65 | item["date"] = date.strftime("%d %B, %Y")
66 | item["imageLink"] = response.css(
67 | ".article-photo .set-image-border::attr(src)"
68 | ).extract_first()
69 | item["source"] = "http://www.readme.lk/"
70 | item["content"] = "\n".join(
71 | response.css("h2 , .td-post-content p::text").extract()
72 | )
73 | item["news_url"] = response.url
74 |
75 | yield item
76 |
--------------------------------------------------------------------------------
/Crawlers/news_sites/spiders/ctoday.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import scrapy
3 | import json
4 | import dateutil.parser as dparser
5 |
6 | from ..items import NewsSitesItem
7 |
8 |
9 | class CtodaySpider(scrapy.Spider):
10 | name = "ctoday"
11 | allowed_domains = ["ceylontoday.lk"]
12 | start_urls = [
13 | "http://www.ceylontoday.lk/news/8",
14 | "http://www.ceylontoday.lk/news/5",
15 | "http://www.ceylontoday.lk/news/14",
16 | "http://www.ceylontoday.lk/news/7",
17 | "http://www.ceylontoday.lk/news/4",
18 | "http://www.ceylontoday.lk/news/6",
19 | "http://www.ceylontoday.lk/news/2",
20 | "http://www.ceylontoday.lk/news/15",
21 | ]
22 |
23 | # base url of api to extract single news article
24 | base_url = "http://site-api.ceylontoday.lk/api/News/getSingleNews?Id="
25 |
26 | def __init__(self, date=None, *args, **kwargs):
27 | super(CtodaySpider, self).__init__(*args, **kwargs)
28 |
29 | if date is not None:
30 | self.dateToMatch = dparser.parse(date).date()
31 | else:
32 | self.dateToMatch = None
33 |
34 | def parse(self, response):
35 | for news_url in response.css(".news-text-link::attr(href)").extract():
36 | news_id = news_url.split("/")
37 | if news_id is not None:
38 | news_id = news_id[-1]
39 | url = self.base_url + news_id
40 |
41 | yield response.follow(url, callback=self.parse_article)
42 |
43 | # next_page = response.css('').extract_first()
44 | # if next_page is not None:
45 | # yield response.follow(next_page, callback=self.parse)
46 |
47 | def parse_article(self, response):
48 | jsonresponse = json.loads(response.body_as_unicode())
49 | news = jsonresponse["data"][0]
50 |
51 | item = NewsSitesItem()
52 |
53 | item["author"] = news["Author_Name"]
54 | item["title"] = news["Title"]
55 | # extract date component and generalize it
56 | date = news["Publish_Date_String"]
57 | if date is None:
58 | return
59 |
60 | date = date.replace("\r", "")
61 | date = date.replace("\t", "")
62 | date = date.replace("\n", "")
63 | date = dparser.parse(date, fuzzy=True).date()
64 |
65 | # don't add news if we are using dateToMatch and date of news
66 | if self.dateToMatch is not None and self.dateToMatch != date:
67 | return
68 |
69 | item["date"] = date.strftime("%d %B, %Y")
70 | item["imageLink"] = news["Header_Image"]
71 | item["source"] = response.url
72 | item["content"] = (
73 | news["HTML_Content"].replace("", "").replace("
", "\n")
74 | )
75 | item["news_url"] = response.url
76 |
77 | yield item
78 |
--------------------------------------------------------------------------------
/Crawlers/news_sites/spiders/nf.py:
--------------------------------------------------------------------------------
1 | import scrapy
2 | import dateutil.parser as dparser
3 |
4 | from ..items import NewsSitesItem
5 |
6 |
7 | class NewsFirstSpider(scrapy.Spider):
8 | name = "nf"
9 | allowed_domains = ["newsfirst.lk"]
10 | start_urls = [
11 | "https://www.newsfirst.lk/category/life/",
12 | "https://www.newsfirst.lk/category/business/",
13 | "https://www.newsfirst.lk/category/sports/",
14 | "https://www.newsfirst.lk/category/world/",
15 | "https://www.newsfirst.lk/category/local/",
16 | ]
17 |
18 | def __init__(self, date=None, *args, **kwargs):
19 | super(NewsFirstSpider, self).__init__(*args, **kwargs)
20 |
21 | if date is not None:
22 | self.dateToMatch = dparser.parse(date, fuzzy=True).date()
23 | else:
24 | self.dateToMatch = None
25 |
26 | def parse(self, response):
27 | # extract news urls from news section
28 | temp = response.css(".news-lf-section a::attr(href)").extract()
29 |
30 | # remove duplicate urls
31 | news_urls = []
32 | [news_urls.append(x) for x in temp if x not in news_urls]
33 |
34 | # remove
35 | if "#" in news_urls:
36 | news_urls.remove("#")
37 |
38 | for news_url in news_urls:
39 | yield response.follow(news_url, callback=self.parse_article)
40 |
41 | next_page = response.css(".next::attr(href)").extract_first()
42 | if next_page is not None:
43 | yield response.follow(next_page, callback=self.parse)
44 |
45 | def parse_article(self, response):
46 | item = NewsSitesItem()
47 |
48 | author = response.css(".artical-new-byline::text").extract_first()
49 | title = response.css(".artical-hd-out::text").extract_first()
50 | item["author"] = " ".join(author.split())
51 | item["title"] = " ".join(title.split())
52 | if (
53 | response.css(".artical-new-byline::text").extract()
54 | and response.css(".artical-new-byline::text").extract()[1]
55 | ):
56 | date = response.css(".artical-new-byline::text").extract()[1]
57 | date = dparser.parse(date, fuzzy=True).date()
58 | # don't add news if we are using dateToMatch and date of news
59 | if self.dateToMatch is not None and self.dateToMatch != date:
60 | return
61 | item["date"] = date.strftime("%d %B, %Y")
62 | else:
63 | return
64 | item["imageLink"] = response.css(
65 | ".main-news-block-artical .img-responsive::attr(src)"
66 | ).extract_first()
67 | item["source"] = "https://www.newsfirst.lk"
68 | item["content"] = "\n".join(
69 | response.css(".editor-styles p::text").extract()
70 | )
71 | item["news_url"] = response.url
72 |
73 | yield item
74 |
--------------------------------------------------------------------------------
/fact-bounty-client/public/static/css/widget.css:
--------------------------------------------------------------------------------
1 | .hoaxy-widget
2 | {
3 | box-sizing:border-box !important;
4 | color:black !important;
5 | background-color:white !important;
6 | border:solid !important;
7 | border-width:thin !important;
8 | border-color:gray !important;
9 | border-radius:10px !important;
10 | width:625px !important;
11 | height:250px !important;
12 |
13 | font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif !important;
14 | color: #212529 !important;
15 | line-height: 1.5 !important;
16 | background:#FAFAFA !important;
17 | }
18 |
19 | .hoaxy-widget-leftdiv
20 | {
21 | float:left !important;
22 | width:250px !important;
23 | height:250px !important;
24 | display:inline-block !important;
25 | margin-right:0px !important;
26 |
27 | font-family:inherit !important;
28 | color:inherit !important;
29 | }
30 |
31 | .hoaxy-widget-leftdiv-toplogo
32 | {
33 | box-sizing:border-box !important;
34 | text-align:center !important;
35 | vertical-align:middle !important;
36 | color:blue !important;
37 | font-size:35px !important;
38 | font-weight:bold !important;
39 | height:90px !important;
40 | padding-top:20px !important;
41 | padding-bottom:20px !important;
42 | padding-left:5px !important;
43 | padding-right:5px !important;
44 |
45 | font-family:inherit !important;
46 | color:inherit !important;
47 | }
48 |
49 | .hoaxy-widget-leftdiv-toplogo img
50 | {
51 | height: 60px !important;
52 | width: 200px !important;
53 | }
54 |
55 | .hoaxy-widget-leftdiv-bottominfo
56 | {
57 | overflow:hidden !important;
58 | word-break:break-word !important;
59 | box-sizing:border-box !important;
60 | padding:5px !important;
61 | font-size:16px !important;
62 | height: 150px !important;
63 |
64 | font-family:inherit !important;
65 | color:inherit !important;
66 | }
67 |
68 | .hoaxy-widget-leftdiv-bottominfo a
69 | {
70 | color: #337ab7 !important;
71 | text-decoration: none !important;
72 | }
73 |
74 | .hoaxy-widget-leftdiv-bottominfo a:hover
75 | {
76 | color: #23527c !important;
77 | text-decoration: underline !important;
78 | }
79 |
80 | .hoaxy-widget-rightdiv
81 | {
82 | margin-left:0px !important;
83 | float:left !important;
84 | width:370px !important;
85 | height:250px !important;
86 | display:inline-block !important;
87 | border-left:1px solid !important;
88 | border-color:#D3D3D3 !important;
89 |
90 | font-family:inherit !important;
91 | color:inherit !important;
92 | }
93 |
94 | .hoaxy-widget-rightdiv-imgvis
95 | {
96 | box-sizing:border-box !important;
97 | width:370px !important;
98 | height:240px !important;
99 | padding-top:5px !important;
100 | padding-right:5px !important;
101 |
102 | font-family:inherit !important;
103 | color:inherit !important;
104 | }
105 |
106 |
--------------------------------------------------------------------------------