├── .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 |
2 |
3 | 4 | 7 |
8 |
-------------------------------------------------------------------------------- /microservices/www/src/template/partials/modules.handlebars: -------------------------------------------------------------------------------- 1 |
2 | 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 |
2 |
3 | 4 | 10 |
11 |
-------------------------------------------------------------------------------- /microservices/www/src/template/partials/footer.handlebars: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /microservices/www/src/template/partials/courses.handlebars: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{courseType}}

4 |
5 |
6 | 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 |

Logged Out!

9 |

You have been successfully logged out of the system!

10 |

Redirecting to home page in 10seconds

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 |
20 |
21 | 22 | 28 |
29 |
30 |
31 |

32 | A simple and effective elearning platform for everyone. 33 |

34 |

Sign up to get started. It's completely free!

35 | 36 |
37 |
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 |
7 |
8 |
9 |

10 | 11 |
12 |
13 |

14 |
15 |
16 |
17 |
18 | 19 |
20 |
21 |
22 |
23 |

Your Badges

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 | [![eGyan Video](https://user-images.githubusercontent.com/22009263/33184648-a2dc2026-d0a3-11e7-880e-ae602fcef1b2.png)](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("
" + message + "
").fadeIn(); 3 | } 4 | 5 | function displayAccordionError(accordion_selector) { 6 | accordion_selector.css("display", "none").html("
Error occured!
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("
" + msg + "

Please try again!

"); 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("
" + res + "

You can now 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("
There were some errors!

" + result.message + "

"); 130 | } catch (e) { 131 | $signup_form.append("
Error occurred! Please try again after some time.
"); 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 |

30 | 31 |
32 | {{courseHeading}} 33 |
34 |

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 |

{{contentHeader}}

74 |
75 |
{{{content}}}
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | 91 |
92 |
93 |

94 |
95 | Feedback 96 |
97 |

98 |
99 |
100 |
101 |
Loading...
102 |
103 |
104 |
105 |
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 += "
Congratulations!

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 += "
" + data[i].name + "
" + points_display + "
"; 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("
No badges to display!
").fadeIn(); 60 | } 61 | }).fail(function (xhr) { 62 | //console.log(xhr.responseText); 63 | $("#badge").css("display", "none").html("
Error occured!
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:
" + ratingMessage + "
").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 ? "" : ""; 174 | var content = "

" + data[0].module_name + " - " + data[0].module_topics[0].topic_name + "

" + 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 + "
"; 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("
Currently Not Available!
").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 |
    • Overview
    • History
    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 |
    • HTML form handling
    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 | HTML5 semantic tags 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.

Server-side scripting
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.
Command line scripting
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.
Writing desktop applications
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 |
ExampleNameResult
+$aIdentityConversion of $a to int or float as appropriate.
-$aNegationOpposite of $a.
$a + $bAdditionSum of $a and $b.
$a - $bSubtractionDifference of $a and $b.
$a * $bMultiplicationProduct of $a and $b.
$a / $bDivisionQuotient of $a and $b.
$a % $bModuloRemainder of $a divided by $b.
$a ** $bExponentiationResult 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 |
ExampleNameResult
$a & $bAndBits 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.
~ $aNotBits that are set in $a are not set, and vice versa.
$a << $bShift leftShift the bits of $a $b steps to the left (each step means "multiply by two")
$a >> $bShift rightShift the bits of $a $b steps to the right (each step means "divide by two")
154 |
4. Comparison Operators
155 |
ExampleNameResult
$a == $bEqualTRUE if $a is equal to $b after type juggling.
$a === $bIdenticalTRUE if $a is equal to $b, and they are of the same type.
$a != $bNot equalTRUE if $a is not equal to $b after type juggling.
$a <> $bNot equalTRUE if $a is not equal to $b after type juggling.
$a !== $bNot identicalTRUE if $a is not equal to $b, or they are not of the same type.
$a < $bLess thanTRUE if $a is strictly less than $b.
$a > $bGreater thanTRUE if $a is strictly greater than $b.
$a <= $bLess than or equal toTRUE if $a is less than or equal to $b.
$a >= $bGreater than or equal toTRUE if $a is greater than or equal to $b.
$a <=> $bSpaceshipAn 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 |
ExampleNameEffect
++$aPre-incrementIncrements $a by one, then returns $a.
$a++Post-incrementReturns $a, then increments $a by one.
--$aPre-decrementDecrements $a by one, then returns $a.
$a--Post-decrementReturns $a, then decrements $a by one.
158 |
7. Logical Operators
159 |
ExampleNameResult
$a and $bAndTRUE if both $a and $b are TRUE.
$a or $bOrTRUE if either $a or $b is TRUE.
$a xor $bXorTRUE if either $a or $b is TRUE, but not both.
! $aNotTRUE if $a is not TRUE.
$a && $bAndTRUE if both $a and $b are TRUE.
$a || $bOrTRUE 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 |
ExampleNameResult
$a + $bUnionUnion of $a and $b.
$a == $bEqualityTRUE if $a and $b have the same key/value pairs.
$a === $bIdentityTRUE if $a and $b have the same key/value pairs in the same order and of the same types.
$a != $bInequalityTRUE if $a is not equal to $b.
$a <> $bInequalityTRUE if $a is not equal to $b.
$a !== $bNon-identityTRUE 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 | --------------------------------------------------------------------------------