├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report_template.md │ └── feature_request_template.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── BasicArch.jpg ├── CODE_OF_CONDUCT.md ├── Design Mockups ├── Confrence_Notify_v2.png ├── Design Mockups │ ├── Assets │ │ ├── Proj_Conf_ntfy_logo-01.png │ │ ├── Proj_Conf_ntfy_logo-01.svg │ │ ├── Proj_Conf_ntfy_logo.ai │ │ ├── Ref_Google_Maps_25_10_2017.png │ │ ├── Ref_Google_Maps_25_10_2017@2x.png │ │ ├── Ref_Google_Maps_25_10_2017_c.png │ │ ├── Ref_Google_Maps_25_10_2017_c@2x.png │ │ ├── Ref_Google_Maps_25_10_2017_cz.png │ │ ├── Ref_Google_Maps_25_10_2017_cz@2x.png │ │ ├── crowded_people_standing_watchi.png │ │ ├── crowded_people_standing_watchi@2x.png │ │ ├── man_standing_in_front_of_peopl.png │ │ ├── man_standing_in_front_of_peopl@2x.png │ │ ├── people_gathering_inside_white_.png │ │ └── people_gathering_inside_white_@2x.png │ └── Code for refernce │ │ └── Confrence_Notify_v2.html └── readme.md ├── LICENSE ├── Notifier-service ├── .env ├── .gitignore ├── README.md ├── jest.config.js ├── package-lock.json ├── package.json ├── src │ ├── app.ts │ ├── controllers │ │ ├── conference.spec.ts │ │ └── conference.ts │ ├── database │ │ └── mongodb.ts │ ├── interfaces │ │ ├── controller.ts │ │ ├── database.ts │ │ ├── listener.ts │ │ ├── model.ts │ │ ├── models │ │ │ └── conference.ts │ │ ├── route.ts │ │ ├── services.ts │ │ ├── services │ │ │ ├── conference.ts │ │ │ ├── listeners │ │ │ │ └── conferenceListener.ts │ │ │ └── streams │ │ │ │ └── conferenceStream.ts │ │ └── stream.ts │ ├── inversify.config.ts │ ├── models │ │ └── conference.ts │ ├── routes │ │ ├── conference.spec.ts │ │ └── conference.ts │ ├── schemas │ │ ├── conferences.ts │ │ └── metadata.ts │ ├── server.ts │ ├── services │ │ ├── conference.spec.ts │ │ ├── conference.ts │ │ ├── listeners │ │ │ └── conferenceListener.ts │ │ └── streams │ │ │ └── conferenceStream.ts │ └── utility │ │ ├── log.spec.ts │ │ └── log.ts ├── tsconfig.json └── tsconfig.prod.json ├── README.md ├── Scrapper-Service ├── .gitignore ├── README.md ├── app.py ├── commons │ ├── __init__.py │ ├── database.py │ ├── errors.py │ └── scrapper.py ├── config.json ├── database │ ├── __init__.py │ └── mdb.py ├── datamodels │ ├── __init__.py │ ├── conference.py │ └── metadata.py ├── plugins │ ├── demo.py │ ├── guide2research.py │ └── wikicfp.py ├── process │ ├── __init__.py │ └── mutiprocessing.py ├── requirements.txt ├── test │ ├── __init__.py │ ├── conference_test.py │ ├── metadata_test.py │ └── mongodb_test.py └── utility │ ├── __init__.py │ ├── adapative_request.py │ ├── argument.py │ ├── logger.py │ └── setup.py ├── Search-Service ├── .env ├── .gitignore ├── package-lock.json ├── package.json ├── src │ ├── app.ts │ └── server.ts └── tsconfig.json ├── User-App ├── .editorconfig ├── .gitignore ├── README.md ├── angular.json ├── browserslist ├── e2e │ ├── protractor.conf.js │ ├── src │ │ ├── app.e2e-spec.ts │ │ └── app.po.ts │ └── tsconfig.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── src │ ├── app │ │ ├── app-routing.module.ts │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── conference │ │ │ ├── conference.component.css │ │ │ ├── conference.component.html │ │ │ ├── conference.component.spec.ts │ │ │ └── conference.component.ts │ │ ├── conferences-list │ │ │ ├── conferences-list.component.css │ │ │ ├── conferences-list.component.html │ │ │ ├── conferences-list.component.spec.ts │ │ │ └── conferences-list.component.ts │ │ ├── header │ │ │ ├── header.component.css │ │ │ ├── header.component.html │ │ │ ├── header.component.spec.ts │ │ │ └── header.component.ts │ │ ├── home │ │ │ ├── home.component.css │ │ │ ├── home.component.html │ │ │ ├── home.component.spec.ts │ │ │ └── home.component.ts │ │ ├── login │ │ │ ├── login.component.css │ │ │ ├── login.component.html │ │ │ ├── login.component.spec.ts │ │ │ └── login.component.ts │ │ ├── models │ │ │ ├── conference.model.ts │ │ │ ├── conferenceDocument.model.ts │ │ │ └── metadata.model.ts │ │ ├── search-page │ │ │ ├── search-page.component.css │ │ │ ├── search-page.component.html │ │ │ ├── search-page.component.spec.ts │ │ │ └── search-page.component.ts │ │ ├── services │ │ │ ├── conferences.service.spec.ts │ │ │ └── conferences.service.ts │ │ └── signup │ │ │ ├── signup.component.css │ │ │ ├── signup.component.html │ │ │ ├── signup.component.spec.ts │ │ │ └── signup.component.ts │ ├── assets │ │ ├── .gitkeep │ │ ├── Proj_Conf_ntfy_logo-01.png │ │ ├── Ref_Google_Maps_25_10_2017.png │ │ ├── Ref_Google_Maps_25_10_2017@2x.png │ │ ├── Ref_Google_Maps_25_10_2017_c.png │ │ ├── Ref_Google_Maps_25_10_2017_c@2x.png │ │ ├── Ref_Google_Maps_25_10_2017_cz.png │ │ ├── Ref_Google_Maps_25_10_2017_cz@2x.png │ │ ├── crowded_people_standing_watchi.png │ │ ├── crowded_people_standing_watchi@2x.png │ │ ├── man_standing_in_front_of_peopl.png │ │ ├── man_standing_in_front_of_peopl@2x.png │ │ ├── people_gathering_inside_white_.png │ │ └── people_gathering_inside_white_@2x.png │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.css │ └── test.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json ├── gssoc_black.png └── package-lock.json /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # CONTRIBUTING 2 | 3 | Thank you for considering to contribute to this project. I came up with the idea personally when I felt that I needed some personalised single source solution of recuring reminder and manager for scientific conferences. So came the idea of conference-notify. In order to contribute do go through the architecture section and then to some basic coding stadards , issue tracking and PR standards. 4 | 5 | 6 | # Understanding the Architecture of the Services 7 | 8 | Irrespective of the deployment strategy the core acrhitecture shall remain this way. 9 | 10 | 11 | 12 | 13 | 14 | The project is built in a microservice fashion with each service exposing some sort of rest api for delivering the information except Scrapper-Service. The scrapper service currently needs to be run as an independent process. The services mostly rely on a common MongoDb and a Elastic Search service for information retrieval and storage. 15 | 16 | The services themselves are written in Python and JavaScript primarily. 17 | 18 | 19 | ### Scrappper-Service 20 | 21 | Move to [Scrapper-Service](https://github.com/rajatkb/Conference-Notify/tree/master/Scrapper-Service) 22 | 23 | ### Notifier-Service 24 | 25 | Not yet updated keep tabs on issue and project board 26 | 27 | ### Search-Service 28 | 29 | Not yet updated keep tabs on issue and project board 30 | 31 | ### User-Application 32 | 33 | Not yet updated keep tabs on issue and project board 34 | 35 | ### ATTENTION 36 | 37 | When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change. 38 | 39 | Please note we have a code of conduct, please follow it in all your interactions with the project. 40 | Pull Request Process 41 | 42 | 43 | 1. Create your own branch with convention "name_of_the_task@yourfirstname" 44 | 45 | git checkout -b 46 | 47 | Do changes and commit on that branch using: 48 | 49 | git add or git add . 50 | git commit -a -m "commit_message" 51 | git push origin 52 | 53 | Once committed the changes are pushed, create a pull request with that branch. 54 | Please make sure to add #issue_number in the description of the PR. 55 | Also make sure to provide the list of features added through the PR. 56 | 57 | 2. Do verify that your branch have got all the changes that are done in the main repo. 58 | Before a branch push or PR, do a pull from remote master to get recent changes by following steps: 59 | 60 | git remote add upstream https://github.com/rajatkb/Conference-Notify.git 61 | git fetch upstream 62 | 63 | Rewrite your master with upstream’s master using git rebase. 64 | 65 | git rebase upstream/master 66 | 67 | Push your updates to master. You may need to force the push with “--force”. 68 | 69 | git push origin master --force 70 | 71 | Before pushing your branch to origin, rebase your branch with the master. 72 | 73 | git pull origin master --rebase 74 | 75 | Push your branch to origin 76 | 77 | git push origin 78 | 79 | 3. Once the PR is submitted the branch will be verified 80 | and merged to master by mentors. 81 | 82 | 4. Update the README.md if needed, accordingly. 83 | Every change must be mentioned if readme requires change. 84 | 85 | [Under GSSOC] 86 | 5. Make sure to label your PR with beginner, 87 | easy, medium, hard. According to the issue it is targetting. 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Issue that this pull request solves 2 | 3 | Closes: # (issue number) 4 | 5 | ## Proposed changes 6 | 7 | Brief description of what is fixed or changed 8 | 9 | ## Types of changes 10 | 11 | _Put an `x` in the boxes that apply_ 12 | 13 | - [ ] Bugfix (non-breaking change which fixes an issue) 14 | - [ ] New feature (non-breaking change which adds functionality) 15 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 16 | - [ ] Documentation update (Documentation content changed) 17 | - [ ] Other (please describe): 18 | 19 | ## Checklist 20 | 21 | _Put an `x` in the boxes that apply_ 22 | 23 | - [ ] My code follows the style guidelines of this project 24 | - [ ] I have performed a self-review of my own code 25 | - [ ] I have commented my code, particularly in hard-to-understand areas 26 | - [ ] I have made corresponding changes to the documentation 27 | - [ ] My changes generate no new warnings 28 | - [ ] My changes does not break the current system and it passes all the current test cases. 29 | 30 | ## Screenshots 31 | 32 | Please attach the screenshots of the changes made in case of change in user interface 33 | 34 | ## Other information 35 | 36 | Any other information that is important to this pull request 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | **/**/__pycache__ 3 | venv/ 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | matrix: 3 | services: 4 | - mongodb 5 | include: 6 | install: 7 | - sudo python -m pip install pymongo 8 | - pip install -r Scrapper-Service/requirements.txt --user 9 | language: python 10 | python: 11 | - "3.6" 12 | script: 13 | - python -m unittest test 14 | 15 | before_script: 16 | - cd User-App 17 | - npm install --save-dev @angular-devkit/build-angular 18 | - npm install -g --silent @angular/cli 19 | language: node_js 20 | node_js: 21 | - "10" 22 | script: 23 | - ng lint 24 | - ng test --watch=false --browsers=ChromeHeadless 25 | - ng build --prod 26 | 27 | before_script: 28 | - cd Notifier-service 29 | - npm install 30 | language: node_js 31 | node_js: 32 | - "10" 33 | script: 34 | - npm run test --watch=false --browsers=ChromeHeadless 35 | - npm run build --prod 36 | 37 | # Uncomment the line below after adding proper test script for Search-Service 38 | before_script: 39 | - cd Search-Service 40 | - npm install 41 | language: node_js 42 | node_js: 43 | - "10" 44 | script: 45 | # - npm run test --watch=false --browsers=ChromeHeadless 46 | - npm run build --prod 47 | 48 | -------------------------------------------------------------------------------- /BasicArch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/BasicArch.jpg -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at rajatk.dev@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /Design Mockups/Confrence_Notify_v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/Design Mockups/Confrence_Notify_v2.png -------------------------------------------------------------------------------- /Design Mockups/Design Mockups/Assets/Proj_Conf_ntfy_logo-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/Design Mockups/Design Mockups/Assets/Proj_Conf_ntfy_logo-01.png -------------------------------------------------------------------------------- /Design Mockups/Design Mockups/Assets/Proj_Conf_ntfy_logo-01.svg: -------------------------------------------------------------------------------- 1 | Proj_Conf_ntfy_logoConference Notify -------------------------------------------------------------------------------- /Design Mockups/Design Mockups/Assets/Proj_Conf_ntfy_logo.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/Design Mockups/Design Mockups/Assets/Proj_Conf_ntfy_logo.ai -------------------------------------------------------------------------------- /Design Mockups/Design Mockups/Assets/Ref_Google_Maps_25_10_2017.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/Design Mockups/Design Mockups/Assets/Ref_Google_Maps_25_10_2017.png -------------------------------------------------------------------------------- /Design Mockups/Design Mockups/Assets/Ref_Google_Maps_25_10_2017@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/Design Mockups/Design Mockups/Assets/Ref_Google_Maps_25_10_2017@2x.png -------------------------------------------------------------------------------- /Design Mockups/Design Mockups/Assets/Ref_Google_Maps_25_10_2017_c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/Design Mockups/Design Mockups/Assets/Ref_Google_Maps_25_10_2017_c.png -------------------------------------------------------------------------------- /Design Mockups/Design Mockups/Assets/Ref_Google_Maps_25_10_2017_c@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/Design Mockups/Design Mockups/Assets/Ref_Google_Maps_25_10_2017_c@2x.png -------------------------------------------------------------------------------- /Design Mockups/Design Mockups/Assets/Ref_Google_Maps_25_10_2017_cz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/Design Mockups/Design Mockups/Assets/Ref_Google_Maps_25_10_2017_cz.png -------------------------------------------------------------------------------- /Design Mockups/Design Mockups/Assets/Ref_Google_Maps_25_10_2017_cz@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/Design Mockups/Design Mockups/Assets/Ref_Google_Maps_25_10_2017_cz@2x.png -------------------------------------------------------------------------------- /Design Mockups/Design Mockups/Assets/crowded_people_standing_watchi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/Design Mockups/Design Mockups/Assets/crowded_people_standing_watchi.png -------------------------------------------------------------------------------- /Design Mockups/Design Mockups/Assets/crowded_people_standing_watchi@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/Design Mockups/Design Mockups/Assets/crowded_people_standing_watchi@2x.png -------------------------------------------------------------------------------- /Design Mockups/Design Mockups/Assets/man_standing_in_front_of_peopl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/Design Mockups/Design Mockups/Assets/man_standing_in_front_of_peopl.png -------------------------------------------------------------------------------- /Design Mockups/Design Mockups/Assets/man_standing_in_front_of_peopl@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/Design Mockups/Design Mockups/Assets/man_standing_in_front_of_peopl@2x.png -------------------------------------------------------------------------------- /Design Mockups/Design Mockups/Assets/people_gathering_inside_white_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/Design Mockups/Design Mockups/Assets/people_gathering_inside_white_.png -------------------------------------------------------------------------------- /Design Mockups/Design Mockups/Assets/people_gathering_inside_white_@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/Design Mockups/Design Mockups/Assets/people_gathering_inside_white_@2x.png -------------------------------------------------------------------------------- /Design Mockups/readme.md: -------------------------------------------------------------------------------- 1 | # 📌 Design Mockups 2 | + This Folder contains the design assets used in this project 3 | + Webpapp designs 4 | + Confrence Notify Logo 5 | -------------------------------------------------------------------------------- /Notifier-service/.env: -------------------------------------------------------------------------------- 1 | MONGO_DB_HOST=localhost 2 | MONGO_DB_PORT=27017 3 | MONGO_DB_NAME=Conference_Notify 4 | SERVER_PORT=3000 5 | LOG_FOLDER=logs 6 | LOG_LEVEL=debug 7 | USER_ORIGIN="http://localhost:4200" 8 | -------------------------------------------------------------------------------- /Notifier-service/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | logs -------------------------------------------------------------------------------- /Notifier-service/README.md: -------------------------------------------------------------------------------- 1 | # NOTIFIER-SERVICE 2 | 3 | It's the service responsible for exposing the scrapped data about the conferences to any kind of frontend through a rest API.The service will also contain a the service for 4 | 5 | **Notify for** 6 | * Notiying user for specific conference, based on subscribtion for 7 | * Notify user for Conference categories or topics , based on subscribtion 8 | 9 | **Notfifaction on** 10 | * Change in deadline 11 | * Submission Reminders 12 | 13 | note: *may add more feature* 14 | 15 | ## Dev Environment Requirement 16 | * NodeJs 17 | * Mongodb (remote/local) 18 | 19 | ### MongoDb Replication setup 20 | * For development purpose , you will have to setup your mongodb , with replication. 21 | * You can resort to using single replication for now , which can scale later on. 22 | * To create replication after installing in windows you can follow [this](https://stackoverflow.com/questions/48139224/mongodb-change-stream-replica-set-limitation) 23 | * For linux , follow the official [docs](https://docs.mongodb.com/manual/reference/configuration-options/#replication-options) , and restart the mongod service or start it with replication option. For quick help look [here](https://www.tutorialspoint.com/mongodb/mongodb_replication.htm) 24 | 25 | 26 | 27 | ## Installation 28 | 29 | **Development** 30 | ```shell 31 | >> cd Notifier-Service 32 | >> npm install 33 | ``` 34 | 35 | **Deployment** 36 | ```shell 37 | >> cd Notifier-Service 38 | >> npm install --only=prod 39 | ``` 40 | 41 | 42 | 43 | 44 | ## Deploying the service 45 | 46 | ```shell 47 | 48 | >> cd Notifier-Service 49 | 50 | //For dev 51 | 52 | >> npm run build 53 | 54 | >> npm start 55 | 56 | //For prod 57 | 58 | >> npm run build 59 | 60 | >> npm run deploy 61 | 62 | ``` 63 | -------------------------------------------------------------------------------- /Notifier-service/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/src'], 3 | transform: { 4 | '^.+\\.tsx?$': 'ts-jest', 5 | }, 6 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$', 7 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], 8 | } 9 | -------------------------------------------------------------------------------- /Notifier-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "notifier-service", 3 | "version": "1.0.0", 4 | "description": "Notification service for Conference-Notify", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "jest --setupFiles dotenv/config reflect-metadata", 8 | "build": "rm -rf build && tsc --build tsconfig.prod.json", 9 | "start": "ts-node-dev --respawn src/server.ts", 10 | "deploy": "ts-node src/server.ts" 11 | }, 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "@types/cors": "^2.8.6", 16 | "cors": "^2.8.5", 17 | "dotenv-safe": "^8.2.0", 18 | "express": "^4.17.1", 19 | "inversify": "^5.0.1", 20 | "mongoose": "^5.9.3", 21 | "reflect-metadata": "^0.1.13", 22 | "rxjs": "^6.5.4", 23 | "winston": "^3.2.1" 24 | }, 25 | "devDependencies": { 26 | "@types/dotenv-safe": "^8.1.0", 27 | "@types/express": "^4.17.2", 28 | "@types/jest": "^25.1.4", 29 | "@types/mongoose": "^5.7.3", 30 | "@types/node": "^13.7.6", 31 | "jest": "^25.2.0", 32 | "ts-jest": "^25.2.1", 33 | "ts-node": "^8.6.2", 34 | "ts-node-dev": "^1.0.0-pre.44", 35 | "typescript": "^3.8.2" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Notifier-service/src/app.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import cors from 'cors'; 3 | import { Application} from 'express'; 4 | import { Server } from 'http'; 5 | import { Logger } from './utility/log'; 6 | import { Route } from './interfaces/route'; 7 | import { Listener } from './interfaces/listener'; 8 | import { AppContainer } from './inversify.config'; 9 | import { Database } from './interfaces/database'; 10 | 11 | 12 | 13 | export class App { 14 | 15 | private logger = new Logger(this.constructor.name).getLogger(); 16 | private app:Application; 17 | private server:Server|undefined; 18 | public databaseobj:Database; 19 | 20 | // listeners to instantiate for listening 21 | private listeners:Listener[] = []; 22 | 23 | // Routes for registering toe express application 24 | private routes:Route[] =[]; 25 | 26 | constructor(private container:AppContainer){ 27 | this.app = express(); 28 | var allowedCORS = [ `http://localhost:${process.env.SERVER_PORT}`, process.env.USER_ORIGIN ]; 29 | var corsOptions = { 30 | origin: (origin: any, callback: any) => { 31 | if (!origin || allowedCORS.indexOf(origin) !== -1) { 32 | callback(null, true) 33 | } else { 34 | callback(new Error('Not allowed by CORS')) 35 | } 36 | } 37 | } 38 | this.app.use(cors( 39 | corsOptions 40 | )); 41 | this.routes = container.getRoutes() 42 | this.databaseobj=container.getDatabase(); 43 | 44 | // EventHandler to trigger Interrupt signal and close the database 45 | process.on('SIGINT', async() => { 46 | this.logger.info("Closing the Database!"); 47 | await this.databaseobj.close(); 48 | process.exit(0); 49 | 50 | }); 51 | this.listeners = container.getListeners() 52 | // this.listeners = container.getAll(Listener) 53 | } 54 | 55 | init():void{ 56 | this.routes.forEach( (route:Route) => { 57 | this.app.use("/"+route.getRouteName() , route.getRouter()) 58 | }) 59 | /* 60 | Default path for anything 61 | */ 62 | this.app.get("/**" , (request , response) => { 63 | response.json({ 64 | status:404, 65 | payload:" (ノಠ益ಠ)ノ彡┻━┻ " 66 | }) 67 | }) 68 | } 69 | 70 | 71 | 72 | 73 | start(callback:(port:Number) => void){ 74 | if(process.env.SERVER_PORT == undefined) 75 | throw new Error("No proper port for server found , configure in .env file") 76 | let port = Number.parseInt(process.env.SERVER_PORT) 77 | this.server = this.app.listen( port , () => { 78 | this.logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>> APPLICATION STARTED <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"); 79 | this.logger.info("Application listening at port :"+ port); 80 | callback(port); 81 | }) 82 | } 83 | } 84 | 85 | -------------------------------------------------------------------------------- /Notifier-service/src/controllers/conference.spec.ts: -------------------------------------------------------------------------------- 1 | import { ConferenceService } from "../interfaces/services/conference" 2 | import { ConferenceController } from "./conference" 3 | 4 | describe("Testing for conference contoller" , () => { 5 | 6 | let ServiceMock = jest.fn() 7 | 8 | let controller = new ConferenceController(new ServiceMock) 9 | 10 | test("Controller instantiation" , () => { 11 | expect(controller).toBeDefined() 12 | }) 13 | test("Controller should have the methods defined" , () => { 14 | expect(controller.getCategories).toBeDefined() 15 | expect(controller.getConferences).toBeDefined() 16 | expect(controller.getConferencesFomCategory).toBeDefined() 17 | expect(controller.getCategories).toBeDefined() 18 | }) 19 | }) 20 | 21 | -------------------------------------------------------------------------------- /Notifier-service/src/controllers/conference.ts: -------------------------------------------------------------------------------- 1 | import { Conference } from '../schemas/conferences' 2 | import { ConferenceService } from '../interfaces/services/conference'; 3 | import { Controller } from '../interfaces/controller'; 4 | import { Response, Request } from 'express' 5 | import { Logger } from '../utility/log'; 6 | import { injectable } from 'inversify'; 7 | 8 | 9 | @injectable() 10 | export class ConferenceController extends Controller { 11 | 12 | private logger = new Logger(this.constructor.name).getLogger(); 13 | 14 | constructor(private conferenceService: ConferenceService) { 15 | super(conferenceService) 16 | } 17 | 18 | 19 | 20 | getOne = async (request: Request, response: Response) => { 21 | let requester = request.ip; 22 | let category: string = request.params.category; 23 | let _id: string = request.params.id; 24 | this.logger.info(this.success("getOne", requester)) 25 | try { 26 | 27 | let result = await this.conferenceService.getOne(_id); 28 | response.json(this.successResponse(result)); 29 | } 30 | catch (e) { 31 | this.logger.error(this.fail("getOne" , requester , e)); 32 | response.json(this.failResponse()) 33 | } 34 | } 35 | 36 | getConferences = async (request: Request, response: Response) => { 37 | let requester = request.ip; 38 | let offset: string = request.params.offset; 39 | let count: string = request.params.count; 40 | try { 41 | let offseti = Number.parseFloat(offset); 42 | let counti = Number.parseFloat(count); 43 | if( Number.isNaN(offseti) || Number.isNaN(counti)){ 44 | throw new Error("Illegal arguments given for route , redirecting") 45 | } 46 | this.logger.info(this.success("getConference", requester)) 47 | try { 48 | 49 | let result: Conference[] = await this.conferenceService 50 | .getConferences( 51 | offseti , 52 | counti); 53 | response.json(this.successResponse(result)); 54 | } 55 | catch (e) { 56 | this.logger.error(this.fail("getConference" , requester , e)); 57 | response.json(this.failResponse()) 58 | } 59 | } catch (e) { 60 | this.logger.warn(this.fail("getConference", requester, e)) 61 | response.redirect('/') 62 | } 63 | } 64 | 65 | getConferencesFomCategory = async (request: Request, response: Response) => { 66 | let offset: string = request.params.offset; 67 | let count: string = request.params.count; 68 | let category: string = request.params.category; 69 | let requester = request.ip 70 | try { 71 | let offseti = Number.parseFloat(offset); 72 | let counti = Number.parseFloat(count); 73 | if( Number.isNaN(offseti) || Number.isNaN(counti)){ 74 | throw new Error("Illegal arguments given for route , redirecting") 75 | } 76 | this.logger.info(this.success("getConferenceFromCategory", requester)) 77 | try { 78 | let result: Conference[] = await this.conferenceService 79 | .getConferencesFromCategory( 80 | category, 81 | offseti , 82 | counti); 83 | response.json(this.successResponse(result)); 84 | } 85 | catch (e) { 86 | this.logger.error(this.fail("getConferenceFromCategory" , requester , e)); 87 | response.json(this.failResponse()) 88 | } 89 | 90 | } catch (e) { 91 | this.logger.error(this.fail("getConferenceFromCategory", requester, e)) 92 | response.redirect('/') 93 | } 94 | } 95 | 96 | 97 | getCategories = async (request: Request, response: Response) => { 98 | let category: string = request.params.category; 99 | let requester = request.ip 100 | try { 101 | this.logger.info(this.success("getCategories", requester)) 102 | let result: Array = await this.conferenceService.getCategories(); 103 | response.json(this.successResponse(result)) 104 | } catch (e) { 105 | this.logger.error(this.fail("getCategories" , requester , e)); 106 | response.json(this.failResponse()) 107 | } 108 | } 109 | } -------------------------------------------------------------------------------- /Notifier-service/src/database/mongodb.ts: -------------------------------------------------------------------------------- 1 | import { injectable, inject } from 'inversify' 2 | import mongoose from 'mongoose'; 3 | import { Database } from '../interfaces/database'; 4 | import { Logger } from '../utility/log'; 5 | 6 | 7 | @injectable() 8 | export class MongoDb extends Database { 9 | private logger = new Logger(this.constructor.name).getLogger(); 10 | protected dbName:string; 11 | public databaseConnection:Promise; 12 | constructor(){ 13 | super() 14 | let databaseName = process.env.MONGO_DB_NAME 15 | if(databaseName == undefined) 16 | throw new Error("No database name provided in environment") 17 | this.dbName = databaseName 18 | this.databaseConnection = mongoose.createConnection(`mongodb://${process.env.MONGO_DB_HOST}:${process.env.MONGO_DB_PORT}/${this.dbName}` , 19 | { useNewUrlParser: true , 20 | useUnifiedTopology: true 21 | } ) 22 | .then((res) => { 23 | this.logger.info(" Connection established succsessfully :) ") 24 | return Promise.resolve(res) 25 | }) 26 | .catch((err) => { 27 | this.logger.info(" Connection Failed!! ") 28 | return Promise.reject(err) 29 | }); 30 | } 31 | public async getConnection():Promise{ 32 | return this.databaseConnection; 33 | }; 34 | public async close():Promise{ 35 | await this.databaseConnection.then(database => { 36 | this.logger.info(" MongoDatabase Connection closed!! "); 37 | database.close() 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Notifier-service/src/interfaces/controller.ts: -------------------------------------------------------------------------------- 1 | import { Service } from './services' 2 | import { injectable } from 'inversify' 3 | 4 | @injectable() 5 | export abstract class Controller { 6 | protected success = (fun: string, info: any) => `Recieved request for ${fun} from: ${info}` 7 | protected fail = (fun: string, info: any, err: any) => `Failed at request for ${fun} from : ${info} : Error encountered : ${err}` 8 | 9 | protected successResponse = (payload:any) => { 10 | return { 11 | status: 200, 12 | payload: payload, 13 | message:"success" 14 | } 15 | } 16 | 17 | protected failResponse = () => { 18 | return { 19 | status:500, 20 | payload:null, 21 | message: "Internal error" 22 | 23 | } 24 | } 25 | 26 | 27 | constructor(service:Service){}; 28 | } -------------------------------------------------------------------------------- /Notifier-service/src/interfaces/database.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | 3 | @injectable() 4 | export abstract class Database { 5 | protected abstract dbName:string; 6 | public abstract async getConnection():Promise; 7 | public abstract async close():Promise; 8 | } -------------------------------------------------------------------------------- /Notifier-service/src/interfaces/listener.ts: -------------------------------------------------------------------------------- 1 | import { Stream } from './stream' 2 | import { injectable } from 'inversify'; 3 | 4 | /** 5 | * Listener class is reponsible for 6 | * establising listeners for any sort of stream 7 | * from the observables provided 8 | * 9 | * @export 10 | * @abstract 11 | * @class Listener 12 | */ 13 | @injectable() 14 | export abstract class Listener { 15 | constructor(stream: Stream){} 16 | } -------------------------------------------------------------------------------- /Notifier-service/src/interfaces/model.ts: -------------------------------------------------------------------------------- 1 | import { Database } from './database'; 2 | import { injectable } from 'inversify'; 3 | 4 | @injectable() 5 | export abstract class Model{ 6 | protected database:Database; 7 | constructor(database:Database){ 8 | this.database = database; 9 | } 10 | } -------------------------------------------------------------------------------- /Notifier-service/src/interfaces/models/conference.ts: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import { ConferenceDocument } from '../../schemas/conferences'; 3 | import { injectable } from 'inversify'; 4 | import { Model } from '../model'; 5 | 6 | /** 7 | * The conference model that needs to be implemented 8 | * The implementation must follow specification and can use whichever 9 | * database object it adheres to. 10 | * @export 11 | * @abstract 12 | * @class ConferenceModel 13 | * @extends {Model} 14 | */ 15 | @injectable() 16 | export abstract class ConferenceModel extends Model{ 17 | protected abstract modelName:string; 18 | abstract async getOne(id: string):Promise 19 | abstract async getConferences(offset:number , range:number):Promise 20 | abstract async getConferencesFromCategory(category:string , offset:number , range:number):Promise 21 | abstract async getCategories():Promise | null> 22 | abstract async makeQuery(callback: (model: mongoose.Model) => Promise): Promise 23 | } -------------------------------------------------------------------------------- /Notifier-service/src/interfaces/route.ts: -------------------------------------------------------------------------------- 1 | import {Controller} from './controller'; 2 | import {Router} from 'express'; 3 | import { Response, Request } from 'express' 4 | import { injectable } from 'inversify'; 5 | 6 | @injectable() 7 | export abstract class Route { 8 | protected abstract routeName:string; 9 | protected router:Router; 10 | constructor(controller:Controller){ 11 | this.router = Router(); 12 | this.router.get("/" , this.default) 13 | } 14 | 15 | getRouter():Router { 16 | return this.router; 17 | } 18 | 19 | getRouteName():string{ 20 | return this.routeName; 21 | } 22 | 23 | default = async (request: Request, response: Response) => { 24 | response.redirect("/"); 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /Notifier-service/src/interfaces/services.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | 3 | @injectable() 4 | export abstract class Service { 5 | 6 | } -------------------------------------------------------------------------------- /Notifier-service/src/interfaces/services/conference.ts: -------------------------------------------------------------------------------- 1 | import {ConferenceModel} from '../models/conference' 2 | import {Service} from '../services' 3 | import { Conference } from '../../schemas/conferences' 4 | import { injectable } from 'inversify' 5 | 6 | @injectable() 7 | export abstract class ConferenceService extends Service{ 8 | 9 | abstract async getConferences(offset:Number , count:Number):Promise; 10 | abstract async getConferencesFromCategory(category:String , offset:Number , count:Number):Promise 11 | abstract async getCategories():Promise> 12 | 13 | abstract async getOne(_id: string):Promise 14 | } -------------------------------------------------------------------------------- /Notifier-service/src/interfaces/services/listeners/conferenceListener.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { ConferenceStream } from "../streams/conferenceStream"; 3 | import { Listener } from "../../listener"; 4 | 5 | /** 6 | * Attaches the listeners to ConferenceStream 7 | * through callbacks or through event pipes/triggers 8 | * 9 | * @export 10 | * @abstract 11 | * @class ConferenceListener 12 | * @extends {Listener} 13 | */ 14 | @injectable() 15 | export abstract class ConferenceListener extends Listener{ 16 | constructor(private conferenceStream:ConferenceStream){ 17 | super(conferenceStream) 18 | } 19 | } -------------------------------------------------------------------------------- /Notifier-service/src/interfaces/services/streams/conferenceStream.ts: -------------------------------------------------------------------------------- 1 | import { ConferenceModel } from "../../models/conference"; 2 | import { Stream } from '../../stream' 3 | import { Observable } from "rxjs"; 4 | import { injectable } from "inversify"; 5 | 6 | /** 7 | * Stream for Conference model , responsible for creating 8 | * constant update stream for the Conference table/collection in database 9 | * 10 | * 11 | * @export 12 | * @abstract 13 | * @class ConferenceStream 14 | * @implements {Stream} 15 | */ 16 | @injectable() 17 | export abstract class ConferenceStream implements Stream{ 18 | constructor(private conferenceModel:ConferenceModel){} 19 | abstract getStream():Observable; 20 | abstract getInsertStream():Observable; 21 | abstract getUpdateStream():Observable; 22 | abstract getDeleteStream():Observable; 23 | abstract getReplaceStream():Observable; 24 | } 25 | -------------------------------------------------------------------------------- /Notifier-service/src/interfaces/stream.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from "rxjs"; 2 | 3 | 4 | 5 | /** 6 | * Stream interface responsible for representing 7 | * a observable that can be subscribed and being listened by a 8 | * listener 9 | * @export 10 | * @interface Stream 11 | */ 12 | export interface Stream{ 13 | getStream():Observable 14 | } 15 | -------------------------------------------------------------------------------- /Notifier-service/src/inversify.config.ts: -------------------------------------------------------------------------------- 1 | import { Container } from "inversify"; 2 | import { Database } from "./interfaces/database"; 3 | import { MongoDb } from "./database/mongodb"; 4 | import { ConferenceModel } from "./interfaces/models/conference"; 5 | import { ConferenceModelMongo } from "./models/conference"; 6 | import { ConferenceServiceI } from "./services/conference"; 7 | import { ConferenceService } from "./interfaces/services/conference"; 8 | import { ConferenceController } from "./controllers/conference"; 9 | import { Route } from "./interfaces/route"; 10 | import { ConferenceRoute } from "./routes/conference"; 11 | import { ConferenceStream } from "./interfaces/services/streams/conferenceStream"; 12 | import { ConferenceStreamMongo } from "./services/streams/conferenceStream"; 13 | import { Listener } from "./interfaces/listener"; 14 | import { ConferenceListenerMongo } from "./services/listeners/conferenceListener"; 15 | 16 | 17 | 18 | export class AppContainer{ 19 | private container:Container; 20 | constructor(){ 21 | this.container = new Container() 22 | this.container.bind(Database).to(MongoDb).inSingletonScope(); 23 | this.container.bind(ConferenceModel).to(ConferenceModelMongo).inSingletonScope(); 24 | this.container.bind(ConferenceService).to(ConferenceServiceI).inSingletonScope(); 25 | this.container.bind(ConferenceController).to(ConferenceController).inSingletonScope(); 26 | this.container.bind(Route).to(ConferenceRoute).inSingletonScope(); 27 | this.container.bind(ConferenceStream).to(ConferenceStreamMongo).inSingletonScope(); 28 | this.container.bind(Listener).to(ConferenceListenerMongo).inSingletonScope(); 29 | } 30 | 31 | public getRoutes():Route[]{ 32 | return this.container.getAll(Route) 33 | } 34 | 35 | public getListeners():Listener[]{ 36 | return this.container.getAll(Listener) 37 | } 38 | public getDatabase():Database{ 39 | let databaseobj=this.container.get(Database); 40 | return databaseobj; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Notifier-service/src/models/conference.ts: -------------------------------------------------------------------------------- 1 | import { Model} from 'mongoose'; 2 | import { Connection } from 'mongoose'; 3 | import { Conference, ConferenceDocument, ConferenceSchema } from '../schemas/conferences'; 4 | import { Database } from '../interfaces/database'; 5 | import { ConferenceModel } from '../interfaces/models/conference'; 6 | import { Logger } from '../utility/log'; 7 | import { injectable } from 'inversify'; 8 | 9 | 10 | @injectable() 11 | export class ConferenceModelMongo extends ConferenceModel { 12 | modelName = "conference" 13 | private model: Promise>; 14 | private connection: Promise; 15 | private logger = new Logger(this.constructor.name).getLogger(); 16 | constructor(database: Database) { 17 | super(database) 18 | this.connection = database.getConnection() 19 | .then((connection: Connection) => { 20 | return Promise.resolve(connection); 21 | }) 22 | .catch((error) => { 23 | let errstring = "Failed at getting connection :" + error; 24 | this.logger.error(errstring); 25 | return Promise.reject(error); 26 | }) 27 | 28 | this.model = this.connection 29 | .then((connection: Connection) => { 30 | let model = connection.model(this.modelName, ConferenceSchema); 31 | return Promise.resolve(model); 32 | }) 33 | .catch((error) => { 34 | let errstring = "Failed at getting connection for model" + error; 35 | this.logger.error(errstring); 36 | return Promise.reject(error); 37 | }) 38 | 39 | } 40 | 41 | 42 | public async makeQuery(callback: (model: Model) => Promise): Promise { 43 | return this.model 44 | .then(callback) 45 | .catch(error => { 46 | this.logger.debug("Failed at" + callback.name + ": error:" + error); 47 | this.logger.error("Failed at" + callback.name + " : model must have failed to initialize , or something error :" + error); 48 | return Promise.reject(new Error("model failed to be initialised")); 49 | }); 50 | } 51 | 52 | 53 | async getOne(id: string): Promise { 54 | this.logger.debug("getOne invoked") 55 | let query = {_id:id} 56 | let result = this.makeQuery((model) => { 57 | return new Promise((resolve, reject) => { 58 | model.findOne(query, (err, res) => { 59 | if (!err) { 60 | resolve(res); 61 | } 62 | else { 63 | reject(err); 64 | } 65 | }) 66 | }) 67 | }) 68 | return result 69 | } 70 | 71 | async getConferences(offset: number, range: number): Promise { 72 | this.logger.debug("getConferences invoked") 73 | let result = this.makeQuery((model) => { 74 | return new Promise((resolve, reject) => { 75 | model.find({ "deadline": { $gte: new Date() } }) 76 | .sort({ 'deadline': 1 }).skip(offset).limit(range).exec((err, res) => { 77 | if (!err) { 78 | resolve(res); 79 | } 80 | else { 81 | reject(err); 82 | } 83 | }) 84 | }) 85 | }) 86 | return result 87 | } 88 | 89 | async getConferencesFromCategory(category: string, offset: number, range: number): Promise { 90 | this.logger.debug("getConferencesFromCategory invoked") 91 | let result = this.makeQuery((model) => { 92 | return new Promise((resolve, reject) => { 93 | model.find({ 'categories': { '$in': [category] }, "deadline": { $gte: new Date() } }) 94 | .sort({ 'deadline': 1 }).skip(offset).limit(range).exec((err, res) => { 95 | if (!err) { 96 | resolve(res); 97 | } 98 | else { 99 | reject(err); 100 | } 101 | }) 102 | }) 103 | }) 104 | return result 105 | } 106 | 107 | async getCategories(): Promise { 108 | this.logger.debug("getCategories invoked") 109 | let result = this.makeQuery((model) => { 110 | return new Promise((resolve, reject) => { 111 | model.distinct(('categories'), (err, res) => { 112 | if (!err) { 113 | resolve(res); 114 | } 115 | else { 116 | reject(err); 117 | } 118 | }) 119 | }) 120 | }) 121 | return result 122 | } 123 | 124 | } 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /Notifier-service/src/routes/conference.spec.ts: -------------------------------------------------------------------------------- 1 | import { ConferenceController } from "../controllers/conference" 2 | import { ConferenceRoute } from "./conference" 3 | 4 | describe("Route testing" , () => { 5 | let MockController = jest.fn() 6 | let mock = new MockController() 7 | mock.getOne = jest.fn() 8 | mock.getCategories = jest.fn() 9 | mock.getConferences = jest.fn() 10 | mock.getConferencesFomCategory = jest.fn() 11 | 12 | let route = new ConferenceRoute(mock) 13 | 14 | test("Route insantiation" , () => { 15 | expect(route.getRouter).toBeDefined() 16 | }) 17 | 18 | }) -------------------------------------------------------------------------------- /Notifier-service/src/routes/conference.ts: -------------------------------------------------------------------------------- 1 | import { Router , Request , Response } from 'express'; 2 | import { ConferenceController } from '../controllers/conference'; 3 | import { Conference } from '../schemas/conferences'; 4 | import { Route } from '../interfaces/route'; 5 | import { injectable } from 'inversify'; 6 | 7 | 8 | /** 9 | * Conference Route attaches the Conference controller 10 | * and handlers to the routes defined 11 | * @export 12 | * @class ConferenceRoute 13 | * @extends {Route} 14 | */ 15 | 16 | @injectable() 17 | export class ConferenceRoute extends Route{ 18 | 19 | protected routeName:string; 20 | constructor(private controller: ConferenceController){ 21 | super(controller) 22 | this.routeName = "conferences" ; 23 | this.setRoutes() 24 | } 25 | 26 | /** 27 | * Routes for Conference routes 28 | * base route : /conferences 29 | * 30 | * - /getOne/:id returns one conference data 31 | * - /:offset/:count (offset[int], count[int]) , returns several conferences , on bad argument returns empty payload 32 | * - /:category/:offset/:count (category[string] , offset[int], count[int]) , returns several conferences , on bad argument returns empty payload 33 | * - /:categories , returns list of categories, returns null payload on fail 34 | * @protected 35 | * @memberof ConferenceRoute 36 | */ 37 | protected setRoutes(){ 38 | this.router.get("/getone/:id",this.controller.getOne) 39 | this.router.get("/:offset/:count" , this.controller.getConferences) 40 | this.router.get("/:category/:offset/:count" , this.controller.getConferencesFomCategory) 41 | this.router.get("/categories" , this.controller.getCategories) 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /Notifier-service/src/schemas/conferences.ts: -------------------------------------------------------------------------------- 1 | import { Metadata } from './metadata'; 2 | import { Schema, Document } from 'mongoose'; 3 | 4 | type Conference = { 5 | title:string; 6 | url:string; 7 | deadline:Date; 8 | _id?: string; 9 | metadata?:{ 10 | [tag:string]:Metadata 11 | }; 12 | 13 | categories?:Array; 14 | dateRange?:Array; 15 | finalDue?:string; 16 | location?:string; 17 | notificationDue?:Date; 18 | bulkText?:string; // optional field 19 | } 20 | 21 | interface ConferenceDocument extends Document { 22 | title:string; 23 | url:string; 24 | deadline:Date; 25 | metadata?:{ 26 | [tag:string]:Metadata 27 | }; 28 | 29 | categories?:Array; 30 | dateRange?:Array; 31 | finalDue?:string; 32 | location?:string; 33 | notificationDue?:Date; 34 | bulkText?:string; // optional field 35 | } 36 | 37 | let ConferenceSchema = new Schema({ 38 | _id: {type:String, required:true}, 39 | title:{type:String , required:true}, 40 | url:{type:String , required:true}, 41 | deadline:{type:Date , required:true}, 42 | } , {strict : false}); 43 | 44 | export {Conference , ConferenceDocument , ConferenceSchema} 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Notifier-service/src/schemas/metadata.ts: -------------------------------------------------------------------------------- 1 | export interface Metadata{ 2 | [scrapper:string]: { 3 | dateExtracted:Date; 4 | websiteUrl:string; 5 | website:string; 6 | domain:string; 7 | } 8 | } -------------------------------------------------------------------------------- /Notifier-service/src/server.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata" 2 | import * as dotenv from 'dotenv-safe'; 3 | import { App } from './app'; 4 | import { AppContainer } from './inversify.config' 5 | 6 | /* 7 | Load the .env file into the process environment variable scope 8 | It's necessary to keep a .env file in the project root 9 | along side package.json 10 | */ 11 | dotenv.config({ 12 | example: './.env' 13 | }); 14 | 15 | /* 16 | * Creating Application Container for all class 17 | * Passing the container to App , for route attachment 18 | */ 19 | 20 | let container = new AppContainer() 21 | let app = new App(container); 22 | app.init() 23 | app.start((port) => { 24 | console.log("Listening on port :" + port); 25 | }) 26 | 27 | -------------------------------------------------------------------------------- /Notifier-service/src/services/conference.spec.ts: -------------------------------------------------------------------------------- 1 | import { ConferenceModel } from '../interfaces/models/conference' 2 | import { ConferenceServiceI } from './conference' 3 | 4 | describe("Testing Conferences Service Implementation " ,() => { 5 | let ModelMock = jest.fn() 6 | let model = new ModelMock() 7 | let conferenceModelMongoMock = jest.fn() 8 | let confModelMongo = new conferenceModelMongoMock() 9 | let categories = ["category1" , "category2"] 10 | let mongoData: any = {title: "t", 11 | url: "u", 12 | deadline: new Date()} 13 | let mongoRes: any = {toObject: jest.fn(()=>mongoData) } 14 | confModelMongo.getOne = jest.fn(()=> Promise.resolve(mongoRes)) 15 | model.getCategories = jest.fn(() => Promise.resolve(["category1" , "category2"])) 16 | model.getConferences = jest.fn( () => Promise.resolve([]) ) 17 | let service = new ConferenceServiceI(model,confModelMongo) 18 | let id= '8bd11a96-ac06-58f5-8727-2ab7f12899c2' 19 | 20 | test("service instantiation" , () => { 21 | expect(service).toBeDefined() 22 | }) 23 | 24 | test("service calls" , async () => { 25 | expect(service.getCategories).toBeDefined() 26 | expect(await service.getCategories()).toEqual(categories) 27 | expect(service.getConferencesFromCategory).toBeDefined() 28 | expect(service.getConferences).toBeDefined() 29 | expect(await service.getConferences(1 , 2)).toEqual([]) 30 | expect(service.getOne).toBeDefined() 31 | expect(await service.getOne(id)).toEqual(mongoData) 32 | }) 33 | }) 34 | 35 | -------------------------------------------------------------------------------- /Notifier-service/src/services/conference.ts: -------------------------------------------------------------------------------- 1 | import { ConferenceService } from '../interfaces/services/conference' 2 | import { ConferenceModel } from '../interfaces/models/conference' 3 | import { Conference } from '../schemas/conferences'; 4 | import { Logger } from '../utility/log'; 5 | import { injectable } from 'inversify'; 6 | import { ConferenceModelMongo } from '../models/conference'; 7 | 8 | 9 | @injectable() 10 | export class ConferenceServiceI extends ConferenceService { 11 | private logger = new Logger(this.constructor.name).getLogger() 12 | constructor(private conferenceModel: ConferenceModel, 13 | private conferenceModelMongo:ConferenceModelMongo) { 14 | super() 15 | } 16 | async getOne(_id: string): Promise { 17 | this.logger.debug("getOne invoked") 18 | return new Promise((resolve, reject) => { 19 | this.conferenceModelMongo.getOne(_id).then((value)=>{ 20 | if(value == null){ 21 | resolve(null) 22 | }else{ 23 | let conference: Conference = value.toObject() 24 | resolve(conference) 25 | } 26 | },(error)=>{ 27 | this.logger.error("getOne retrieval failed: " + error) 28 | reject(error) 29 | }) 30 | }) 31 | 32 | } 33 | 34 | 35 | async getConferences(offset:number , count:number):Promise{ 36 | this.logger.debug("getConferences invoked") 37 | return new Promise((resolve , reject) => { 38 | this.conferenceModel.getConferences(offset, count).then( value => { 39 | if(value == []){ 40 | resolve([]) 41 | } 42 | else{ 43 | let conference:Conference[] = value!?.map(val => val.toObject()); 44 | resolve(conference) 45 | } 46 | }).catch(err => { 47 | this.logger.error("getConferences retrieval failed: "+ err) 48 | reject(err) 49 | }) 50 | }) 51 | } 52 | 53 | async getConferencesFromCategory(category:string , offset:number , count:number):Promise{ 54 | this.logger.debug("getConferencesFromCategory invoked") 55 | return new Promise((resolve , reject) => { 56 | this.conferenceModel.getConferencesFromCategory(category, offset, count).then( value => { 57 | if(value == []){ 58 | resolve([]) 59 | } 60 | else{ 61 | let conference:Conference[] = value!?.map(val => val.toObject()); 62 | resolve(conference) 63 | } 64 | }).catch(err => { 65 | this.logger.error("getConferencesFromCategory retrieval failed: "+ err) 66 | reject(err) 67 | }) 68 | }) 69 | } 70 | 71 | async getCategories():Promise>{ 72 | this.logger.debug("getCategories invoked") 73 | return new Promise((resolve , reject) => { 74 | this.conferenceModel.getCategories().then( value => { 75 | if(value == []){ 76 | resolve([]) 77 | } 78 | else{ 79 | let conference:string[] = value!; 80 | resolve(conference) 81 | } 82 | }).catch(err => { 83 | this.logger.error("getCategories retrieval failed: "+ err) 84 | reject(err) 85 | }) 86 | }) 87 | } 88 | } -------------------------------------------------------------------------------- /Notifier-service/src/services/listeners/conferenceListener.ts: -------------------------------------------------------------------------------- 1 | import { ConferenceStream } from '../../interfaces/services/streams/conferenceStream' 2 | import { injectable } from 'inversify'; 3 | import { Logger } from '../../utility/log'; 4 | import { ConferenceListener } from '../../interfaces/services/listeners/conferenceListener'; 5 | 6 | @injectable() 7 | export class ConferenceListenerMongo extends ConferenceListener { 8 | 9 | private logger = new Logger(this.constructor.name).getLogger() 10 | 11 | constructor(conferenceStream:ConferenceStream) { 12 | super(conferenceStream) 13 | this.logger.info("Conference Listener started") 14 | conferenceStream.getStream().subscribe({ 15 | next: (data:Object) => { 16 | console.log(data) 17 | this.logger.debug(`recieving data from stream : ${JSON.stringify(data)}`) 18 | }, 19 | error: (error) => this.logger.error(error), 20 | complete: () => this.logger.warn("Stream is detached and complete, should not happen !!") 21 | }) 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Notifier-service/src/services/streams/conferenceStream.ts: -------------------------------------------------------------------------------- 1 | import { Model } from "mongoose"; 2 | import { ConferenceModel } from "../../interfaces/models/conference"; 3 | import { ConferenceDocument } from "../../schemas/conferences"; 4 | import { injectable } from "inversify"; 5 | import { Logger } from "../../utility/log"; 6 | import { Observable, Observer } from 'rxjs' 7 | import { share,filter } from 'rxjs/operators' 8 | 9 | import { ConferenceStream } from "../../interfaces/services/streams/conferenceStream"; 10 | 11 | 12 | @injectable() 13 | export class ConferenceStreamMongo extends ConferenceStream { 14 | 15 | private logger = new Logger(this.constructor.name).getLogger() 16 | private streamObs$: Observable 17 | private insertstreamObs$: Observable; 18 | private updatestreamObs$: Observable; 19 | private deletestreamObs$: Observable; 20 | private replacestreamObs$: Observable; 21 | constructor(conferenceModel: ConferenceModel) { 22 | super(conferenceModel) 23 | this.logger.info("Conference Stream started") 24 | let changeStream = conferenceModel.makeQuery(async (model: Model) => { 25 | try { 26 | let stream = await model.watch([] , {fullDocument: 'updateLookup'}); 27 | return Promise.resolve(stream) 28 | } catch (err) { 29 | this.logger.error(`Failed to get a watch stream :${err}`) 30 | return Promise.reject(err) 31 | } 32 | }) 33 | 34 | this.streamObs$ = Observable.create((observer: Observer) => { 35 | changeStream.then(stream => { 36 | stream.on("change", (data:Object) => { 37 | observer.next(data) 38 | }) 39 | }).catch(error => { 40 | this.logger.error(`change stream failed:${error}`) 41 | observer.error(error) 42 | observer.complete() 43 | }) 44 | }) 45 | .pipe( 46 | share() 47 | ) 48 | this.insertstreamObs$ = this.getStream().pipe(filter(data => { 49 | return data.operationType == 'insert' 50 | })) 51 | this.updatestreamObs$ = this.getStream().pipe(filter(data => { 52 | return data.operationType == 'update' 53 | })) 54 | this.deletestreamObs$ = this.getStream().pipe(filter(data => { 55 | return data.operationType == 'delete' 56 | })) 57 | this.replacestreamObs$ = this.getStream().pipe(filter(data => { 58 | return data.operationType == 'replace' 59 | })) 60 | 61 | } 62 | 63 | public getStream(): Observable { 64 | return this.streamObs$; 65 | } 66 | public getInsertStream():Observable { 67 | return this.insertstreamObs$; 68 | } 69 | public getUpdateStream():Observable { 70 | return this.updatestreamObs$; 71 | } 72 | public getDeleteStream():Observable { 73 | return this.deletestreamObs$; 74 | } 75 | public getReplaceStream():Observable { 76 | return this.replacestreamObs$; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Notifier-service/src/utility/log.spec.ts: -------------------------------------------------------------------------------- 1 | import {Logger} from './log' 2 | 3 | 4 | test("Testing for environment variable" , () => { 5 | expect(process.env.LOG_FOLDER).toBeDefined() 6 | expect(process.env.LOG_LEVEL).toBeDefined() 7 | } ) 8 | 9 | test("Testing for Logger " , () => { 10 | let logger = new Logger("test"); 11 | let log = logger.getLogger() 12 | expect(log).toBeDefined() 13 | expect(log.info).toBeDefined() 14 | expect(log.warn).toBeDefined() 15 | expect(log.debug).toBeDefined() 16 | expect(log.error).toBeDefined() 17 | }) 18 | 19 | -------------------------------------------------------------------------------- /Notifier-service/src/utility/log.ts: -------------------------------------------------------------------------------- 1 | import winston from 'winston'; 2 | 3 | export class Logger{ 4 | 5 | 6 | private logger:winston.Logger; 7 | constructor(filename:string){ 8 | this.logger = winston.createLogger({ 9 | level: process.env.LOG_LEVEL, 10 | transports: [ 11 | new winston.transports.Console({ format: winston.format.colorize({all:true}),}), 12 | new winston.transports.File({ filename: `${process.env.LOG_FOLDER}/${filename}.log` }) 13 | ] , 14 | 15 | format: winston.format.combine( 16 | winston.format.label({ 17 | label: filename 18 | }), 19 | 20 | winston.format.timestamp(), 21 | winston.format.printf((info) => { 22 | return `${info.timestamp} - ${info.label}:[${info.level}]: ${info.message}`; 23 | }) 24 | ) 25 | 26 | }) 27 | } 28 | 29 | getLogger():winston.Logger{ 30 | return this.logger; 31 | } 32 | 33 | } 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Notifier-service/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 7 | "lib": [ "es6" , "dom" ], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | "outDir": "./build", /* Redirect output structure to the directory. */ 16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */ 27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | "types": ["reflect-metadata" , "jest"], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | 63 | /* Advanced Options */ 64 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 65 | }, 66 | 67 | } 68 | -------------------------------------------------------------------------------- /Notifier-service/tsconfig.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 7 | "lib": [ "es6" , "dom" ], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | "outDir": "./build", /* Redirect output structure to the directory. */ 16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */ 27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | "types": ["reflect-metadata" , "jest"], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | 63 | /* Advanced Options */ 64 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 65 | }, 66 | "exclude": ["node_modules", "**/*.spec.ts"] 67 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Conference-Notify 2 | 3 | ## [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/dwyl/esta/issues) 4 | 5 | Conference-Notify will be an open source web based application that will aggregate conference information from wikicfp , guide2research and other such websites to create a single point of aggregated information and build index over the same. These information can then be searched by users through plain text queries. On finding relevant conferences the user can create recurring notifiers for themselves for the date reminders which can be enabled on both mobile devices and through browser notification. 6 | 7 | ## Getting Started 8 | 9 | These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system. You can also refer to this [helper document](https://docs.google.com/document/d/1gAd7DHDg7xybD6H72HAGCQFdPDTqC9SDejJCdPK0wd4/edit?usp=sharing) 10 | 11 | ## Prerequisites 12 | 13 | ### MongoDB Installation 14 | #### Local 15 | This runs MongoDB in your own local machine. 16 | 1. Download [MongoDB](https://www.mongodb.com/download-center/community) 17 | 2. Install MongoDB by the following installation instructions. 18 | 19 | #### Atlas 20 | * MongoDB Atlas is the global cloud database service for modern applications. 21 | 1. Go to [MongoDB Atlas](https://www.mongodb.com/cloud/atlas) 22 | 2. Create an account or sign in into your mongoDB account. 23 | 3. Depending on the usage, select various features. 24 | For more information, Visit [MongoDB Atlas Docs](https://docs.atlas.mongodb.com/) 25 | 26 | ### Python 3.6 Installation: 27 | 28 | #### Windows: 29 | 30 | 1. Download [Python](https://www.python.org/downloads/) 31 | 2. Install it and ensure that the interpreter will be placed in your execution path. 32 | 33 | #### Linux: 34 | 35 | 1. Ubuntu 17.10, Ubuntu 18.04 (and above) come with Python 3.6 by default. You should be able to invoke it with the command python3. 36 | 37 | 2. Ubuntu 16.10 and 17.04 do not come with Python 3.6 by default, but it is in the Universe repository. You should be able to install it with the following commands: 38 | 39 | `sudo apt-get update` 40 | `sudo apt-get install python3.6` 41 | 42 | You can then invoke it with the command python3.6. 43 | 44 | 3. If you are using Ubuntu 14.04 or 16.04, Python 3.6 is not in the Universe repository, and you need to get it from a Personal Package Archive (PPA). For example, to install Python from the “deadsnakes” PPA, do the following: 45 | 46 | `sudo add-apt-repository ppa:deadsnakes/ppa` 47 | `sudo apt-get update` 48 | `sudo apt-get install python3.6` 49 | 50 | As above, invoke with the command python3.6. 51 | 52 | #### MacOS / MacOS X 53 | 54 | 1. Install Homebrew by pasting the below code in terminal 55 | `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"` 56 | 2. Accept the required permissions and finish the installation. 57 | 3. Install python 3.6 from 58 | `brew install python3` 59 | 60 | ### Pymongo Client 61 | 1. Install the Pymongo from the command line using 62 | `python -m pip install pymongo` 63 | For various driver installations, Check [Installation](https://pymongo.readthedocs.io/en/stable/installation.html) 64 | 2. For more information, Visit [Pymongo client](https://www.mongodb.com/blog/post/getting-started-with-python-and-mongodb) 65 | 66 | ### Node.js 67 | Download [node.js](https://nodejs.org/en/download/) and install it. 68 | 69 | ### Angular 6+ 70 | Download Angular 6+ from command line 71 | `npm install -g @angular/cli` 72 | 73 | For more information about Angular, Visit [Docs](https://angular.io/guide/setup-local) 74 | 75 | ### Elastic Search 76 | Elasticsearch is a real-time, distributed storage, search, and analytics engine. 77 | #### Installation 78 | 1. Verify that your system meets the [minimum JVM requirements](https://www.elastic.co/support/matrix#matrix_jvm) for Elasticsearch. 79 | 80 | 2. Installation process for different systems: 81 | 82 | #### Windows: 83 | a. Download the Elasticsearch 7.6.2 Windows zip file from the [Elasticsearch download](https://www.elastic.co/downloads/elasticsearch) page. 84 | b. Extract the contents of the zip file to a directory on your computer, for example, C:\Program Files. 85 | c. Open a command prompt as an Administrator and navigate to the directory that contains the extracted files, 86 | For example: 87 | `cd C:\Program Files\elasticsearch-7.6.2` 88 | d. Start Elasticsearch: 89 | `bin\elasticsearch.bat` 90 | 91 | #### Debian (Ubuntu): 92 | 93 | `curl -L -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.6.2-amd64.deb` 94 | `sudo dpkg -i elasticsearch-7.6.2-amd64.deb` 95 | `sudo /etc/init.d/elasticsearch start` 96 | 97 | #### rpm (Redhat/Centos/Fedora): 98 | 99 | `curl -L -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.6.2-x86_64.rpm` 100 | `sudo rpm -i elasticsearch-7.6.2-x86_64.rpm` 101 | `sudo service elasticsearch start` 102 | 103 | #### mac (MAC OS X): 104 | 105 | `curl -L -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.6.2-darwin-x86_64.tar.gz` 106 | `tar -xzvf elasticsearch-7.6.2-darwin-x86_64.tar.gz` 107 | `cd elasticsearch-7.6.2` 108 | `./bin/elasticsearch` 109 | 110 | #### brew (MAC OS X): 111 | 112 | `brew tap elastic/tap` 113 | `brew install elastic/tap/elasticsearch-full` 114 | `elasticsearch` 115 | 116 | #### linux : 117 | 118 | `curl -L -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.6.2-linux-x86_64.tar.gz` 119 | `tar -xzvf elasticsearch-7.6.2-linux-x86_64.tar.gz` 120 | `cd elasticsearch-7.6.2` 121 | `./bin/elasticsearch` 122 | 123 | 3. Make sure Elastic search is up and running: 124 | a. To test that the Elasticsearch daemon is up and running, try sending an HTTP GET request on port 9200. 125 | `curl http://127.0.0.1:9200` 126 | b. On Windows, if you don’t have cURL installed, point your browser to the URL. 127 | c. The output appears similar to this: 128 | 129 |
130 | {    
131 |   "name" : "QtI5dUu",  
132 |   "cluster_name" : "elasticsearch",  
133 |   "cluster_uuid" : "DMXhqzzjTGqEtDlkaMOzlA",  
134 |   "version" : {  
135 |     "number" : "7.6.2",  
136 |     "build_flavor" : "default",  
137 |     "build_type" : "tar",  
138 |     "build_hash" : "00d8bc1",  
139 |     "build_date" : "2018-06-06T16:48:02.249996Z",  
140 |     "build_snapshot" : false,  
141 |     "lucene_version" : "7.3.1",  
142 |     "minimum_wire_compatibility_version" : "5.6.0",  
143 |     "minimum_index_compatibility_version" : "5.0.0"  
144 |   },  
145 |   "tagline" : "You Know, for Search"  
146 | }
147 | 
148 |
149 | 150 | ### Installing 151 | 152 | The project is divided into several components, i.e services 153 | 154 | * Notifier-Service 155 | * Scrapper-Service 156 | * Search-Service 157 | 158 | None of this services requires any instllation and can be executed on the fly 159 | 160 | **Other services coming soon** 161 | 162 | 163 | 164 | ## Deployment 165 | 166 | **Scrapper-Service** 167 | Note: Make sure the configuration file is properly configured for usage, since app.py is reading configuration from the file 168 | 169 | ``` 170 | >> cd Scrapper-Service 171 | 172 | >> python app.py --help 173 | usage: app.py [-h] [-c CONFIG] [-l {debug,warn,error,info}] [-t TEST] 174 | [-ls {console,file}] 175 | 176 | optional arguments: 177 | -h, --help show this help message and exit 178 | -c CONFIG, --config CONFIG 179 | Specify config.json file ,default: config.json 180 | -l {debug,warn,error,info}, --log {debug,warn,error,info} 181 | Specify the debug level ,default: debug 182 | -t TEST, --test TEST Specify whether to test app initialization or run the 183 | scrappers ,default: True 184 | -ls {console,file}, --logStream {console,file} 185 | Specify whether to print logs on terminal or to file 186 | ,default: console 187 | 188 | >> python -m unittest test 189 | It will run all the unit tests written and kept under test folder 190 | 191 | ``` 192 | 193 | **Notifier-Service** 194 | 195 | ```shell 196 | 197 | >> cd Notifier-Service 198 | 199 | //For dev 200 | 201 | >> npm run-script build 202 | 203 | >> npm start 204 | 205 | //For prod 206 | 207 | >> npm run-script build 208 | 209 | >> npm run-script run 210 | 211 | ``` 212 | 213 | 214 | ## Built With 215 | 216 | * [pymongo](https://api.mongodb.com/python/current/) - Mongo client for python 217 | 218 | 219 | 220 | ## Contributing 221 | 222 | Please read [CONTRIBUTING.md](https://github.com/rajatkb/Conference-Notify/blob/master/.github/CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us. 223 | 224 | 225 | ## 👨 Project Admin 226 | 227 | - Rajat Kanti Bhattacharjee

