├── .gitignore
├── hasura.yaml
├── .hasuraignore
├── microservices
├── www
│ ├── docker-config.yaml
│ ├── src
│ │ ├── public
│ │ │ ├── img
│ │ │ │ ├── logo.png
│ │ │ │ ├── header_img.png
│ │ │ │ ├── logo_mini.png
│ │ │ │ ├── logo_mini_fav.png
│ │ │ │ └── video_placeholder.png
│ │ │ ├── uploads
│ │ │ │ ├── php.png
│ │ │ │ ├── html5.png
│ │ │ │ ├── badges
│ │ │ │ │ ├── pro_badge.png
│ │ │ │ │ ├── member_badge.png
│ │ │ │ │ ├── beginner_badge.png
│ │ │ │ │ ├── ultimate_badge.png
│ │ │ │ │ └── intermediate_badge.png
│ │ │ │ └── html5_semantic_tags.png
│ │ │ ├── css
│ │ │ │ ├── themes
│ │ │ │ │ └── default
│ │ │ │ │ │ └── assets
│ │ │ │ │ │ ├── fonts
│ │ │ │ │ │ ├── icons.eot
│ │ │ │ │ │ ├── icons.otf
│ │ │ │ │ │ ├── icons.ttf
│ │ │ │ │ │ ├── icons.woff
│ │ │ │ │ │ └── icons.woff2
│ │ │ │ │ │ └── images
│ │ │ │ │ │ └── flags.png
│ │ │ │ └── style.css
│ │ │ └── js
│ │ │ │ ├── student_home.js
│ │ │ │ ├── student_common.js
│ │ │ │ ├── common.js
│ │ │ │ ├── course_home.js
│ │ │ │ ├── logout.js
│ │ │ │ ├── homepage.js
│ │ │ │ ├── course_accordion.js
│ │ │ │ ├── auth.js
│ │ │ │ ├── student_main.js
│ │ │ │ └── course_main.js
│ │ ├── template
│ │ │ ├── partials
│ │ │ │ ├── scriptsUserCommon.handlebars
│ │ │ │ ├── scriptsCommon.handlebars
│ │ │ │ ├── navBasic.handlebars
│ │ │ │ ├── modules.handlebars
│ │ │ │ ├── navStudent.handlebars
│ │ │ │ ├── footer.handlebars
│ │ │ │ ├── courses.handlebars
│ │ │ │ ├── modalLogin.handlebars
│ │ │ │ └── modalSignUp.handlebars
│ │ │ ├── layouts
│ │ │ │ └── main.handlebars
│ │ │ ├── logout.handlebars
│ │ │ ├── index.handlebars
│ │ │ ├── student.handlebars
│ │ │ └── course.handlebars
│ │ ├── package.json
│ │ ├── data-query.js
│ │ └── server.js
│ ├── Dockerfile
│ ├── README.md
│ └── k8s.yaml
└── README.md
├── conf
├── session-store.yaml
├── gateway.yaml
├── authorized-keys.yaml
├── http-directives.conf
├── domains.yaml
├── postgres.yaml
├── ci.yaml
├── filestore.yaml
├── notify.yaml
├── README.md
├── routes.yaml
└── auth.yaml
├── migrations
├── 1517759998068_demo_content.down.sql
├── 1511336750791_drop_relationship_user_badge_status_table_badge_status.up.yaml
├── 1517740283581_add_relationship_enrolled_count_table_course_details.down.yaml
├── 1517740347759_add_relationship_avg_course_rating_table_course_details.down.yaml
├── 1511336653999_add_relationship_user_badge_status_table_badge_status.up.yaml
├── 1517757204788_Update_permission_user_table_user_other_details.up.yaml
├── 1511337601240_modify_permission_user_table_enrolled_count.up.yaml
├── 1517757306956_Update_permission_anonymous_table_user_other_details.up.yaml
├── 1511337593698_modify_permission_anonymous_table_enrolled_count.up.yaml
├── 1511337048372_add_relationship_module_topics_table_module_details.up.yaml
├── 1511343438733_modify_permission_user_table_course_rating.up.yaml
├── 1511343486328_modify_permission_user_table_course_status.up.yaml
├── 1511336842472_add_relationship_user_badge_status_table_badge_details.up.yaml
├── 1511337642524_modify_permission_user_table_avg_course_rating.up.yaml
├── 1511343088059_modify_permission_user_table_user_other_details.up.yaml
├── 1511343539563_modify_permission_user_table_module_details.up.yaml
├── 1511336943803_add_relationship_user_course_status_table_course_details.up.yaml
├── 1511336964876_add_relationship_user_course_rating_table_course_details.up.yaml
├── 1511337065542_add_relationship_user_topic_status_table_module_details.up.yaml
├── 1511337614362_modify_permission_anonymous_table_avg_course_rating.up.yaml
├── 1511343593274_modify_permission_user_table_topic_status.up.yaml
├── 1517740283581_add_relationship_enrolled_count_table_course_details.up.yaml
├── 1511343565097_modify_permission_user_table_topic_details.up.yaml
├── 1517740347759_add_relationship_avg_course_rating_table_course_details.up.yaml
├── 1511337554690_modify_permission_user_table_course_details.up.yaml
├── 1511337531704_modify_permission_anonymous_table_course_details.up.yaml
├── 1511337635455_modify_permission_anonymous_table_avg_course_rating.up.yaml
├── README.md
├── 1511343800042_modify_permission_user_table_course_rating.up.yaml
├── 1511335553183_add_all_existing_table_or_view.up.yaml
├── 1511335541859_run_sql_migration.up.yaml
└── 1517759998068_demo_content.up.sql
├── clusters.yaml
├── LICENSE
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .hasura
2 | *node_modules
3 |
--------------------------------------------------------------------------------
/hasura.yaml:
--------------------------------------------------------------------------------
1 | name: egyan
2 | platformVersion: v0.15.8
3 |
--------------------------------------------------------------------------------
/.hasuraignore:
--------------------------------------------------------------------------------
1 | # gitignore like file
2 | # files and directories mentioned here will be ignored while publishing to hasura
3 |
--------------------------------------------------------------------------------
/microservices/www/docker-config.yaml:
--------------------------------------------------------------------------------
1 | image: "nodejs-express:"
2 | port: 8080
3 | volume_mounts: null
4 | env_variables: []
5 |
--------------------------------------------------------------------------------
/conf/session-store.yaml:
--------------------------------------------------------------------------------
1 | # Kubernetes volume object for Redis session store
2 | volume: {{ cluster.metadata.sessionStore.volume|json }}
3 |
--------------------------------------------------------------------------------
/microservices/www/src/public/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anantajitjg/eGyan/HEAD/microservices/www/src/public/img/logo.png
--------------------------------------------------------------------------------
/microservices/www/src/public/uploads/php.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anantajitjg/eGyan/HEAD/microservices/www/src/public/uploads/php.png
--------------------------------------------------------------------------------
/microservices/www/src/public/img/header_img.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anantajitjg/eGyan/HEAD/microservices/www/src/public/img/header_img.png
--------------------------------------------------------------------------------
/microservices/www/src/public/img/logo_mini.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anantajitjg/eGyan/HEAD/microservices/www/src/public/img/logo_mini.png
--------------------------------------------------------------------------------
/microservices/www/src/public/uploads/html5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anantajitjg/eGyan/HEAD/microservices/www/src/public/uploads/html5.png
--------------------------------------------------------------------------------
/microservices/www/src/public/img/logo_mini_fav.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anantajitjg/eGyan/HEAD/microservices/www/src/public/img/logo_mini_fav.png
--------------------------------------------------------------------------------
/microservices/www/src/template/partials/scriptsUserCommon.handlebars:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/microservices/www/src/public/img/video_placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anantajitjg/eGyan/HEAD/microservices/www/src/public/img/video_placeholder.png
--------------------------------------------------------------------------------
/microservices/www/src/public/uploads/badges/pro_badge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anantajitjg/eGyan/HEAD/microservices/www/src/public/uploads/badges/pro_badge.png
--------------------------------------------------------------------------------
/microservices/www/src/public/uploads/badges/member_badge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anantajitjg/eGyan/HEAD/microservices/www/src/public/uploads/badges/member_badge.png
--------------------------------------------------------------------------------
/microservices/www/src/public/uploads/html5_semantic_tags.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anantajitjg/eGyan/HEAD/microservices/www/src/public/uploads/html5_semantic_tags.png
--------------------------------------------------------------------------------
/migrations/1517759998068_demo_content.down.sql:
--------------------------------------------------------------------------------
1 | DELETE FROM badge_details;
2 | DELETE FROM course_details;
3 | DELETE FROM module_details;
4 | DELETE FROM topic_details;
5 |
--------------------------------------------------------------------------------
/microservices/www/src/public/uploads/badges/beginner_badge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anantajitjg/eGyan/HEAD/microservices/www/src/public/uploads/badges/beginner_badge.png
--------------------------------------------------------------------------------
/microservices/www/src/public/uploads/badges/ultimate_badge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anantajitjg/eGyan/HEAD/microservices/www/src/public/uploads/badges/ultimate_badge.png
--------------------------------------------------------------------------------
/clusters.yaml:
--------------------------------------------------------------------------------
1 | - alias: hasura
2 | config:
3 | configmap: controller-conf
4 | namespace: hasura
5 | data: null
6 | kubeContext: flashiness71
7 | name: flashiness71
8 |
--------------------------------------------------------------------------------
/microservices/www/src/public/uploads/badges/intermediate_badge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anantajitjg/eGyan/HEAD/microservices/www/src/public/uploads/badges/intermediate_badge.png
--------------------------------------------------------------------------------
/microservices/www/src/public/css/themes/default/assets/fonts/icons.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anantajitjg/eGyan/HEAD/microservices/www/src/public/css/themes/default/assets/fonts/icons.eot
--------------------------------------------------------------------------------
/microservices/www/src/public/css/themes/default/assets/fonts/icons.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anantajitjg/eGyan/HEAD/microservices/www/src/public/css/themes/default/assets/fonts/icons.otf
--------------------------------------------------------------------------------
/microservices/www/src/public/css/themes/default/assets/fonts/icons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anantajitjg/eGyan/HEAD/microservices/www/src/public/css/themes/default/assets/fonts/icons.ttf
--------------------------------------------------------------------------------
/microservices/www/src/public/css/themes/default/assets/fonts/icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anantajitjg/eGyan/HEAD/microservices/www/src/public/css/themes/default/assets/fonts/icons.woff
--------------------------------------------------------------------------------
/microservices/www/src/public/css/themes/default/assets/fonts/icons.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anantajitjg/eGyan/HEAD/microservices/www/src/public/css/themes/default/assets/fonts/icons.woff2
--------------------------------------------------------------------------------
/microservices/www/src/public/css/themes/default/assets/images/flags.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anantajitjg/eGyan/HEAD/microservices/www/src/public/css/themes/default/assets/images/flags.png
--------------------------------------------------------------------------------
/migrations/1511336750791_drop_relationship_user_badge_status_table_badge_status.up.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | relationship: user_badge_status
3 | table: badge_status
4 | type: drop_relationship
5 |
--------------------------------------------------------------------------------
/migrations/1517740283581_add_relationship_enrolled_count_table_course_details.down.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | relationship: enrolled_count
3 | table: course_details
4 | type: drop_relationship
5 |
--------------------------------------------------------------------------------
/microservices/www/src/public/js/student_home.js:
--------------------------------------------------------------------------------
1 | $(function () {
2 | //help popup
3 | $('.tooltip_btn').popup({
4 | popup: $('.help_popup'),
5 | on: 'click',
6 | });
7 | });
--------------------------------------------------------------------------------
/migrations/1517740347759_add_relationship_avg_course_rating_table_course_details.down.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | relationship: avg_course_rating
3 | table: course_details
4 | type: drop_relationship
5 |
--------------------------------------------------------------------------------
/conf/gateway.yaml:
--------------------------------------------------------------------------------
1 | # Configuration for opening the the Gateway to the external world
2 | # Filled in from cluster metadata, contains Kubernete spec for the service object
3 | {{ cluster.metadata.gateway|json }}
4 |
--------------------------------------------------------------------------------
/microservices/www/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mhart/alpine-node:7.6.0
2 |
3 | WORKDIR /src
4 |
5 | # Add app source files
6 | ADD src /src
7 |
8 | #install node modules
9 | RUN npm install
10 |
11 | CMD ["node", "server.js"]
12 |
--------------------------------------------------------------------------------
/migrations/1511336653999_add_relationship_user_badge_status_table_badge_status.up.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | name: user_badge_status
3 | table: badge_status
4 | using:
5 | foreign_key_constraint_on: badge_id
6 | type: create_object_relationship
7 |
--------------------------------------------------------------------------------
/migrations/1517757204788_Update_permission_user_table_user_other_details.up.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | permission:
3 | check:
4 | user_id:
5 | $eq: X-HASURA-USER-ID
6 | role: user
7 | table: user_other_details
8 | type: create_insert_permission
9 |
--------------------------------------------------------------------------------
/migrations/1511337601240_modify_permission_user_table_enrolled_count.up.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | permission:
3 | columns:
4 | - course_id
5 | - enrolled
6 | filter: {}
7 | role: user
8 | table: enrolled_count
9 | type: create_select_permission
10 |
--------------------------------------------------------------------------------
/migrations/1517757306956_Update_permission_anonymous_table_user_other_details.up.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | permission:
3 | check:
4 | user_id:
5 | $eq: X-HASURA-USER-ID
6 | role: anonymous
7 | table: user_other_details
8 | type: create_insert_permission
9 |
--------------------------------------------------------------------------------
/migrations/1511337593698_modify_permission_anonymous_table_enrolled_count.up.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | permission:
3 | columns:
4 | - course_id
5 | - enrolled
6 | filter: {}
7 | role: anonymous
8 | table: enrolled_count
9 | type: create_select_permission
10 |
--------------------------------------------------------------------------------
/microservices/www/src/public/js/student_common.js:
--------------------------------------------------------------------------------
1 | $(function () {
2 | $.ajaxSetup({
3 | crossDomain: true,
4 | xhrFields: {
5 | withCredentials: true
6 | },
7 | headers: {
8 | 'X-Hasura-Role': 'user'
9 | }
10 | });
11 | });
--------------------------------------------------------------------------------
/migrations/1511337048372_add_relationship_module_topics_table_module_details.up.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | name: module_topics
3 | table: module_details
4 | using:
5 | foreign_key_constraint_on:
6 | column: module_id
7 | table: topic_details
8 | type: create_array_relationship
9 |
--------------------------------------------------------------------------------
/migrations/1511343438733_modify_permission_user_table_course_rating.up.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | permission:
3 | columns:
4 | - user_id
5 | - course_id
6 | - rating
7 | filter: {}
8 | role: user
9 | table: course_rating
10 | type: create_select_permission
11 |
--------------------------------------------------------------------------------
/migrations/1511343486328_modify_permission_user_table_course_status.up.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | permission:
3 | columns:
4 | - user_id
5 | - course_id
6 | - status
7 | filter: {}
8 | role: user
9 | table: course_status
10 | type: create_select_permission
11 |
--------------------------------------------------------------------------------
/migrations/1511336842472_add_relationship_user_badge_status_table_badge_details.up.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | name: user_badge_status
3 | table: badge_details
4 | using:
5 | foreign_key_constraint_on:
6 | column: badge_id
7 | table: badge_status
8 | type: create_array_relationship
9 |
--------------------------------------------------------------------------------
/migrations/1511337642524_modify_permission_user_table_avg_course_rating.up.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | permission:
3 | columns:
4 | - course_id
5 | - rating
6 | - count
7 | filter: {}
8 | role: user
9 | table: avg_course_rating
10 | type: create_select_permission
11 |
--------------------------------------------------------------------------------
/migrations/1511343088059_modify_permission_user_table_user_other_details.up.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | permission:
3 | columns:
4 | - user_id
5 | - name
6 | - points
7 | filter: {}
8 | role: user
9 | table: user_other_details
10 | type: create_select_permission
11 |
--------------------------------------------------------------------------------
/migrations/1511343539563_modify_permission_user_table_module_details.up.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | permission:
3 | columns:
4 | - module_id
5 | - module_name
6 | - course_id
7 | filter: {}
8 | role: user
9 | table: module_details
10 | type: create_select_permission
11 |
--------------------------------------------------------------------------------
/microservices/www/src/template/partials/scriptsCommon.handlebars:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/migrations/1511336943803_add_relationship_user_course_status_table_course_details.up.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | name: user_course_status
3 | table: course_details
4 | using:
5 | foreign_key_constraint_on:
6 | column: course_id
7 | table: course_status
8 | type: create_array_relationship
9 |
--------------------------------------------------------------------------------
/migrations/1511336964876_add_relationship_user_course_rating_table_course_details.up.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | name: user_course_rating
3 | table: course_details
4 | using:
5 | foreign_key_constraint_on:
6 | column: course_id
7 | table: course_rating
8 | type: create_array_relationship
9 |
--------------------------------------------------------------------------------
/migrations/1511337065542_add_relationship_user_topic_status_table_module_details.up.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | name: user_topic_status
3 | table: module_details
4 | using:
5 | foreign_key_constraint_on:
6 | column: module_id
7 | table: topic_status
8 | type: create_array_relationship
9 |
--------------------------------------------------------------------------------
/migrations/1511337614362_modify_permission_anonymous_table_avg_course_rating.up.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | permission:
3 | columns:
4 | - course_id
5 | - count
6 | - rating
7 | filter: {}
8 | role: anonymous
9 | table: avg_course_rating
10 | type: create_select_permission
11 |
--------------------------------------------------------------------------------
/migrations/1511343593274_modify_permission_user_table_topic_status.up.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | permission:
3 | columns:
4 | - user_id
5 | - topic_id
6 | - topic_points
7 | - module_id
8 | filter: {}
9 | role: user
10 | table: topic_status
11 | type: create_select_permission
12 |
--------------------------------------------------------------------------------
/microservices/README.md:
--------------------------------------------------------------------------------
1 | # microservices
2 |
3 | Everything regarding the micro-services you add to the project is kept here. Each microservice is a directory containing a `k8s.yaml` file which holds the Kubernetes objects required for it and a `src` directory, if continuous integration is configured.
4 |
5 | `k8s.yaml` can also be templated.
6 |
--------------------------------------------------------------------------------
/migrations/1517740283581_add_relationship_enrolled_count_table_course_details.up.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | name: enrolled_count
3 | table: course_details
4 | using:
5 | manual_configuration:
6 | column_mapping:
7 | course_id: course_id
8 | remote_table: enrolled_count
9 | type: create_object_relationship
10 |
--------------------------------------------------------------------------------
/migrations/1511343565097_modify_permission_user_table_topic_details.up.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | permission:
3 | columns:
4 | - topic_id
5 | - topic_name
6 | - topic_content
7 | - topic_points
8 | - module_id
9 | filter: {}
10 | role: user
11 | table: topic_details
12 | type: create_select_permission
13 |
--------------------------------------------------------------------------------
/migrations/1517740347759_add_relationship_avg_course_rating_table_course_details.up.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | name: avg_course_rating
3 | table: course_details
4 | using:
5 | manual_configuration:
6 | column_mapping:
7 | course_id: course_id
8 | remote_table: avg_course_rating
9 | type: create_object_relationship
10 |
--------------------------------------------------------------------------------
/migrations/1511337554690_modify_permission_user_table_course_details.up.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | permission:
3 | columns:
4 | - name
5 | - about
6 | - syllabus
7 | - course_logo
8 | - active
9 | - course_id
10 | filter: {}
11 | role: user
12 | table: course_details
13 | type: create_select_permission
14 |
--------------------------------------------------------------------------------
/microservices/www/src/template/partials/navBasic.handlebars:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/microservices/www/src/template/partials/modules.handlebars:
--------------------------------------------------------------------------------
1 |
2 | Syllabus
3 |
4 |
5 |
Loading Modules...
6 |
7 |
--------------------------------------------------------------------------------
/migrations/1511337531704_modify_permission_anonymous_table_course_details.up.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | permission:
3 | columns:
4 | - course_id
5 | - name
6 | - about
7 | - syllabus
8 | - course_logo
9 | - active
10 | filter: {}
11 | role: anonymous
12 | table: course_details
13 | type: create_select_permission
14 |
--------------------------------------------------------------------------------
/microservices/www/src/public/js/common.js:
--------------------------------------------------------------------------------
1 | var rootURL = window.location.protocol + "//" + window.location.host;
2 | var cluster_name = "flashiness71";
3 | var auth_query_url = "https://auth." + cluster_name + ".hasura-app.io/v1";
4 | var data_query_url = "https://data." + cluster_name + ".hasura-app.io/v1/query";
5 | var app_url = "https://www." + cluster_name + ".hasura-app.io";
6 |
7 | console.log(rootURL);
--------------------------------------------------------------------------------
/microservices/www/src/public/js/course_home.js:
--------------------------------------------------------------------------------
1 | $(function () {
2 | //For closing messages
3 | $(".message .close").on('click', function () {
4 | $(this).closest('.message').transition('fade');
5 | });
6 | //Sidebar for Handheld Devices
7 | $("#module_sidebar").sidebar({
8 | context: $('#course_content_segment')
9 | }).sidebar('attach events', '#course_module .item');
10 | });
--------------------------------------------------------------------------------
/migrations/1511337635455_modify_permission_anonymous_table_avg_course_rating.up.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | role: anonymous
3 | table: avg_course_rating
4 | type: drop_select_permission
5 | - args:
6 | permission:
7 | columns:
8 | - course_id
9 | - count
10 | - rating
11 | filter: {}
12 | role: anonymous
13 | table: avg_course_rating
14 | type: create_select_permission
15 |
--------------------------------------------------------------------------------
/conf/authorized-keys.yaml:
--------------------------------------------------------------------------------
1 | # Defines where SSH keys are stored on the cluster.
2 | #
3 | # In this current configuration, it points to the `authorizedKeys`
4 | # key of configmap `ssh-authorized-keys` which is created
5 | # during project creation.
6 | #
7 | # All SSH keys added to the cluster (using `hasura ssh-key add`)
8 | # are stored in this ConfigMap
9 |
10 | configMapKeyRef:
11 | name: ssh-authorized-keys
12 | key: authorizedKeys
13 |
--------------------------------------------------------------------------------
/conf/http-directives.conf:
--------------------------------------------------------------------------------
1 | # You can add nginx directives that can go into the 'http' section of the gateway here
2 | gzip on;
3 | gzip_http_version 1.1;
4 | gzip_min_length 1024;
5 | gzip_types text/plain text/css text/javascript application/json application/x-javascript application/javascript text/xml application/xml application/xml+rss;
6 | gzip_proxied any;
7 | gzip_disable msie6;
8 | gzip_comp_level 1;
9 |
--------------------------------------------------------------------------------
/migrations/README.md:
--------------------------------------------------------------------------------
1 | # migrations
2 |
3 | All database migrations are stored in this directory.
4 |
5 | You can create a migration using hasuractl migration commands or using the api-console.
6 |
7 | Four files are created per migration:
8 |
9 | - -name.up.yaml
10 | - -name.up.sql
11 | - -name.down.yaml
12 | - -name.down.sql
13 |
14 | You can apply these migrations on any cluster you add later also.
--------------------------------------------------------------------------------
/microservices/www/src/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "egyan",
3 | "version": "1.0.0",
4 | "description": "eGyan - Simple and effective elearning platform for everyone",
5 | "scripts": {
6 | "start": "node server.js"
7 | },
8 | "author": "",
9 | "license": "ISC",
10 | "dependencies": {
11 | "body-parser": "^1.17.2",
12 | "express": "^4.14.0",
13 | "express-handlebars": "^3.0.0",
14 | "express-session": "^1.15.6",
15 | "request": "^2.86.0"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/microservices/www/src/template/partials/navStudent.handlebars:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/microservices/www/src/template/partials/footer.handlebars:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/microservices/www/src/template/partials/courses.handlebars:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
Loading Courses...
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/microservices/www/src/template/layouts/main.handlebars:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{title}}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | {{{body}}}
15 |
16 |
17 |
--------------------------------------------------------------------------------
/migrations/1511343800042_modify_permission_user_table_course_rating.up.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | role: user
3 | table: course_rating
4 | type: drop_select_permission
5 | - args:
6 | permission:
7 | check:
8 | user_id: REQ_USER_ID
9 | role: user
10 | table: course_rating
11 | type: create_insert_permission
12 | - args:
13 | permission:
14 | columns:
15 | - user_id
16 | - course_id
17 | - rating
18 | filter: {}
19 | role: user
20 | table: course_rating
21 | type: create_select_permission
22 |
--------------------------------------------------------------------------------
/conf/domains.yaml:
--------------------------------------------------------------------------------
1 | # Domain configuration for the gateway
2 | # Hasura allots a cluster-name.hasura-app.io for every cluster
3 |
4 | {{ cluster.name }}.hasura-app.io:
5 | ssl:
6 | type: LetsEncrypt
7 | conf:
8 | account: {{ cluster.name }}
9 |
10 | # Point your own domain to the IP of the cluster and add the domain here
11 | # Example configuration for domain with free SSL certs from LetsEncypt
12 | #
13 | # example-domain.com:
14 | # ssl:
15 | # type: LetsEncrypt
16 | # conf: {}
17 |
18 | # Example configurtaion for a domain without SSL
19 | #
20 | # example-domain.com:
21 | # ssl: null
22 |
--------------------------------------------------------------------------------
/conf/postgres.yaml:
--------------------------------------------------------------------------------
1 | # Configuration for the postgres service
2 | #
3 | # Databse username and password are resolved from the secrets on the cluster
4 | #
5 | # Note that these values are read only on initilisation and modifying
6 | # them in `hasura secrets` will not change them on postgres
7 | #
8 | # Volume is the Kubernetes volume object where the database is stored
9 |
10 | database: hasuradb
11 | port: "5432"
12 | user:
13 | secretKeyRef:
14 | key: postgres.user
15 | name: hasura-secrets
16 | password:
17 | secretKeyRef:
18 | key: postgres.password
19 | name: hasura-secrets
20 | volume: {{ cluster.metadata.postgres.volume|json }}
21 |
--------------------------------------------------------------------------------
/microservices/www/src/template/logout.handlebars:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{> navBasic }}
5 |
6 |
7 |
8 |
9 |
You have been successfully logged out of the system!
10 |
Redirecting to home page in 10 seconds
11 |
12 |
13 |
14 | {{> footer }}
15 |
16 |
17 | {{> scriptsCommon }}
18 | {{> scriptsUserCommon }}
--------------------------------------------------------------------------------
/conf/ci.yaml:
--------------------------------------------------------------------------------
1 | # Defines continuous integration for the project
2 | # registry defines a custom docker registry to push built images
3 | registry: {{ cluster.metadata.registry|json }}
4 | remotes:
5 | # Default remote to push code and configuration
6 | {{ cluster.name }}:
7 | <<: {}
8 |
9 | '{{ cluster.metadata.namespaces.user }}.www':
10 | www:
11 | dockerfile: microservices/www/Dockerfile
12 | path: microservices/www
13 | # Add new remotes here above this line
14 | # Example:
15 |
16 | # remote-name:
17 | # namespace.service-name:
18 | # container-name:
19 | # path: services/app
20 | # dockerfile: services/app/Dockerfile
21 |
--------------------------------------------------------------------------------
/microservices/www/README.md:
--------------------------------------------------------------------------------
1 | # Quickstart - Build your own Docker image#
2 |
3 | Build the Docker image using the following command
4 |
5 | ```bash
6 | $ docker build -t nodejs-express: .
7 | ```
8 |
9 | Run the Docker container using the command below.
10 |
11 | ```bash
12 | $ docker run -d -p 8080:8080 nodejs-express:
13 | ```
14 |
15 | # Quickstart - git based pipeline
16 |
17 | Follow the steps mentioned below for git based pipeline
18 |
19 | 1. Ensure that you have a git project
20 | 2. Edit `app/src/server.js`
21 | 3. Commit your changes
22 |
23 | ```bash
24 | $ git add .
25 | $ git commit -m "message"
26 | ```
27 |
28 | 4. Push the changes to git
29 |
30 | ```bash
31 | $ git push master
32 | ```
33 |
34 | # Advanced usage
35 |
36 | ### **Port**
37 |
38 | Default Port for application is `8080` .
39 |
--------------------------------------------------------------------------------
/conf/filestore.yaml:
--------------------------------------------------------------------------------
1 | # Configuration for Hausra Filestore service
2 |
3 | # hookUrl
4 | # -------
5 | # defines the URL to be contacted for enforcing permissions
6 | # The filestore service has the following built in hooks:
7 | #
8 | # Private: Only logged in users can read and upload
9 | # hookUrl: http://localhost:8080/v1/hooks/user-read-write
10 | #
11 | # Public: Anybody can read, but only logged in users can upload
12 | # hookUrl: http://localhost:8080/v1/hooks/public-read-user-write
13 | #
14 | # Read Only: Anybody can read, but no one can upload
15 | # hookUrl: http://localhost:8080/v1/hooks/public-read
16 | #
17 | # Custom Permission URL: For any other custom permissions, you need to define your own service
18 | #
19 | # Default permissions are admin-only
20 |
21 | hookUrl: null
22 |
23 | # volume
24 | # -------
25 | # Volume defines the Kubernetes volume to be mounted for storing the files, this is usually filled in from the metadata.
26 |
27 | volume: {{ cluster.metadata.filestore.volume|json }}
28 |
--------------------------------------------------------------------------------
/microservices/www/src/public/js/logout.js:
--------------------------------------------------------------------------------
1 | $(function () {
2 | $('.logout_btn').click(function () {
3 | $(this).addClass("loading disabled");
4 | $.ajax({
5 | method: "POST",
6 | url: auth_query_url + "/user/logout",
7 | contentType: "application/json"
8 | }).done(function (res) {
9 | //console.log(res);
10 | window.location = "/logout";
11 | }).fail(function (xhr) {
12 | console.log("Error logging you out!");
13 | });
14 | });
15 | //redirect counter
16 | var $redirect_counter = $('#redirect_counter');
17 | if ($redirect_counter.length > 0) {
18 | var counter = parseInt($redirect_counter.text());
19 | setInterval(function () {
20 | if (counter > 0) {
21 | counter = counter - 1;
22 | $redirect_counter.text(counter);
23 | } else {
24 | window.location = "/";
25 | }
26 | }, 1000);
27 | }
28 | });
--------------------------------------------------------------------------------
/migrations/1511335553183_add_all_existing_table_or_view.up.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | name: badge_details
3 | type: add_existing_table_or_view
4 | - args:
5 | name: badge_status
6 | type: add_existing_table_or_view
7 | - args:
8 | name: course_rating
9 | type: add_existing_table_or_view
10 | - args:
11 | name: course_status
12 | type: add_existing_table_or_view
13 | - args:
14 | name: course_details
15 | type: add_existing_table_or_view
16 | - args:
17 | name: module_details
18 | type: add_existing_table_or_view
19 | - args:
20 | name: topic_details
21 | type: add_existing_table_or_view
22 | - args:
23 | name: user_other_details
24 | type: add_existing_table_or_view
25 | - args:
26 | name: topic_status
27 | type: add_existing_table_or_view
28 | - args:
29 | name: avg_course_rating
30 | type: add_existing_table_or_view
31 | - args:
32 | name: enrolled_count
33 | type: add_existing_table_or_view
34 | - args:
35 | name: total_users
36 | type: add_existing_table_or_view
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Anantajit JG
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/microservices/www/k8s.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | items:
3 | - apiVersion: extensions/v1beta1
4 | kind: Deployment
5 | metadata:
6 | creationTimestamp: null
7 | labels:
8 | app: www
9 | hasuraService: custom
10 | name: www
11 | namespace: '{{ cluster.metadata.namespaces.user }}'
12 | spec:
13 | replicas: 1
14 | strategy: {}
15 | template:
16 | metadata:
17 | creationTimestamp: null
18 | labels:
19 | app: www
20 | spec:
21 | containers:
22 | - image: hasura/hello-world:latest
23 | imagePullPolicy: IfNotPresent
24 | name: www
25 | ports:
26 | - containerPort: 8080
27 | protocol: TCP
28 | resources: {}
29 | securityContext: {}
30 | terminationGracePeriodSeconds: 0
31 | status: {}
32 | - apiVersion: v1
33 | kind: Service
34 | metadata:
35 | creationTimestamp: null
36 | labels:
37 | app: www
38 | hasuraService: custom
39 | name: www
40 | namespace: '{{ cluster.metadata.namespaces.user }}'
41 | spec:
42 | ports:
43 | - port: 80
44 | protocol: TCP
45 | targetPort: 8080
46 | selector:
47 | app: www
48 | type: ClusterIP
49 | status:
50 | loadBalancer: {}
51 | kind: List
52 | metadata: {}
53 |
--------------------------------------------------------------------------------
/conf/notify.yaml:
--------------------------------------------------------------------------------
1 | # Configuration for Hasura Notify service
2 | #
3 | # All options are configured to read from the secret called hasura-secrets
4 | # To enable a provider:
5 | # 1. Add the required secrets. Checkout hasura secrets --help for more information
6 | # 2. modify the default value to the provider that you want
7 |
8 | email:
9 | # default can take values 'smtp' or 'sparkPost
10 | default: null
11 | providers:
12 | smtp:
13 | hostname: ""
14 | password:
15 | secretKeyRef:
16 | key: notify.smtp.password
17 | name: hasura-secrets
18 | port: 465
19 | username:
20 | secretKeyRef:
21 | key: notify.smtp.username
22 | name: hasura-secrets
23 | sparkPost:
24 | apiKey:
25 | secretKeyRef:
26 | key: notify.sparkpost.key
27 | name: hasura-secrets
28 | sms:
29 | # default can take values 'msg91' or 'twilio'
30 | default: null
31 | providers:
32 | msg91:
33 | from: ""
34 | authKey:
35 | secretKeyRef:
36 | key: notify.msg91.key
37 | name: hasura-secrets
38 | twilio:
39 | from: ""
40 | accountSid:
41 | secretKeyRef:
42 | key: notify.twilio.accountsid
43 | name: hasura-secrets
44 | authToken:
45 | secretKeyRef:
46 | key: notify.twilio.authtoken
47 | name: hasura-secrets
48 |
--------------------------------------------------------------------------------
/microservices/www/src/template/partials/modalLogin.handlebars:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/microservices/www/src/template/partials/modalSignUp.handlebars:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/microservices/www/src/template/index.handlebars:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
18 |
19 |
38 |
39 |
40 |
41 |
42 | {{> courses courseType="Courses" courseTypeId="course_accordion" }}
43 |
44 |
45 |
46 | {{> footer }}
47 |
48 |
49 | {{> modalLogin }} {{> modalSignUp }}
50 |
51 |
52 | {{> scriptsCommon }}
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/conf/README.md:
--------------------------------------------------------------------------------
1 | # conf
2 |
3 | This directory has the configuration to deploy your project on any Hasura cluster. With the secrets (like API keys, client secrets) and this configuration, you can deploy the project on any Hasura cluster.
4 |
5 | The configuration is templated so that it can be seamlessly used across multiple clusters. Templating also helps with the management of different kinds of clusters, for example you may have a dev, staging and production clusters where the configuration might differ slightly. The templates are written in [pongo2](https://github.com/flosch/pongo2), a jinja like templating language.
6 |
7 | A context variable called `cluster` is available to be used in the templates. This is an extension of the cluster object present in `clusters.yaml` file. Apart from the keys present in `clusters.yaml`, a key called `metadata` with some cluster specific information will be present.
8 |
9 | If you need to add your own variables, you can add them under the `data` key in `clusters.yaml` and this will be available as `cluster.data`
10 |
11 | The entire context variable `cluster` can be viewed anytime using the command:
12 |
13 | ```bash
14 | $ hasura cluster template-context
15 | ```
16 |
17 | A typical context is as follows:
18 |
19 | ```yaml
20 | name: h34-ambitious93-stg
21 | alias: hasura
22 | kubeContext: ambitious93
23 | config:
24 | configmap: controller-conf
25 | namespace: hasura
26 | metadata:
27 | filestore:
28 | volume:
29 | hostPath:
30 | path: /data/hasura.io/filestore
31 | name: filestore-pv
32 | gateway:
33 | externalIPs:
34 | - 111.222.33.44
35 | ports:
36 | - name: http
37 | port: 80
38 | protocol: TCP
39 | targetPort: 80
40 | - name: https
41 | port: 443
42 | protocol: TCP
43 | targetPort: 443
44 | - name: ssh
45 | port: 22
46 | protocol: TCP
47 | targetPort: 22
48 | namespaces:
49 | hasura: hasura
50 | user: default
51 | postgres:
52 | volume:
53 | hostPath:
54 | path: /data/hasura.io/postgres
55 | name: postgres-pv
56 | registry: null
57 | sessionStore:
58 | volume:
59 | hostPath:
60 | path: /data/hasura.io/redis
61 | name: redis-pv
62 | data: null
63 | ```
64 |
--------------------------------------------------------------------------------
/microservices/www/src/public/js/homepage.js:
--------------------------------------------------------------------------------
1 | // Trigger Login/Sign Up Modal
2 | function triggerModal(selector) {
3 | $(selector).modal({
4 | onHidden: function () {
5 | $('form').form('clear');
6 | $('form .message').html("");
7 | $(".main_loader").css("display", "none");
8 | $("form .button").removeClass("disabled");
9 | $('.user_error').css("display", "none");
10 | }
11 | }).modal('show');
12 | }
13 |
14 | $(function () {
15 | // fix menu when passed
16 | $('.masthead')
17 | .visibility({
18 | once: false,
19 | onBottomPassed: function () {
20 | $('.fixed.menu').transition('fade in');
21 | },
22 | onBottomPassedReverse: function () {
23 | $('.fixed.menu').transition('fade out');
24 | }
25 | });
26 | //Login/Sign Up Modal
27 | $('.signup_trigger').click(function () {
28 | triggerModal(".signup_modal");
29 | });
30 | $('.login_trigger').click(function () {
31 | triggerModal(".login_modal");
32 | });
33 |
34 | //Load Courses
35 | var fetch_course_query = {
36 | "type": "select",
37 | "args": {
38 | "table": "course_details",
39 | "columns": [
40 | "course_id", "name", "about", "course_logo",
41 | {
42 | "name": "enrolled_count",
43 | "columns": ["enrolled"]
44 | },
45 | {
46 | "name": "avg_course_rating",
47 | "columns": ["count", "rating"]
48 | }
49 | ],
50 | "where": {
51 | "active": true
52 | },
53 | "order_by": ["-avg_course_rating.rating", "-avg_course_rating.count", "-enrolled_count.enrolled"]
54 | }
55 | };
56 | $.ajax({
57 | method: "POST",
58 | url: data_query_url,
59 | data: JSON.stringify(fetch_course_query),
60 | contentType: "application/json"
61 | }).done(function (data) {
62 | //console.log(data);
63 | displayAccordion(data, $('#course_accordion'), "normal");
64 | }).fail(function (xhr) {
65 | console.log(xhr);
66 | displayAccordionError($('#course_accordion'));
67 | }).always(function () {
68 | $(".course_loader").hide();
69 | });
70 | });
--------------------------------------------------------------------------------
/microservices/www/src/template/student.handlebars:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{> navStudent }}
5 |
6 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
Total Points:
32 |
33 |
Loading Badges...
34 |
35 |
36 |
37 |
38 |
39 |
40 | {{> courses courseType="Active Courses" courseTypeId="active_course_accordion" }}
41 |
42 |
43 | {{> courses courseType="Completed Courses" courseTypeId="completed_course_accordion" }}
44 |
45 |
46 | {{> courses courseType="Available Courses" courseTypeId="available_course_accordion" }}
47 |
48 |
49 |
50 |
51 | {{> footer }}
52 |
53 |
54 | {{> scriptsCommon }} {{> scriptsUserCommon }}
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # eGyan
2 | eGyan is a web application built with Node.js (Express) and [Hasura](https://hasura.io/) Platform. It is a simple and effective eLearning app for everyone.
3 |
4 | ## Overview
5 | This provides you with a great starting point for building a full-fledged eLearning application. In eGyan a user/student needs to register with a username and password for accessing the course content and to save his course progress. Courses are categorized based on activity- Active courses, completed courses, courses yet to be enrolled/Available Courses. Each courses are divided into separate modules. Each module contains different topics. Each topics are points based. So, students can accumulate points to collect different badges. This will make learning interesting! In order to collect points they have to mark each topic as completed!
6 |
7 | [](https://youtu.be/5VeZMmC7Idc)
8 |
9 | ## Setup
10 |
11 | * [Initial Setup](#initial-setup)
12 | * [Project Structure](#project-structure)
13 | * [Hasura cluster](#hasura-cluster)
14 | * [Deploy the Project](#deploy-the-project)
15 | * [Accessing Console](#accessing-console)
16 |
17 | ### Initial Setup
18 | #### Setup the Hasura CLI
19 |
20 | The hasura CLI is a command line utility to help you get your backend setup quickly. It helps you create projects, manage clusters and manage microservices and explore APIs running on the cluster.
21 | The instructions for setup are available from [here](https://docs.hasura.io/0.15/manual/tutorial/1-setup-hasura-cli.html).
22 |
23 | ##### Login
24 |
25 | Next, login or register by running the following command:
26 |
27 | ```
28 | $ hasura login
29 | ```
30 | This command will open up the browser and allow you to register with a new account (or login to your existing account).
31 |
32 | #### Clone the project
33 | Now, clone this project by running this command
34 | ```
35 | $ hasura clone anantajitjg/egyan
36 | ```
37 |
38 | ### Project Structure
39 |
40 | The project (a.k.a. project directory) has a particular directory structure and it has to be maintained strictly, else `hasura` cli would not work as expected. A *representative project* is shown below:
41 |
42 | ```
43 | .
44 | ├── hasura.yaml
45 | ├── clusters.yaml
46 | ├── conf
47 | │ ├── authorized-keys.yaml
48 | │ ├── auth.yaml
49 | │ ├── ci.yaml
50 | │ ├── domains.yaml
51 | │ ├── filestore.yaml
52 | │ ├── gateway.yaml
53 | │ ├── http-directives.conf
54 | │ ├── notify.yaml
55 | │ ├── postgres.yaml
56 | │ ├── routes.yaml
57 | │ └── session-store.yaml
58 | ├── migrations
59 | └── microservices
60 | ├── adminer
61 | │ └── k8s.yaml
62 | └── www
63 | ├── src/
64 | ├── k8s.yaml
65 | └── Dockerfile
66 | ```
67 |
68 | #### `hasura.yaml`
69 |
70 | This file contains some metadata about the project, namely a name, description and some keywords. Also contains `platformVersion` which says which Hasura platform version is compatible with this project.
71 |
72 | #### `clusters.yaml`
73 |
74 | Info about the clusters added to this project can be found in this file. Each cluster is defined by it's name allotted by Hasura. While adding the cluster to the project you are prompted to give an alias, which is just hasura by default. The `kubeContext` mentions the name of kubernetes context used to access the cluster, which is also managed by hasura. The `config` key denotes the location of cluster's metadata on the cluster itself. This information is parsed and cluster's metadata is appended while conf is rendered. `data` key is for holding custom variables that you can define.
75 |
76 | ### Hasura cluster
77 |
78 | We need a Hasura cluster where we can deploy our project.
79 | ***The instructions for creating a Hasura cluster*** are available from [here](https://docs.hasura.io/0.15/manual/tutorial/3-hasura-cluster.html).
80 |
81 | #### Get cluster information and the microservices running
82 |
83 | Inside your project directory, run:
84 |
85 | ```
86 | $ hasura cluster status
87 | ```
88 | #### common.js
89 | Edit cluster name in common.js (`microservices > www > src > public > js`) with your current cluster name. For example, if the cluster name is `flashiness71` (existing one), then
90 | ```javascript
91 | var cluster_name = "flashiness71";
92 | ```
93 |
94 | ### Deploy the Project
95 |
96 | ```
97 | $ git add .
98 | $ git commit -m "some message"
99 | $ git push hasura master
100 | ```
101 | Once this project is deployed on to a hasura cluster, you will have eGyan app running at **`https://www..hasura-app.io`**
102 |
103 | ### Accessing Console
104 |
105 | Now that you have deployed the project on your cluster, you would want to manage the schema and explore APIs.
106 |
107 | Access the **api-console** via the following command:
108 |
109 | ```
110 | $ hasura api-console
111 | ```
112 |
113 | This will open up Console UI on the browser. You can access it at [http://localhost:9695](http://localhost:9695)
114 |
115 | ## License
116 | eGyan is open-sourced software licensed under the MIT license. See the [LICENSE](https://raw.githubusercontent.com/anantajitjg/eGyan/master/LICENSE) for more.
117 |
--------------------------------------------------------------------------------
/microservices/www/src/public/js/course_accordion.js:
--------------------------------------------------------------------------------
1 | function displayAccordionInfo(accordion_selector, message) {
2 | accordion_selector.css("display", "none").html("").fadeIn();
3 | }
4 |
5 | function displayAccordionError(accordion_selector) {
6 | accordion_selector.css("display", "none").html("Please refresh this page or come back later!
").fadeIn();
7 | }
8 |
9 | function getCourseStatusContent(id, status) {
10 | var status_content = "",
11 | button_content = "";
12 | if (status === "active" || status === "completed" || status === "enroll") {
13 | button_content = "";
23 | }
24 | return status_content + button_content;
25 | }
26 |
27 | function displayAccordion(data, accordion_selector, course_status) {
28 | var courses = [],
29 | starred_courses = [],
30 | unstarred_courses = [],
31 | unenrolled_courses = [];
32 | var content = "",
33 | student_content = "";
34 | var total_courses = data.length;
35 | if (total_courses > 0) {
36 | for (var i = 0; i < total_courses; i++) {
37 | if (data[i].avg_course_rating !== null && data[i].enrolled_count !== null) {
38 | starred_courses.push(data[i]);
39 | } else {
40 | if (data[i].enrolled_count === null) {
41 | unenrolled_courses.push(data[i]);
42 | } else {
43 | unstarred_courses.push(data[i]);
44 | }
45 | }
46 | }
47 | unstarred_courses = unstarred_courses.concat(unenrolled_courses);
48 | courses = starred_courses.concat(unstarred_courses);
49 | for (var i = 0; i < courses.length; i++) {
50 | var course_info = {};
51 | course_info.id = courses[i].course_id;
52 | course_info.name = courses[i].name;
53 | course_info.about = courses[i].about;
54 | course_info.logo = courses[i].course_logo;
55 | course_info.rating = courses[i].avg_course_rating ? courses[i].avg_course_rating.rating : 0;
56 | course_info.users_rated = courses[i].avg_course_rating ? courses[i].avg_course_rating.count : 0;
57 | course_info.user_rating = courses[i].user_course_rating ? courses[i].user_course_rating.length > 0 ? courses[i].user_course_rating[0].rating : 0 : 0;
58 | course_info.enrolled = courses[i].enrolled_count ? courses[i].enrolled_count.enrolled : 0;
59 | //console.table(course_info);
60 | var active_class = course_status === "active" ? "active" : "";
61 | var rating_display = course_info.users_rated > 0 ? course_info.rating + " out of 5 by " + course_info.users_rated : "Not yet Rated! ";
62 | var user_rating_display = course_status === "completed" ? course_info.user_rating > 0 ? "Your Rating: " + course_info.user_rating + "
" : "" : "";
63 | var rounded_rating = Math.round(course_info.rating);
64 | //content for accordion
65 | content += " " + course_info.name + "
About:
" + course_info.about + "
Total Enrolled:
" + course_info.enrolled + "
Rating:
" + rating_display + user_rating_display + "
" + getCourseStatusContent(course_info.id, course_status) + "
";
66 | }
67 | accordion_selector.css("display", "none").html(content).fadeIn(function () {
68 | if (course_status === "active") {
69 | accordion_selector.accordion({
70 | exclusive: false
71 | });
72 | } else {
73 | accordion_selector.accordion();
74 | }
75 | $('.ui.rating').rating('disable');
76 | });
77 | } else {
78 | if (course_status === "active") {
79 | displayAccordionInfo(accordion_selector, "No Active Courses! Why don't you try some from Available Courses!");
80 | } else if (course_status === "completed") {
81 | displayAccordionInfo(accordion_selector, "Not Completed any Courses!");
82 | } else {
83 | displayAccordionInfo(accordion_selector, "No courses to display!");
84 | }
85 | }
86 | }
--------------------------------------------------------------------------------
/microservices/www/src/public/js/auth.js:
--------------------------------------------------------------------------------
1 | $(function () {
2 | var $login_form = $('#login_form');
3 | var $signup_form = $('#signup_form');
4 | $login_form.submit(function (e) {
5 | e.preventDefault();
6 | });
7 | $signup_form.submit(function (e) {
8 | e.preventDefault();
9 | });
10 | var fields_rules = {
11 | username: {
12 | identifier: 'username',
13 | rules: [{
14 | type: 'empty',
15 | prompt: 'Please enter a username'
16 | }]
17 | },
18 | password: {
19 | identifier: 'password',
20 | rules: [{
21 | type: 'empty',
22 | prompt: 'Please enter your password'
23 | }, {
24 | type: 'minLength[8]',
25 | prompt: 'Your password must be at least {ruleValue} characters'
26 | }]
27 | }
28 | };
29 | // login form
30 | //=================================================
31 | $login_form.form({
32 | fields: fields_rules,
33 | onFailure: function () {
34 | $(".login_modal").modal("refresh");
35 | },
36 | onInvalid: function () {
37 | if ($('.user_error').text() !== '') {
38 | $('.user_error').css("display", "none");
39 | }
40 | },
41 | onSuccess: function (e) {
42 | e.preventDefault();
43 | var fields = $login_form.form('get values');
44 | //console.log(fields);
45 | $("#login_loader").css("display", "inline");
46 | $("#login_btn").addClass("disabled");
47 | $('form .message').html("");
48 | $('.user_error').css("display", "none");
49 | var req_body = {
50 | "provider": "username",
51 | "data": fields
52 | };
53 | $.ajax({
54 | method: "POST",
55 | url: auth_query_url + "/login",
56 | xhrFields: {
57 | withCredentials: true
58 | },
59 | data: JSON.stringify(req_body),
60 | contentType: "application/json"
61 | }).done(function (res) {
62 | //console.log(res);
63 | var user_id = res.hasura_id;
64 | $.getJSON(rootURL + "/setinfo", { user_id: user_id})
65 | .done(function(json) {
66 | //console.log(json);
67 | window.location = "/student";
68 | }).fail(function(xhr) {
69 | //console.log(xhr);
70 | onLoginFail("Failed to login!");
71 | });
72 | }).fail(function (xhr) {
73 | //console.log(xhr);
74 | onLoginFail("Username or Password is wrong!");
75 | }).always(function () {
76 | $("#login_loader").css("display", "none");
77 | });
78 | }
79 | });
80 | function onLoginFail(msg) {
81 | $("#login_btn").removeClass("disabled");
82 | $login_form.append("");
83 | $(".login_modal").modal("refresh");
84 | }
85 | // signup form
86 | //=================================================
87 | fields_rules.name = {
88 | identifier: 'name',
89 | rules: [{
90 | type: 'empty',
91 | prompt: 'Please enter your name'
92 | }]
93 | };
94 | $signup_form.form({
95 | fields: fields_rules,
96 | onFailure: function () {
97 | $(".signup_modal").modal("refresh");
98 | },
99 | onInvalid: function () {
100 | if ($('.user_error').text() !== '') {
101 | $('.user_error').css("display", "none");
102 | }
103 | },
104 | onSuccess: function (e) {
105 | e.preventDefault();
106 | var fields = $signup_form.form('get values');
107 | //console.log(fields);
108 | $("#signup_loader").css("display", "inline");
109 | $("#signup_btn").addClass("disabled");
110 | $('form .message').html("");
111 | $('.user_error').css("display", "none");
112 | var data = JSON.stringify(fields);
113 | $.ajax({
114 | method: "POST",
115 | url: rootURL + "/signup",
116 | data: data,
117 | contentType: "application/json"
118 | }).done(function (res) {
119 | $signup_form.append("You can now
Log In
with your username and password!
");
120 | $(".signup_modal").modal("refresh");
121 | $('.login_trigger').click(function () {
122 | triggerModal(".login_modal");
123 | });
124 | }).fail(function (xhr) {
125 | //console.log(xhr);
126 | $("#signup_btn").removeClass("disabled");
127 | try {
128 | var result = JSON.parse(xhr.responseText);
129 | $signup_form.append("");
130 | } catch (e) {
131 | $signup_form.append("
");
132 | } finally {
133 | $(".signup_modal").modal("refresh");
134 | }
135 | }).always(function () {
136 | $("#signup_loader").css("display", "none");
137 | });
138 | }
139 | });
140 | });
--------------------------------------------------------------------------------
/microservices/www/src/template/course.handlebars:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{> navStudent }}
5 |
6 |
7 |
8 |
9 | {{#if courseStatus.available}}
10 |
11 |
12 |
You have enrolled in {{courseHeading}} course!
13 |
14 | {{else if courseStatus.completed}}
15 |
16 |
17 |
You have successfully completed {{courseHeading}} course!
18 |
19 | {{/if}}
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
35 |
36 |
37 |
38 |
About:
39 |
40 |
{{courseDescription}}
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
54 |
55 |
56 |
57 |
58 |
61 |
62 |
63 |
64 |
65 | {{> modules }}
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
{{{content}}}
76 |
77 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
106 |
107 |
108 |
109 |
110 | {{> footer }}
111 |
112 |
113 |
118 |
119 | {{> scriptsCommon }} {{> scriptsUserCommon }}
120 |
121 |
122 |
--------------------------------------------------------------------------------
/microservices/www/src/public/js/student_main.js:
--------------------------------------------------------------------------------
1 | function displayUserInfo(user_id) {
2 | var fetch_name_query = {
3 | "type": "select",
4 | "args": {
5 | "table": "user_other_details",
6 | "columns": [
7 | "user_id", "name", "points"
8 | ],
9 | "where": {
10 | "user_id": user_id
11 | }
12 | }
13 | };
14 | $.ajax({
15 | method: "POST",
16 | url: data_query_url,
17 | data: JSON.stringify(fetch_name_query),
18 | contentType: "application/json"
19 | }).done(function (data) {
20 | //console.log(data);
21 | $("#displayName .content").html("Hi " + data[0].name + " ");
22 | $('#displayName').transition({
23 | animation: 'fade right',
24 | duration: '400ms'
25 | });
26 | $("#total_points").html(data[0].points + "pts").fadeIn();
27 | displayBadges();
28 | }).fail(function (xhr) {
29 | //console.log(xhr.responseText);
30 | });
31 | }
32 | //For displaying Badges
33 | function displayBadges() {
34 | $.ajax({
35 | method: "GET",
36 | url: rootURL + "/fetch/badge",
37 | contentType: "application/json"
38 | }).done(function (data) {
39 | //console.log(data);
40 | var total_badges = data.length;
41 | if (total_badges > 0) {
42 | var badge_msg_content = "",
43 | badge_content = "";
44 | for (var i = 0; i < total_badges; i++) {
45 | if (data[i].user_badge_status.length === 0) {
46 | badge_msg_content += "You have received " + data[i].name + "! " + data[i].badge_description + "
";
47 | }
48 | }
49 | for (var i = total_badges - 1; i >= 0; i--) {
50 | var points_display = data[i].points > 0 ? "" + data[i].points + "pts
" : "";
51 | badge_content += "";
52 | }
53 | $("#user_badge_message").css("display", "none").html(badge_msg_content).fadeIn();
54 | $("#badge").css("display", "none").html(badge_content).fadeIn();
55 | $(".message .close").on('click', function () {
56 | $(this).closest('.message').transition('fade');
57 | });
58 | } else {
59 | $("#badge").css("display", "none").removeClass("loader_content").html("").fadeIn();
60 | }
61 | }).fail(function (xhr) {
62 | //console.log(xhr.responseText);
63 | $("#badge").css("display", "none").html("Please refresh this page or come back later!
").fadeIn();
64 | }).always(function () {
65 | $("#badge .loader").hide();
66 | });
67 | }
68 |
69 | function displayCourses(user_id) {
70 | var fetch_courses_query = {
71 | "type": "select",
72 | "args": {
73 | "table": "course_details",
74 | "columns": [
75 | "course_id", "name", "about", "course_logo",
76 | {
77 | "name": "enrolled_count",
78 | "columns": ["enrolled"]
79 | },
80 | {
81 | "name": "avg_course_rating",
82 | "columns": ["count", "rating"]
83 | },
84 | {
85 | "name": "user_course_rating",
86 | "columns": ["rating"],
87 | "where": {
88 | "user_id": user_id
89 | }
90 | },
91 | {
92 | "name": "user_course_status",
93 | "columns": ["status"],
94 | "where": {
95 | "user_id": user_id
96 | }
97 | }
98 | ],
99 | "where": {
100 | "active": true
101 | },
102 | "order_by": ["-avg_course_rating.rating", "-avg_course_rating.count", "-enrolled_count.enrolled"]
103 | }
104 | };
105 | $.ajax({
106 | method: "POST",
107 | url: data_query_url,
108 | data: JSON.stringify(fetch_courses_query),
109 | contentType: "application/json"
110 | }).done(function (data) {
111 | //console.log(data);
112 | var not_enrolled_courses = [],
113 | completed_courses = [],
114 | active_courses = [];
115 | for (var i = 0; i < data.length; i++) {
116 | if (data[i].user_course_status.length > 0) {
117 | if (data[i].user_course_status[0].status) {
118 | completed_courses.push(data[i]);
119 | } else {
120 | active_courses.push(data[i]);
121 | }
122 | } else {
123 | not_enrolled_courses.push(data[i]);
124 | }
125 | }
126 | displayAccordion(not_enrolled_courses, $("#available_course_accordion"), "enroll");
127 | displayAccordion(completed_courses, $("#completed_course_accordion"), "completed");
128 | displayAccordion(active_courses, $("#active_course_accordion"), "active");
129 | }).fail(function (xhr) {
130 | //console.log(xhr.responseText);
131 | displayAccordionError($(".main_accordion"));
132 | }).always(function () {
133 | $(".course_loader").hide();
134 | });
135 | }
136 |
137 | $(function () {
138 | //get user info
139 | $.ajax({
140 | method: "GET",
141 | url: auth_query_url + "/user/info",
142 | contentType: "application/json"
143 | }).done(function (res) {
144 | //console.log(res);
145 | displayUserInfo(res.hasura_id);
146 | displayCourses(res.hasura_id);
147 | }).fail(function (xhr) {
148 | console.log(xhr.responseText);
149 | //window.location = "/";
150 | });
151 | });
--------------------------------------------------------------------------------
/conf/routes.yaml:
--------------------------------------------------------------------------------
1 | # HTTP routes configured on the Gateway
2 | # -------------------------------------
3 | # This section lets you define the mapping between HTTP(S) routes on your domain(s) ond
4 | # the microservices running on your cluster
5 | #
6 | # The standard convention is to expose a microservice on a subdomain. The
7 | # default services (data, auth, filestore, notify etc.) on the platform are
8 | # exposed on their respective subdomains.
9 | #
10 | # Syntax
11 | # ------
12 | # The configuration syntax is fairly simple. It is a simple map, where the keys
13 | # are subdomains, while the values are the corsesponding subdomain configuration
14 | #
15 | # So, the structure of this file looks like this:
16 | #
17 | # subdomain1: subdomain1 configuration
18 | # subdomain2: subdomain2 configuration
19 | # .
20 | # .
21 | # subdomainn: subdomainn configuration
22 | #
23 | # > Subdomain's Configuration
24 | # -----------------------
25 | # Each subdomain's configuration is again a map. This time the keys are
26 | # location (path) prefixes and the values are the corresponding location
27 | # configuration. This lets you define the action that the gateway needs
28 | # to take when the location is matched.
29 | #
30 | # Expanding the above conf structure, we get
31 | #
32 | # subdomain1:
33 | # location1: location configuration
34 | # location2: location configuration
35 | # .
36 | # .
37 | # locationn: location configuration
38 | #
39 | # A location/path is the part of the url after the domain. For example:
40 | # In https://data.hello99.hasura-app.io/v1/query, /v1/query is the path
41 | #
42 | # Example 1:
43 | #
44 | # data:
45 | # /:
46 | # upstreamService:
47 | # name: data
48 | # namespace: {{ cluster.metadata.namespaces.hasura }}
49 | # upstreamServicePath: /
50 | # upstreamServicePort: 80
51 | #
52 | # Here, we are configuring the gateway to send any request on data
53 | # subdomain to the 'data' microservice in the hasura namespace if
54 | # the location has a prefix '/'. Since every location starts with
55 | # '/', this means that any request arriving on the data subdomain
56 | # is received by the data microservice.
57 | #
58 | # Example 2:
59 | #
60 | # api:
61 | # /shipping:
62 | # upstreamService:
63 | # name: shipping
64 | # namespace: {{ cluster.metadata.namespaces.user }}
65 | # upstreamServicePath: /
66 | # upstreamServicePort: 80
67 | # /recommed:
68 | # upstreamService:
69 | # name: recommend
70 | # namespace: {{ cluster.metadata.namespaces.user }}
71 | # upstreamServicePath: /
72 | # upstreamServicePort: 80
73 | #
74 | # Here, anything on api.hello99.hasura-app.io/shipping/* -> shipping/
75 | # api.hello99.hasura-app.io/recommend/* -> recommend/
76 | #
77 | # NOTE:
78 | # 1. Since the convention is to deploy a microservice on each domain, you'll rarely
79 | # see a configuration which has a location prefix other than '/'
80 | # 2. If in case the location matches to more than one rule, the configuration
81 | # related to the more specific rule is used
82 | #
83 | # > Location Configuration
84 | # ----------------------
85 | # The following options are available:
86 | # key required default
87 | # ------------------------------------------------
88 | # upstreamService Yes -
89 | # upstreamServicePort Yes -
90 | # upstreamServicePath Yes -
91 | # enableAuth No true
92 | # restrictToRoles No null
93 | # corsPolicy No []
94 | # enableWebsockets No true
95 | # locationDirectives No ""
96 | #
97 | # - upstreamService:
98 | # The service to forward the request to
99 | # - upstreamServicePort:
100 | # The port on which the service is running
101 | # - upstreamServicePath:
102 | # The path to which the request has to be forwarded
103 | # - enableAuth:
104 | # This enables the session middlaware on the gateway to intercept the
105 | # request and resolve the user's session based on Authorization header
106 | # or the Cookie
107 | # - restrictToRoles:
108 | # When the session middleware is enabled, restricts the matched URLs to
109 | # be only accessed by users which have at least one of these roles. This
110 | # is usually used to restrict access to admin only services like adminer etc.
111 | # - corsPolicy:
112 | # Can take the following 3 values:
113 | # 1. "allow_all": Cross origin requests from any domain are allowed
114 | # Eg. corsPolicy: allow_all
115 | # 2. "upstream" : The upstream service should handle CORS requests.
116 | # Eg. corsPolicy: upstream
117 | # 3. Array of allowed origins: This allows the listed origins along
118 | # with all the subdomains on the current domain to make CORS requests.
119 | # - enableWebsockets:
120 | # Whether to allow websockets
121 | # - locationDirectives:
122 | # (Advanced) Additional nginx directives that need to go into the
123 | # location block
124 |
125 | auth:
126 | /:
127 | upstreamService:
128 | name: auth
129 | namespace: {{ cluster.metadata.namespaces.hasura }}
130 | upstreamServicePath: /
131 | upstreamServicePort: 80
132 | corsPolicy: allow_all
133 | data:
134 | /:
135 | upstreamService:
136 | name: data
137 | namespace: {{ cluster.metadata.namespaces.hasura }}
138 | upstreamServicePath: /
139 | upstreamServicePort: 80
140 | corsPolicy: allow_all
141 | filestore:
142 | /:
143 | upstreamService:
144 | name: filestore
145 | namespace: {{ cluster.metadata.namespaces.hasura }}
146 | upstreamServicePath: /
147 | upstreamServicePort: 80
148 | corsPolicy: allow_all
149 | # To change the max file size that can be uploaded,
150 | # change the value of client_max_body_size
151 | locationDirectives: |
152 | proxy_request_buffering off;
153 | client_max_body_size 100M;
154 | notify:
155 | /:
156 | upstreamService:
157 | name: notify
158 | namespace: {{ cluster.metadata.namespaces.hasura }}
159 | upstreamServicePath: /
160 | upstreamServicePort: 80
161 | corsPolicy: allow_all
162 | www:
163 | /:
164 | corsPolicy: allow_all
165 | upstreamService:
166 | name: www
167 | namespace: '{{ cluster.metadata.namespaces.user }}'
168 | upstreamServicePath: /
169 | upstreamServicePort: 80
170 |
171 | # To see actual endpoints for these routes, use `$ hasura cluster status`
172 |
--------------------------------------------------------------------------------
/conf/auth.yaml:
--------------------------------------------------------------------------------
1 | # Configuration for Hasura Auth
2 |
3 | # All values in this configuration are strings, including boolean and integer
4 | # values.
5 |
6 | # Configuration for default providers
7 | # Each provider has the following fields:
8 | # `enabled` : To mark if the provider is enabled. Valid values are "true" or
9 | # "false".
10 | # `defaultRoles`: Specify the roles that get added when a user signs-up. By
11 | # default the user role is added (even when the list does not contain "user").
12 | # If you do not want any extra roles, leave it as an empty list.
13 | # Example: the below two examples are same
14 | # defaultRoles: ["user", "admin"]
15 | # defaultRoles: ["admin"]
16 | defaultProviders:
17 | username:
18 | enabled: "true"
19 | defaultRoles: []
20 | email:
21 | enabled: "true"
22 | defaultRoles: []
23 | mobile:
24 | enabled: "true"
25 | defaultRoles: []
26 | mobile-password:
27 | enabled: "false"
28 | defaultRoles: []
29 | google:
30 | enabled: "false"
31 | defaultRoles: []
32 | facebook:
33 | enabled: "false"
34 | defaultRoles: []
35 | github:
36 | enabled: "false"
37 | defaultRoles: []
38 | linkedin:
39 | enabled: "false"
40 | defaultRoles: []
41 |
42 | # Session related configuration
43 | session:
44 | # Name of the cookie. This is usually set to your cluster's domain. No need
45 | # to edit this in normal circumstances.
46 | cookieName: {{ cluster.name }}
47 | # if the cookie should be sent over https only. Stick to "true".
48 | cookieSecure: "true"
49 | # The default age of a user session in seconds. Default: 181440 (3 weeks)
50 | sessionAge: "1814400"
51 |
52 | # Configuration related to the email provider
53 | email:
54 | # email address of the sender for verification emails
55 | verifyEmailFrom: getstarteduser@hasura.io
56 | # Name of the sender for verification emails
57 | verifEmailFromName: admin
58 | # Subject for verification emails
59 | verifyEmailSubject: MyAwesomeApp - Verify your account
60 | # Template for verification emails. HTML can be used in the template. The
61 | # template is a Jinja template. Leave the "{{token}}" as it is. It will be
62 | # used by the auth service to inject the actual token when sending the email.
63 | verifyTemplate: |
64 | Hi, Please click on
65 | https://auth.{{ cluster.name }}.hasura-app.io/v1/providers/email/verify-email?token={{ "{{token}}" }}
66 | to verify your email.
67 | # Email verification token expiry time in days
68 | verifyTokenExpires: "7"
69 |
70 | # email address of the sender for forgot password emails
71 | forgotPassEmailFrom: getstarteduser@hasura.io
72 | # Name of the sender for forgot password emails
73 | forgotPassEmailFromName: admin
74 | # Subject for forgot password emails
75 | forgotPassEmailSubject: MyAwesomeApp - Reset password request
76 | # Template for forgot password emails. HTML can be used in the template. The
77 | # template is a Jinja template. Leave the "{{token}}" as it is. It will be
78 | # used by the auth service to inject the actual token when sending the email.
79 | forgotPassTemplate: |
80 | Hi, Click on
81 | https://auth.{{ cluster.name }}.hasura-app.io/v1/providers/email/reset-password?token={{ "{{token}}" }}
82 | to reset your password.
83 | # Forgot password reset token expiry time in days
84 | resetTokenExpires: "7"
85 |
86 | # Configuration for the mobile provider
87 | mobile:
88 | # Template for the SMS that is sent. This is a Jinja template. Leave the
89 | # "{{otp}}" as it is. It will be used by the auth service to inject the
90 | # actual token.
91 | smsTemplate: |
92 | Verify your acccount with MyAwesomeApp! Your OTP is {{ "{{otp}}" }}.
93 | # OTP expiry time in minutes
94 | otpExpiryTime: "15"
95 |
96 | # Configuration for the mobile-password provider
97 | mobilePassword:
98 | # Template for the SMS that is sent. This is a Jinja template. Leave the
99 | # "{{otp}}" as it is. It will be used by the auth service to inject the
100 | # actual token.
101 | smsTemplate: |
102 | Verify your acccount with MyAwesomeApp! Your OTP is {{ "{{otp}}" }}.
103 | # OTP expiry time in minutes
104 | otpExpiryTime: "15"
105 |
106 | # Configuration for password
107 | password:
108 | # minimum length of the password allowed.
109 | minLength: "8"
110 |
111 | # Below fields are all optional
112 | #
113 | # Configuration for google provider
114 | #google:
115 | # # list of the all the client ids generated for your Google app
116 | # clientIds: ["xxxxxx", "yyyyyy"]
117 | #
118 | # Configuration for facebook provider
119 | #facebook:
120 | # # your facebook app client id
121 | # clientId: xxxxxxxxx
122 | # # your facebook app client secret
123 | # clientSecret:
124 | # secretKeyRef:
125 | # key: auth.facebook.client.secret
126 | # name: hasura-secrets
127 | #
128 | # Configuration for github provider
129 | #github:
130 | # # your github app client id
131 | # clientId: xxxxxxxxx
132 | # # your github app client secret
133 | # clientSecret:
134 | # secretKeyRef:
135 | # key: auth.github.client.secret
136 | # name: hasura-secrets
137 | #
138 | # Configuration for linkedin provider
139 | #linkedin:
140 | # # your linkedin app client id
141 | # clientId: xxxxxxxxx
142 | # # your linkedin app client secret
143 | # clientSecret:
144 | # secretKeyRef:
145 | # key: auth.linkedin.client.secret
146 | # name: hasura-secrets
147 |
148 | # Configuration for adding a custom provider
149 | #customProviders:
150 | # myCustomProvider:
151 | # enabled: "true",
152 | # hooks:
153 | # signup: "https://mycustomprovider.test42.hasura-app.io/signup"
154 | # login: "https://mycustomprovider.test42.hasura-app.io/login"
155 | # merge: "https://mycustomprovider.test42.hasura-app.io/merge"
156 | # defaultRoles: ["admin"]
157 |
158 |
159 | # The below fields are used by the platform when initializing. Please do not
160 | # edit these configuration
161 | postgres:
162 | database: hasuradb
163 | host: postgres.{{ cluster.metadata.namespaces.hasura }}
164 | password:
165 | secretKeyRef:
166 | key: postgres.password
167 | name: hasura-secrets
168 | port: "5432"
169 | user:
170 | secretKeyRef:
171 | key: postgres.user
172 | name: hasura-secrets
173 | redis:
174 | cred: null
175 | host: session-redis.{{ cluster.metadata.namespaces.hasura }}
176 | port: "6379"
177 | notifyDomain: http://notify.{{ cluster.metadata.namespaces.hasura }}
178 | superUser:
179 | password:
180 | secretKeyRef:
181 | key: auth.admin.password
182 | name: hasura-secrets
183 | username: admin
184 | # optional fields
185 | # email: getstarteduser@hasura.io
186 | # mobile: 987654321
187 |
--------------------------------------------------------------------------------
/microservices/www/src/data-query.js:
--------------------------------------------------------------------------------
1 | let DataQuery = function () {
2 | let me = this;
3 | /* ----------- course related queries -------------*/
4 |
5 | //To select course details
6 | me.fetchCourseDetails = function (user_id, course_id) {
7 | let query = {
8 | "type": "select",
9 | "args": {
10 | "table": "course_details",
11 | "columns": ["course_id", "name", "about", "syllabus", "course_logo",
12 | {
13 | "name": "user_course_status",
14 | "columns": ["status"],
15 | "where": {
16 | "user_id": user_id
17 | }
18 | }
19 | ],
20 | "where": {
21 | "$and": [{
22 | "course_id": course_id
23 | },
24 | {
25 | "active": true
26 | }
27 | ]
28 | }
29 |
30 | }
31 | };
32 | return query;
33 | };
34 | //To insert course status
35 | me.insertCourseStatus = function (user_id, course_id) {
36 | let query = {
37 | "type": "insert",
38 | "args": {
39 | "table": "course_status",
40 | "objects": [{
41 | "user_id": user_id,
42 | "course_id": course_id
43 | }]
44 | }
45 | };
46 | return query;
47 | };
48 | //To fetch course status
49 | me.fetchCourseStatus = function (user_id, course_id) {
50 | let query = {
51 | "type": "select",
52 | "args": {
53 | "table": "module_details",
54 | "columns": [{
55 | "name": "module_topics",
56 | "columns": ["topic_id"]
57 | }, {
58 | "name": "user_topic_status",
59 | "columns": ["topic_id"],
60 | "where": {
61 | "user_id": user_id
62 | }
63 | }],
64 | "where": {
65 | "course_id": course_id
66 | }
67 | }
68 | };
69 | return query;
70 | };
71 | //To update course status
72 | me.updateCourseStatus = function (user_id, course_id) {
73 | let query = {
74 | "type": "update",
75 | "args": {
76 | "table": "course_status",
77 | "$set": {
78 | "status": true
79 | },
80 | "where": {
81 | "$and": [{
82 | "user_id": user_id
83 | },
84 | {
85 | "course_id": course_id
86 | }
87 | ]
88 | }
89 | }
90 | };
91 | return query;
92 | };
93 |
94 | /* ----------- user related queries -------------*/
95 |
96 | //To insert user Full Name
97 | me.insertFullName = function (user_id, name) {
98 | let query = {
99 | "type": "insert",
100 | "args": {
101 | "table": "user_other_details",
102 | "objects": [{
103 | "user_id": user_id,
104 | "name": name
105 | }]
106 | }
107 | };
108 | return query;
109 | };
110 | //To select user points
111 | me.fetchUserPoints = function (user_id) {
112 | let query = {
113 | "type": "select",
114 | "args": {
115 | "table": "user_other_details",
116 | "columns": ["points"],
117 | "where": {
118 | "user_id": user_id
119 | }
120 | }
121 | };
122 | return query;
123 | };
124 | //To update user points
125 | me.updateUserPoints = function (user_id, topic_points) {
126 | let query = {
127 | "type": "update",
128 | "args": {
129 | "table": "user_other_details",
130 | "$inc": {
131 | "points": topic_points
132 | },
133 | "where": {
134 | "user_id": user_id
135 | }
136 | }
137 | };
138 | return query;
139 | };
140 | //To select badge details
141 | me.fetchBadgeDetails = function (user_id, points) {
142 | let query = {
143 | "type": "select",
144 | "args": {
145 | "table": "badge_details",
146 | "columns": [
147 | "badge_id", "name", "points", "badge_logo", "badge_description",
148 | {
149 | "name": "user_badge_status",
150 | "columns": ["display_status"],
151 | "where": {
152 | "user_id": user_id
153 | }
154 | }
155 | ],
156 | "where": {
157 | "points": {
158 | "$lte": points
159 | }
160 | },
161 | "order_by": "-points"
162 | }
163 | };
164 | return query;
165 | };
166 | //To insert user badges
167 | me.insertUserBadge = function (badgeArray) {
168 | let query = {
169 | "type": "insert",
170 | "args": {
171 | "table": "badge_status",
172 | "objects": badgeArray
173 | }
174 | };
175 | return query;
176 | };
177 |
178 | /* ----------- topic related queries -------------*/
179 |
180 | //To fetch topic points
181 | me.fetchTopicPoints = function (topic_id) {
182 | let query = {
183 | "type": "select",
184 | "args": {
185 | "table": "topic_details",
186 | "columns": ["topic_points"],
187 | "where": {
188 | "topic_id": topic_id
189 | }
190 | }
191 | };
192 | return query;
193 | };
194 | //To insert topic status
195 | me.insertTopicStatus = function (user_id, topic_id, topic_points, module_id) {
196 | let query = {
197 | "type": "insert",
198 | "args": {
199 | "table": "topic_status",
200 | "objects": [{
201 | "user_id": user_id,
202 | "topic_id": topic_id,
203 | "topic_points": topic_points,
204 | "module_id": module_id
205 | }]
206 | }
207 | };
208 | return query;
209 | };
210 | };
211 |
212 | module.exports = DataQuery;
--------------------------------------------------------------------------------
/migrations/1511335541859_run_sql_migration.up.yaml:
--------------------------------------------------------------------------------
1 | - args:
2 | sql: |+
3 | CREATE TABLE course_rating (
4 | user_id integer NOT NULL,
5 | course_id integer NOT NULL,
6 | rating integer NOT NULL,
7 | CONSTRAINT rating_check CHECK (((rating >= 1) AND (rating <= 5)))
8 | );
9 |
10 |
11 | CREATE TABLE badge_details (
12 | badge_id integer NOT NULL,
13 | name text,
14 | points integer,
15 | badge_logo text,
16 | badge_description text
17 | );
18 |
19 |
20 | CREATE SEQUENCE badge_details_badge_id_seq
21 | START WITH 1
22 | INCREMENT BY 1
23 | NO MINVALUE
24 | NO MAXVALUE
25 | CACHE 1;
26 |
27 |
28 | ALTER SEQUENCE badge_details_badge_id_seq OWNED BY badge_details.badge_id;
29 |
30 | CREATE TABLE badge_status (
31 | user_id integer NOT NULL,
32 | badge_id integer NOT NULL,
33 | display_status boolean DEFAULT true
34 | );
35 |
36 |
37 | CREATE TABLE course_details (
38 | course_id integer NOT NULL,
39 | name text,
40 | about text,
41 | syllabus text,
42 | course_logo text,
43 | active boolean DEFAULT false
44 | );
45 |
46 |
47 | CREATE SEQUENCE course_details_course_id_seq
48 | START WITH 1
49 | INCREMENT BY 1
50 | NO MINVALUE
51 | NO MAXVALUE
52 | CACHE 1;
53 |
54 |
55 | ALTER SEQUENCE course_details_course_id_seq OWNED BY course_details.course_id;
56 |
57 |
58 | CREATE TABLE course_status (
59 | user_id integer NOT NULL,
60 | course_id integer NOT NULL,
61 | status boolean DEFAULT false
62 | );
63 |
64 | CREATE TABLE module_details (
65 | module_id integer NOT NULL,
66 | module_name text,
67 | course_id integer
68 | );
69 |
70 |
71 | CREATE SEQUENCE module_details_module_id_seq
72 | START WITH 1
73 | INCREMENT BY 1
74 | NO MINVALUE
75 | NO MAXVALUE
76 | CACHE 1;
77 |
78 |
79 | ALTER SEQUENCE module_details_module_id_seq OWNED BY module_details.module_id;
80 |
81 |
82 | CREATE TABLE topic_details (
83 | topic_id integer NOT NULL,
84 | topic_name text,
85 | topic_content text,
86 | topic_points integer,
87 | module_id integer
88 | );
89 |
90 | CREATE SEQUENCE topic_details_topic_id_seq
91 | START WITH 1
92 | INCREMENT BY 1
93 | NO MINVALUE
94 | NO MAXVALUE
95 | CACHE 1;
96 |
97 | ALTER SEQUENCE topic_details_topic_id_seq OWNED BY topic_details.topic_id;
98 |
99 |
100 | CREATE TABLE topic_status (
101 | user_id integer NOT NULL,
102 | topic_id integer NOT NULL,
103 | topic_points integer,
104 | module_id integer
105 | );
106 |
107 |
108 | CREATE TABLE user_other_details (
109 | user_id integer NOT NULL,
110 | name text,
111 | points integer DEFAULT 0
112 | );
113 |
114 |
115 | ALTER TABLE ONLY badge_details ALTER COLUMN badge_id SET DEFAULT nextval('badge_details_badge_id_seq'::regclass);
116 |
117 |
118 | ALTER TABLE ONLY course_details ALTER COLUMN course_id SET DEFAULT nextval('course_details_course_id_seq'::regclass);
119 |
120 |
121 | ALTER TABLE ONLY module_details ALTER COLUMN module_id SET DEFAULT nextval('module_details_module_id_seq'::regclass);
122 |
123 |
124 | ALTER TABLE ONLY topic_details ALTER COLUMN topic_id SET DEFAULT nextval('topic_details_topic_id_seq'::regclass);
125 |
126 |
127 | ALTER TABLE ONLY badge_details
128 | ADD CONSTRAINT badge_details_pkey PRIMARY KEY (badge_id);
129 |
130 | ALTER TABLE ONLY badge_details
131 | ADD CONSTRAINT badge_details_points_key UNIQUE (points);
132 |
133 | ALTER TABLE ONLY badge_status
134 | ADD CONSTRAINT badge_status_pkey PRIMARY KEY (user_id, badge_id);
135 |
136 | ALTER TABLE ONLY course_details
137 | ADD CONSTRAINT course_details_pkey PRIMARY KEY (course_id);
138 |
139 | ALTER TABLE ONLY course_rating
140 | ADD CONSTRAINT course_rating_pkey PRIMARY KEY (user_id, course_id);
141 |
142 | ALTER TABLE ONLY course_status
143 | ADD CONSTRAINT course_status_pkey PRIMARY KEY (user_id, course_id);
144 |
145 | ALTER TABLE ONLY module_details
146 | ADD CONSTRAINT module_details_pkey PRIMARY KEY (module_id);
147 |
148 | ALTER TABLE ONLY topic_details
149 | ADD CONSTRAINT topic_details_pkey PRIMARY KEY (topic_id);
150 |
151 | ALTER TABLE ONLY topic_status
152 | ADD CONSTRAINT topic_status_pkey PRIMARY KEY (user_id, topic_id);
153 |
154 | ALTER TABLE ONLY user_other_details
155 | ADD CONSTRAINT user_other_details_pkey PRIMARY KEY (user_id);
156 |
157 | ALTER TABLE ONLY badge_status
158 | ADD CONSTRAINT badge_status_badge_id_fkey FOREIGN KEY (badge_id) REFERENCES badge_details(badge_id);
159 |
160 | ALTER TABLE ONLY badge_status
161 | ADD CONSTRAINT badge_status_user_id_fkey FOREIGN KEY (user_id) REFERENCES user_other_details(user_id);
162 |
163 | ALTER TABLE ONLY course_rating
164 | ADD CONSTRAINT course_rating_course_id_fkey FOREIGN KEY (course_id) REFERENCES course_details(course_id);
165 |
166 | ALTER TABLE ONLY course_rating
167 | ADD CONSTRAINT course_rating_course_id_fkey1 FOREIGN KEY (course_id, user_id) REFERENCES course_status(course_id, user_id);
168 |
169 | ALTER TABLE ONLY course_rating
170 | ADD CONSTRAINT course_rating_user_id_fkey FOREIGN KEY (user_id) REFERENCES user_other_details(user_id);
171 |
172 | ALTER TABLE ONLY course_status
173 | ADD CONSTRAINT course_status_course_id_fkey FOREIGN KEY (course_id) REFERENCES course_details(course_id);
174 |
175 | ALTER TABLE ONLY course_status
176 | ADD CONSTRAINT course_status_user_id_fkey FOREIGN KEY (user_id) REFERENCES user_other_details(user_id);
177 |
178 | ALTER TABLE ONLY module_details
179 | ADD CONSTRAINT module_details_course_id_fkey FOREIGN KEY (course_id) REFERENCES course_details(course_id);
180 |
181 | ALTER TABLE ONLY topic_details
182 | ADD CONSTRAINT topic_details_module_id_fkey FOREIGN KEY (module_id) REFERENCES module_details(module_id);
183 |
184 | ALTER TABLE ONLY topic_status
185 | ADD CONSTRAINT topic_status_module_id_fkey FOREIGN KEY (module_id) REFERENCES module_details(module_id);
186 |
187 | ALTER TABLE ONLY topic_status
188 | ADD CONSTRAINT topic_status_topic_id_fkey FOREIGN KEY (topic_id) REFERENCES topic_details(topic_id);
189 |
190 | ALTER TABLE ONLY topic_status
191 | ADD CONSTRAINT topic_status_user_id_fkey FOREIGN KEY (user_id) REFERENCES user_other_details(user_id);
192 |
193 | CREATE VIEW avg_course_rating AS
194 | SELECT course_id, COUNT(course_id), ROUND(AVG(rating),1) AS rating
195 | FROM course_rating
196 | GROUP BY course_id;
197 |
198 | CREATE VIEW enrolled_count AS
199 | SELECT course_id, COUNT(user_id) AS enrolled
200 | FROM course_status
201 | GROUP BY course_id;
202 |
203 | CREATE VIEW total_users AS
204 | SELECT COUNT(user_id) AS total
205 | FROM user_other_details;
206 |
207 | type: run_sql
208 |
--------------------------------------------------------------------------------
/microservices/www/src/public/js/course_main.js:
--------------------------------------------------------------------------------
1 | var user_id = null,
2 | course_id = null,
3 | elemTopic = null,
4 | topicId = null,
5 | moduleId = null;
6 | //insert user rating
7 | function insertRating(rating) {
8 | var insert_rating_query = {
9 | "type": "insert",
10 | "args": {
11 | "table": "course_rating",
12 | "objects": [{
13 | "user_id": user_id,
14 | "course_id": course_id,
15 | "rating": rating
16 | }]
17 | }
18 | };
19 | $.ajax({
20 | method: "POST",
21 | url: data_query_url,
22 | data: JSON.stringify(insert_rating_query),
23 | contentType: "application/json"
24 | }).done(function (data) {
25 | //console.log(data);
26 | if (data.affected_rows === 1) {
27 | $("#rating_msg").html("" + rating + "
");
28 | } else {
29 | $("#rating_msg").html("(Not yet Rated!)");
30 | }
31 | }).fail(function (xhr) {
32 | //console.log(xhr.responseText);
33 | });
34 | }
35 | //fetch user rating
36 | function fetchUserRating() {
37 | //query data
38 | var fetch_rating_query = {
39 | "type": "select",
40 | "args": {
41 | "table": "course_rating",
42 | "columns": [
43 | "rating"
44 | ],
45 | "where": {
46 | "$and": [{
47 | "user_id": user_id
48 | },
49 | {
50 | "course_id": course_id
51 | }
52 | ]
53 | }
54 | }
55 | };
56 | $.ajax({
57 | method: "POST",
58 | url: data_query_url,
59 | data: JSON.stringify(fetch_rating_query),
60 | contentType: "application/json"
61 | }).done(function (data) {
62 | //console.log(data);
63 | var elem = $("#user_feedback");
64 | var dataRating = 0;
65 | var ratingMessage = "(Not yet Rated!)";
66 | if (data.length > 0) {
67 | dataRating = data[0].rating;
68 | ratingMessage = "" + dataRating + "
";
69 | }
70 | elem.css("display", "none").html("Your Rating:
").fadeIn();
71 | if (data.length > 0) {
72 | $('#user_course_rating').rating("disable");
73 | } else {
74 | $('#user_course_rating').rating({
75 | onRate: function (rating) {
76 | $(this).rating("disable");
77 | $("#rating_msg").html("
");
78 | insertRating(rating);
79 | }
80 | });
81 | }
82 | }).fail(function (xhr) {
83 | //console.log(xhr.responseText);
84 | }).always(function () {
85 | $("#user_feedback .loader").hide();
86 | });
87 | }
88 | //display message on course complete
89 | function courseCompleted() {
90 | $.ajax({
91 | method: "GET",
92 | url: rootURL + "/course/complete",
93 | data: {
94 | course_id: course_id
95 | },
96 | contentType: "application/json"
97 | }).done(function (data) {
98 | //console.log(data);
99 | if (data.courseStatus === "completed") {
100 | $("#completed_modal .header").html("Excellent!");
101 | $("#completed_modal .completed_content").html("You have completed this course. Please provide your valuable feedback in the Feedback
section!");
102 | $("#completed_modal").modal('show');
103 | $("#feedback_section_btn").click(function () {
104 | $('html, body').animate({
105 | scrollTop: $("#feedback_section").offset().top
106 | }, 750, function () {
107 | $("#completed_modal").modal('hide');
108 | });
109 | });
110 | }
111 | }).fail(function (xhr) {
112 | //console.log(xhr.responseText);
113 | });
114 | }
115 |
116 | function topicCompleted() {
117 | $("#topic_completed_btn").click(function () {
118 | $(this).addClass("disabled loading");
119 | $.ajax({
120 | method: "POST",
121 | url: rootURL + "/topic/complete",
122 | data: JSON.stringify({
123 | topic_id: topicId,
124 | module_id: moduleId
125 | }),
126 | contentType: "application/json"
127 | }).done(function (data) {
128 | //console.log(data);
129 | if (data.topicStatus === "completed") {
130 | $("#topic_completed_btn").fadeOut(function () {
131 | $(this).removeClass("loading").addClass("green").html(" COMPLETED").fadeIn();
132 | elemTopic.data("status", 1).attr("data-status", "1");
133 | elemTopic.find(".content").append(" ");
134 | });
135 | courseCompleted();
136 | }
137 | }).fail(function (xhr) {
138 | //console.log(xhr.responseText);
139 | });
140 | });
141 | }
142 |
143 | function getTopicContent(status) {
144 | //query data
145 | var fetch_topic_details_query = {
146 | "type": "select",
147 | "args": {
148 | "table": "module_details",
149 | "columns": [
150 | "module_name",
151 | {
152 | "name": "module_topics",
153 | "columns": [
154 | "topic_name", "topic_content", "topic_points"
155 | ],
156 | "where": {
157 | "topic_id": topicId
158 | }
159 | }
160 | ],
161 | "where": {
162 | "module_id": moduleId
163 | }
164 | }
165 | };
166 | $.ajax({
167 | method: "POST",
168 | url: data_query_url,
169 | data: JSON.stringify(fetch_topic_details_query),
170 | contentType: "application/json"
171 | }).done(function (data) {
172 | //console.log(data);
173 | var status_display = status === 1 ? " COMPLETED " : " MARK AS COMPLETED ";
174 | var content = "" + data[0].module_topics[0].topic_points + "pts
" + data[0].module_topics[0].topic_content + "" + status_display + "
";
175 | $("#module_content").html(content).fadeIn();
176 | topicCompleted();
177 | $('.course_video').embed({
178 | parameters: {
179 | rel: 0
180 | }
181 | });
182 | }).fail(function (xhr) {
183 | //console.log(xhr.responseText);
184 | }).always(function () {
185 | $("#module_content .loader").hide();
186 | });
187 | }
188 |
189 | function toggleContentDisplay() {
190 | $(".topic_item").click(function () {
191 | if (!$(this).hasClass("active")) {
192 | $("#module_content").html("
").fadeIn();
193 | $(".syllabus_btn").removeClass("active");
194 | $(".topic_item").removeClass("active");
195 | $(this).addClass("active");
196 | elemTopic = $(this);
197 | topicId = $(this).data("topicid");
198 | moduleId = $(this).data("moduleid");
199 | var status = $(this).data("status");
200 | $("#syllabus").fadeOut(function () {
201 | getTopicContent(status);
202 | });
203 | }
204 | })
205 | $(".syllabus_btn").click(function () {
206 | if (!$(this).hasClass("active")) {
207 | $("#module_content").html("
").fadeIn();
208 | $(this).addClass("active");
209 | $(".topic_item").removeClass("active");
210 | $("#module_content").fadeOut(function () {
211 | $("#syllabus").fadeIn();
212 | });
213 | }
214 | });
215 | }
216 | //fetch modules and topics for each modules
217 | function getModules() {
218 | var fecth_modules_query = {
219 | "type": "select",
220 | "args": {
221 | "table": "module_details",
222 | "columns": [
223 | "module_id",
224 | "module_name",
225 | {
226 | "name": "module_topics",
227 | "columns": [
228 | "topic_id", "topic_name"
229 | ],
230 | "order_by": "+topic_id"
231 | }, {
232 | "name": "user_topic_status",
233 | "columns": [
234 | "user_id", "topic_id"
235 | ],
236 | "where": {
237 | "user_id": user_id
238 | },
239 | "order_by": "+topic_id"
240 | }
241 | ],
242 | "where": {
243 | "course_id": course_id
244 | },
245 | "order_by": "+module_id"
246 | }
247 | };
248 | $.ajax({
249 | method: "POST",
250 | url: data_query_url,
251 | data: JSON.stringify(fecth_modules_query),
252 | contentType: "application/json"
253 | }).done(function (data) {
254 | //console.log(data);
255 | var module_length = data.length;
256 | if (module_length > 0) {
257 | var content = "";
258 | for (var i = 0; i < module_length; i++) {
259 | var active = i === 0 ? "active" : "";
260 | content += " " + data[i].module_name + "
";
261 | for (var j = 0; j < data[i].module_topics.length; j++) {
262 | var topic_stat = 0;
263 | for (var k = 0; k < data[i].user_topic_status.length; k++) {
264 | if (data[i].user_topic_status[k]) {
265 | if (data[i].user_topic_status[k].topic_id === data[i].module_topics[j].topic_id) {
266 | topic_stat = 1;
267 | }
268 | }
269 | }
270 | var status_display = topic_stat === 1 ? "
" : "";
271 | content += "
" + data[i].module_topics[j].topic_name + status_display + "
";
272 | }
273 | content += "
";
274 | }
275 | $('.main_course_accordion').css("display", "none").html(content).fadeIn(function () {
276 | $(this).accordion();
277 | });
278 | toggleContentDisplay();
279 | } else {
280 | $('.main_course_accordion').css("display", "none").html("").fadeIn();
281 | }
282 | }).fail(function (xhr) {
283 | //console.log(xhr.responseText);
284 | }).always(function () {
285 | $(".main_course_accordion .loader").hide();
286 | });
287 | }
288 | $(function () {
289 | //get course id
290 | course_id = parseInt(window.location.pathname.split('/')[3]);
291 | //get user info
292 | $.ajax({
293 | method: "GET",
294 | url: auth_query_url + "/user/info",
295 | contentType: "application/json"
296 | }).done(function (res) {
297 | //console.log(res);
298 | user_id = res.hasura_id;
299 | getModules();
300 | fetchUserRating();
301 | }).fail(function (xhr) {
302 | //console.log(xhr.responseText);
303 | window.location = "/";
304 | });
305 | });
--------------------------------------------------------------------------------
/microservices/www/src/server.js:
--------------------------------------------------------------------------------
1 | let hbs = require('express-handlebars'),
2 | path = require('path'),
3 | express = require('express'),
4 | request = require('request'),
5 | session = require('express-session');
6 | bodyParser = require('body-parser');
7 |
8 | // custom modules
9 | let DataQuery = require('./data-query');
10 |
11 | let app = express();
12 | let dataQuery = new DataQuery();
13 | app.use(bodyParser.json());
14 | app.set('env', 'production'); //development or production
15 | // session handling: dev only
16 | app.use(session({
17 | secret: 'eGyan Development Mode',
18 | cookie: {maxAge: 1814400},
19 | resave: false,
20 | saveUninitialized: true
21 | }));
22 |
23 | // view
24 | //===============================================================
25 | app.set('views', path.join(__dirname, "template"));
26 | app.engine('handlebars', hbs({
27 | defaultLayout: 'main',
28 | layoutsDir: path.join(app.get('views'), 'layouts'),
29 | partialsDir: path.join(app.get('views'), 'partials')
30 | }));
31 | app.set('view engine', 'handlebars');
32 |
33 | // config
34 | //===============================================================
35 | let data_url = "";
36 | let auth_url = "";
37 | let headers = {
38 | 'Content-Type': 'application/json'
39 | };
40 | if (app.get('env') === "development") {
41 | const cluster_name = process.env.CLUSTER_NAME;
42 | headers.Authorization = 'Bearer ' + process.env.ADMIN_TOKEN;
43 | auth_url = `https://auth.${cluster_name}.hasura-app.io`;
44 | data_url = `https://data.${cluster_name}.hasura-app.io`;
45 | } else {
46 | auth_url = "http://auth.hasura";
47 | data_url = "http://data.hasura";
48 | }
49 | headers['X-Hasura-Role'] = 'admin';
50 | headers['X-Hasura-User-Id'] = 1;
51 | let auth_query_url = auth_url + "/v1";
52 | let data_query_url = data_url + "/v1/query";
53 |
54 | // custom functions
55 | //===============================================================
56 | function getBasicAuthInfo(req) {
57 | let info = { id: 0, role: 'anonymous'};
58 | if(app.get('env') === "development") {
59 | if(req.session && req.session.userAuth && req.session.userAuth.id && req.session.userAuth.role){
60 | info = req.session.userAuth;
61 | }
62 | }
63 | if (req.get('X-Hasura-Role')) {
64 | if (req.get('X-Hasura-User-Id')) {
65 | info.id = parseInt(req.get('X-Hasura-User-Id'));
66 | }
67 | info.role = req.get('X-Hasura-Role');
68 | }
69 | return info;
70 | }
71 |
72 | function makePOSTRequest(data, callback) {
73 | request({
74 | url: data_query_url,
75 | forever: true,
76 | gzip: true,
77 | jar: true,
78 | method: "POST",
79 | json: true,
80 | headers: headers,
81 | body: data
82 | }, callback);
83 | }
84 |
85 | // routes
86 | //===============================================================
87 | app.get('/', function (req, res) {
88 | let userInfo = getBasicAuthInfo(req);
89 | if (userInfo.role === "user" || userInfo.role === "admin") {
90 | res.redirect('/student');
91 | } else {
92 | res.render('index', {
93 | title: "eGyan - Simple and Effective Elearning Platform for Everyone"
94 | });
95 | }
96 | });
97 |
98 | app.get('/student', function (req, res) {
99 | let userInfo = getBasicAuthInfo(req);
100 | if (userInfo.role === "user" || userInfo.role === "admin") {
101 | res.render('student', {
102 | title: "eGyan - Student Home"
103 | });
104 | } else {
105 | res.redirect('/');
106 | }
107 | });
108 |
109 | app.get('/course/id/:id', function (req, res) {
110 | let userInfo = getBasicAuthInfo(req);
111 | if (userInfo.role === "user" || userInfo.role === "admin") {
112 | let course_id = parseInt(req.params.id) ? parseInt(req.params.id) : null;
113 | let status = req.query.status ? req.query.status : "";
114 | if (course_id !== null && (status === "active" || status === "completed" || status === "available")) {
115 | //fetch course details
116 | makePOSTRequest(dataQuery.fetchCourseDetails(userInfo.id, course_id), function (error, response) {
117 | if (response.statusCode == 200) {
118 | if (response.body.length > 0) {
119 | let courseStatusInfo = {};
120 | let statusCheck = response.body[0].user_course_status.length;
121 | courseStatusInfo.available = statusCheck === 0 ? true : false;
122 | courseStatusInfo.completed = statusCheck > 0 ? response.body[0].user_course_status[0].status : false;
123 | if (statusCheck === 0) {
124 | //enroll in course
125 | makePOSTRequest(dataQuery.insertCourseStatus(userInfo.id, course_id), function (error, response) {
126 | if (response.body.affected_rows < 1) {
127 | res.redirect('/student');
128 | }
129 | });
130 | }
131 | res.render('course', {
132 | title: 'eGyan - ' + response.body[0].name + ' Course',
133 | courseStatus: courseStatusInfo,
134 | courseLogo: response.body[0].course_logo,
135 | courseHeading: response.body[0].name,
136 | courseDescription: response.body[0].about,
137 | contentHeader: "Syllabus",
138 | content: response.body[0].syllabus
139 | });
140 | } else {
141 | res.redirect('/student');
142 | }
143 | } else {
144 | res.redirect('/student');
145 | }
146 | });
147 | } else {
148 | res.redirect('/student');
149 | }
150 | } else {
151 | res.redirect('/');
152 | }
153 | });
154 |
155 | app.get('/logout', function (req, res) {
156 | if(app.get('env') === "development") {
157 | if(req.session && req.session.userAuth && req.session.userAuth.id && req.session.userAuth.role){
158 | delete req.session.userAuth;
159 | }
160 | }
161 | let userInfo = getBasicAuthInfo(req);
162 | if (userInfo.role === "user" || userInfo.role === "admin") {
163 | res.redirect('/student');
164 | } else {
165 | res.render('logout', {
166 | title: 'Logged Out!'
167 | });
168 | }
169 | });
170 |
171 | app.post("/signup", function (req, res) {
172 | let name = req.body.name;
173 | let username = req.body.username;
174 | let password = req.body.password;
175 | if (name.trim() === "" || username.trim() == "" || password.trim() === "") {
176 | res.status(400).send("Invalid input values!");
177 | } else {
178 | //Making HTTP request
179 | if(req.get('X-Hasura-Base-Domain')) {
180 | headers['X-Hasura-Base-Domain'] = req.get('X-Hasura-Base-Domain');
181 | }
182 | request({
183 | url: auth_query_url + '/signup',
184 | method: "POST",
185 | headers: headers,
186 | json: true,
187 | body: {
188 | "provider": "username",
189 | "data": {
190 | "username": username,
191 | "password": password
192 | }
193 | }
194 | }, function (error, response) {
195 | if (error) {
196 | return res.status(500).send(error.toString());
197 | }
198 | if (response.statusCode == 200) {
199 | let user_id = response.body.hasura_id;
200 | //now, insert the name
201 | makePOSTRequest(dataQuery.insertFullName(user_id, name), function (error, response) {
202 | if (response.body.affected_rows >= 1) {
203 | res.status(200).send("Successfully Registered!");
204 | } else {
205 | res.status(500).send(JSON.stringify({
206 | message: "There was some problem registering!"
207 | }));
208 | }
209 | });
210 | } else {
211 | res.status(response.statusCode).send(JSON.stringify(response.body));
212 | }
213 | });
214 | }
215 | });
216 |
217 | //For fetching badge details
218 | app.get('/fetch/badge', function (req, res) {
219 | let userInfo = getBasicAuthInfo(req);
220 | if (userInfo.role === "user" || userInfo.role === "admin") {
221 | //first fetch points
222 | makePOSTRequest(dataQuery.fetchUserPoints(userInfo.id), function (error, response) {
223 | if (response.statusCode == 200) {
224 | let points = response.body[0].points;
225 | //now, fetch badge details
226 | makePOSTRequest(dataQuery.fetchBadgeDetails(userInfo.id, points), function (error, response) {
227 | if (response.statusCode == 200) {
228 | let total_badges = response.body.length;
229 | if (total_badges > 0) {
230 | let user_badge_arr = [];
231 | for (let i = 0; i < total_badges; i++) {
232 | if (response.body[i].user_badge_status.length === 0) {
233 | let badge_info = {};
234 | badge_info.user_id = userInfo.id;
235 | badge_info.badge_id = response.body[i].badge_id;
236 | user_badge_arr.push(badge_info);
237 | }
238 | }
239 | if (user_badge_arr.length > 0) {
240 | //insert badge for user
241 | makePOSTRequest(dataQuery.insertUserBadge(user_badge_arr), function (error, response) {});
242 | }
243 | }
244 | res.status(200).json(response.body);
245 | } else {
246 | res.status(400).send("Invalid request!");
247 | }
248 | });
249 |
250 | } else {
251 | res.status(500).send("Error!");
252 | }
253 | });
254 | } else {
255 | res.status("403").send("Not allowed!");
256 | }
257 | });
258 |
259 | //For completing a topic and updating points
260 | app.post('/topic/complete', function (req, res) {
261 | let userInfo = getBasicAuthInfo(req);
262 | if (userInfo.role === "user" || userInfo.role === "admin") {
263 | let topic_id = req.body.topic_id;
264 | let module_id = req.body.module_id;
265 | //first fetch topic points
266 | makePOSTRequest(dataQuery.fetchTopicPoints(topic_id), function (error, response) {
267 | if (response.statusCode == 200) {
268 | if (response.body.length > 0) {
269 | let topic_points = response.body[0].topic_points;
270 | //now insert topic status
271 | makePOSTRequest(dataQuery.insertTopicStatus(userInfo.id, topic_id, topic_points, module_id), function (error, response) {
272 | if (response.body.affected_rows >= 1) {
273 | //now update user points
274 | makePOSTRequest(dataQuery.updateUserPoints(userInfo.id, topic_points), function (error, response) {
275 | if (response.body.affected_rows >= 1) {
276 | res.status(200).json({
277 | topicStatus: "completed"
278 | });
279 | } else {
280 | res.status(200).json({
281 | topicStatus: "uncompleted"
282 | });
283 | }
284 | });
285 | } else {
286 | res.status(400).send("Invalid request!");
287 | }
288 | });
289 | } else {
290 | res.status(404).send("Not Found!");
291 | }
292 | } else {
293 | res.status(500).send("Error!");
294 | }
295 | });
296 |
297 | } else {
298 | res.status("403").send("Not allowed!");
299 | }
300 | });
301 |
302 | //For completing the course
303 | app.get('/course/complete', function (req, res) {
304 | let userInfo = getBasicAuthInfo(req);
305 | if (userInfo.role === "user" || userInfo.role === "admin") {
306 | let course_id = parseInt(req.query.course_id);
307 | //first, fetch course status
308 | makePOSTRequest(dataQuery.fetchCourseStatus(userInfo.id, course_id), function (error, response) {
309 | if (response.statusCode == 200) {
310 | let module_length = response.body.length;
311 | if (module_length > 0) {
312 | let status_check = 0;
313 | for (let i = 0; i < module_length; i++) {
314 | if (response.body[i].module_topics.length === response.body[i].user_topic_status.length) {
315 | status_check++;
316 | }
317 | }
318 | if (status_check === module_length) {
319 | //now update course status
320 | makePOSTRequest(dataQuery.updateCourseStatus(userInfo.id, course_id), function (error, response) {
321 | if (response.body.affected_rows >= 1) {
322 | res.status(200).json({
323 | courseStatus: "completed"
324 | });
325 | } else {
326 | res.status(200).json({
327 | courseStatus: "uncompleted"
328 | });
329 | }
330 | });
331 | } else {
332 | res.status(200).json({
333 | courseStatus: "uncompleted"
334 | });
335 | }
336 | } else {
337 | res.status(400).send("Invalid request!");
338 | }
339 | } else {
340 | res.status(500).send("Error!");
341 | }
342 | });
343 | } else {
344 | res.status("403").send("Not allowed!");
345 | }
346 | });
347 |
348 | app.get('/setinfo', function (req, res) {
349 | const msg = {"message": "done"};
350 | if(app.get('env') === "development") {
351 | let user_id = req.query.user_id;
352 | request({
353 | url: auth_query_url + '/admin/user/' + user_id,
354 | method: "GET",
355 | headers: headers,
356 | json: true
357 | }, function (error, response) {
358 | if (error) {
359 | return res.status(500).send(error.toString());
360 | }
361 | if (response.statusCode == 200) {
362 | let hasura_id = response.body.hasura_id;
363 | let role = response.body.hasura_roles[0];
364 | req.session.userAuth = { id: user_id, role: role };
365 | res.status(200).json(msg);
366 | } else {
367 | res.status(response.statusCode).json(response.body);
368 | }
369 | });
370 | } else {
371 | res.status(200).json(msg);
372 | }
373 | });
374 |
375 | app.get('/getinfo', function (req, res) {
376 | res.json(getBasicAuthInfo(req));
377 | });
378 |
379 | // loading static files
380 | app.use(express.static(path.join(__dirname, 'public')));
381 |
382 | // listens for connections on the port 8080
383 | app.listen(8080, function () {
384 | console.log('egyan app listening on port 8080!');
385 | });
--------------------------------------------------------------------------------
/microservices/www/src/public/css/style.css:
--------------------------------------------------------------------------------
1 | /*-------------- Homepage Styles and Common Styles -------------*/
2 |
3 | .hidden.menu {
4 | display: none;
5 | }
6 |
7 | .masthead.segment {
8 | min-height: 450px;
9 | padding: 1em 0em;
10 | }
11 |
12 | .ui.inverted.masthead.segment {
13 | background: url('/img/header_img.png') center no-repeat;
14 | background-size: cover;
15 | }
16 |
17 | .masthead .logo.item img {
18 | margin-right: 1em;
19 | }
20 |
21 | .masthead .ui.menu .ui.button {
22 | margin-left: 0.5em;
23 | }
24 |
25 | .masthead h2.ui.header {
26 | margin-top: 1.8em;
27 | margin-bottom: 0em;
28 | font-size: 3em;
29 | font-weight: normal;
30 | }
31 |
32 | .masthead h3 {
33 | font-size: 1.4em;
34 | font-weight: normal;
35 | padding-bottom: 0.7em;
36 | }
37 |
38 | .masthead hr {
39 | border: 0;
40 | border-top: 1px solid #B7B7B7;
41 | width: 90%;
42 | }
43 |
44 | .ui.vertical.stripe {
45 | padding: 5em 0em;
46 | }
47 |
48 | .ui.vertical.stripe h3 {
49 | font-size: 2em;
50 | }
51 |
52 | .ui.vertical.stripe .button+h3,
53 | .ui.vertical.stripe p+h3 {
54 | margin-top: 3em;
55 | }
56 |
57 | .ui.vertical.stripe .floated.image {
58 | clear: both;
59 | }
60 |
61 | .ui.vertical.stripe p {
62 | font-size: 1.33em;
63 | }
64 |
65 | .ui.vertical.stripe .horizontal.divider {
66 | margin: 3em 0em;
67 | }
68 |
69 | .quote.stripe.segment {
70 | padding: 0em;
71 | }
72 |
73 | .quote.stripe.segment .grid .column {
74 | padding-top: 5em;
75 | padding-bottom: 5em;
76 | }
77 |
78 | .footer.segment {
79 | padding: 5em 0em;
80 | }
81 |
82 | .main_hr {
83 | border: 0;
84 | border-top: 1px solid #a7a7a7;
85 | }
86 |
87 | #course_content {
88 | margin: 0;
89 | padding: 0;
90 | border: 0;
91 | box-shadow: none;
92 | background: #fff;
93 | }
94 |
95 | .course_content {
96 | width: 90%;
97 | margin-top: 2em;
98 | }
99 |
100 | #main_course_container .course_content {
101 | min-height: 250px;
102 | }
103 |
104 | .clear_fix {
105 | clear: both;
106 | }
107 |
108 | #course_accordion .content {
109 | padding: 1em 1.8em;
110 | }
111 |
112 | .star_wrapper {
113 | float: right;
114 | margin-top: 3px;
115 | margin-right: 10px;
116 | }
117 |
118 | .ui.star.rating .active.icon {
119 | color: #E9AE6C !important;
120 | text-shadow: 0 -1px 0 #DA8A31, -1px 0 0 #DA8A31, 0 1px 0 #DA8A31, 1px 0 0 #DA8A31 !important;
121 | }
122 |
123 | .flat.button {
124 | border-radius: 0;
125 | }
126 |
127 | @media only screen and (min-width: 480px) {
128 | #user_content .ui.card>.content>.header,
129 | #user_content .ui.cards>.card>.content>.header {
130 | font-size: 1.2em;
131 | }
132 | }
133 |
134 | @media only screen and (max-width: 767px) {
135 | .ui.fixed.menu {
136 | display: none !important;
137 | }
138 | .secondary.pointing.menu .item,
139 | .secondary.pointing.menu .menu {
140 | display: none;
141 | }
142 | .masthead.segment {
143 | min-height: 350px;
144 | }
145 | .masthead h2.ui.header {
146 | font-size: 1.8em;
147 | margin-top: 2.5em;
148 | }
149 | .masthead h3 {
150 | margin-top: 0.9em;
151 | font-size: 1.2em;
152 | }
153 | h1.ui.header {
154 | font-size: 1.8rem;
155 | }
156 | #course_description {
157 | margin-left: 1em;
158 | }
159 | #user_content h2.ui.header,
160 | #feedback_section h2.ui.header {
161 | font-size: 1.4rem;
162 | }
163 | #user_course_rating {
164 | margin-left: 2.1em;
165 | }
166 | }
167 |
168 | @media only screen and (max-width: 480px) {
169 | .ui.fixed.menu .item>.button {
170 | padding-bottom: 1em;
171 | padding-top: 1em;
172 | font-size: 0.75em;
173 | }
174 | .masthead h3 {
175 | font-size: 1em;
176 | }
177 | .brand_image {
178 | width: 140px;
179 | }
180 | h1.ui.header {
181 | font-size: 1.4rem;
182 | }
183 | }
184 |
185 | .ui.fixed.menu .brand_image {
186 | width: 120px;
187 | }
188 |
189 |
190 | /*-------------- End of Homepage Styles -------------*/
191 |
192 |
193 | /*-------------- Modal Styles -----------------------*/
194 |
195 | .ui.basic.modal {
196 | background-color: #333;
197 | border: 1px solid #009c95;
198 | }
199 |
200 | .ui.basic.modal .ui.segment {
201 | background: none;
202 | box-shadow: none;
203 | border-radius: 0;
204 | border: 0;
205 | }
206 |
207 | .ui.basic.modal .ui.stacked.segment::after,
208 | .ui.basic.modal .ui.stacked.segment::before {
209 | border-top: 0;
210 | background: none;
211 | }
212 |
213 | .ui.basic.modal input::-webkit-selection {
214 | background-color: rgba(210, 210, 210, 0.9);
215 | }
216 |
217 | .ui.basic.modal input::-moz-selection {
218 | background-color: rgba(210, 210, 210, 0.9);
219 | }
220 |
221 | .ui.basic.modal input::selection {
222 | background-color: rgba(210, 210, 210, 0.9);
223 | }
224 |
225 | .ui.basic.modal .field>label,
226 | .ui.basic.modal .ui.input {
227 | color: #fff;
228 | }
229 |
230 | .ui.basic.modal .field.error .input,
231 | .ui.basic.modal .fields.error .field .input {
232 | color: #9F3A38;
233 | }
234 |
235 | .ui.basic.modal input:not([type]),
236 | .ui.basic.modal input[type="date"],
237 | .ui.basic.modal input[type="datetime-local"],
238 | .ui.basic.modal input[type="email"],
239 | .ui.basic.modal input[type="number"],
240 | .ui.basic.modal input[type="password"],
241 | .ui.basic.modal input[type="search"],
242 | .ui.basic.modal input[type="tel"],
243 | .ui.basic.modal input[type="time"],
244 | .ui.basic.modal input[type="text"],
245 | .ui.basic.modal input[type="file"],
246 | .ui.basic.modal input[type="url"] {
247 | color: #fff;
248 | }
249 |
250 |
251 | /*-------------- Flat Form Styles -------------------*/
252 |
253 | .ui.form input:not([type]),
254 | .ui.form input[type="date"],
255 | .ui.form input[type="datetime-local"],
256 | .ui.form input[type="email"],
257 | .ui.form input[type="number"],
258 | .ui.form input[type="password"],
259 | .ui.form input[type="search"],
260 | .ui.form input[type="tel"],
261 | .ui.form input[type="time"],
262 | .ui.form input[type="text"],
263 | .ui.form input[type="file"],
264 | .ui.form input[type="url"] {
265 | background: transparent;
266 | border: none;
267 | border-radius: 0em;
268 | box-shadow: none;
269 | }
270 |
271 | .ui.form input:not([type]):focus,
272 | .ui.form input[type="date"]:focus,
273 | .ui.form input[type="datetime-local"]:focus,
274 | .ui.form input[type="email"]:focus,
275 | .ui.form input[type="number"]:focus,
276 | .ui.form input[type="password"]:focus,
277 | .ui.form input[type="search"]:focus,
278 | .ui.form input[type="tel"]:focus,
279 | .ui.form input[type="time"]:focus,
280 | .ui.form input[type="text"]:focus,
281 | .ui.form input[type="file"]:focus,
282 | .ui.form input[type="url"]:focus {
283 | border-color: #009c95;
284 | border-radius: 0em;
285 | background: transparent;
286 | box-shadow: 0px 0em 0em 0em rgba(34, 36, 38, 0.35) inset;
287 | }
288 |
289 | .ui.form input[type="text"],
290 | .ui.form input[type="email"],
291 | .ui.form input[type="date"],
292 | .ui.form input[type="password"],
293 | .ui.form input[type="number"],
294 | .ui.form input[type="url"],
295 | .ui.form input[type="tel"] {
296 | border-bottom: 1px solid #DDDDDD;
297 | }
298 |
299 | .ui.form .field.error input:not([type]),
300 | .ui.form .field.error input[type=text],
301 | .ui.form .field.error input[type=email],
302 | .ui.form .field.error input[type=search],
303 | .ui.form .field.error input[type=password],
304 | .ui.form .field.error input[type=date],
305 | .ui.form .field.error input[type=datetime-local],
306 | .ui.form .field.error input[type=tel],
307 | .ui.form .field.error input[type=time],
308 | .ui.form .field.error input[type=file],
309 | .ui.form .field.error input[type=url],
310 | .ui.form .field.error input[type=number],
311 | .ui.form .field.error select,
312 | .ui.form .field.error textarea,
313 | .ui.form .fields.error .field input:not([type]),
314 | .ui.form .fields.error .field input[type=text],
315 | .ui.form .fields.error .field input[type=email],
316 | .ui.form .fields.error .field input[type=search],
317 | .ui.form .fields.error .field input[type=password],
318 | .ui.form .fields.error .field input[type=date],
319 | .ui.form .fields.error .field input[type=datetime-local],
320 | .ui.form .fields.error .field input[type=tel],
321 | .ui.form .fields.error .field input[type=time],
322 | .ui.form .fields.error .field input[type=file],
323 | .ui.form .fields.error .field input[type=url],
324 | .ui.form .fields.error .field input[type=number],
325 | .ui.form .fields.error .field select,
326 | .ui.form .fields.error .field textarea {
327 | background: transparent;
328 | }
329 |
330 | .ui.form .field.error input:not([type]):focus,
331 | .ui.form .field.error input[type=text]:focus,
332 | .ui.form .field.error input[type=email]:focus,
333 | .ui.form .field.error input[type=search]:focus,
334 | .ui.form .field.error input[type=password]:focus,
335 | .ui.form .field.error input[type=date]:focus,
336 | .ui.form .field.error input[type=datetime-local]:focus,
337 | .ui.form .field.error input[type=tel]:focus,
338 | .ui.form .field.error input[type=time]:focus,
339 | .ui.form .field.error input[type=file]:focus,
340 | .ui.form .field.error input[type=url]:focus,
341 | .ui.form .field.error input[type=number]:focus,
342 | .ui.form .field.error select:focus,
343 | .ui.form .field.error textarea:focus {
344 | background: transparent;
345 | }
346 |
347 | .ui.form .ui.icon.input>.icon {
348 | width: 1em;
349 | }
350 |
351 |
352 | /*--------------------
353 | Placeholder
354 | ---------------------*/
355 |
356 |
357 | /* browsers require these rules separate */
358 |
359 | .ui.input input::-webkit-input-placeholder {
360 | color: rgba(140, 140, 140, 0.87);
361 | }
362 |
363 | .ui.input input::-moz-placeholder {
364 | color: rgba(140, 140, 140, 0.87);
365 | }
366 |
367 | .ui.input input:-ms-input-placeholder {
368 | color: rgba(140, 140, 140, 0.87);
369 | }
370 |
371 | .ui.form ::-webkit-input-placeholder {
372 | color: rgba(140, 140, 140, 0.87);
373 | }
374 |
375 | .ui.form :-ms-input-placeholder {
376 | color: rgba(140, 140, 140, 0.87);
377 | }
378 |
379 | .ui.form ::-moz-placeholder {
380 | color: rgba(140, 140, 140, 0.87);
381 | }
382 |
383 | .ui.form :focus::-webkit-input-placeholder {
384 | color: rgba(89, 89, 89, 0.87);
385 | }
386 |
387 | .ui.form :focus:-ms-input-placeholder {
388 | color: rgba(89, 89, 89, 0.87);
389 | }
390 |
391 | .ui.form :focus::-moz-placeholder {
392 | color: rgba(89, 89, 89, 0.87);
393 | }
394 |
395 |
396 | /*--------- Loader Styles -------------*/
397 |
398 | .main_loader {
399 | display: inline;
400 | padding-left: 0.7em;
401 | display: none;
402 | }
403 |
404 | .loader_text {
405 | display: inline;
406 | padding-left: 0.55em;
407 | }
408 |
409 |
410 | /*---- Accordion styles -------------*/
411 |
412 | .ui.vertical.accordion.menu .item {
413 | -webkit-user-select: text;
414 | -moz-user-select: text;
415 | -ms-user-select: text;
416 | user-select: text;
417 | }
418 |
419 | .ui.vertical.accordion.menu .title {
420 | -webkit-user-select: none;
421 | -moz-user-select: none;
422 | -ms-user-select: none;
423 | user-select: none;
424 | }
425 |
426 | .ui.styled.accordion .title {
427 | color: rgba(0, 0, 0, .7);
428 | }
429 |
430 | .ui.accordion.menu.main_accordion {
431 | border: 0;
432 | padding-bottom: 2em;
433 | }
434 |
435 | .ui.vertical.menu.main_accordion {
436 | box-shadow: none;
437 | }
438 |
439 | .ui.accordion.menu.main_accordion .item {
440 | padding: 1em 0em;
441 | }
442 |
443 | .ui.accordion.menu.main_accordion .item .title {
444 | padding: 0.9em;
445 | background: #009c95;
446 | color: #fff;
447 | }
448 |
449 | .ui.accordion.menu.main_accordion .item .title:hover,
450 | .ui.accordion.menu.main_accordion .item .title.active {
451 | background: #21b1ab;
452 | transition: background 0.5s ease;
453 | -webkit-transition: background 0.5s ease;
454 | -moz-transition: background 0.5s ease;
455 | -o-transition: background 0.5s ease;
456 | }
457 |
458 | .ui.accordion.menu.main_accordion .item::before {
459 | background: none;
460 | }
461 |
462 | .ui.accordion.menu.main_accordion .image {
463 | border-radius: 0;
464 | }
465 |
466 | .ui.accordion.menu.main_accordion .avatar {
467 | margin-right: 0.5em;
468 | width: 1.8em;
469 | height: 1.8em;
470 | }
471 |
472 | #active_course_accordion .item .title,
473 | #completed_course_accordion .item .title,
474 | #available_course_accordion .item .title {
475 | background: none;
476 | color: #757575;
477 | border: 1px solid rgba(34, 36, 38, .19);
478 | border-radius: .28571429rem;
479 | transition: all 0.5s ease;
480 | -webkit-transition: all 0.5s ease;
481 | -moz-transition: all 0.5s ease;
482 | -o-transition: all 0.5s ease;
483 | }
484 |
485 | #active_course_accordion .item .title:hover,
486 | #completed_course_accordion .item .title:hover,
487 | #available_course_accordion .item .title:hover,
488 | #active_course_accordion .item .title.active,
489 | #completed_course_accordion .item .title.active,
490 | #available_course_accordion .item .title.active {
491 | background: rgba(183, 183, 183, 0.27);
492 | color: #333;
493 | }
494 |
495 | #active_course_accordion .content,
496 | #completed_course_accordion .content,
497 | #available_course_accordion .content {
498 | padding: 1.4em 1.8em 0em;
499 | }
500 |
501 | @media only screen and (max-width: 767px) {
502 | .ui.accordion.menu.main_accordion .item .title,
503 | .star_wrapper .ui.rating {
504 | font-size: 0.9em;
505 | }
506 | .ui.accordion.menu.main_accordion .item .title {
507 | padding: 1.2em 0.75em;
508 | }
509 | .ui.accordion.menu.main_accordion .content,
510 | .ui.accordion.menu.main_accordion .ui.button {
511 | font-size: 0.84rem;
512 | }
513 | .ui.accordion.menu.main_accordion .ui.button {
514 | padding-top: 1em;
515 | padding-bottom: 1em;
516 | }
517 | .course_detail {
518 | margin-left: 1.2em;
519 | }
520 | .star_wrapper {
521 | margin-right: 0;
522 | }
523 | }
524 |
525 | @media only screen and (max-width: 480px) {
526 | .ui.accordion.menu.main_accordion .item .title,
527 | .star_wrapper .ui.rating {
528 | font-size: 0.84em;
529 | }
530 | .ui.accordion.menu.main_accordion .avatar {
531 | width: 1.4em;
532 | height: 1.4em;
533 | }
534 | .ui.accordion.menu.main_accordion .content,
535 | .ui.accordion.menu.main_accordion .ui.button {
536 | font-size: 0.7rem;
537 | }
538 | .course_detail {
539 | margin-left: 1.8em;
540 | }
541 | .star_wrapper {
542 | margin-top: 2px;
543 | }
544 | }
545 |
546 |
547 | /*------------ Student Home Styles -------------*/
548 |
549 | .inline_header {
550 | display: inline;
551 | }
552 |
553 | #message_display {
554 | margin-top: 1.8em;
555 | margin-bottom: 0em;
556 | }
557 |
558 | .ui.icon.button.tooltip_btn {
559 | margin-left: 0.7em;
560 | position: relative;
561 | top: -5px;
562 | }
563 |
564 | #displayName {
565 | display: none;
566 | }
567 |
568 | #total_points {
569 | display: none;
570 | }
571 |
572 | .basic_content {
573 | padding: 1.2em 0em;
574 | }
575 |
576 | #user_content {
577 | margin-top: 0.55em;
578 | margin-bottom: 2em;
579 | }
580 |
581 | #user_content .ui.card>.image,
582 | #user_content .ui.cards>.card>.image {
583 | padding: 1.2em;
584 | }
585 |
586 | #user_content .ui.card>.image>img,
587 | #user_content .ui.cards>.card>.image>img {
588 | width: auto;
589 | margin: 0 auto;
590 | }
591 |
592 | #user_content .ui.cards>.card>.content {
593 | min-height: 95px;
594 | background: #e7e7e7;
595 | position: relative;
596 | }
597 |
598 | #badge {
599 | margin-bottom: 1.2em;
600 | width: 100%;
601 | }
602 |
603 | .badge_points {
604 | width: 100%;
605 | position: absolute;
606 | bottom: 10px;
607 | left: 0;
608 | text-align: center;
609 | }
610 |
611 | .user_rating {
612 | padding-top: 1em;
613 | }
614 |
615 | .ui.positive.button {
616 | background-color: #5BB47C;
617 | }
618 |
619 | .ui.positive.button:hover {
620 | background-color: #5abc71;
621 | }
622 |
623 | .ui.positive.button:active,
624 | .ui.positive.button:focus {
625 | background-color: #459B64;
626 | }
627 |
628 |
629 | /*------- Course Home Styles ---------*/
630 |
631 | #course_message_display {
632 | margin: 0;
633 | padding: 0;
634 | }
635 |
636 | #course_message_display .message {
637 | margin-top: 2.1em;
638 | }
639 |
640 | #course_module {
641 | border: none;
642 | margin: 0 1em 0.75em 1em;
643 | }
644 |
645 | #course_module .item::before {
646 | background: none;
647 | }
648 |
649 | #course_module a.item {
650 | margin-left: 0.75em;
651 | background: #00b5ad;
652 | color: #fff;
653 | border: none;
654 | border-radius: 0;
655 | -webkit-transition: background 0.1s ease, color 0.1s ease;
656 | transition: background 0.1s ease, color 0.1s ease;
657 | }
658 |
659 | #course_module a.item:hover {
660 | color: #fff;
661 | background-color: #009c95;
662 | }
663 |
664 | #course_module .item>i.icon {
665 | margin: 0;
666 | }
667 |
668 | #module_column {
669 | width: 21%;
670 | }
671 |
672 | #topic_column {
673 | width: 79%;
674 | }
675 |
676 | .ui.styled.accordion.main_course_accordion {
677 | box-shadow: none;
678 | border: 0;
679 | }
680 |
681 | .ui.list.topic_list .item.active {
682 | color: rgba(0, 0, 0, .8);
683 | }
684 |
685 | .ui.list.topic_list>a.item.active i.icon {
686 | color: #5BB47C;
687 | }
688 |
689 | .course_segment {
690 | min-height: 550px;
691 | }
692 |
693 | #module_segment,
694 | #module_sidebar {
695 | padding: 0;
696 | }
697 |
698 | #module_sidebar {
699 | display: none;
700 | }
701 |
702 | #topic_content {
703 | padding: 2.1em;
704 | overflow: scroll;
705 | max-height: 450px;
706 | }
707 |
708 | #topic_content p {
709 | padding: 0.5em 0.7em;
710 | }
711 |
712 | .topic_detail,
713 | #module_content {
714 | min-height: 270px;
715 | }
716 |
717 | #module_content {
718 | display: none;
719 | }
720 |
721 | #module_content .ui.top.right.attached.label {
722 | border-radius: 0 0 0 .28571429rem;
723 | }
724 |
725 | pre {
726 | box-sizing: border-box;
727 | width: 100%;
728 | margin: 0;
729 | overflow: auto;
730 | overflow-y: hidden;
731 | font-size: 0.84em;
732 | line-height: 1.55em;
733 | background: #efefef;
734 | border: 1px solid #9DC0BE;
735 | padding: 1.2em 1.8em;
736 | color: #545454;
737 | }
738 |
739 | #feedback_section {
740 | margin-top: 1.8em;
741 | }
742 |
743 | #user_feedback {
744 | margin: 1.8em;
745 | padding: 1.2em;
746 | min-height: 59px;
747 | }
748 |
749 | #user_feedback .loader {
750 | left: 2.1em;
751 | }
752 |
753 | #rating_msg {
754 | display: inline;
755 | padding-left: 0.75em;
756 | }
757 |
758 | #rating_msg .ui.orange.label {
759 | background-color: #DA8A31 !important;
760 | border-color: #DA8A31 !important;
761 | }
762 |
763 | #completed_modal .completed_content {
764 | padding: 1.8em;
765 | margin: 2em;
766 | font-size: 1.1em;
767 | color: #7fb091;
768 | }
769 |
770 | @media only screen and (max-width: 1200px) {
771 | #module_sidebar {
772 | display: block;
773 | }
774 | #module_column {
775 | display: none;
776 | }
777 | #topic_column {
778 | width: 100%;
779 | }
780 | #course_module {
781 | display: inherit;
782 | display: -webkit-box;
783 | display: -ms-flexbox;
784 | display: flex;
785 | }
786 | }
787 |
788 |
789 | /*------- Other styles ---------*/
790 |
791 | #platform_link {
792 | background: none !important;
793 | }
794 |
795 | #platform_link:hover {
796 | background: none !important;
797 | }
798 |
799 | .base_container {
800 | padding-top: 1.8em;
801 | min-height: 400px;
802 | }
803 |
804 | .loader_content {
805 | min-height: 75px;
806 | }
--------------------------------------------------------------------------------
/migrations/1517759998068_demo_content.up.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO badge_details (badge_id, name, points, badge_logo, badge_description) VALUES (1, 'Member Badge', 0, 'member_badge.png', 'Thank you for becoming a member of eGyan!');
2 | INSERT INTO badge_details (badge_id, name, points, badge_logo, badge_description) VALUES (2, 'Beginner Badge', 50, 'beginner_badge.png', 'Way to go! Keep collecting.');
3 | INSERT INTO badge_details (badge_id, name, points, badge_logo, badge_description) VALUES (3, 'Intermediate Badge', 250, 'intermediate_badge.png', 'You have got this for collecting 250pts');
4 | INSERT INTO badge_details (badge_id, name, points, badge_logo, badge_description) VALUES (4, 'Pro Badge', 500, 'pro_badge.png', 'You have got this for collecting 500pts');
5 | INSERT INTO badge_details (badge_id, name, points, badge_logo, badge_description) VALUES (5, 'Ultimate Badge', 1000, 'ultimate_badge.png', 'You are a champion!');
6 |
7 | INSERT INTO course_details (course_id, name, about, syllabus, course_logo, active) VALUES (1, 'Introduction to HTML5', 'This course give you a brief introduction to HTML5. HTML5 is a markup language used for structuring and presenting content on the World Wide Web. It is the fifth and current version of the HTML standard.', '
8 | Intro
9 |
10 | Basics
11 | Basic Syntax HTML5 Tags (Basic) HTML5 form
12 | ', 'html5.png', true);
13 | INSERT INTO course_details (course_id, name, about, syllabus, course_logo, active) VALUES (2, 'Beginning PHP', 'This course gives you a brief introduction to PHP. PHP is a server-side scripting language designed primarily for web development but also used as a general-purpose programming language.', '
14 | Overview
15 | Introduction Why PHP? Your First PHP file
16 | Basics
17 | Basic Syntax Types Variables Constants Expressions Operators Control Structures Functions
18 | Forms
19 |
20 | ', 'php.png', true);
21 |
22 | INSERT INTO module_details (module_id, module_name, course_id) VALUES (1, 'Intro', 1);
23 | INSERT INTO module_details (module_id, module_name, course_id) VALUES (2, 'Basics', 1);
24 | INSERT INTO module_details (module_id, module_name, course_id) VALUES (3, 'Overview', 2);
25 | INSERT INTO module_details (module_id, module_name, course_id) VALUES (4, 'Basics', 2);
26 | INSERT INTO module_details (module_id, module_name, course_id) VALUES (5, 'Forms', 2);
27 |
28 | INSERT INTO topic_details (topic_id, topic_name, topic_content, topic_points, module_id) VALUES (1, 'Overview', 'HTML5 is a markup language used for structuring and presenting content on the World Wide Web. It is the fifth and current version of the HTML standard. It was published in October 2014 by the World Wide Web Consortium (W3C) to improve the language with support for the latest multimedia, while keeping it both easily readable by humans and consistently understood by computers and devices such as web browsers, parsers, etc. HTML5 is intended to subsume not only HTML 4, but also XHTML 1 and DOM Level 2 HTML.
', 5, 1);
29 | INSERT INTO topic_details (topic_id, topic_name, topic_content, topic_points, module_id) VALUES (2, 'History', 'The Web Hypertext Application Technology Working Group (WHATWG) began work on the new standard in 2004. At that time, HTML 4.01 had not been updated since 2000, and the World Wide Web Consortium (W3C) was focusing future developments on XHTML 2.0. In 2009, the W3C allowed the XHTML 2.0 Working Group''s charter to expire and decided not to renew it. W3C and WHATWG are currently working together on the development of HTML5.
The Mozilla Foundation and Opera Software presented a position paper at a World Wide Web Consortium (W3C) workshop in June 2004, focusing on developing technologies that are backward compatible with existing browsers, including an initial draft specification of Web Forms 2.0. The workshop concluded with a vote—8 for, 14 against—for continuing work on HTML. Immediately after the workshop, the Web Hypertext Application Technology Working Group (WHATWG) was formed to start work based upon that position paper, and a second draft, Web Applications 1.0, was also announced. The two specifications were later merged to form HTML5 . The HTML5 specification was adopted as the starting point of the work of the new HTML working group of the W3C in 2007.
On 14 February 2011, the W3C extended the charter of its HTML Working Group with clear milestones for HTML5. In May 2011, the working group advanced HTML5 to "Last Call", an invitation to communities inside and outside W3C to confirm the technical soundness of the specification. The W3C developed a comprehensive test suite to achieve broad interoperability for the full specification by 2014, which was the target date for recommendation. In January 2011, the WHATWG renamed its "HTML5" living standard to "HTML". The W3C nevertheless continued its project to release HTML5.
In July 2012, WHATWG and W3C decided on a degree of separation. W3C will continue the HTML5 specification work, focusing on a single definitive standard, which is considered as a "snapshot" by WHATWG. The WHATWG organization will continue its work with HTML5 as a "Living Standard". The concept of a living standard is that it is never complete and is always being updated and improved. New features can be added but functionality will not be removed.
In December 2012, W3C designated HTML5 as a Candidate Recommendation. The criterion for advancement to W3C Recommendation is "two 100% complete and fully interoperable implementations".
On 16 September 2014, W3C moved HTML5 to Proposed Recommendation.
On 28 October 2014 , HTML5 was released as a stable W3C Recommendation, bringing the specification process to completion.
On 1 November 2016 , HTML5.1 was released as a stable W3C Recommendation.
', 5, 1);
30 |
31 | INSERT INTO topic_details (topic_id, topic_name, topic_content, topic_points, module_id) VALUES (3, 'Basic Syntax', '
32 |
33 |
34 | <!DOCTYPE html>
35 | <html>
36 | <head>
37 | <meta charset="UTF-8">
38 | <title></title>
39 | </head>
40 | <body>
41 | </body>
42 | </html>
43 |
44 |
45 |
46 | <!DOCTYPE html> declares the document type, i.e. html. All the html pages start with <html> and ends with </html>. The character encoding is specified as, <meta charset="UTF-8">. Page title is specified inside title tag in head section. Content of the document in body tag.
', 10, 2);
47 | INSERT INTO topic_details (topic_id, topic_name, topic_content, topic_points, module_id) VALUES (4, 'HTML5 Tags (Basic)', 'Tags that have Semantic Meaning
48 | Basic Tags and there uses are described below.
49 |
50 | <header> - For defining the header of a section/document.
51 | <nav> - For specifying navigation links.
52 | <article> - For specifying an article.
53 | <section> - For specifying a section of a document or an article.
54 | <aside> - For specifying content aside from the page content.
55 | <footer> - For specifying footer of a section/document.
56 |
57 |
58 | Form Elements
59 |
60 | <datalist> - For specifying a list of pre-defined options for input controls.
61 | <keygen> - Defines a key-pair generator field.
62 | <output> - Defines the result of a calculation.
63 |
64 | Graphics Elements
65 |
66 | <canvas> - Draw graphics using scripting (JavaScript).
67 | <svg> - Draw scalable vector graphics (svg). Useful for displaying vector based graphics.
68 |
69 | Multimedia Elements
70 |
71 | <audio> - Defines audio content.
72 | <embed> - Defines a container for an external application.
73 | <source> - Defines multiple media resources for media elements (<video> and <audio>).
74 | <track> - Defines text tracks for media elements (<video> and <audio>)
75 | <video> - Defines video.
76 | ', 25, 2);
77 | INSERT INTO topic_details (topic_id, topic_name, topic_content, topic_points, module_id) VALUES (5, 'HTML5 form', 'In HTML5, form elements and attributes have a greater degree of semantic mark-up than HTML4 and remove the need for tedious scripting and styling that was required in HTML4. The forms features in HTML5 provide a better experience for users by making forms more consistent across different web sites and giving immediate feedback to the user even if scripting is disabled in their browser.
78 | Now, <input> element has new values for the type attribute. Some of these type attribute values are, email (The element represents email address), search (The element represents search entry field), tel (the element represents a control for editing a telephone number. You can use attributes such as pattern and maxlength to restrict values entered in the control), url (The element represents a control for editing a URL).
79 |
The <input> element also has new attributes. Some of these are, list (The ID of a <datalist> element whose content, <option> elements, are to be used as hints and are displayed as proposals in the suggestion area of the input field), pattern (A regular expression that the control’s value is checked against, which can be used with type values of text, tel, search, url, and email), form (A string indicating which <form> element this input is part of. An input can only be in one form), formmethod (A string indicating which HTTP method (GET or POST) should be used when submitting; it overrides the method of the <form> element, if defined. The formmethod only applies when the type is image or submit, and the form attribute has been set).
80 |
Various attributes are also available for other form elements. The placeholder attribute on <input> and <textarea> elements provides a hint to the user of what can be entered in the field. The placeholder text must not contain carriage returns or line-feeds. The autofocus attribute lets you specify that a form control should have input focus when the page loads, unless the user overrides it, for example by typing in a different control.
81 | HTML5 provides syntax and API items to support client-side validation of forms. While this functionality does not replace server-side validation, which is still necessary for security and data integrity, client-side validation can support a better user experience by giving the user immediate feedback about the input data. One such example is the required attribute (specifies that the user must fill in a value before submitting a form).
', 10, 2);
82 |
83 | INSERT INTO topic_details (topic_id, topic_name, topic_content, topic_points, module_id) VALUES (6, 'Introduction', 'PHP (recursive acronym for PHP: Hypertext Preprocessor) is a widely-used open source general-purpose scripting language that is especially suited for web development and can be embedded into HTML. What distinguishes PHP from something like client-side JavaScript (client-side scripting language) is that the code is executed on the server (server-side scripting language), generating HTML which is then sent to the client. The client would receive the results of running that script, but would not know what the underlying code was.
There are three main areas where PHP scripts are used.
This is the most traditional and main target field for PHP. You need three things to make this work: the PHP parser (CGI or server module), a web server and a web browser . You need to run the web server, with a connected PHP installation. You can access the PHP program output with a web browser, viewing the PHP page through the server. Refer Note for installing PHP on your computer.
You can make a PHP script to run it without any server or browser. You only need the PHP parser to use it this way. This type of usage is ideal for scripts regularly executed using cron (on *nix or Linux) or Task Scheduler (on Windows). These scripts can also be used for simple text processing tasks.
PHP is probably not the very best language to create a desktop application with a graphical user interface, but if you know PHP very well, and would like to use some advanced PHP features in your client-side applications you can also use PHP-GTK to write such programs. You also have the ability to write cross-platform applications this way.
Note: To install PHP on your home, install PHP development environment. One of the most popular one is XAMPP .
', 10, 3);
84 | INSERT INTO topic_details (topic_id, topic_name, topic_content, topic_points, module_id) VALUES (7, 'Why PHP?', ' You can use PHP to write cross-platform applications.i.e. It can be used on all major operating systems, including Linux, many Unix variants (including HP-UX, Solaris and OpenBSD), Microsoft Windows, Mac OS X, RISC OS, and probably others. It is completely free. The best things in using PHP are that it is extremely simple for a newcomer, but offers many advanced features for a professional programmer. You have the choice of using procedural programming or object oriented programming (OOP), or a mixture of both. It supports various databases. It is compatible with almost all servers used today (Apache, IIS, etc.) PHP’s other abilities includes outputting images, PDF files and even Flash movies (using libswf and Ming) generated on the fly. ', 5, 3);
85 | INSERT INTO topic_details (topic_id, topic_name, topic_content, topic_points, module_id) VALUES (8, 'Your First PHP file', 'PHP files have extension .php . Below example is a simple PHP file that can output a paragraph, containing a text, Hello World!
86 |
87 | <!DOCTYPE HTML>
88 | <html>
89 | <head>
90 | <title>First PHP file</title>
91 | </head>
92 | <body>
93 | <?php echo "<p>Hello World!</p>"; ?>
94 | </body>
95 | </html>
96 |
97 |
98 |
Note:
99 |
If you have setup XAMPP; Run Apache, then go to root directory where you have setup XAMPP. Go inside htdocs directory, then save the above file as first.php. Then open your favorite web browser, then go to this url: http://localhost/first.php
100 |
', 10, 3);
101 |
102 | INSERT INTO topic_details (topic_id, topic_name, topic_content, topic_points, module_id) VALUES (9, 'Basic syntax', 'When PHP parses a file, it looks for opening and closing tags, which are <?php and ?> which tell PHP to start and stop interpreting the code between them. Parsing in this manner allows PHP to be embedded in all sorts of different documents, as everything outside of a pair of opening and closing tags is ignored by the PHP parser .
103 |
104 | <?php
105 | //php code
106 | ?>
107 |
108 | In the above code // indicates a single line comment. It will not be executed as part of program. Comments are a greater way to explain your code. For multi-line comment, /* comments here */, can be used. When you are writing php codes always make sure to end php statements with a semicolon . For example, <?php echo "Hello World!"; ?>. echo statement in php is used to output data to the screen.
109 |
110 |
Note:
111 |
If a file is pure PHP code, it is preferable to omit the PHP closing tag at the end of the file. This prevents accidental whitespace or new lines being added after the PHP closing tag, which may cause unwanted effects.
112 |
', 10, 4);
113 | INSERT INTO topic_details (topic_id, topic_name, topic_content, topic_points, module_id) VALUES (10, 'Types', 'PHP supports ten primitive types.
114 | Four scalar types:
115 | boolean (It can be either TRUE or FALSE)integer (non-decimal number between -2,147,483,648 and 2,147,483,647)float (Floating point numbers, also known as "floats", "doubles", or "real numbers")string (sequence of characters; for example, "Hello World!")
116 | Four compound types:
117 | array (An array in PHP is actually an ordered map. A map is a type that associates values to keys.)object (An individual instance of the data structure defined by a class. You define a class once and then make many objects that belong to it. Objects are also known as instance.)callable (Callbacks can be denoted by callable type hint as of PHP 5.4)iterable (Iterable is a pseudo-type introduced in PHP 7.1. Iterable can be used as a parameter type to indicate that a function requires a set of values, but does not care about the form of the value set since it will be used with foreach.)
118 | And finally two special types:
119 | resource (A resource is a special variable, holding a reference to an external resource. Resources are created and used by special functions.)NULL (The special NULL value represents a variable with no value. NULL is the only possible value of type null.) ', 5, 4);
120 | INSERT INTO topic_details (topic_id, topic_name, topic_content, topic_points, module_id) VALUES (11, 'Variables', 'Variables in PHP are represented by a dollar sign followed by the name of the variable . The variable name is case-sensitive. A valid variable name starts with a letter or underscore, followed by any number of letters, numbers, or underscores. For example,
121 |
122 | <?php
123 | $course="Beginning PHP";
124 | ?>
125 |
126 | By default, variables are always assigned by value . That is to say, when you assign an expression to a variable, the entire value of the original expression is copied into the destination variable. This means, for instance, that after assigning one variable’s value to another, changing one of those variables will have no effect on the other.
127 | PHP also offers another way to assign values to variables: assign by reference . This means that the new variable simply references (in other words, "becomes an alias for" or "points to") the original variable. Changes to the new variable affect the original, and vice versa. To assign by reference, simply prepend an ampersand (&) to the beginning of the variable which is being assigned (the source variable).
', 5, 4);
128 | INSERT INTO topic_details (topic_id, topic_name, topic_content, topic_points, module_id) VALUES (12, 'Constants', 'A constant is an identifier (name) for a simple value. As the name suggests, that value cannot change during the execution of the script. A constant is case-sensitive by default. By convention, constant identifiers are always uppercase. A valid constant name starts with a letter or underscore, followed by any number of letters, numbers, or underscores. You can access constants anywhere in your script without regard to scope. To create a constant, use the define() function. For example,
129 |
130 | <?php
131 | define("ENV","development");
132 | ?>
133 | ', 5, 4);
134 | INSERT INTO topic_details (topic_id, topic_name, topic_content, topic_points, module_id) VALUES (13, 'Expressions', 'Expressions are the most important building blocks of PHP. In PHP, almost anything you write is an expression. The simplest yet most accurate way to define an expression is "anything that has a value".
The most basic forms of expressions are constants and variables. When you type "$a = 5", you’re assigning ’5’ into $a. ’5’, obviously, has the value 5, or in other words ’5’ is an expression with the value of 5 (in this case, ’5’ is an integer constant).
After this assignment, you’d expect $a’s value to be 5 as well, so if you wrote $b = $a, you’d expect it to behave just as if you wrote $b = 5. In other words, $a is an expression with the value of 5 as well. If everything works right, this is exactly what will happen.
', 5, 4);
135 | INSERT INTO topic_details (topic_id, topic_name, topic_content, topic_points, module_id) VALUES (14, 'Operators', 'An operator is something that takes one or more values (or expressions, in programming jargon) and yields another value (so that the construction itself becomes an expression).
136 | 1. Arithmetic Operators
137 | Example Name Result +$a Identity Conversion of $a to int or float as appropriate. -$a Negation Opposite of $a . $a + $b Addition Sum of $a and $b . $a - $b Subtraction Difference of $a and $b . $a * $b Multiplication Product of $a and $b . $a / $b Division Quotient of $a and $b . $a % $b Modulo Remainder of $a divided by $b . $a ** $b Exponentiation Result of raising $a to the $b ’th power.
138 | 2. Assignment Operators
139 | The basic assignment operator is "=" . The value of an assignment expression is the value assigned. That is, the value of "$a = 3" is 3. In addition to the basic assignment operator, there are "combined operators" for all of the binary arithmetic, array union and string operators that allow you to use a value in an expression and then set its value to the result of that expression. For example,
140 |
141 | <?php
142 |
143 | $a = 3;
144 | $a += 5; // sets $a to 8, as if we had said: $a = $a + 5;
145 | $b = "Hello ";
146 | $b .= "There!"; // sets $b to "Hello There!", just like $b = $b . "There!";
147 |
148 | ?>
149 |
150 |
151 | 3. Bitwise Operators
152 | Bitwise operators allow evaluation and manipulation of specific bits within an integer.
153 | Example Name Result $a & $bAnd Bits that are set in both $a and $b are set. $a | $bOr (inclusive or) Bits that are set in either $a or $b are set. $a ^ $bXor (exclusive or) Bits that are set in $a or $b but not both are set. ~ $aNot Bits that are set in $a are not set, and vice versa. $a << $bShift left Shift the bits of $a $b steps to the left (each step means "multiply by two") $a >> $bShift right Shift the bits of $a $b steps to the right (each step means "divide by two")
154 | 4. Comparison Operators
155 | Example Name Result $a == $b Equal TRUE if $a is equal to $b after type juggling.$a === $b Identical TRUE if $a is equal to $b , and they are of the same type.$a != $b Not equal TRUE if $a is not equal to $b after type juggling.$a <> $b Not equal TRUE if $a is not equal to $b after type juggling.$a !== $b Not identical TRUE if $a is not equal to $b , or they are not of the same type.$a < $b Less than TRUE if $a is strictly less than $b .$a > $b Greater than TRUE if $a is strictly greater than $b .$a <= $b Less than or equal to TRUE if $a is less than or equal to $b .$a >= $b Greater than or equal to TRUE if $a is greater than or equal to $b .$a <=> $b Spaceship An integer less than, equal to, or greater than zero when $a is respectively less than, equal to, or greater than $b (PHP 7).
156 | 5. Incrementing/Decrementing Operators
157 | Example Name Effect ++$a Pre-increment Increments $a by one, then returns $a . $a++ Post-increment Returns $a , then increments $a by one. --$a Pre-decrement Decrements $a by one, then returns $a . $a-- Post-decrement Returns $a , then decrements $a by one.
158 | 7. Logical Operators
159 | Example Name Result $a and $b And TRUE if both $a and $b are TRUE .$a or $b Or TRUE if either $a or $b is TRUE .$a xor $b Xor TRUE if either $a or $b is TRUE , but not both.! $a Not TRUE if $a is not TRUE .$a && $b And TRUE if both $a and $b are TRUE .$a || $b Or TRUE if either $a or $b is TRUE .
160 | 8. String Operators
161 | There are two string operators. The first is the concatenation operator, ". " , which returns the concatenation of its right and left arguments. The second is the concatenating assignment operator, ".= " , which appends the argument on the right side to the argument on the left side.
162 | 9. Array Operators
163 | Example Name Result $a + $b Union Union of $a and $b . $a == $b Equality TRUE if $a and $b have the same key/value pairs.$a === $b Identity TRUE if $a and $b have the same key/value pairs in the same order and of the same types.$a != $b Inequality TRUE if $a is not equal to $b .$a <> $b Inequality TRUE if $a is not equal to $b .$a !== $b Non-identity TRUE if $a is not identical to $b .
', 15, 4);
164 | INSERT INTO topic_details (topic_id, topic_name, topic_content, topic_points, module_id) VALUES (15, 'Control Structures', 'In a program, a control structure determines the order in which statements are executed.
165 | if
166 | The if construct is one of the most important features of many languages, PHP included. It allows for conditional execution of code fragments. PHP features an if structure that is similar to that of C:
167 |
168 | if (expr)
169 | statement
170 |
171 | Example:
172 | The following example would display a is bigger than b if $a is bigger than $b
173 |
174 | <?php
175 | if ($a > $b)
176 | echo "a is bigger than b";
177 | ?>
178 |
179 | else
180 | Often you’d want to execute a statement if a certain condition is met, and a different statement if the condition is not met. This is what else is for.
181 | Example:
182 | following code would display a is greater than b if $a is greater than $b, and a is NOT greater than b otherwise:
183 |
184 | <?php
185 | if ($a > $b) {
186 | echo "a is greater than b";
187 | } else {
188 | echo "a is NOT greater than b";
189 | }
190 | ?>
191 |
192 | elseif/else if
193 | elseif, as its name suggests, is a combination of if and else. Like else, it extends an if statement to execute a different statement in case the original if expression evaluates to FALSE. However, unlike else, it will execute that alternative expression only if the elseif conditional expression evaluates to TRUE.
194 | Example:
195 | For example, the following code would display a is bigger than b, a equal to b or a is smaller than b:
196 |
197 | <?php
198 | if ($a > $b) {
199 | echo "a is bigger than b";
200 | } elseif ($a == $b) {
201 | echo "a is equal to b";
202 | } else {
203 | echo "a is smaller than b";
204 | }
205 | ?>
206 |
207 | while
208 | while loops are the simplest type of loop in PHP. They behave just like their C counterparts. The basic form of a while statement is:
209 |
210 | while (expr)
211 | statement
212 |
213 | do-while
214 | do-while loops are very similar to while loops, except the truth expression is checked at the end of each iteration instead of in the beginning. The main difference from regular while loops is that the first iteration of a do-while loop is guaranteed to run. The syntax of a do-while loop is:
215 |
216 | do {
217 | code;
218 | } while (condition is true);
219 |
220 | for
221 | for loops are the most complex loops in PHP. They behave like their C counterparts. The syntax of a for loop is:
222 |
223 | for (expr1; expr2; expr3)
224 | statement
225 |
226 | The first expression (expr1) is evaluated (executed) once unconditionally at the beginning of the loop. In the beginning of each iteration, expr2 is evaluated. If it evaluates to TRUE, the loop continues and the nested statement(s) are executed. If it evaluates to FALSE, the execution of the loop ends. At the end of each iteration, expr3 is evaluated (executed).
227 | Example:
228 | following example displays number 1 to 10
229 |
230 | <?php
231 | for ($i = 1; $i <= 10; $i++) {
232 | echo $i;
233 | }
234 | ?>
235 |
236 | foreach
237 | The foreach construct provides an easy way to iterate over arrays. foreach works only on arrays and objects, and will issue an error when you try to use it on a variable with a different data type or an uninitialized variable. There are two syntaxes:
238 |
239 | foreach (array_expression as $value)
240 | statement
241 |
242 |
243 | foreach (array_expression as $key => $value)
244 | statement
245 |
246 | switch
247 | The switch statement is similar to a series of IF statements on the same expression. In many occasions, you may want to compare the same variable (or expression) with many different values, and execute a different piece of code depending on which value it equals to. This is exactly what the switch statement is for.
248 | Example: switch structure
249 |
250 | <?php
251 | switch ($i) {
252 | case 0:
253 | echo "i equals 0";
254 | break;
255 | case 1:
256 | echo "i equals 1";
257 | break;
258 | case 2:
259 | echo "i equals 2";
260 | break;
261 | }
262 | ?>
263 | ', 20, 4);
264 | INSERT INTO topic_details (topic_id, topic_name, topic_content, topic_points, module_id) VALUES (16, 'Functions', 'User-defined functions
265 | A function may be defined using syntax such as the following:
266 |
267 | <?php
268 | function foo($arg_1, $arg_2, /* ..., */ $arg_n)
269 | {
270 | echo "Example";
271 | return $retval;
272 | }
273 | ?>
274 |
275 | In the above example, $arg_1, $arg_2, /* ..., */ $arg_n represents argument list . Information may be passed to functions via the argument list, which is a comma-delimited list of expressions. The arguments are evaluated from left to right. $retval represents the returning value of the function. Values are returned by using the optional return statement . Any type may be returned, including arrays and objects. This causes the function to end its execution immediately and pass control back to the line from which it was called. Any valid PHP code may appear inside a function, even other functions and class definitions.
276 | Internal (built-in) functions
277 | PHP comes standard with many functions and constructs. There are also functions that require specific PHP extensions compiled in, otherwise fatal "undefined function" errors will appear. For example, to use image functions such as imagecreatetruecolor(), PHP must be compiled with GD support. Or, to use mysql_connect(), PHP must be compiled with MySQL support. There are many core functions that are included in every version of PHP, such as the string and variable functions. A call to phpinfo() or get_loaded_extensions() will show which extensions are loaded into PHP.
278 |
279 |
Note:
280 |
PHP does not support function overloading, nor is it possible to undefine or redefine previously-declared functions.
281 |
', 15, 4);
282 |
283 | INSERT INTO topic_details (topic_id, topic_name, topic_content, topic_points, module_id) VALUES (17, 'HTML form handling', 'One of the most powerful features of PHP is the way it handles HTML forms. The basic concept that is important to understand is that any form element will automatically be available to your PHP scripts. i.e. When a form is submitted to a PHP script, the information from that form is readily available. There are few ways to access this information, for example:
284 | Example: A simple HTML form for login
285 |
286 | form action="home.php" method="POST">
287 | <p>Username: <input type="text" name="username" /></p>
288 | <p>Password: <input type="password" name="pass" /></p>
289 | <p><input type="submit" value="Login" /></p>
290 | </form>
291 |
292 | When the user fills in this form and hits the submit button, the home.php page is called. In this file you would write something like this:
293 | home.php
294 |
295 | Welcome <?php echo htmlspecialchars($_POST[’username’]); ?>.
296 |
297 | Apart from the htmlspecialchars() it should be obvious what this does. htmlspecialchars() makes sure any characters that are special in html are properly encoded so people can’t inject HTML tags or Javascript into your page. The $_POST[’username’] variable will be automatically set for you by PHP. You don’t want to print the user password, right? you can store password using, $_POST[’pass’] . $_POST is a superglobal which contains all POST data. Notice how the method of our form is POST . If we used the method GET then our form information would live in the $_GET superglobal instead (Remember that, when you want to submit some sensitive information such as password, never use the GET method). You may also use the $_REQUEST superglobal, if you do not care about the source of your request data. It contains the merged information of GET, POST and COOKIE data.
298 | Using a GET form is similar except you’ll use the appropriate GET predefined variable instead. GET also applies to the QUERY_STRING (the information after the ’?’ in a URL). So, for example, http://www.example.com/page.php?id=1 contains GET data which is accessible with $_GET[’id’].
299 |
300 |
Note:
301 |
Superglobals — Superglobals are built-in variables that are always available in all scopes.
302 |
', 30, 5);
303 |
--------------------------------------------------------------------------------