[](https://github.com/rajatkb) [](https://www.linkedin.com/in/rajatkb/)

228 | 229 | ## 👬 Mentors 230 | | Name | Point of Contact | 231 | | ------- | ------- | 232 | | Maham Arif |

[](https://github.com/MahamArif) [](https://www.linkedin.com/in/maham-arif/)

| 233 | | Anoop Singh |

[](https://github.com/anoopsingh1996) [](https://linkedin.com/in/anoopsingh1996)

| 234 | | Sagar Sehgal |

[](https://github.com/sagar-sehgal) [](https://www.linkedin.com/in/sagar-sehgal/)

| 235 | 236 | 237 | Feel free to ask your queries!! 🙌 238 | 239 | 240 | #### Contributors 241 | 242 | Waiting for some 🧐 243 | 244 | ## License 245 | 246 | This project is licensed under the GPL License - see the [LICENSE.md](https://github.com/rajatkb/Conference-Notify/blob/master/LICENSE) file for details 247 | 248 | ## Acknowledgement 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | A big thanks to GirlScript foundation for having this project under [GirlScript Summer of Code](https://www.gssoc.tech/) 257 | -------------------------------------------------------------------------------- /Scrapper-Service/.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | **/__pycache__ -------------------------------------------------------------------------------- /Scrapper-Service/README.md: -------------------------------------------------------------------------------- 1 | # SCRAPPER-SERVICE 2 | 3 | It's the service responsible for scrapping information about conferences and putting all these information into the db storage. Currently the default The `app.py` will be running as a daemon in a micro instance for filling information into a database. Current choice of db is Mongo. THe application can later be extended for different db and index service later. 4 | 5 | ## Dev Environment Requirement 6 | * Any MongoDB installation (ATLAS / Local) 7 | * Python 3.6+ 8 | 9 | ## Python packages required 10 | * pymongo driver 11 | * Beautiful Soup 12 | * Requests 13 | * logging (python default) 14 | * unittest (for unit test of course) 15 | * bs4 16 | * certifi 17 | * chardet 18 | * idna 19 | * linecache2 20 | * six 21 | * soupsieve 22 | * traceback2 23 | * urllib3 24 | * dill 25 | * html5lib 26 | 27 | ## Schema Used 28 | 29 | Conference 30 | | Field | Type | 31 | | ------------- | ------------- | 32 | | title | string | 33 | | url | string | 34 | | deadline | Date | 35 | | metadata | {Metadata} | 36 | | categories | Array | 37 | | dateRange | Array | 38 | | finalDue | string | 39 | | location | string | 40 | | notificationDue | Date | 41 | | bulkText | string | 42 | 43 | The fields used as index are : `url`, `title`, `deadline` and `categories`. 44 | 45 | ## Deploying the service 46 | 47 | ```shell 48 | 49 | >> cd Scrapper-Service/ 50 | 51 | >> python app.py --help 52 | usage: app.py [-h] [-c CONFIG] [-l {debug,warn,error,info}] [-t TEST] 53 | [-ls {console,file}] 54 | 55 | optional arguments: 56 | -h, --help show this help message and exit 57 | -c CONFIG, --config CONFIG 58 | Specify config.json file 59 | -l {debug,warn,error,info}, --log {debug,warn,error,info} 60 | Specify the debug level ,default: debug 61 | -t TEST, --test TEST Specify whether to test app initialization or run the 62 | scrappers ,default: True 63 | -ls {console,file}, --logStream {console,file} 64 | Specify whether to print logs on terminal or to file 65 | ,default: console 66 | (base) 67 | ``` 68 | 69 | ## Deploying Scraper service in different modes 70 | 71 | ```bash 72 | # running the scraper with a custom configuration file 73 | >> python app.py -c 74 | or 75 | >> python app.py --config 76 | 77 | # running the scraper service with logs of specific type 78 | >> python app.py -l debug # for only debug logs 79 | >> python app.py -l warn # for only warn logs 80 | >> python app.py -l error # for only error logs 81 | >> python app.py -l info # for only info logs 82 | 83 | # running the scraper service with different test modes 84 | >> python app.py --t False 85 | or 86 | >> python app.py --test False 87 | 88 | # running the scraper service with different logstream settings 89 | >> python app.py -ls console # for logging to console 90 | >> python app.py -ls file # to save logs to file 91 | ``` 92 | 93 | Jump to `demo.py` for implementing a scrapper from scratch and configuring it to run. 94 | 95 | ## Test 96 | 97 | 98 | Test whether the initialization is working or not 99 | 100 | 101 | ```shell 102 | 103 | >> python app.py -l debug -ls console --test True 104 | 105 | ``` 106 | 107 | Test whether the run is working or not 108 | 109 | 110 | ```shell 111 | 112 | >> python app.py -l debug -ls file --test False 113 | 114 | ``` 115 | 116 | We are focusing on doing test driven development, so downn the line things are not unpredicatable. 117 | 118 | Before deploying the current build , it's recommended that you run the test suite once. 119 | 120 | ```shell 121 | 122 | >> python -m unittest test -v 123 | test__getitem__ (test.metadata_test.MetadataTestCase) ... ok 124 | test__str__ (test.metadata_test.MetadataTestCase) ... ok 125 | test_data (test.metadata_test.MetadataTestCase) ... ok 126 | test_query_dict (test.metadata_test.MetadataTestCase) ... ok 127 | 128 | ---------------------------------------------------------------------- 129 | Ran 4 tests in 0.002s 130 | 131 | OK 132 | (base) 133 | 134 | 135 | ``` 136 | 137 | If this succeeds , you can move forward with deployment. If this fails. Please mark the build number and raise an issue. Althouth it should not happen because builds and PR is maintained by the author. But in case things blow up you know where to raise issue, 😉😉 138 | 139 | 140 | ## How to Contribute ? 141 | 142 | * Add new scrappers by implementing the interface Scrapper.py and add the new scrapper information inside the config.json file so that the app.py file can pick up the required class when starting 143 | 144 | * Provide better implementation changes to the base design of the service. 145 | 146 | * Follow up on the Issues tab for bugs and improvement requirements 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /Scrapper-Service/app.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import json 3 | import argparse 4 | import importlib 5 | import traceback 6 | import os 7 | from utility import str2bool , get_logger , print_start , LOG_STREAM_OPTIONS , LOG_LEVEL_OPTIONS 8 | from process import MultiProcessingContext 9 | 10 | 11 | ## 12 | # 13 | # Test whether the initialization is working or not 14 | # >> python app.py -l debug -ls console --test True 15 | # 16 | # Test whether the run is working or not 17 | # >> python app.py -l debug -ls file --test False 18 | # 19 | # 20 | 21 | parser = argparse.ArgumentParser() 22 | parser.add_argument( 23 | "-c", 24 | "--config", 25 | default="config.json", 26 | type=str, 27 | action="store", 28 | dest="config", 29 | help="Specify config.json file", 30 | ) 31 | parser.add_argument( 32 | "-l", 33 | "--log", 34 | default="debug", 35 | action="store", 36 | dest="log_level", 37 | choices= LOG_LEVEL_OPTIONS, 38 | help="Specify the debug level ,default: %(default)s", 39 | ) 40 | parser.add_argument( 41 | "-t", 42 | "--test", 43 | default=True, 44 | type=str2bool, 45 | action="store", 46 | dest="test", 47 | help="Specify whether to test app initialization or run the scrappers ,default: %(default)s", 48 | ) 49 | parser.add_argument( 50 | "-ls", 51 | "--logStream", 52 | default="console", 53 | type=str, 54 | action="store", 55 | dest="log_stream", 56 | choices= LOG_STREAM_OPTIONS, 57 | help="Specify whether to print logs on terminal or to file ,default: %(default)s", 58 | ) 59 | values = parser.parse_args() 60 | 61 | log_level = values.log_level.lower() 62 | log_stream = values.log_stream.lower() 63 | 64 | if log_level not in LOG_LEVEL_OPTIONS: 65 | raise ValueError( 66 | "Unsupported log level. Supported levels: debug , warn , info , error" 67 | ) 68 | if log_stream not in LOG_STREAM_OPTIONS: 69 | raise ValueError("Unsupported log stream. Supported levels: file , console") 70 | 71 | 72 | is_test = values.test 73 | 74 | CONFIG = values.config 75 | 76 | 77 | 78 | def parse_dbconfig(configuration): 79 | db_configuration = configuration["database"] 80 | path = db_configuration["plugin"]["filename"] 81 | classname = db_configuration["plugin"]["class"] 82 | module = importlib.import_module(path, ".") 83 | Database = module.__getattribute__(classname) 84 | return Database , db_configuration 85 | 86 | 87 | if __name__ == "__main__": 88 | 89 | with open(CONFIG) as file: 90 | configuration = json.load(file) 91 | 92 | Database_module , db_configuration = parse_dbconfig(configuration) 93 | 94 | ## reading logging configuration 95 | logging_configuration = configuration["logging"] 96 | log_folder = logging_configuration["output"] 97 | 98 | if not log_folder in os.listdir('.'): 99 | os.mkdir(log_folder) 100 | 101 | logger = get_logger(__name__, log_level, log_stream , log_folder) 102 | ## logger for main thread 103 | 104 | ## logger test in main thread 105 | print_start(logger) 106 | logger.info("Application started , Extracting all the plugins") 107 | 108 | 109 | ## handles creating mutiple process 110 | ## from single process using MultiProcessing 111 | 112 | 113 | import_list = configuration["plugins"] 114 | 115 | with MultiProcessingContext( log_level , log_stream , log_folder) as execute: 116 | 117 | for attr in import_list: 118 | path = attr["filename"] 119 | class_name = attr["class"] 120 | plugin_module = importlib.import_module(path, ".") 121 | scrapper = plugin_module.__getattribute__(class_name) 122 | 123 | try: 124 | 125 | if is_test: 126 | scrapper( log_level = log_level, 127 | log_stream = log_stream , 128 | log_folder = log_folder, 129 | database_module = Database_module, 130 | db_configuration = db_configuration 131 | ) 132 | else: 133 | execute( scrapper , log_level = log_level, 134 | log_stream = log_stream , 135 | log_folder = log_folder, 136 | database_module = Database_module, 137 | db_configuration = db_configuration 138 | ) 139 | 140 | except Exception as e: 141 | logger.error("{} execute failed for".format(class_name)) 142 | traceback.print_exception(type(e), e, e.__traceback__) 143 | logger.info("Scrapping done from all Scrapper plugins") 144 | -------------------------------------------------------------------------------- /Scrapper-Service/commons/__init__.py: -------------------------------------------------------------------------------- 1 | from .scrapper import Scrapper 2 | from .errors import PageParsingError 3 | from .database import Database -------------------------------------------------------------------------------- /Scrapper-Service/commons/database.py: -------------------------------------------------------------------------------- 1 | from abc import ABC , abstractclassmethod , abstractmethod , abstractproperty 2 | import logging 3 | from logging import Logger 4 | 5 | class Database: 6 | 7 | @abstractclassmethod 8 | def __init__( self , logger:Logger , database_name:str , collection_name:str , 9 | host:str ='localhost' , port:int=27017 , maxPoolSize:int = None , **kwargs): 10 | """ Database interface 11 | Arguments: 12 | logger {[logging]} -- logger passed by user 13 | database_name {[type]} -- name of database to be used 14 | collection_name {[type]} -- collection / table name for the db 15 | 16 | Keyword Arguments: 17 | host {str} -- host for db (default: {'localhost'}) 18 | port {int} -- port for db (default: {27017}) 19 | maxPoolSize {[type]} -- maxpoolsize for db (default: {None}) 20 | 21 | Raises: 22 | e: error when database connection fails. These are unhandled connection 23 | and the application must stop immeditely in such cases 24 | """ 25 | 26 | 27 | pass 28 | 29 | @abstractclassmethod 30 | def __del__(self): 31 | pass 32 | 33 | @abstractclassmethod 34 | def put(self , conference_data): 35 | pass -------------------------------------------------------------------------------- /Scrapper-Service/commons/errors.py: -------------------------------------------------------------------------------- 1 | class PageParsingError(ValueError): 2 | """[Raised when parsing fails] 3 | """ 4 | pass -------------------------------------------------------------------------------- /Scrapper-Service/commons/scrapper.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import datetime 3 | from utility import get_logger , print_start , AdaptiveRequest 4 | from datamodels import Conference, Metadata 5 | from abc import ABC , abstractclassmethod , abstractmethod , abstractproperty 6 | import requests 7 | 8 | class Scrapper(ABC): 9 | 10 | def get_date(self , string , fmt = "%b %d, %Y" ): 11 | """ Utility for converting date given string 12 | amd format of date time 13 | Arguments: 14 | string {[str]} -- date in string 15 | 16 | Keyword Arguments: 17 | fmt {str} -- formatter for date (default: {"%b %d, %Y"}) 18 | 19 | Returns: 20 | [datetime] -- time in datetime object format 21 | """ 22 | string = string.strip() 23 | try: 24 | return datetime.datetime.strptime(string , fmt ) 25 | except Exception as e: 26 | self.logger.warn("Bad string format error : {}".format(e)) 27 | return string 28 | 29 | 30 | 31 | 32 | def __init__(self , context_name , log_level , log_stream , log_folder , database_module , db_configuration , **kwargs): 33 | self.logger = get_logger(context_name , log_level , log_stream , log_folder) 34 | print_start(self.logger) 35 | self.db = database_module(self.logger , **db_configuration) 36 | self.logger.info("{} setup complete !!".format(context_name)) 37 | self.arequest = AdaptiveRequest() 38 | 39 | if self.db != None: 40 | self.push_todb = self.db.put 41 | else: 42 | raise ValueError("No database parameter given") 43 | 44 | def get_page(self, qlink , debug_msg = "failed to extract page", **kwargs): 45 | """[summary] 46 | 47 | Arguments: 48 | qlink {[str]} -- link to request 49 | 50 | Keyword Arguments: 51 | debug_msg {str} -- debug log message for failing (default: {"failed to extract page"}) 52 | 53 | Raises: 54 | requests.HTTPError: if page not found 55 | requests.Timeout: if no reponse from server , default is 1sec 56 | Returns: 57 | [request] -- request page 58 | """ 59 | req = self.arequest.get(qlink, **kwargs) 60 | if 200 <= req.status_code <=299: 61 | self.logger.debug(debug_msg) 62 | else: 63 | raise requests.HTTPError 64 | return req 65 | 66 | 67 | def run(self): 68 | """ [run function] 69 | to be called by the main.py and is not to be 70 | extended or reimplemented. Run contains necessary runtime 71 | methods for making sure the the user implemented method gets called 72 | """ 73 | self.logger.info("Scrapper routine started !!") 74 | self.parse_action() 75 | self.logger.info("Scrapper routine done !!") 76 | 77 | 78 | @abstractclassmethod 79 | def parse_action(cls): 80 | """[parse_action] 81 | TO BE IMPLEMENTED BY THE USER 82 | The function is intended to return a iterator 83 | of the objects of Conference() 84 | the dbaction passes in the database push function , so that 85 | implementor can decide for themselves. One can also implement this method 86 | using Ayncio / Thread / MultiProcessing 87 | 88 | """ -------------------------------------------------------------------------------- /Scrapper-Service/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "database" : { 3 | "host":"localhost", 4 | "port":27017, 5 | "database_name":"Conference_Notify", 6 | "collection_name":"conferences", 7 | "plugin":{ 8 | "filename":"database.mdb", 9 | "class":"MongoDatabase" 10 | } 11 | 12 | }, 13 | "logging":{ 14 | "output":"logs" 15 | }, 16 | "plugins":[ 17 | { "filename":"plugins.wikicfp", 18 | "class":"WikiCfpScrapper" 19 | }, 20 | { 21 | "filename":"plugins.demo", 22 | "class":"DemoScrapper" 23 | }, 24 | { 25 | "filename": "plugins.guide2research", 26 | "class": "Guide2ResearchScrapper" 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /Scrapper-Service/database/__init__.py: -------------------------------------------------------------------------------- 1 | from .mdb import MongoDatabase -------------------------------------------------------------------------------- /Scrapper-Service/database/mdb.py: -------------------------------------------------------------------------------- 1 | from pymongo import MongoClient 2 | import pymongo 3 | import logging 4 | from logging import Logger 5 | from datamodels import Conference, Metadata 6 | from commons import Database 7 | import time 8 | 9 | class MongoDatabase(Database): 10 | 11 | def __init__( self , logger:Logger , database_name:str , collection_name:str , 12 | host:str ='localhost' , port:int=27017 , maxPoolSize:int = None , **kwargs): 13 | """Mongo database object 14 | Arguments: 15 | logger {[logging]} -- logger passed by user 16 | database_name {[type]} -- name of database to be used 17 | collection_name {[type]} -- collection for the mongodb db 18 | 19 | Keyword Arguments: 20 | host {str} -- host for mongodb (default: {'localhost'}) 21 | port {int} -- port for mongodb (default: {27017}) 22 | maxPoolSize {[type]} -- maxpoolsize for mongo db (default: {None}) 23 | 24 | Raises: 25 | e: error when database connection fails. These are unhandled connection 26 | and the application must stop immeditely in such cases 27 | """ 28 | self.logger = logger 29 | try: 30 | self.logger.debug("Using Database name {}".format(database_name)) 31 | self.logger.debug("Using address {}:{}".format(host , port)) 32 | client = MongoClient(host , int(port) , maxPoolSize = maxPoolSize) 33 | self.client = client 34 | db = client[database_name] ## Create a new database if not existing 35 | ## 36 | ## Quirks of pymongo client , any error from this statement below 37 | ## leads to unsuported operation for database , where as intended 38 | ## strcuture is a collection. Should be addressed in the pymongo 39 | # self.logger.debug("Using Collection name {}".format("conferences")) 40 | collection = db[collection_name] 41 | client.server_info() 42 | self.logger.info("Succefully created mongodb client connection on host:{} , port:{} ".format(host , port)) 43 | self.db = db 44 | self.collection = collection 45 | index_info = collection.index_information() 46 | possible_index = Conference.index() # -> [(string,bool)] 47 | possible_index = filter(lambda x: (x[0]+"_1") not in index_info , possible_index) 48 | for idx , unique in possible_index: 49 | collection.create_index([(idx , pymongo.ASCENDING )] , unique = unique ) 50 | 51 | except Exception as e: 52 | self.logger.error("Failed to initiate mongodb client error: {}".format(e)) 53 | raise e 54 | 55 | 56 | def __del__(self): 57 | self.logger.info("Closing connection to mongodb !!") 58 | self.client.close() 59 | self.logger.info("Succesfully Closed connection to mongodb !!") 60 | 61 | def put(self , conference_data): 62 | res = 0 63 | if not isinstance(conference_data , Conference): 64 | raise ValueError("Provided data is not in proper format as required by db") 65 | else: 66 | _id = conference_data._id 67 | try: 68 | res = self.collection.update_one( {'_id':_id,'deadline':{'$lte':conference_data.querydata['deadline']}} ,conference_data.get_query(), upsert = True) 69 | self.logger.debug(""" Value inserted message matched count: {} modified count: {} upserted id: {}""" 70 | .format(res.matched_count , res.modified_count , res.upserted_id)) 71 | return res 72 | except Exception as e: 73 | self.logger.error("Failed to commit data error : {}".format(e)) 74 | print("Failed to commit data error : {}".format(e)) 75 | 76 | 77 | -------------------------------------------------------------------------------- /Scrapper-Service/datamodels/__init__.py: -------------------------------------------------------------------------------- 1 | from .conference import Conference 2 | from .metadata import Metadata -------------------------------------------------------------------------------- /Scrapper-Service/datamodels/conference.py: -------------------------------------------------------------------------------- 1 | from .metadata import Metadata 2 | import datetime 3 | import uuid 4 | 5 | class Conference: 6 | 7 | @staticmethod 8 | def index(): 9 | """Get fields for indexing 10 | Returns: 11 | List[(String , Boolean)] -- List of fields that should be indexed along with should it be unique or not. 12 | """ 13 | return [('url' , False) , ('deadline' , False) , ('title' , False) , ('categories' , False)] 14 | 15 | def __init__(self, title , url , deadline , metadata, **kwargs): 16 | """[Conference class] 17 | Used for modeling the data of conferences 18 | 19 | Arguments: 20 | title {[string]} -- title of conference 21 | url {[string]} -- url of conference 22 | deadline {[datetime , string]} -- submission deadline 23 | metadata {Metadata} -- contains meta information 24 | 25 | **kwargs 26 | dateRange : array[datetime , datetime] 27 | location: string 28 | notificationDue: datetime 29 | finalDue: datetime 30 | categories: array[string] 31 | bulkText: string 32 | """ 33 | ## Cleaning title text 34 | title = title.split(" ") 35 | title = list(map(lambda x: x.strip() , title)) 36 | title = " ".join(title) 37 | self.title = title 38 | self.url = url.strip() 39 | 40 | if not isinstance(deadline , datetime.datetime): 41 | raise ValueError("deadline is not datetime , deadline value: {}".format(deadline)) 42 | 43 | if not isinstance(metadata , Metadata): 44 | raise ValueError("metadata passed is not instance of the Metadata data model") 45 | 46 | self.deadline = deadline 47 | 48 | self.querydata = kwargs 49 | self.querydata["title"] = title 50 | self.querydata["url"] = url 51 | self.querydata["deadline"] = deadline 52 | self.querydata.update(metadata.get_query()) 53 | ## Db compatibility 54 | self._id = self.__generate_uuid() 55 | ## A conference is bound to have unique link 56 | self.querydata['_id'] = self._id 57 | 58 | categories = self.querydata.pop("categories",None) 59 | 60 | self.query = {"$set": self.querydata} 61 | 62 | if categories is not None: 63 | category_query = {"categories":{"$each":categories}} 64 | self.query["$addToSet"]=category_query 65 | 66 | 67 | def __generate_uuid(self): 68 | return str(uuid.uuid5(uuid.NAMESPACE_URL,self.url)) 69 | 70 | def get_query(self): 71 | """Returns the query dictionary usable for update and upsert 72 | Returns: 73 | [dictionary] -- [dictionary appropriate for using in update statement for inserting data in mongo] 74 | """ 75 | return self.query 76 | 77 | def __str__(self): 78 | return str(self.querydata) 79 | 80 | def __getitem__(self , attr): 81 | return self.querydata[attr] 82 | 83 | -------------------------------------------------------------------------------- /Scrapper-Service/datamodels/metadata.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | class Metadata: 4 | def __init__(self , worker , date_extracted , website_url , domain_url , domain_name , **kwargs): 5 | """Container for meta information inserted by the parser 6 | 7 | Arguments: 8 | worker {[string]} -- current worker thread 9 | date_extracted {[type]} -- which date information was extracted 10 | website_url {[type]} -- the url of the page 11 | domain {[type]} -- domain address 12 | """ 13 | if not isinstance(date_extracted , datetime.datetime): 14 | raise ValueError("deadline is not datetime , deadline value: {}".format(date_extracted)) 15 | self.worker = worker 16 | self.key = "metadata."+worker 17 | self.querydata = { 18 | self.key: { 19 | "dateExtracted":date_extracted, 20 | "websiteUrl":website_url, 21 | "website":domain_url, 22 | "domain":domain_name 23 | } 24 | } 25 | self.querydata[self.key].update(kwargs) 26 | 27 | 28 | def data(self): 29 | return self.querydata[self.key] 30 | 31 | def get_query(self): 32 | return self.querydata 33 | 34 | def __str__(self): 35 | return str(self.querydata) 36 | 37 | def __getitem__(self , attr): 38 | return self.querydata[self.key][attr] -------------------------------------------------------------------------------- /Scrapper-Service/plugins/demo.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import logging 3 | from bs4 import BeautifulSoup 4 | from commons import Scrapper 5 | from datamodels import Conference , Metadata 6 | import datetime 7 | 8 | # Once you are done implementing below 9 | # you can move to config.json and change settings like this 10 | # ........ 11 | # ............. 12 | # 13 | # "load":[ 14 | # { "filename":"Scrappers.wikicfp", 15 | # "class":"WikiCfpScrapper" 16 | # }, 17 | # { 18 | # "filename":"Scrappers.demo", 19 | # "class":"DemoScrapper" 20 | # } 21 | # ..... 22 | # ] 23 | 24 | 25 | class DemoScrapper(Scrapper): 26 | 27 | def __init__(self , **config): 28 | """Demo scrapper to demonstrate how to create Scrapper 29 | 30 | Arguments: 31 | **config : holds configuration to be passed to super 32 | """ 33 | super().__init__(context_name =__name__ , **config) 34 | self.logger.info("Demo initialized !!!") 35 | pass 36 | ## Initialize 37 | 38 | def __del__(self): 39 | self.logger.info("{} done scrapping !!!".format(__name__)) 40 | 41 | def parse_action(self): 42 | 43 | meta = Metadata(__name__ , datetime.datetime.now() , 44 | website_url="somename.co.in/link" , 45 | domain_url="somename.co.in" , 46 | domain_name="somename" , **{ "extra":"info you want to keep"} ) 47 | 48 | data = Conference(**{ "title": "" , "url": "" , 49 | "deadline":datetime.datetime.now() , "metadata":meta}) 50 | 51 | ## There are other optional fields also for conference 52 | ## check out the docstring 53 | ## Once done you can call dbaction 54 | 55 | ## Use the already provided method from Scrapper class like 56 | ## getDate , getPage etc. 57 | ## They are tested methods and have lesser chance of breaking your code. 58 | 59 | # self.getPage(" -- some page link --" , " -- some debug message --") 60 | # 61 | # PARSE DATA 62 | # 63 | # self.push_todb(data) 64 | 65 | 66 | self.logger.info("Yay !! data was put into db hopefully !! Check error logs , i.e run with log level error") 67 | # Remember this function will only be called once by the 68 | # run method of parent class so implement your loop inside here 69 | # and call the dbaction to put the data. 70 | 71 | -------------------------------------------------------------------------------- /Scrapper-Service/plugins/wikicfp.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import logging 3 | from bs4 import BeautifulSoup 4 | from commons import Scrapper,PageParsingError 5 | from datamodels import Conference , Metadata 6 | import datetime 7 | 8 | 9 | class WikiCfpScrapper(Scrapper): 10 | def __init__(self , **kwargs): 11 | super().__init__( context_name = __name__ , **kwargs) 12 | self.base_address = "http://www.wikicfp.com" 13 | self.site_name = "wikicfp" 14 | self.logger.info("{} initialized !!!".format(__name__)) 15 | 16 | def __del__(self): 17 | self.logger.info("{} done scrapping !!!".format(__name__)) 18 | 19 | def extract_and_put(self ,linkSet:set , category:str , link:str): 20 | base_address= self.base_address 21 | for name , clink in self.iterate_links(category , link): 22 | if clink in linkSet: 23 | continue 24 | try: 25 | qlink = base_address + clink 26 | req = self.get_page(qlink , "Page extracted for conference : {} , category : {} , link: {} extracted".format(name ,category, clink)) 27 | try: 28 | conference_data = self.parse_conference_page_info(req.content , qlink ) 29 | ## Error from DB insertion should not be handled 30 | ## Since this means there is fault in data or connection 31 | ## Process must be restarted 32 | self.push_todb(conference_data) 33 | linkSet.add(clink) 34 | totalLink = len(linkSet) 35 | self.logger.debug("Total unique conference links till now :{}".format(totalLink)) 36 | if totalLink % 500 == 0: 37 | self.logger.info("Total unique conference links till now :{}".format(totalLink)) 38 | 39 | except Exception as e: 40 | self.logger.error("Error when parsing link: {} exception: {}".format(clink, e)) 41 | except requests.HTTPError as e: 42 | self.logger.error("Error when requesting html failed :{}".format(e)) 43 | except requests.Timeout as e: 44 | self.logger.error("Timeout when requesting html : {}".format(e)) 45 | except Exception as e: 46 | self.logger.error("Error occured where requesting html :{}".format(e)) 47 | 48 | 49 | 50 | def parse_action(self): 51 | linkSet = set() 52 | try: 53 | c_list = self.category_list() 54 | for category ,link in c_list: 55 | self.extract_and_put(linkSet , category , link) 56 | except Exception as e: 57 | self.logger.error("Failed at extracting category page of {} error: {}".format(self.base_address , e)) 58 | 59 | 60 | def category_list(self ): 61 | base_address = self.base_address 62 | site_name = self.site_name 63 | req = self.get_page("{}/cfp/allcat?sortby=1".format(base_address), "{} category page extracted".format(site_name)) # getting page listed in specific order 64 | 65 | page_dom = BeautifulSoup(req.content , 'html.parser') 66 | table_container = page_dom.find(attrs={"class":"contsec"}) ## table in the page 67 | if table_container is None: 68 | self.logger.error("{} no element contsec at category page".format(site_name)) 69 | raise self.PageParsingError("{} no element contsec at category page".format(site_name)) 70 | 71 | anchors = table_container.select("tr td a") 72 | if len(anchors) == 0: 73 | self.logger.error("{} no anchor found".format(site_name)) 74 | raise self.PageParsingError("{} no anchor found".format(site_name)) 75 | 76 | anchors = anchors[1:] 77 | links = map(lambda anchor: (anchor.text, anchor["href"]) , anchors) 78 | links = sorted(links , key = lambda link: link[0]) 79 | return links 80 | 81 | def next_anchor(self , base_address:str , category:str , link:str): 82 | """ Gives the next anchor from a particular page to the next page 83 | Also parses the page for relevant information and gives the table container 84 | 85 | Arguments: 86 | base_address {str} -- the anchor link to visit 87 | category {str} -- the category we are working with . used for logging 88 | link {str} -- the link used for extraction 89 | 90 | Raises: 91 | self.PageParsingError: When is fails to parse 92 | 93 | Returns: 94 | [(BeautifulSoup, str)] 95 | """ 96 | 97 | req = self.get_page(base_address+link , "{} : {} page extracted".format(category , link)) 98 | page_dom = BeautifulSoup(req.content , 'html.parser') 99 | page_dom = page_dom.find(attrs={"class":"contsec"}) 100 | page_dom = page_dom.find(name = "center") 101 | 102 | table_container = page_dom.find(name = "form" , attrs={"name":"myform"} , recursive= False) 103 | table_container = table_container.find(name = "table" , recursive = False) 104 | if table_container is None: 105 | self.logger.error("{} no element form ".format(category)) 106 | raise self.PageParsingError("{} no element form ".format(category)) 107 | trs = table_container.find_all("tr" , recursive = False) 108 | content = trs[-2] 109 | tr = trs[-1] 110 | anchors = tr.find_all("a") 111 | anchor = list(filter(lambda x: x.text == "next" ,anchors))[0] 112 | return (content,anchor) 113 | 114 | 115 | def iterate_pages(self, base_address:str , category:str , link:str): 116 | count = 0 117 | try: 118 | curr_table_container , next_a = self.next_anchor(base_address , category , link) 119 | count = count + 1 120 | self.logger.debug("{} category link \" {} \" extracted ".format(category , link)) 121 | except Exception as e: 122 | self.logger.warn("No. of link extracted: {} , Could not extract anchor information for category {} page {} error:{}" 123 | .format(count,category , link , e)) 124 | return 125 | yield curr_table_container 126 | prev_link = link 127 | link = next_a["href"] 128 | 129 | while True: 130 | try: 131 | curr_table_container , next_a= self.next_anchor(base_address , category , link) 132 | count = count + 1 133 | self.logger.debug("{} category link \" {} \" extracted ".format(category , link)) 134 | except Exception as e: 135 | self.logger.warn("No. of link extracted: {} , Could not extract anchor information for category {} page {} error:{}" 136 | .format(count,category , link , e)) 137 | break 138 | yield curr_table_container 139 | link = next_a["href"] 140 | if prev_link == link and prev_link is not None: 141 | break 142 | prev_link = link 143 | 144 | def iterate_categories(self ,category:str , link:str , base_address:str): 145 | try: 146 | self.logger.info("starting with {} category ".format(category)) 147 | yield from self.iterate_pages(base_address , category , link) 148 | self.logger.info("done with {} category ".format(category)) 149 | except Exception as e: 150 | self.logger.warn("Failed to extract complete data from {} starting at {} exception:{} " 151 | .format(category , link , e)) 152 | 153 | def iterate_links(self ,category:str , link:str): 154 | base_address = self.base_address 155 | for parsed_pages in self.iterate_categories(category , link , base_address): 156 | links = parsed_pages.select("a") 157 | links = map(lambda x: (x.text , x["href"]) , links) 158 | for link in links: 159 | yield link 160 | 161 | 162 | 163 | def extract_info(self , page_dom): 164 | info_table = page_dom.select("table.gglu") 165 | list_info_title = map(lambda x: x.text.strip() , info_table[0].select("th")) 166 | list_info_content = map(lambda x:x.text.strip() , info_table[0].select("td")) 167 | info = {"dateRange":"" , "location":"" , "deadline":"" , "notificationDue":"" , "finalDue":""} 168 | for title , content in zip(list_info_title , list_info_content): 169 | if title == "When": 170 | dates = list(map( lambda x: self.get_date(x) , content.split("-"))) 171 | info["dateRange"] = dates 172 | elif title == "Where": 173 | info["location"] = content 174 | elif title == "Submission Deadline": 175 | info["deadline"] = self.get_date(content) 176 | elif title == "Notification Due": 177 | info["notificationDue"] = self.get_date(content) 178 | elif title == "Final Version Due": 179 | info["finalDue"] = self.get_date(content) 180 | return info 181 | 182 | def extract_categories(self , page_dom:BeautifulSoup): 183 | anchor_list = page_dom.select("table.gglu tr td a") 184 | anchor_list = map(lambda x: x.text , anchor_list[1:]) 185 | return list(anchor_list) 186 | 187 | def create_metadata(self, website_url , domain_url , domain_name , **kwargs): 188 | return Metadata(__name__ , datetime.datetime.now() , website_url , domain_url , domain_name , **kwargs) 189 | 190 | def parse_conference_page_info(self , page:str , qlink): 191 | page_dom = BeautifulSoup(page , 'html.parser') 192 | title = page_dom.find(name = "span" , attrs={"property":"v:description"}).text 193 | url = page_dom.find(name = "a" , attrs={"target":"_newtab"})["href"] 194 | info = self.extract_info(page_dom) 195 | if "deadline" not in info: 196 | raise ValueError("Deadline is a mandatory field, could not parse the page") 197 | categories = self.extract_categories(page_dom) 198 | bulk_text = "" 199 | try: 200 | qresult = page_dom.select("div.cfp") 201 | bulk_text = qresult[0].text 202 | except Exception as e: 203 | self.logger.warn("Failed to parse bulk text information css query result: {} error : {} ".format(qresult, e)) 204 | 205 | metadata = self.create_metadata(qlink , self.base_address , self.site_name ) 206 | 207 | return Conference( **{ "title":title , "url":url , 208 | "categories":categories , "bulkText":bulk_text , "metadata":metadata} , **info ) 209 | 210 | 211 | 212 | 213 | -------------------------------------------------------------------------------- /Scrapper-Service/process/__init__.py: -------------------------------------------------------------------------------- 1 | from .mutiprocessing import MultiProcessingContext -------------------------------------------------------------------------------- /Scrapper-Service/process/mutiprocessing.py: -------------------------------------------------------------------------------- 1 | from utility import get_logger , print_start 2 | from multiprocessing import Process 3 | import dill 4 | 5 | def dill_encode(runnable): 6 | runnable_serialized = dill.dumps(runnable) 7 | return runnable_serialized 8 | 9 | def dill_decode_run(runnable_serialized): 10 | runnable_class , args = dill.loads(runnable_serialized) 11 | try: 12 | runnable_obj = runnable_class(**args) 13 | runnable_obj.run() 14 | except Exception as e: 15 | print("[dill routine]: Runnable failed in runtime due to error raised {}".format(e)) 16 | 17 | 18 | 19 | class MultiProcessingContext: 20 | 21 | def __init__(self , log_level , log_stream , log_folder="logs"): 22 | """[Multi Processing class] 23 | Responsible for running the lambda functions passed in 24 | inside threads 25 | Arguments: 26 | log_level {[string]} -- Levels of log for each process 27 | log_stream {[string]} -- Stream of log for each process 28 | 29 | """ 30 | self.logger = get_logger(__name__, log_level, log_stream , log_folder) 31 | self.process_list=[] 32 | 33 | def __execute__(self , runnable , **kwargs): 34 | """Start execution of MultiProcessingContext 35 | Returns: 36 | None 37 | """ 38 | self.logger.info(''' 39 | Thread Process initiated 40 | ''') 41 | ## start process for each call 42 | 43 | serialized = dill_encode((runnable , kwargs)) 44 | p= Process(target= dill_decode_run , args=(serialized,)) 45 | ## append the each process in list 46 | self.process_list.append(p) 47 | ## start calling process 48 | p.start() 49 | 50 | def __enter__(self): 51 | return self.__execute__ 52 | 53 | def __exit__(self , exception_type, exception_value, traceback): 54 | ## job of each process is completed 55 | for process in self.process_list: 56 | process.join() 57 | 58 | -------------------------------------------------------------------------------- /Scrapper-Service/requirements.txt: -------------------------------------------------------------------------------- 1 | beautifulsoup4==4.8.2 2 | bs4==0.0.1 3 | certifi==2019.11.28 4 | chardet==3.0.4 5 | idna==2.9 6 | linecache2==1.0.0 7 | pymongo==3.10.1 8 | requests==2.23.0 9 | six==1.14.0 10 | soupsieve==1.9.5 11 | traceback2==1.4.0 12 | unittest2==1.1.0 13 | urllib3==1.25.8 14 | dill==0.3.1.1 15 | html5lib==1.0.1 16 | -------------------------------------------------------------------------------- /Scrapper-Service/test/__init__.py: -------------------------------------------------------------------------------- 1 | from .metadata_test import MetadataTestCase 2 | from .conference_test import ConferenceTestCase 3 | from .mongodb_test import MongoDbTestCase -------------------------------------------------------------------------------- /Scrapper-Service/test/conference_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from datamodels import Conference 3 | from datamodels import Metadata 4 | from datetime import datetime 5 | 6 | 7 | class ConferenceTestCase(unittest.TestCase): 8 | def __init__(self, methodName): 9 | super().__init__(methodName) 10 | 11 | def get_valid_data(self): 12 | return Conference( title="something", 13 | url="anything", deadline=datetime.now(), 14 | metadata=Metadata(__name__, datetime.now(), "something.com/something", "something.com","anythingProd" )) 15 | 16 | def get_invalid_data(self): 17 | return Conference( title="something", 18 | url="anything", deadline="anything", 19 | metadata="anything") 20 | 21 | def test_valid_data(self): 22 | self.get_valid_data() 23 | 24 | def test_invalid_data(self): 25 | success = True 26 | try: 27 | self.get_invalid_data() 28 | success = False 29 | except Exception as e: 30 | print("Object initialisation failed successfully ",e) 31 | finally: 32 | self.assertEqual(success , True) 33 | 34 | 35 | if __name__ == '__main__': 36 | unittest.main() 37 | -------------------------------------------------------------------------------- /Scrapper-Service/test/metadata_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from datamodels import Metadata 3 | from datetime import datetime 4 | # Scrapper-Service/datamodels 5 | 6 | class MetadataTestCase(unittest.TestCase): 7 | def __init__(self, methodName): 8 | super().__init__(methodName) 9 | 10 | def get_valid_data(self): 11 | return Metadata(__name__, datetime.now(), "something.com/something", "something.com","anythingProd" ) 12 | 13 | def get_invalid_data(self): 14 | return Metadata(__name__, "anything", "something.com/something", "something.com","anythingProd" ) 15 | 16 | 17 | def test_valid_data(self): 18 | self.get_valid_data() 19 | 20 | def test_invalid_data(self): 21 | success = True 22 | try: 23 | self.get_invalid_data() 24 | success = False 25 | except Exception as e: 26 | print("Object initialisation failed successfully ",e) 27 | finally: 28 | self.assertEqual(success , True , "Metadata taking in bad datetime value, can pass string in it") 29 | 30 | 31 | 32 | if __name__ == '__main__': 33 | unittest.main() -------------------------------------------------------------------------------- /Scrapper-Service/test/mongodb_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import json 3 | import warnings 4 | from datamodels import Conference 5 | from datamodels import Metadata 6 | from database import mdb 7 | from datetime import datetime 8 | from datetime import timedelta 9 | from logging import Logger 10 | 11 | class MongoDbTestCase(unittest.TestCase): 12 | def __init__(self, methodName): 13 | super().__init__(methodName) 14 | self.mongo_db = mdb.MongoDatabase(Logger,"Conference_Notify","conferences") 15 | 16 | def test_insert_datetime(self): 17 | self.assertDictEqual.__self__.maxDiff = None 18 | with warnings.catch_warnings(): 19 | warnings.simplefilter("ignore") 20 | conf = Conference(title="Something", 21 | url="www.something.com", 22 | deadline=datetime.now(), 23 | metadata= Metadata("something", 24 | datetime.now(), 25 | "www.something.com", 26 | "www.something.com", 27 | "something" 28 | ), 29 | dateRange = [datetime.now(),datetime.now()+timedelta(days=10)], 30 | finalDue = datetime.now(), 31 | location = "something", 32 | categories = ["something"], 33 | bulkText = "somthing" 34 | ) 35 | 36 | flag = None 37 | is_inserted = self.mongo_db.put(conf) 38 | if(is_inserted !=None): 39 | flag = True 40 | else: 41 | flag = False 42 | 43 | self.assertEqual(True,flag) 44 | 45 | def test_valid_datetime(self): 46 | self.assertDictEqual.__self__.maxDiff = None 47 | with warnings.catch_warnings(): 48 | warnings.simplefilter("ignore") 49 | conf = Conference(title="Something", 50 | url="www.something.com", 51 | deadline=datetime.now()+timedelta(days=10), 52 | metadata= Metadata("something", 53 | datetime.now(), 54 | "www.something.com\somthing.html", 55 | "www.something.com", 56 | "something" 57 | ), 58 | dateRange = [datetime.now(),datetime.now()+timedelta(days=10)], 59 | finalDue = datetime.now(), 60 | location = "something", 61 | categories = ["something"], 62 | bulkText = "somthing" 63 | ) 64 | 65 | flag = None 66 | is_inserted = self.mongo_db.put(conf) 67 | if(is_inserted !=None): 68 | flag = True 69 | else: 70 | flag = False 71 | 72 | self.assertEqual(True,flag) 73 | 74 | def test_invalid_datetime(self): 75 | self.assertDictEqual.__self__.maxDiff = None 76 | with warnings.catch_warnings(): 77 | warnings.simplefilter("ignore") 78 | conf = Conference(title="Something", 79 | url="www.something.com", 80 | deadline=datetime.now()-timedelta(days=10), 81 | metadata= Metadata("something", 82 | datetime.now(), 83 | "www.something.com\somthing.html", 84 | "www.something.com", 85 | "something" 86 | ), 87 | dateRange = [datetime.now(),datetime.now()-timedelta(days=10)], 88 | finalDue = datetime.now(), 89 | location = "something", 90 | categories = ["something"], 91 | bulkText = "somthing" 92 | ) 93 | 94 | is_inserted = self.mongo_db.put(conf) 95 | self.assertEqual(None,is_inserted) 96 | 97 | if __name__ == '__main__': 98 | unittest.main() 99 | -------------------------------------------------------------------------------- /Scrapper-Service/utility/__init__.py: -------------------------------------------------------------------------------- 1 | from .argument import str2bool 2 | from .logger import * 3 | from .adapative_request import AdaptiveRequest -------------------------------------------------------------------------------- /Scrapper-Service/utility/adapative_request.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import math 3 | 4 | class AdaptiveRequest: 5 | def __init__(self): 6 | self.max_wait_time = 10 7 | self.num_fail = 0 8 | self.num_success = 0 9 | def get(self , link, **kwargs ): 10 | try: 11 | res = requests.get(link , timeout = self.max_wait_time, **kwargs) 12 | self.num_success +=1 13 | return res 14 | except (requests.HTTPError , requests.ConnectionError) as err: 15 | self.num_fail= self.num_fail+1 16 | if self.num_fail != self.num_success: 17 | self.max_wait_time = math.pow( 10 + 1/(self.num_success - self.num_fail) , self.num_fail) 18 | else: 19 | self.max_wait_time += 1 20 | raise err -------------------------------------------------------------------------------- /Scrapper-Service/utility/argument.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | def str2bool(v): 3 | if isinstance(v, bool): 4 | return v 5 | if v.lower() in ('yes', 'true', 't', 'y', '1'): 6 | return True 7 | elif v.lower() in ('no', 'false', 'f', 'n', '0'): 8 | return False 9 | else: 10 | raise argparse.ArgumentTypeError('Boolean value expected.') 11 | 12 | 13 | ## TO-DO Move the arg parsing to this Argparsing class 14 | ## Specific to our project 15 | class ArgumentParsing: 16 | def __init__(self): 17 | pass 18 | 19 | -------------------------------------------------------------------------------- /Scrapper-Service/utility/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | LOG_STREAM_OPTIONS = ["console" , "file"] 4 | 5 | LOG_LEVEL_DEFAULTS = { 6 | "debug": logging.DEBUG, 7 | "warn": logging.WARN, 8 | "info": logging.INFO, 9 | "error": logging.ERROR, 10 | } 11 | 12 | LOG_LEVEL_OPTIONS = LOG_LEVEL_DEFAULTS.keys() 13 | 14 | def get_logger(context_name , log_level , log_stream_option , log_folder="logs"): 15 | 16 | if log_stream_option == "file": 17 | log_stream = logging.FileHandler( "{}/{}.log".format(log_folder , context_name) ) 18 | elif log_stream_option == "console": 19 | log_stream = logging.StreamHandler() 20 | else: 21 | raise ValueError("Bad log stream option given , legal values : {}".format(LOG_STREAM_OPTIONS)) 22 | 23 | if not log_level in LOG_LEVEL_OPTIONS: 24 | raise ValueError("Bad log level value given , legal values :{}".format(LOG_STREAM_OPTIONS)) 25 | log_level = LOG_LEVEL_DEFAULTS[log_level] 26 | 27 | logger = logging.getLogger(context_name) 28 | logger.setLevel(log_level) 29 | log_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 30 | log_stream.setFormatter(log_format) 31 | logger.addHandler(log_stream) 32 | # Works as a separator , more readable logs 33 | return logger 34 | 35 | def print_start(logger): 36 | logger.info(''' 37 | 38 | _________ .__ __ __ 39 | / _____/ ______________ _|__| ____ ____ _______/ |______ ________/ |_ 40 | \_____ \_/ __ \_ __ \ \/ / |/ ___\/ __ \ / ___/\ __\__ \ \_ __ \ __| 41 | / \ ___/| | \/\ /| \ \__\ ___/ \___ \ | | / __ \| | \/| | 42 | /_______ /\___ >__| \_/ |__|\___ >___ > /____ > |__| (____ /__| |__| 43 | \/ \/ \/ \/ \/ \/ 44 | 45 | ''') -------------------------------------------------------------------------------- /Scrapper-Service/utility/setup.py: -------------------------------------------------------------------------------- 1 | ## TO-DO 2 | # Move configuration parsing 3 | # from app.py to this utlility class 4 | # It does setup and create any setup like 5 | # 1. Reading class loading list return generator for same 6 | # 2. Extracting db configuration 7 | # ///////// Any future setup and tidy task //////////// 8 | class Setup: 9 | def __init__(self , *args , **kwargs): 10 | pass -------------------------------------------------------------------------------- /Search-Service/.env: -------------------------------------------------------------------------------- 1 | MONGO_DB_HOST=localhost 2 | MONGO_DB_PORT=27017 3 | MONGO_DB_NAME=Conference_Notify 4 | ES_HOST=localhost 5 | ES_PORT-9200 6 | SERVER_PORT=3020 7 | LOG_FOLDER=logs 8 | LOG_LEVEL=debug -------------------------------------------------------------------------------- /Search-Service/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | logs -------------------------------------------------------------------------------- /Search-Service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "search-service", 3 | "version": "1.0.0", 4 | "description": "responsible for integrating search service with ES backend", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "tsc", 9 | "start": "ts-node-dev --respawn src/server.ts", 10 | "run": "node build/server.js" 11 | }, 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "@types/dotenv-safe": "^8.1.0", 16 | "dotenv": "^8.2.0", 17 | "dotenv-safe": "^8.2.0", 18 | "express": "^4.17.1", 19 | "mongoose": "^5.9.3", 20 | "winston": "^3.2.1" 21 | }, 22 | "devDependencies": { 23 | "@types/express": "^4.17.2", 24 | "@types/mongoose": "^5.7.3", 25 | "@types/node": "^13.7.6", 26 | "ts-node": "^8.6.2", 27 | "ts-node-dev": "^1.0.0-pre.44", 28 | "typescript": "^3.8.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Search-Service/src/app.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/Search-Service/src/app.ts -------------------------------------------------------------------------------- /Search-Service/src/server.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from 'dotenv-safe'; 2 | /* 3 | Load the .env file into the process environment variable scope 4 | It's necessary to keep a .env file in the project root 5 | along side package.json 6 | */ 7 | dotenv.config({ 8 | example: './.env' 9 | }); 10 | -------------------------------------------------------------------------------- /Search-Service/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 7 | // "lib": [], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | "outDir": "./build", /* Redirect output structure to the directory. */ 16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */ 27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | 63 | /* Advanced Options */ 64 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /User-App/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /User-App/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events*.json 15 | speed-measure-plugin*.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /User-App/README.md: -------------------------------------------------------------------------------- 1 | # UserApp 2 | 3 | This will be front end of Conference-Notify project. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 28 | 29 | ## Work Done till now 30 | 31 | -> Basic Angular app created 32 | -> Routes for login,signup and home page added 33 | -------------------------------------------------------------------------------- /User-App/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "User-App": { 7 | "projectType": "application", 8 | "schematics": {}, 9 | "root": "", 10 | "sourceRoot": "src", 11 | "prefix": "app", 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/User-App", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "tsconfig.app.json", 21 | "aot": true, 22 | "assets": [ 23 | "src/favicon.ico", 24 | "src/assets" 25 | ], 26 | "styles": [ 27 | "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", 28 | "src/styles.css" 29 | ], 30 | "scripts": [] 31 | }, 32 | "configurations": { 33 | "production": { 34 | "fileReplacements": [ 35 | { 36 | "replace": "src/environments/environment.ts", 37 | "with": "src/environments/environment.prod.ts" 38 | } 39 | ], 40 | "optimization": true, 41 | "outputHashing": "all", 42 | "sourceMap": false, 43 | "extractCss": true, 44 | "namedChunks": false, 45 | "extractLicenses": true, 46 | "vendorChunk": false, 47 | "buildOptimizer": true, 48 | "budgets": [ 49 | { 50 | "type": "initial", 51 | "maximumWarning": "2mb", 52 | "maximumError": "5mb" 53 | }, 54 | { 55 | "type": "anyComponentStyle", 56 | "maximumWarning": "6kb" 57 | } 58 | ] 59 | } 60 | } 61 | }, 62 | "serve": { 63 | "builder": "@angular-devkit/build-angular:dev-server", 64 | "options": { 65 | "browserTarget": "User-App:build" 66 | }, 67 | "configurations": { 68 | "production": { 69 | "browserTarget": "User-App:build:production" 70 | } 71 | } 72 | }, 73 | "extract-i18n": { 74 | "builder": "@angular-devkit/build-angular:extract-i18n", 75 | "options": { 76 | "browserTarget": "User-App:build" 77 | } 78 | }, 79 | "test": { 80 | "builder": "@angular-devkit/build-angular:karma", 81 | "options": { 82 | "main": "src/test.ts", 83 | "polyfills": "src/polyfills.ts", 84 | "tsConfig": "tsconfig.spec.json", 85 | "karmaConfig": "karma.conf.js", 86 | "assets": [ 87 | "src/favicon.ico", 88 | "src/assets" 89 | ], 90 | "styles": [ 91 | "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", 92 | "src/styles.css" 93 | ], 94 | "scripts": [] 95 | } 96 | }, 97 | "lint": { 98 | "builder": "@angular-devkit/build-angular:tslint", 99 | "options": { 100 | "tsConfig": [ 101 | "tsconfig.app.json", 102 | "tsconfig.spec.json", 103 | "e2e/tsconfig.json" 104 | ], 105 | "exclude": [ 106 | "**/node_modules/**" 107 | ] 108 | } 109 | }, 110 | "e2e": { 111 | "builder": "@angular-devkit/build-angular:protractor", 112 | "options": { 113 | "protractorConfig": "e2e/protractor.conf.js", 114 | "devServerTarget": "User-App:serve" 115 | }, 116 | "configurations": { 117 | "production": { 118 | "devServerTarget": "User-App:serve:production" 119 | } 120 | } 121 | } 122 | } 123 | } 124 | }, 125 | "defaultProject": "User-App", 126 | "cli": { 127 | "analytics": "a9318e63-3eba-4c7c-9fa1-0c0e874086cb" 128 | } 129 | } -------------------------------------------------------------------------------- /User-App/browserslist: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /User-App/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | 'browserName': 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 31 | } 32 | }; -------------------------------------------------------------------------------- /User-App/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('Welcome to User-App!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /User-App/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css('app-root h1')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /User-App/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /User-App/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/User-App'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /User-App/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "user-app", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "^9.0.4", 15 | "@angular/cdk": "^9.1.0", 16 | "@angular/common": "~9.0.4", 17 | "@angular/compiler": "~9.0.4", 18 | "@angular/core": "~9.0.4", 19 | "@angular/flex-layout": "^9.0.0-beta.29", 20 | "@angular/forms": "~9.0.4", 21 | "@angular/material": "^9.1.0", 22 | "@angular/platform-browser": "~9.0.4", 23 | "@angular/platform-browser-dynamic": "~9.0.4", 24 | "@angular/router": "~9.0.4", 25 | "rxjs": "~6.5.4", 26 | "tslib": "^1.10.0", 27 | "zone.js": "~0.10.2" 28 | }, 29 | "devDependencies": { 30 | "@angular-devkit/build-angular": "~0.901.0-next.1", 31 | "@angular/cli": "~9.1.0-next.1", 32 | "@angular/compiler-cli": "~9.0.4", 33 | "@angular/language-service": "~9.0.4", 34 | "@types/jasmine": "~3.3.8", 35 | "@types/jasminewd2": "~2.0.3", 36 | "@types/node": "^12.11.1", 37 | "codelyzer": "^5.1.2", 38 | "jasmine-core": "~3.4.0", 39 | "jasmine-spec-reporter": "~4.2.1", 40 | "karma": "~4.1.0", 41 | "karma-chrome-launcher": "~2.2.0", 42 | "karma-coverage-istanbul-reporter": "~2.0.1", 43 | "karma-jasmine": "~2.0.1", 44 | "karma-jasmine-html-reporter": "^1.4.0", 45 | "protractor": "~5.4.0", 46 | "ts-node": "~7.0.0", 47 | "tslint": "~5.15.0", 48 | "typescript": "^3.6.4" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /User-App/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { LoginComponent } from './login/login.component'; 4 | import { SignupComponent } from './signup/signup.component'; 5 | import { HomeComponent } from './home/home.component'; 6 | import { SearchPageComponent } from './search-page/search-page.component'; 7 | import { ConferencesListComponent } from './conferences-list/conferences-list.component'; 8 | import { ConferenceComponent } from './conference/conference.component'; 9 | 10 | 11 | const routes: Routes = [ 12 | {path: '', component: HomeComponent}, 13 | {path: 'login', component: LoginComponent}, 14 | {path: 'signup', component: SignupComponent}, 15 | {path: 'search-page', component: SearchPageComponent}, 16 | {path: 'conferences', component: ConferencesListComponent}, 17 | {path: 'conferences/conference', component: ConferenceComponent} 18 | ]; 19 | 20 | @NgModule({ 21 | imports: [RouterModule.forRoot(routes)], 22 | exports: [RouterModule] 23 | }) 24 | export class AppRoutingModule { } 25 | -------------------------------------------------------------------------------- /User-App/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/User-App/src/app/app.component.css -------------------------------------------------------------------------------- /User-App/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 | -------------------------------------------------------------------------------- /User-App/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(async(() => { 7 | TestBed.configureTestingModule({ 8 | imports: [ 9 | RouterTestingModule 10 | ], 11 | declarations: [ 12 | AppComponent 13 | ], 14 | }).compileComponents(); 15 | })); 16 | 17 | it('should create the app', () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.debugElement.componentInstance; 20 | expect(app).toBeTruthy(); 21 | }); 22 | 23 | it(`should have as title 'User-App'`, () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | const app = fixture.debugElement.componentInstance; 26 | expect(app.title).toEqual('User-App'); 27 | }); 28 | 29 | }); 30 | -------------------------------------------------------------------------------- /User-App/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { 9 | title = 'User-App'; 10 | } 11 | -------------------------------------------------------------------------------- /User-App/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { HttpClientModule } from '@angular/common/http'; 4 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 5 | import { MatCardModule } from '@angular/material/card'; 6 | import { MatInputModule } from '@angular/material/input'; 7 | import { MatToolbarModule } from '@angular/material/toolbar'; 8 | import { MatExpansionModule } from '@angular/material/expansion'; 9 | import { MatPaginatorModule } from '@angular/material/paginator'; 10 | import { MatButtonModule } from '@angular/material/button'; 11 | import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; 12 | import { FlexLayoutModule } from '@angular/flex-layout'; 13 | 14 | import { AppRoutingModule } from './app-routing.module'; 15 | import { AppComponent } from './app.component'; 16 | import { HeaderComponent } from './header/header.component'; 17 | import { LoginComponent } from './login/login.component'; 18 | import { SignupComponent } from './signup/signup.component'; 19 | import { HomeComponent } from './home/home.component'; 20 | import { SearchPageComponent } from './search-page/search-page.component'; 21 | import { ConferencesListComponent } from './conferences-list/conferences-list.component'; 22 | import { ConferenceComponent } from './conference/conference.component'; 23 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 24 | 25 | @NgModule({ 26 | declarations: [ 27 | AppComponent, 28 | HeaderComponent, 29 | LoginComponent, 30 | SignupComponent, 31 | HomeComponent, 32 | SearchPageComponent, 33 | ConferencesListComponent, 34 | ConferenceComponent 35 | ], 36 | imports: [ 37 | BrowserModule, 38 | AppRoutingModule, 39 | BrowserAnimationsModule, 40 | MatCardModule, 41 | MatButtonModule, 42 | MatToolbarModule, 43 | MatExpansionModule, 44 | MatProgressSpinnerModule, 45 | MatPaginatorModule, 46 | MatInputModule, 47 | FlexLayoutModule, 48 | HttpClientModule 49 | FormsModule, 50 | ReactiveFormsModule 51 | ], 52 | providers: [], 53 | bootstrap: [AppComponent] 54 | }) 55 | export class AppModule { } 56 | -------------------------------------------------------------------------------- /User-App/src/app/conference/conference.component.css: -------------------------------------------------------------------------------- 1 | .conference { 2 | width: 80%; 3 | height: 100vh; 4 | margin: 0% 8%; 5 | } 6 | mat-card { 7 | padding: 2% 2%; 8 | margin: 2% 0%; 9 | background-color: rgb(243, 239, 239); 10 | } 11 | mat-card-title { 12 | text-align: center; 13 | } 14 | mat-card-content { 15 | text-align: justify; 16 | font-size: 16px; 17 | } 18 | mat-card-footer { 19 | text-align: center; 20 | } 21 | -------------------------------------------------------------------------------- /User-App/src/app/conference/conference.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | {{val.title}} 5 | 6 | 7 |
8 | {{val.bulkText}}

9 | Categories:[ {{val.categories}} ]
10 | DateRange: [ {{val.dateRange}} ]
11 | For more detail visit website here
12 | FinalDue: {{val.finalDue}}
13 | Location: {{val.location}} 14 |
15 | 16 | Deadline: {{val.deadline}} 17 | 18 |
19 |
20 | -------------------------------------------------------------------------------- /User-App/src/app/conference/conference.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ConferenceComponent } from './conference.component'; 4 | 5 | describe('ConferenceComponent', () => { 6 | let component: ConferenceComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ConferenceComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ConferenceComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /User-App/src/app/conference/conference.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ConferencesService } from '../services/conferences.service'; 3 | import { Conference } from '../models/conference.model'; 4 | 5 | @Component({ 6 | selector: 'app-conference', 7 | templateUrl: './conference.component.html', 8 | styleUrls: ['./conference.component.css'] 9 | }) 10 | export class ConferenceComponent implements OnInit { 11 | public conference: Conference[]; 12 | constructor(public conferencesservice: ConferencesService) { } 13 | 14 | ngOnInit(): void { 15 | this.conference = this.conferencesservice.getConference(); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /User-App/src/app/conferences-list/conferences-list.component.css: -------------------------------------------------------------------------------- 1 | .main{ 2 | background-color: rgb(218, 214, 214); 3 | height: 100vh; 4 | } 5 | .head{ 6 | text-align: center; 7 | padding:2%; 8 | } 9 | .head h1 { 10 | font-size: 28px; 11 | font-weight: 400; 12 | } 13 | mat-card { 14 | height: 400px; 15 | width: 500px; 16 | text-align: justify; 17 | margin: 0% 6%; 18 | } 19 | mat-card-title { 20 | margin: 0% 4%; 21 | font-size: 20px; 22 | } 23 | mat-card-content { 24 | margin: 0% 4%; 25 | font-size: 18px; 26 | } 27 | mat-card-footer { 28 | margin: 0% 4%; 29 | text-align: center; 30 | font-size: 16px; 31 | } 32 | a { 33 | text-decoration: none; 34 | } 35 | -------------------------------------------------------------------------------- /User-App/src/app/conferences-list/conferences-list.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

List of Conferences near you

4 |
5 |
6 |
7 | 8 | 9 | {{conference.title}}
10 |
11 | Categories: [{{conference.categories}}]

12 | For detailed information, visit
Conference_url
13 | Location: {{conference.location}}

14 | Notification Due: {{conference.notificationDue}}
15 | FinalDue: {{conference.finalDue}}



16 | 17 | Date Range: {{conference.dateRange}}
Deadline: {{conference.deadline}}
18 | 19 | 20 |
21 |
22 |
23 | -------------------------------------------------------------------------------- /User-App/src/app/conferences-list/conferences-list.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ConferencesListComponent } from './conferences-list.component'; 4 | 5 | describe('ConferencesListComponent', () => { 6 | let component: ConferencesListComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ConferencesListComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ConferencesListComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /User-App/src/app/conferences-list/conferences-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy, HostListener } from '@angular/core'; 2 | import { Conference } from '../models/conference.model'; 3 | import { ConferencesService } from '../services/conferences.service'; 4 | 5 | @Component({ 6 | selector: 'app-conferences-list', 7 | templateUrl: './conferences-list.component.html', 8 | styleUrls: ['./conferences-list.component.css'] 9 | }) 10 | 11 | export class ConferencesListComponent implements OnInit, OnDestroy { 12 | public conferences: Conference[] = []; 13 | public conferenceList: Array = []; 14 | offset = 0; 15 | count = 3; 16 | constructor( public conferencesservice: ConferencesService) { } 17 | 18 | @HostListener('window:scroll', ['$event']) 19 | scrollHandler(event) { 20 | this.getConferenceslist(this.offset, this.count); 21 | } 22 | 23 | ngOnInit() { 24 | this.getConferenceslist(this.offset, this.count); 25 | } 26 | 27 | getConferenceslist(offSet, Count) { 28 | this.conferenceList.push(this.conferencesservice.getConferences(offSet, Count)); 29 | const len = this.conferenceList.length; 30 | let i = 0; 31 | while (i < len) { 32 | this.conferenceList[i].forEach(conference => { 33 | this.conferences.push(conference); 34 | }); 35 | i = i + 1; 36 | } 37 | this.offset += this.count; 38 | this.count += this.count; 39 | } 40 | ngOnDestroy() { 41 | 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /User-App/src/app/header/header.component.css: -------------------------------------------------------------------------------- 1 | ul { 2 | list-style: none; 3 | padding: 0; 4 | margin: 0; 5 | } 6 | img { 7 | padding-top: 10px; 8 | width: 200px; 9 | height: 60px; 10 | } 11 | 12 | a { 13 | text-decoration: none; 14 | font-size: 18px; 15 | } 16 | /* a routerLinkActive { 17 | color: #6652FF; 18 | } */ 19 | 20 | .vl { 21 | margin-top: 2px; 22 | border-left: 4px solid #6652FF; 23 | height: 30px; 24 | } 25 | 26 | ul { 27 | display: flex; 28 | } 29 | mat-toolbar { 30 | height: 60px; 31 | background-color: white; 32 | } 33 | 34 | .spacer { 35 | flex: 1 1 auto; 36 | } 37 | -------------------------------------------------------------------------------- /User-App/src/app/header/header.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 24 | 25 | -------------------------------------------------------------------------------- /User-App/src/app/header/header.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HeaderComponent } from './header.component'; 4 | 5 | describe('HeaderComponent', () => { 6 | let component: HeaderComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ HeaderComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HeaderComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /User-App/src/app/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-header', 5 | templateUrl: './header.component.html', 6 | styleUrls: ['./header.component.css'] 7 | }) 8 | export class HeaderComponent implements OnInit { 9 | userIsAuthenticated = false; 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | onLogout() { 15 | 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /User-App/src/app/home/home.component.css: -------------------------------------------------------------------------------- 1 | .heading { 2 | height: 190px; 3 | background-color: #6652FF; 4 | color: white; 5 | text-align: center; 6 | font-size: 30px; 7 | padding-top: 90px; 8 | } 9 | .txt{ 10 | padding: 8px; 11 | font-weight: 500; 12 | } 13 | .subhead { 14 | font-size: 16px; 15 | } 16 | .attend-btn { 17 | padding: 10px 50px 10px 50px; 18 | font-size: 14px; 19 | border-radius: 8px; 20 | background-color: white; 21 | } 22 | .searchbox { 23 | display: inline; 24 | } 25 | .searchbox input { 26 | width: 30%; 27 | margin: 3% 0% 1% 30%; 28 | height: 30px; 29 | text-indent: 1%; 30 | } 31 | .searchbox button { 32 | width: 10%; 33 | height: 40px; 34 | background-color: #6652FF; 35 | color: white; 36 | } 37 | .head { 38 | font-weight: 500; 39 | margin-bottom: 5px; 40 | } 41 | .conference-head { 42 | font-size: 18px; 43 | margin: 4% 10%; 44 | } 45 | .view-txt{ 46 | float: right; 47 | } 48 | a { 49 | text-decoration: none; 50 | } 51 | #conference-list mat-card { 52 | height: 280px; 53 | width: 250px; 54 | align-self: center; 55 | margin: 0% 0% 4% 10%; 56 | color: rgb(6,69,94); 57 | border-radius: 10px; 58 | } 59 | .title-dir { 60 | display: inline; 61 | } 62 | .title-txt { 63 | color: black; 64 | font-weight: 500; 65 | font-size: 14px; 66 | /* margin: 20%; */ 67 | } 68 | .dir { 69 | float: right; 70 | margin-left: 10%; 71 | } 72 | 73 | /* mat-card-title{ 74 | padding-top: 70px 75 | } */ 76 | -------------------------------------------------------------------------------- /User-App/src/app/home/home.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | The real world is calling

4 |

Attend a conference to meet people, try something new, or do moreof what you love

5 | 6 |
7 |
8 | 11 |
12 |

Conferences near you

13 |
14 | 15 | Explore knowledge in your area in future 16 | 17 | 18 | view all 19 | 20 |
21 |
22 |
23 | 24 | 25 | 26 | Friday, March 13, 2020
27 |
28 | Women Welfare and education conference

Kalanmassy, Kochi-672732
29 |
Directions
30 |
31 |
32 |
33 | 34 | 35 | 36 | Friday, March 13, 2020
37 |
38 | Women Welfare and education conference

Kalanmassy, Kochi-672732
39 |
Directions
40 |
41 |
42 |
43 | 44 | 45 | 46 | Friday, March 13, 2020
47 |
48 | Women Welfare and education conference

Kalanmassy, Kochi-672732
49 |
Directions
50 |
51 |
52 |
53 |
54 | -------------------------------------------------------------------------------- /User-App/src/app/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HomeComponent } from './home.component'; 4 | import { RouterTestingModule } from '@angular/router/testing'; 5 | 6 | describe('HomeComponent', () => { 7 | let component: HomeComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async(() => { 11 | TestBed.configureTestingModule({ 12 | imports: [ RouterTestingModule] , 13 | declarations: [ HomeComponent ] 14 | }) 15 | .compileComponents(); 16 | })); 17 | 18 | beforeEach(() => { 19 | fixture = TestBed.createComponent(HomeComponent); 20 | component = fixture.componentInstance; 21 | fixture.detectChanges(); 22 | }); 23 | 24 | it('should create', () => { 25 | expect(component).toBeTruthy(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /User-App/src/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | @Component({ 5 | selector: 'app-home', 6 | templateUrl: './home.component.html', 7 | styleUrls: ['./home.component.css'] 8 | }) 9 | export class HomeComponent implements OnInit { 10 | 11 | constructor(private router: Router) { } 12 | 13 | ngOnInit(): void { 14 | } 15 | navigateToSearchPage() { 16 | this.router.navigate(['search-page']); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /User-App/src/app/login/login.component.css: -------------------------------------------------------------------------------- 1 | .main { 2 | text-align: center; 3 | margin-top: 2%; 4 | } 5 | h2 { 6 | font-size: 28px; 7 | color: rgb(15, 15, 228); 8 | font-weight: 600; 9 | font-family: 'Courier New', Courier, monospace; 10 | } 11 | mat-card { 12 | margin:2% 0% 6% 15%; 13 | padding:4%; 14 | width:60%; 15 | background-color: rgb(241, 239, 239); 16 | font-size: 18px; 17 | } 18 | mat-form-field { 19 | width: 80%; 20 | margin-left: 8%; 21 | } 22 | button{ 23 | margin-left: 50%; 24 | } 25 | -------------------------------------------------------------------------------- /User-App/src/app/login/login.component.html: -------------------------------------------------------------------------------- 1 |
2 |

LOGIN FORM

3 |
4 | 5 |
6 | 7 | 12 | Please Enter a valid Email ID! 13 | 14 | 15 | 16 | 21 | Please Enter a valid Password! 22 | 23 |
24 | 27 |
28 |
29 | -------------------------------------------------------------------------------- /User-App/src/app/login/login.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LoginComponent } from './login.component'; 4 | 5 | describe('LoginComponent', () => { 6 | let component: LoginComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ LoginComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(LoginComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /User-App/src/app/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { NgForm } from '@angular/forms'; 3 | 4 | @Component({ 5 | selector: 'app-login', 6 | templateUrl: './login.component.html', 7 | styleUrls: ['./login.component.css'] 8 | }) 9 | export class LoginComponent implements OnInit { 10 | 11 | constructor() { } 12 | 13 | ngOnInit(): void { 14 | } 15 | OnLogin(form: NgForm) { 16 | console.log(form); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /User-App/src/app/models/conference.model.ts: -------------------------------------------------------------------------------- 1 | import { Metadata } from './metadata.model'; 2 | 3 | export class Conference { 4 | title: string; 5 | url: string; 6 | deadline: Date; 7 | metadata?: { 8 | [tag: string]: Metadata 9 | }; 10 | categories?: Array; 11 | dateRange?: Array; 12 | finalDue?: string; 13 | location?: string; 14 | notificationDue?: Date; 15 | bulkText?: string; // optional field 16 | } 17 | -------------------------------------------------------------------------------- /User-App/src/app/models/conferenceDocument.model.ts: -------------------------------------------------------------------------------- 1 | import { Metadata } from './metadata.model'; 2 | 3 | export class ConferenceDocument { 4 | title: string; 5 | url: string; 6 | deadline: Date; 7 | metadata?: { 8 | [tag: string]: Metadata 9 | }; 10 | 11 | categories?: Array; 12 | dateRange?: Array; 13 | finalDue?: string; 14 | location?: string; 15 | notificationDue?: Date; 16 | bulkText?: string; // optional field 17 | } 18 | -------------------------------------------------------------------------------- /User-App/src/app/models/metadata.model.ts: -------------------------------------------------------------------------------- 1 | export class Metadata { 2 | [scrapper: string]: { 3 | dateExtracted: Date; 4 | websiteUrl: string; 5 | website: string; 6 | domain: string; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /User-App/src/app/search-page/search-page.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/User-App/src/app/search-page/search-page.component.css -------------------------------------------------------------------------------- /User-App/src/app/search-page/search-page.component.html: -------------------------------------------------------------------------------- 1 |

HERE SEARCH PAGE CONTENT WILL GO!

2 | -------------------------------------------------------------------------------- /User-App/src/app/search-page/search-page.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SearchPageComponent } from './search-page.component'; 4 | 5 | describe('SearchPageComponent', () => { 6 | let component: SearchPageComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ SearchPageComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SearchPageComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /User-App/src/app/search-page/search-page.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-search-page', 5 | templateUrl: './search-page.component.html', 6 | styleUrls: ['./search-page.component.css'] 7 | }) 8 | export class SearchPageComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /User-App/src/app/services/conferences.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { ConferencesService } from './conferences.service'; 4 | 5 | describe('ConferencesService', () => { 6 | let service: ConferencesService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(ConferencesService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /User-App/src/app/services/conferences.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Conference } from '../models/conference.model'; 4 | 5 | @Injectable({ 6 | providedIn: 'root' 7 | }) 8 | export class ConferencesService { 9 | constructor(private http: HttpClient) { } 10 | 11 | getConferences(offset, count) { 12 | const conferences: Conference[] = []; 13 | this.http.get('http://localhost:3000/conferences/' + offset + '/' + count).subscribe((conference) => { 14 | const key = 'payload'; 15 | conference[key].forEach( 16 | val => { 17 | conferences.push(val); 18 | } 19 | ); 20 | }); 21 | return conferences; 22 | } 23 | 24 | getConference() { 25 | const conference: Conference[] = []; 26 | this.http.get('http://localhost:3000/conferences/getone').subscribe(conferenceobj => { 27 | const key = 'payload'; 28 | conference.push(conferenceobj[key]); 29 | }); 30 | return conference; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /User-App/src/app/signup/signup.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/User-App/src/app/signup/signup.component.css -------------------------------------------------------------------------------- /User-App/src/app/signup/signup.component.html: -------------------------------------------------------------------------------- 1 |

Welcome to the signup page :)

2 | -------------------------------------------------------------------------------- /User-App/src/app/signup/signup.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SignupComponent } from './signup.component'; 4 | 5 | describe('SignupComponent', () => { 6 | let component: SignupComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ SignupComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SignupComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /User-App/src/app/signup/signup.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-signup', 5 | templateUrl: './signup.component.html', 6 | styleUrls: ['./signup.component.css'] 7 | }) 8 | export class SignupComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /User-App/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/User-App/src/assets/.gitkeep -------------------------------------------------------------------------------- /User-App/src/assets/Proj_Conf_ntfy_logo-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/User-App/src/assets/Proj_Conf_ntfy_logo-01.png -------------------------------------------------------------------------------- /User-App/src/assets/Ref_Google_Maps_25_10_2017.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/User-App/src/assets/Ref_Google_Maps_25_10_2017.png -------------------------------------------------------------------------------- /User-App/src/assets/Ref_Google_Maps_25_10_2017@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/User-App/src/assets/Ref_Google_Maps_25_10_2017@2x.png -------------------------------------------------------------------------------- /User-App/src/assets/Ref_Google_Maps_25_10_2017_c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/User-App/src/assets/Ref_Google_Maps_25_10_2017_c.png -------------------------------------------------------------------------------- /User-App/src/assets/Ref_Google_Maps_25_10_2017_c@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/User-App/src/assets/Ref_Google_Maps_25_10_2017_c@2x.png -------------------------------------------------------------------------------- /User-App/src/assets/Ref_Google_Maps_25_10_2017_cz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/User-App/src/assets/Ref_Google_Maps_25_10_2017_cz.png -------------------------------------------------------------------------------- /User-App/src/assets/Ref_Google_Maps_25_10_2017_cz@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/User-App/src/assets/Ref_Google_Maps_25_10_2017_cz@2x.png -------------------------------------------------------------------------------- /User-App/src/assets/crowded_people_standing_watchi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/User-App/src/assets/crowded_people_standing_watchi.png -------------------------------------------------------------------------------- /User-App/src/assets/crowded_people_standing_watchi@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/User-App/src/assets/crowded_people_standing_watchi@2x.png -------------------------------------------------------------------------------- /User-App/src/assets/man_standing_in_front_of_peopl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/User-App/src/assets/man_standing_in_front_of_peopl.png -------------------------------------------------------------------------------- /User-App/src/assets/man_standing_in_front_of_peopl@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/User-App/src/assets/man_standing_in_front_of_peopl@2x.png -------------------------------------------------------------------------------- /User-App/src/assets/people_gathering_inside_white_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/User-App/src/assets/people_gathering_inside_white_.png -------------------------------------------------------------------------------- /User-App/src/assets/people_gathering_inside_white_@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/User-App/src/assets/people_gathering_inside_white_@2x.png -------------------------------------------------------------------------------- /User-App/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /User-App/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /User-App/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/User-App/src/favicon.ico -------------------------------------------------------------------------------- /User-App/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | UserApp 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /User-App/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /User-App/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags.ts'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | -------------------------------------------------------------------------------- /User-App/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | html, body { height: 100%; } 4 | body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } 5 | 6 | html, body { height: 100%; } 7 | body { margin: 0; font-family: 'Roboto', sans-serif; } 8 | -------------------------------------------------------------------------------- /User-App/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /User-App/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /User-App/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ] 21 | }, 22 | "angularCompilerOptions": { 23 | "fullTemplateTypeCheck": true, 24 | "strictInjectionParameters": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /User-App/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /User-App/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "array-type": false, 5 | "arrow-parens": false, 6 | "deprecation": { 7 | "severity": "warning" 8 | }, 9 | "component-class-suffix": true, 10 | "contextual-lifecycle": true, 11 | "directive-class-suffix": true, 12 | "directive-selector": [ 13 | true, 14 | "attribute", 15 | "app", 16 | "camelCase" 17 | ], 18 | "component-selector": [ 19 | true, 20 | "element", 21 | "app", 22 | "kebab-case" 23 | ], 24 | "import-blacklist": [ 25 | true, 26 | "rxjs/Rx" 27 | ], 28 | "interface-name": false, 29 | "max-classes-per-file": false, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-consecutive-blank-lines": false, 47 | "no-console": [ 48 | true, 49 | "debug", 50 | "info", 51 | "time", 52 | "timeEnd", 53 | "trace" 54 | ], 55 | "no-empty": false, 56 | "no-inferrable-types": [ 57 | true, 58 | "ignore-params" 59 | ], 60 | "no-non-null-assertion": true, 61 | "no-redundant-jsdoc": true, 62 | "no-switch-case-fall-through": true, 63 | "no-use-before-declare": true, 64 | "no-var-requires": false, 65 | "object-literal-key-quotes": [ 66 | true, 67 | "as-needed" 68 | ], 69 | "object-literal-sort-keys": false, 70 | "ordered-imports": false, 71 | "quotemark": [ 72 | true, 73 | "single" 74 | ], 75 | "trailing-comma": false, 76 | "no-conflicting-lifecycle": true, 77 | "no-host-metadata-property": true, 78 | "no-input-rename": true, 79 | "no-inputs-metadata-property": true, 80 | "no-output-native": true, 81 | "no-output-on-prefix": true, 82 | "no-output-rename": true, 83 | "no-outputs-metadata-property": true, 84 | "template-banana-in-box": true, 85 | "template-no-negated-async": true, 86 | "use-lifecycle-interface": true, 87 | "use-pipe-transform-interface": true 88 | }, 89 | "rulesDirectory": [ 90 | "codelyzer" 91 | ] 92 | } -------------------------------------------------------------------------------- /gssoc_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajatkb/Conference-Notify/a483dc74c8dbdac03be3a6d4b54375539e158282/gssoc_black.png -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1 3 | } 4 | --------------------------------------------------------------------------------