├── .bowerrc ├── .env.config ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── about.md ├── app.js ├── app.json ├── app ├── client │ ├── assets │ │ ├── images │ │ │ ├── Banner.jpg │ │ │ ├── GitHub-Mark-64px.png │ │ │ ├── favicon.png │ │ │ ├── logo-color.svg │ │ │ └── logo.svg │ │ ├── particles.json │ │ ├── schools.csv │ │ └── schools.json │ ├── index.html │ ├── src │ │ ├── app.js │ │ ├── constants.js │ │ ├── interceptors │ │ │ └── AuthInterceptor.js │ │ ├── modules │ │ │ ├── Session.js │ │ │ └── Utils.js │ │ ├── routes.js │ │ └── services │ │ │ ├── AuthService.js │ │ │ ├── SettingsService.js │ │ │ └── UserService.js │ ├── stylesheets │ │ ├── _admin.scss │ │ ├── _animation.scss │ │ ├── _application.scss │ │ ├── _base.scss │ │ ├── _branding.scss │ │ ├── _custom.scss │ │ ├── _dashboard.scss │ │ ├── _login.scss │ │ ├── _page.scss │ │ ├── _sidebar.scss │ │ ├── _team.scss │ │ ├── _verify.scss │ │ ├── normalize.css │ │ └── site.scss │ └── views │ │ ├── 404.html │ │ ├── about │ │ └── about.html │ │ ├── admin │ │ ├── admin.html │ │ ├── adminCtrl.js │ │ ├── settings │ │ │ ├── adminSettingsCtrl.js │ │ │ └── settings.html │ │ ├── stats │ │ │ ├── adminStatsCtrl.js │ │ │ └── stats.html │ │ ├── user │ │ │ ├── adminUserCtrl.js │ │ │ └── user.html │ │ └── users │ │ │ ├── adminUsersCtrl.js │ │ │ └── users.html │ │ ├── application │ │ ├── application.html │ │ └── applicationCtrl.js │ │ ├── base.html │ │ ├── confirmation │ │ ├── confirmation.html │ │ └── confirmationCtrl.js │ │ ├── dashboard │ │ ├── dashboard.html │ │ └── dashboardCtrl.js │ │ ├── login │ │ ├── login.html │ │ └── loginCtrl.js │ │ ├── reset │ │ ├── reset.html │ │ └── resetCtrl.js │ │ ├── sidebar │ │ ├── sidebar.html │ │ └── sidebarCtrl.js │ │ ├── team │ │ ├── team.html │ │ └── teamCtrl.js │ │ └── verify │ │ ├── verify.html │ │ └── verifyCtrl.js └── server │ ├── controllers │ ├── SettingsController.js │ └── UserController.js │ ├── models │ ├── Settings.js │ └── User.js │ ├── routes.js │ ├── routes │ ├── api.js │ └── auth.js │ ├── services │ ├── email.js │ └── stats.js │ └── templates │ ├── email-basic │ ├── html.hbs │ ├── style.css │ └── text.hbs │ ├── email-link-action │ ├── html.hbs │ ├── style.css │ └── text.hbs │ └── email-verify │ ├── html.hbs │ ├── style.css │ └── text.hbs ├── bower.json ├── config ├── admin.js └── settings.js ├── docs └── images │ └── screenshots │ ├── admin-users.png │ ├── application.png │ ├── dashboard.png │ ├── login.png │ ├── settings.png │ └── stats.png ├── gulpfile.js ├── package-lock.json ├── package.json └── scripts ├── acceptUsers.js ├── createHellaUsers.js ├── getVerifyToken.js └── testChangePassword.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "app/client/plugins" 3 | } 4 | -------------------------------------------------------------------------------- /.env.config: -------------------------------------------------------------------------------- 1 | # 'dev' or 'production' 2 | NODE_ENV= 'dev' 3 | 4 | # Connection string URI for your MongoDB database 5 | DATABASE='' 6 | 7 | # Port that this app runs on 8 | PORT='3000' 9 | 10 | # Long random string used to verify JWT tokens for user authentication 11 | JWT_SECRET='shhhh super secret code here bro' 12 | 13 | # Root URL for this app 14 | ROOT_URL='http://localhost:3000' 15 | 16 | # Credentials for the admin user created at app initialization 17 | ADMIN_EMAIL='admin@example.com' 18 | ADMIN_PASS='party' 19 | 20 | # Used to send verification, registration, and confirmation emails 21 | EMAIL_CONTACT='Hackathon Team ' 22 | EMAIL_HOST='smtp.gmail.com' 23 | EMAIL_USER='foo@bar.com' 24 | EMAIL_PASS='password' 25 | EMAIL_PORT='465' 26 | EMAIL_HEADER_IMAGE='https://s3.amazonaws.com/hackmit-assets/Banner_600.jpg' 27 | 28 | # Information linked at the bottom of emails 29 | EMAIL_ADDRESS='team@example.com' 30 | HACKATHON_NAME='Hackathon' 31 | TWITTER_HANDLE='hackathon' 32 | FACEBOOK_HANDLE='hackathon' 33 | 34 | # Limits the number of users that can join a team 35 | TEAM_MAX_SIZE=4 36 | 37 | # Used to send error messages to your Slack team when the API catches an error 38 | SLACK_HOOK='https://hooks.slack.com/services/yourapikey' 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | db/ 2 | node_modules/ 3 | app/client/build/ 4 | app/client/plugins/ 5 | *.log 6 | .env 7 | .vscode/ 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | All kinds of contributions to Quill are greatly appreciated. For someone 5 | unfamiliar with the code base, the most efficient way to contribute is usually 6 | to submit a [feature request](#feature-requests) or [bug report](#bug-reports). 7 | 8 | If you want to dive into the source code, you can submit a [patch](#patches) as 9 | well. Working on [existing issues][issues] is super helpful! 10 | 11 | Feature Requests 12 | ---------------- 13 | 14 | Do you have an idea for an awesome new feature for Quill? Please [submit a 15 | feature request][issue]. It's great to hear about new ideas. 16 | 17 | If you are inclined to do so, you're welcome to [fork][fork] Quill, work on 18 | implementing the feature yourself, and submit a patch. In this case, it's 19 | *highly recommended* that you first [open an issue][issue] describing your 20 | enhancement to get early feedback on the new feature that you are implementing. 21 | This will help avoid wasted efforts and ensure that your work is incorporated 22 | into the code base. 23 | 24 | Bug Reports 25 | ----------- 26 | 27 | Did something go wrong with Quill? Sorry about that! Bug reports are greatly 28 | appreciated! 29 | 30 | When you [submit a bug report][issue], please include relevant information such 31 | as Quill version, operating system, configuration, error messages, and steps to 32 | reproduce the bug. The more details you can include, the easier it is to find 33 | and fix the bug. 34 | 35 | Patches 36 | ------- 37 | 38 | Want to hack on Quill? Awesome! 39 | 40 | If there are [open issues][issues], you're more than welcome to work on those - 41 | this is probably the best way to contribute to Quill. If you have your own 42 | ideas, that's great too! In that case, before working on substantial changes to 43 | the code base, it is *highly recommended* that you first [open an issue][issue] 44 | describing what you intend to work on. 45 | 46 | **Patches are generally submitted as pull requests.** Patches are also 47 | [accepted over email][email]. 48 | 49 | Any changes to the code base should follow the style and coding conventions 50 | used in the rest of the project. The version history should be clean, and 51 | commit messages should be descriptive and [properly formatted][commit-messages]. 52 | 53 | --- 54 | 55 | If you have any questions about anything, feel free to [ask][email]! 56 | 57 | *Thanks to Anish Athalye for allowing Quill to shamelessly steal this contributing guide from [Gavel][gavel]!* 58 | 59 | [issue]: https://github.com/techx/quill/issues/new 60 | [issues]: https://github.com/techx/quill/issues 61 | [fork]: https://github.com/techx/quill/fork 62 | [email]: mailto:quill@hackmit.org 63 | [commit-messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html 64 | [gavel]: https://github.com/anishathalye/gavel 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quill 2 | Registration, for hackers! 3 | 4 | Quill is a registration system designed especially for hackathons. For hackers, it’s a clean and streamlined interface to submit registration and confirmation information. For hackathon organizers, it’s an easy way to manage applications, view registration stats, and more! 5 | 6 | ![Login Splash](./docs/images/screenshots/login.png) 7 | 8 | # Features 9 | ## Quill for Users 10 | ### Dashboard 11 | ![Dashboard](./docs/images/screenshots/dashboard.png) 12 | 13 | After users login, the Dashboard displays the user’s application status and status-specific prompts to resend a verification email, view/edit their application or confirmation forms. 14 | 15 | Statuses: 16 | - Unverified: users have not verified the email address they registered with 17 | - Incomplete, registration open: the user has not submitted their application, but the registration deadline has not passed 18 | - Incomplete, registration closed: the user has not submitted, but the registration deadline has passed 19 | - Submitted, registration open 20 | - Submitted, registration closed 21 | - Admitted / unconfirmed: the user has been admitted to the event, but has not confirmed their attendance and submitted their confirmation form 22 | - Admitted / confirmation deadline passed: the user has been admitted, but did not confirm their attendance before the deadline 23 | - Waitlisted: the user was not admitted to the event 24 | - Confirmed: the user has been admitted and has confirmed their attendance 25 | - User declined admission: the user has been admitted, but will not be attending the event 26 | 27 | ### Application 28 | ![Application](./docs/images/screenshots/application.png) 29 | 30 | The Application tab takes users to their registration or confirmation form. 31 | 32 | ### Team Registration 33 | Hackathons commonly allow participants to register and be admitted as a team. The Team tab allows users to create or join a team with other users. 34 | 35 | ## Quill for Admins 36 | Admins can view stats, look through applications, or edit settings from the Admin panel. 37 | 38 | ### Stats 39 | ![Stats](./docs/images/screenshots/stats.png) 40 | 41 | The Stats tab summarizes useful registration statistics on the number of users in each stage of the process, demographic information, and miscellaneous event preferences like shirt sizes, dietary restrictions, or reimbursement requests. 42 | 43 | ### Users Table 44 | ![Users table](./docs/images/screenshots/admin-users.png) 45 | 46 | The Users tab displays a table of users where admins can: 47 | 1. Search for a user by name 48 | 2. Quick-view user applications in a pop-up modal 49 | 3. See a user’s application status (verified, submitted, admitted, and confirmed) at-a-glance 50 | 4. See responses to other miscellaneous fields on the application 51 | 5. Open and edit an individual application 52 | 6. Admit users manually 53 | 7. Mark users as checked-in at the event day-of 54 | 55 | ### Settings 56 | ![Settings](./docs/images/screenshots/settings.png) 57 | 58 | On the Settings tab, admins can easily control their event application timeline by setting registration / confirmation deadlines. They can also write custom waitlist, acceptance, and confirmation copy that users will see on their dashboard throughout the application process. The custom copy is interpreted as Markdown, so HTML and images can be added. 59 | 60 | # Setup 61 | 62 | ### Requirements 63 | | Requirement | Version | 64 | | ------------------------------------------- | ------- | 65 | | [GCC 4.6](https://gcc.gnu.org) | `4.6+` | 66 | | [Node.js](http://nodejs.org) | `8.0+` | 67 | | [MongoDB](https://www.mongodb.com/) | `3.0+` | 68 | 69 | > _Updating to the latest releases is recommended_. 70 | 71 | Run the following commands to check the current installed versions: 72 | 73 | ```shell 74 | gcc --version 75 | node -v 76 | mongo --version 77 | ``` 78 | How to upgrade to latest releases: 79 | - GCC: https://wiki.gentoo.org/wiki/Upgrading_GCC 80 | - Node.js: https://nodejs.org/en/download/ 81 | - MongoDB: https://docs.mongodb.com/manual/administration/install-community/ 82 | 83 | ### Deploying locally 84 | Getting a local instance of Quill up and running takes less than 5 minutes! Start by setting up the database. Ideally, you should run MongoDB as a daemon with a secure configuration (with most linux distributions, you should be able to install it with your package manager, and it'll be set up as a daemon). Although not recommended for production, when running locally for development, you could do it like this 85 | 86 | ``` 87 | mkdir db 88 | mongod --dbpath db --bind_ip 127.0.0.1 --nohttpinterface 89 | ``` 90 | 91 | Install the necessary dependencies: 92 | ``` 93 | npm install 94 | bower install 95 | npm run config 96 | ``` 97 | 98 | Edit the configuration file in `.env` for your setup, and then run the application: 99 | ``` 100 | gulp server 101 | ``` 102 | 103 | # Customizing for your event 104 | 105 | ###### _If you're using Quill for your event, please add yourself to this [list][users]. It takes less than a minute, but knowing that our software is helping real events keeps us going ♥_ 106 | ### Copy 107 | If you’d like to customize the text that users see on their dashboards, edit them at `client/src/constants.js`. 108 | 109 | ### Branding / Assets 110 | Customize the color scheme and hosted assets by editing `client/stylesheets/_custom.scss`. Don’t forget to use your own email banner, favicon, and logo (color/white) in the `assets/images/` folder as well! 111 | 112 | ### Application questions 113 | If you want to change the application questions, edit: 114 | - `client/views/application/` 115 | - `server/models/User.js` 116 | - `client/views/admin/user/` and `client/views/admin/users/` to render the updated form properly in the admin view 117 | 118 | If you want stats for your new fields: 119 | - Recalculate them in `server/services/stats.js` 120 | - Display them on the admin panel by editing `client/views/admin/stats/` 121 | 122 | ### Email Templates 123 | To customize the verification and confirmation emails for your event, put your new email templates in `server/templates/` and edit `server/services/email.js` 124 | 125 | # License 126 | Copyright (c) 2015-2020 Edwin Zhang (https://github.com/ehzhang). Released under AGPLv3. See [`LICENSE.txt`][license] for details. 127 | -------------------------------------------------------------------------------- /about.md: -------------------------------------------------------------------------------- 1 | # What is Hackathon/HackIIITV? 2 |

A Hackathon is any event of any duration where people come together to solve problems. 3 | There is given a set of Problems to the team. Team has to give solution of the problem. it may be idea of the problem or implementation like app, web based etc. within the duration. HackIIITV is not just a coding contest rather, its notion is to get the most effective solutions to real-life problems. It is a team-based event in which each team is given a problem set consisting of real-life problems and the teams have to come up with a practical solution for the same. The teams have to fabricate programs to implement their ideas & also present it, however complex it is, to the judges. All of this has to be done within a period of 24 hours, so your management, team-work, and planning skills matter here. As you can see HackIIITV is a blend of programming, creative thinking, time management and presentation skills,

4 |

Rules and Regulations

5 | 32 | 33 | #

Venue:

34 | Sabar Hostel premises 35 | #

Time:

36 | Date: 5th October 2020 (Monday) 10:00 AM to 6th October 2020 (Tuesday) 10:00 AM. 37 | # Code of Conduct 38 | 39 | [Code of Conduct](https://github.com/iiitv/getmein-web/blob/master/CODE_OF_CONDUCT.md) 40 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | // Load the dotfiles. 2 | require('dotenv').load({silent: true}); 3 | 4 | var express = require('express'); 5 | 6 | // Middleware! 7 | var bodyParser = require('body-parser'); 8 | var methodOverride = require('method-override'); 9 | var morgan = require('morgan'); 10 | 11 | var mongoose = require('mongoose'); 12 | var port = process.env.PORT || 3000; 13 | var database = process.env.DATABASE || process.env.MONGODB_URI || "mongodb://localhost:27017"; 14 | 15 | var settingsConfig = require('./config/settings'); 16 | var adminConfig = require('./config/admin'); 17 | 18 | var app = express(); 19 | 20 | // Connect to mongodb 21 | mongoose.connect(database); 22 | 23 | app.use(morgan('dev')); 24 | 25 | app.use(bodyParser.urlencoded({ 26 | extended: true 27 | })); 28 | app.use(bodyParser.json()); 29 | 30 | app.use(methodOverride()); 31 | 32 | app.use(express.static(__dirname + '/app/client')); 33 | 34 | // Routers ===================================================================== 35 | 36 | var apiRouter = express.Router(); 37 | require('./app/server/routes/api')(apiRouter); 38 | app.use('/api', apiRouter); 39 | 40 | var authRouter = express.Router(); 41 | require('./app/server/routes/auth')(authRouter); 42 | app.use('/auth', authRouter); 43 | 44 | require('./app/server/routes')(app); 45 | 46 | // listen (start app with node server.js) ====================================== 47 | app.listen(port); 48 | console.log("App listening on port " + port); 49 | 50 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Quill", 3 | "description": "Registration, for hackathons!", 4 | "env": { 5 | "NODE_ENV": { 6 | "description": "dev or production", 7 | "value": "dev" 8 | }, 9 | "JWT_SECRET": { 10 | "description": "Long random string used to verify JWT tokens for user authentication", 11 | "generator": "secret" 12 | }, 13 | "ROOT_URL": { 14 | "description": "Root URL for your registration system", 15 | "value": "http://localhost:3000" 16 | }, 17 | "ADMIN_EMAIL": { 18 | "description": "Credentials for the admin user created at app initialization", 19 | "value": "admin@example.com" 20 | }, 21 | "ADMIN_PASS": "party", 22 | "EMAIL_ADDRESS": { 23 | "description": "The email address that is included in the 'email us' link at the bottom of emails.", 24 | "value": "team@example.com" 25 | }, 26 | "HACKATHON_NAME": "Hackathon", 27 | "TWITTER_HANDLE": { 28 | "description": "Everything after https://twitter.com/", 29 | "value": "hackathon" 30 | }, 31 | "FACEBOOK_HANDLE": { 32 | "description": "Everything after https://facebook.com/", 33 | "value": "hackathon" 34 | }, 35 | "EMAIL_CONTACT": { 36 | "description": "Used to send verification, registration, and confirmation emails", 37 | "value": "Hackathon Team " 38 | }, 39 | "EMAIL_HOST": "smtp.gmail.com", 40 | "EMAIL_USER": "foo@bar.com", 41 | "EMAIL_PASS": "password", 42 | "EMAIL_PORT": "465", 43 | "EMAIL_HEADER_IMAGE": "https://s3.amazonaws.com/hackmit-assets/Banner_600.jpg", 44 | "TEAM_MAX_SIZE": { 45 | "description": "Limits the number of users that can join a team", 46 | "value": "4" 47 | }, 48 | "SLACK_HOOK": { 49 | "description": "Used to send error messages to your Slack team when the API catches an error", 50 | "value": "https://hooks.slack.com/services/your-api-key" 51 | } 52 | }, 53 | "addons": [ 54 | "mongolab" 55 | ], 56 | "keywords": ["quill", "node", "express", "mongo"] 57 | } 58 | -------------------------------------------------------------------------------- /app/client/assets/images/Banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iiitv/hackiiitv/9cd90dd5140bc617c1613516aa788885446f86b6/app/client/assets/images/Banner.jpg -------------------------------------------------------------------------------- /app/client/assets/images/GitHub-Mark-64px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iiitv/hackiiitv/9cd90dd5140bc617c1613516aa788885446f86b6/app/client/assets/images/GitHub-Mark-64px.png -------------------------------------------------------------------------------- /app/client/assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iiitv/hackiiitv/9cd90dd5140bc617c1613516aa788885446f86b6/app/client/assets/images/favicon.png -------------------------------------------------------------------------------- /app/client/assets/images/logo-color.svg: -------------------------------------------------------------------------------- 1 | logo_v2 -------------------------------------------------------------------------------- /app/client/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | logo_v2 -------------------------------------------------------------------------------- /app/client/assets/particles.json: -------------------------------------------------------------------------------- 1 | { 2 | "particles": { 3 | "number": { 4 | "value": 256, 5 | "density": { 6 | "enable": true, 7 | "value_area": 315.67400345663043 8 | } 9 | }, 10 | "color": { 11 | "value": "#ffffff" 12 | }, 13 | "shape": { 14 | "type": "circle", 15 | "stroke": { 16 | "width": 0, 17 | "color": "#000000" 18 | }, 19 | "polygon": { 20 | "nb_sides": 5 21 | }, 22 | "image": { 23 | "src": "img/github.svg", 24 | "width": 100, 25 | "height": 100 26 | } 27 | }, 28 | "opacity": { 29 | "value": 1, 30 | "random": true, 31 | "anim": { 32 | "enable": true, 33 | "speed": 1, 34 | "opacity_min": 0, 35 | "sync": false 36 | } 37 | }, 38 | "size": { 39 | "value": 3, 40 | "random": true, 41 | "anim": { 42 | "enable": false, 43 | "speed": 4, 44 | "size_min": 0.3, 45 | "sync": false 46 | } 47 | }, 48 | "line_linked": { 49 | "enable": false, 50 | "distance": 150, 51 | "color": "#ffffff", 52 | "opacity": 0.4, 53 | "width": 1 54 | }, 55 | "move": { 56 | "enable": true, 57 | "speed": 1, 58 | "direction": "none", 59 | "random": true, 60 | "straight": false, 61 | "out_mode": "out", 62 | "bounce": false, 63 | "attract": { 64 | "enable": false, 65 | "rotateX": 600, 66 | "rotateY": 600 67 | } 68 | } 69 | }, 70 | "interactivity": { 71 | "detect_on": "canvas", 72 | "events": { 73 | "onhover": { 74 | "enable": true, 75 | "mode": "bubble" 76 | }, 77 | "onclick": { 78 | "enable": true, 79 | "mode": "repulse" 80 | }, 81 | "resize": true 82 | }, 83 | "modes": { 84 | "grab": { 85 | "distance": 400, 86 | "line_linked": { 87 | "opacity": 1 88 | } 89 | }, 90 | "bubble": { 91 | "distance": 250, 92 | "size": 0, 93 | "duration": 2, 94 | "opacity": 0, 95 | "speed": 3 96 | }, 97 | "repulse": { 98 | "distance": 400, 99 | "duration": 0.4 100 | }, 101 | "push": { 102 | "particles_nb": 4 103 | }, 104 | "remove": { 105 | "particles_nb": 2 106 | } 107 | } 108 | }, 109 | "retina_detect": true 110 | } -------------------------------------------------------------------------------- /app/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | HackIIITV 2020 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 |
57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /app/client/src/app.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('reg', [ 2 | 'ui.router', 3 | ]); 4 | 5 | app 6 | .config([ 7 | '$httpProvider', 8 | function($httpProvider){ 9 | 10 | // Add auth token to Authorization header 11 | $httpProvider.interceptors.push('AuthInterceptor'); 12 | 13 | }]) 14 | .run([ 15 | 'AuthService', 16 | 'Session', 17 | function(AuthService, Session){ 18 | 19 | // Startup, login if there's a token. 20 | var token = Session.getToken(); 21 | if (token){ 22 | AuthService.loginWithToken(token); 23 | } 24 | 25 | }]); 26 | 27 | -------------------------------------------------------------------------------- /app/client/src/constants.js: -------------------------------------------------------------------------------- 1 | angular.module('reg') 2 | .constant('EVENT_INFO', { 3 | NAME: 'HackIIITV 2020', 4 | }) 5 | .constant('DASHBOARD', { 6 | UNVERIFIED: 'You should have received an email asking you verify your email. Click the link in the email and you can start your application!', 7 | INCOMPLETE_TITLE: 'You still need to complete your application!', 8 | INCOMPLETE: 'If you do not complete your application before the [APP_DEADLINE]), you will not be considered for the admissions lottery!', 9 | SUBMITTED_TITLE: 'Your application has been submitted!', 10 | SUBMITTED: 'Feel free to edit it at any time. However, once registration is closed, you will not be able to edit it any further.\nAdmissions will be determined by a random lottery. Please make sure your information is accurate before registration is closed!', 11 | CLOSED_AND_INCOMPLETE_TITLE: 'Unfortunately, registration has closed, and the lottery process has begun.', 12 | CLOSED_AND_INCOMPLETE: 'Because you have not completed your profile in time, you will not be eligible for the lottery process.', 13 | ADMITTED_AND_CAN_CONFIRM_TITLE: 'You must confirm by [CONFIRM_DEADLINE].', 14 | ADMITTED_AND_CANNOT_CONFIRM_TITLE: 'Your confirmation deadline of [CONFIRM_DEADLINE]) has passed.', 15 | ADMITTED_AND_CANNOT_CONFIRM: 'Although you were accepted, you did not complete your confirmation in time.\nUnfortunately, this means that you will not be able to attend the event, as we must begin to accept other applicants on the waitlist.\nWe hope to see you again next year!', 16 | CONFIRMED_NOT_PAST_TITLE: 'You can edit your confirmation information until [CONFIRM_DEADLINE])', 17 | DECLINED: 'We\'re sorry to hear that you won\'t be able to make it to HackIIITV 2019! :(\nMaybe next year! We hope you see you again soon.)', 18 | }) 19 | .constant('TEAM',{ 20 | NO_TEAM_REG_CLOSED: 'Unfortunately, it\'s too late to enter the lottery with a team.\nHowever, you can still form teams on your own before or during the event!', 21 | }); 22 | -------------------------------------------------------------------------------- /app/client/src/interceptors/AuthInterceptor.js: -------------------------------------------------------------------------------- 1 | angular.module('reg') 2 | .factory('AuthInterceptor', [ 3 | 'Session', 4 | function(Session){ 5 | return { 6 | request: function(config){ 7 | var token = Session.getToken(); 8 | if (token){ 9 | config.headers['x-access-token'] = token; 10 | } 11 | return config; 12 | } 13 | }; 14 | }]); -------------------------------------------------------------------------------- /app/client/src/modules/Session.js: -------------------------------------------------------------------------------- 1 | angular.module('reg') 2 | .service('Session', [ 3 | '$rootScope', 4 | '$window', 5 | function($rootScope, $window){ 6 | 7 | this.create = function(token, user){ 8 | $window.localStorage.jwt = token; 9 | $window.localStorage.userId = user._id; 10 | $window.localStorage.currentUser = JSON.stringify(user); 11 | $rootScope.currentUser = user; 12 | }; 13 | 14 | this.destroy = function(onComplete){ 15 | delete $window.localStorage.jwt; 16 | delete $window.localStorage.userId; 17 | delete $window.localStorage.currentUser; 18 | $rootScope.currentUser = null; 19 | if (onComplete){ 20 | onComplete(); 21 | } 22 | }; 23 | 24 | this.getToken = function(){ 25 | return $window.localStorage.jwt; 26 | }; 27 | 28 | this.getUserId = function(){ 29 | return $window.localStorage.userId; 30 | }; 31 | 32 | this.getUser = function(){ 33 | return JSON.parse($window.localStorage.currentUser); 34 | }; 35 | 36 | this.setUser = function(user){ 37 | $window.localStorage.currentUser = JSON.stringify(user); 38 | $rootScope.currentUser = user; 39 | }; 40 | 41 | }]); -------------------------------------------------------------------------------- /app/client/src/modules/Utils.js: -------------------------------------------------------------------------------- 1 | angular.module('reg') 2 | .factory('Utils', [ 3 | function(){ 4 | return { 5 | isRegOpen: function(settings){ 6 | return Date.now() > settings.timeOpen && Date.now() < settings.timeClose; 7 | }, 8 | isAfter: function(time){ 9 | return Date.now() > time; 10 | }, 11 | formatTime: function(time){ 12 | 13 | if (!time){ 14 | return "Invalid Date"; 15 | } 16 | 17 | date = new Date(time); 18 | // Hack for timezone 19 | return moment(date).format('dddd, MMMM Do YYYY, h:mm a') + 20 | " " + date.toTimeString().split(' ')[2]; 21 | 22 | } 23 | }; 24 | }]); -------------------------------------------------------------------------------- /app/client/src/routes.js: -------------------------------------------------------------------------------- 1 | angular.module('reg') 2 | .config([ 3 | '$stateProvider', 4 | '$urlRouterProvider', 5 | '$locationProvider', 6 | function ( 7 | $stateProvider, 8 | $urlRouterProvider, 9 | $locationProvider) { 10 | 11 | // For any unmatched url, redirect to /state1 12 | $urlRouterProvider.otherwise("/404"); 13 | 14 | // Set up de states 15 | $stateProvider 16 | .state('login', { 17 | url: "/login", 18 | templateUrl: "views/login/login.html", 19 | controller: 'LoginCtrl', 20 | data: { 21 | requireLogin: false 22 | }, 23 | resolve: { 24 | 'settings': function (SettingsService) { 25 | return SettingsService.getPublicSettings(); 26 | } 27 | } 28 | }) 29 | .state('about', { 30 | url: "/about", 31 | templateUrl: "views/about/about.html", 32 | data: { 33 | requireLogin: false 34 | } 35 | }) 36 | .state('app', { 37 | views: { 38 | '': { 39 | templateUrl: "views/base.html" 40 | }, 41 | 'sidebar@app': { 42 | templateUrl: "views/sidebar/sidebar.html", 43 | controller: 'SidebarCtrl', 44 | resolve: { 45 | 'settings': function (SettingsService) { 46 | return SettingsService.getPublicSettings(); 47 | } 48 | } 49 | 50 | } 51 | }, 52 | data: { 53 | requireLogin: true 54 | } 55 | }) 56 | .state('app.dashboard', { 57 | url: "/", 58 | templateUrl: "views/dashboard/dashboard.html", 59 | controller: 'DashboardCtrl', 60 | resolve: { 61 | currentUser: function (UserService) { 62 | return UserService.getCurrentUser(); 63 | }, 64 | settings: function (SettingsService) { 65 | return SettingsService.getPublicSettings(); 66 | } 67 | }, 68 | }) 69 | .state('app.application', { 70 | url: "/application", 71 | templateUrl: "views/application/application.html", 72 | controller: 'ApplicationCtrl', 73 | data: { 74 | requireVerified: true 75 | }, 76 | resolve: { 77 | currentUser: function (UserService) { 78 | return UserService.getCurrentUser(); 79 | }, 80 | settings: function (SettingsService) { 81 | return SettingsService.getPublicSettings(); 82 | } 83 | } 84 | }) 85 | .state('app.confirmation', { 86 | url: "/confirmation", 87 | templateUrl: "views/confirmation/confirmation.html", 88 | controller: 'ConfirmationCtrl', 89 | data: { 90 | requireAdmitted: true 91 | }, 92 | resolve: { 93 | currentUser: function (UserService) { 94 | return UserService.getCurrentUser(); 95 | } 96 | } 97 | }) 98 | .state('app.team', { 99 | url: "/team", 100 | templateUrl: "views/team/team.html", 101 | controller: 'TeamCtrl', 102 | data: { 103 | requireVerified: true 104 | }, 105 | resolve: { 106 | currentUser: function (UserService) { 107 | return UserService.getCurrentUser(); 108 | }, 109 | settings: function (SettingsService) { 110 | return SettingsService.getPublicSettings(); 111 | } 112 | } 113 | }) 114 | .state('app.admin', { 115 | views: { 116 | '': { 117 | templateUrl: "views/admin/admin.html", 118 | controller: 'AdminCtrl' 119 | } 120 | }, 121 | data: { 122 | requireAdmin: true 123 | } 124 | }) 125 | .state('app.admin.stats', { 126 | url: "/admin", 127 | templateUrl: "views/admin/stats/stats.html", 128 | controller: 'AdminStatsCtrl' 129 | }) 130 | .state('app.admin.users', { 131 | url: "/admin/users?" + 132 | '&page' + 133 | '&size' + 134 | '&query', 135 | templateUrl: "views/admin/users/users.html", 136 | controller: 'AdminUsersCtrl' 137 | }) 138 | .state('app.admin.user', { 139 | url: "/admin/users/:id", 140 | templateUrl: "views/admin/user/user.html", 141 | controller: 'AdminUserCtrl', 142 | resolve: { 143 | 'user': function ($stateParams, UserService) { 144 | return UserService.get($stateParams.id); 145 | } 146 | } 147 | }) 148 | .state('app.admin.settings', { 149 | url: "/admin/settings", 150 | templateUrl: "views/admin/settings/settings.html", 151 | controller: 'AdminSettingsCtrl', 152 | }) 153 | .state('reset', { 154 | url: "/reset/:token", 155 | templateUrl: "views/reset/reset.html", 156 | controller: 'ResetCtrl', 157 | data: { 158 | requireLogin: false 159 | } 160 | }) 161 | .state('verify', { 162 | url: "/verify/:token", 163 | templateUrl: "views/verify/verify.html", 164 | controller: 'VerifyCtrl', 165 | data: { 166 | requireLogin: false 167 | } 168 | }) 169 | .state('404', { 170 | url: "/404", 171 | templateUrl: "views/404.html", 172 | data: { 173 | requireLogin: false 174 | } 175 | }); 176 | 177 | $locationProvider.html5Mode({ 178 | enabled: true, 179 | }); 180 | 181 | }]) 182 | .run([ 183 | '$rootScope', 184 | '$state', 185 | 'Session', 186 | function ( 187 | $rootScope, 188 | $state, 189 | Session) { 190 | 191 | $rootScope.$on('$stateChangeSuccess', function () { 192 | document.body.scrollTop = document.documentElement.scrollTop = 0; 193 | }); 194 | 195 | $rootScope.$on('$stateChangeStart', function (event, toState, toParams) { 196 | var requireLogin = toState.data.requireLogin; 197 | var requireAdmin = toState.data.requireAdmin; 198 | var requireVerified = toState.data.requireVerified; 199 | var requireAdmitted = toState.data.requireAdmitted; 200 | 201 | if (requireLogin && !Session.getToken()) { 202 | event.preventDefault(); 203 | $state.go('login'); 204 | } 205 | 206 | if (requireAdmin && !Session.getUser().admin) { 207 | event.preventDefault(); 208 | $state.go('app.dashboard'); 209 | } 210 | 211 | if (requireVerified && !Session.getUser().verified) { 212 | event.preventDefault(); 213 | $state.go('app.dashboard'); 214 | } 215 | 216 | if (requireAdmitted && !Session.getUser().status.admitted) { 217 | event.preventDefault(); 218 | $state.go('app.dashboard'); 219 | } 220 | 221 | }); 222 | 223 | }]); -------------------------------------------------------------------------------- /app/client/src/services/AuthService.js: -------------------------------------------------------------------------------- 1 | angular.module('reg') 2 | .factory('AuthService', [ 3 | '$http', 4 | '$rootScope', 5 | '$state', 6 | '$window', 7 | 'Session', 8 | function($http, $rootScope, $state, $window, Session) { 9 | var authService = {}; 10 | 11 | function loginSuccess(data, cb){ 12 | // Winner winner you get a token 13 | Session.create(data.token, data.user); 14 | 15 | if (cb){ 16 | cb(data.user); 17 | } 18 | } 19 | 20 | function loginFailure(data, cb){ 21 | $state.go('login'); 22 | if (cb) { 23 | cb(data); 24 | } 25 | } 26 | 27 | authService.loginWithPassword = function(email, password, onSuccess, onFailure) { 28 | return $http 29 | .post('/auth/login', { 30 | email: email, 31 | password: password 32 | }) 33 | .success(function(data){ 34 | loginSuccess(data, onSuccess); 35 | }) 36 | .error(function(data){ 37 | loginFailure(data, onFailure); 38 | }); 39 | }; 40 | 41 | authService.loginWithToken = function(token, onSuccess, onFailure){ 42 | return $http 43 | .post('/auth/login', { 44 | token: token 45 | }) 46 | .success(function(data){ 47 | loginSuccess(data, onSuccess); 48 | }) 49 | .error(function(data, statusCode){ 50 | if (statusCode === 400){ 51 | Session.destroy(loginFailure); 52 | } 53 | }); 54 | }; 55 | 56 | authService.logout = function(callback) { 57 | // Clear the session 58 | Session.destroy(callback); 59 | $state.go('login'); 60 | }; 61 | 62 | authService.register = function(email, password, onSuccess, onFailure) { 63 | return $http 64 | .post('/auth/register', { 65 | email: email, 66 | password: password 67 | }) 68 | .success(function(data){ 69 | loginSuccess(data, onSuccess); 70 | }) 71 | .error(function(data){ 72 | loginFailure(data, onFailure); 73 | }); 74 | }; 75 | 76 | authService.verify = function(token, onSuccess, onFailure) { 77 | return $http 78 | .get('/auth/verify/' + token) 79 | .success(function(user){ 80 | Session.setUser(user); 81 | if (onSuccess){ 82 | onSuccess(user); 83 | } 84 | }) 85 | .error(function(data){ 86 | if (onFailure) { 87 | onFailure(data); 88 | } 89 | }); 90 | }; 91 | 92 | authService.resendVerificationEmail = function(onSuccess, onFailure){ 93 | return $http 94 | .post('/auth/verify/resend', { 95 | id: Session.getUserId() 96 | }); 97 | }; 98 | 99 | authService.sendResetEmail = function(email){ 100 | return $http 101 | .post('/auth/reset', { 102 | email: email 103 | }); 104 | }; 105 | 106 | authService.resetPassword = function(token, pass, onSuccess, onFailure){ 107 | return $http 108 | .post('/auth/reset/password', { 109 | token: token, 110 | password: pass 111 | }) 112 | .success(onSuccess) 113 | .error(onFailure); 114 | }; 115 | 116 | return authService; 117 | } 118 | ]); -------------------------------------------------------------------------------- /app/client/src/services/SettingsService.js: -------------------------------------------------------------------------------- 1 | angular.module('reg') 2 | .factory('SettingsService', [ 3 | '$http', 4 | function($http){ 5 | 6 | var base = '/api/settings/'; 7 | 8 | return { 9 | getPublicSettings: function(){ 10 | return $http.get(base); 11 | }, 12 | updateRegistrationTimes: function(open, close){ 13 | return $http.put(base + 'times', { 14 | timeOpen: open, 15 | timeClose: close, 16 | }); 17 | }, 18 | updateConfirmationTime: function(time){ 19 | return $http.put(base + 'confirm-by', { 20 | time: time 21 | }); 22 | }, 23 | getWhitelistedEmails: function(){ 24 | return $http.get(base + 'whitelist'); 25 | }, 26 | updateWhitelistedEmails: function(emails){ 27 | return $http.put(base + 'whitelist', { 28 | emails: emails 29 | }); 30 | }, 31 | updateWaitlistText: function(text){ 32 | return $http.put(base + 'waitlist', { 33 | text: text 34 | }); 35 | }, 36 | updateAcceptanceText: function(text){ 37 | return $http.put(base + 'acceptance', { 38 | text: text 39 | }); 40 | }, 41 | updateConfirmationText: function(text){ 42 | return $http.put(base + 'confirmation', { 43 | text: text 44 | }); 45 | }, 46 | updateAllowMinors: function(allowMinors){ 47 | return $http.put(base + 'minors', { 48 | allowMinors: allowMinors 49 | }); 50 | }, 51 | }; 52 | 53 | } 54 | ]); 55 | -------------------------------------------------------------------------------- /app/client/src/services/UserService.js: -------------------------------------------------------------------------------- 1 | angular.module('reg') 2 | .factory('UserService', [ 3 | '$http', 4 | 'Session', 5 | function($http, Session){ 6 | 7 | var users = '/api/users'; 8 | var base = users + '/'; 9 | 10 | return { 11 | 12 | // ---------------------- 13 | // Basic Actions 14 | // ---------------------- 15 | getCurrentUser: function(){ 16 | return $http.get(base + Session.getUserId()); 17 | }, 18 | 19 | get: function(id){ 20 | return $http.get(base + id); 21 | }, 22 | 23 | getAll: function(){ 24 | return $http.get(base); 25 | }, 26 | 27 | getPage: function(page, size, text){ 28 | return $http.get(users + '?' + $.param( 29 | { 30 | text: text, 31 | page: page ? page : 0, 32 | size: size ? size : 50 33 | }) 34 | ); 35 | }, 36 | 37 | updateProfile: function(id, profile){ 38 | return $http.put(base + id + '/profile', { 39 | profile: profile 40 | }); 41 | }, 42 | 43 | updateConfirmation: function(id, confirmation){ 44 | return $http.put(base + id + '/confirm', { 45 | confirmation: confirmation 46 | }); 47 | }, 48 | 49 | declineAdmission: function(id){ 50 | return $http.post(base + id + '/decline'); 51 | }, 52 | 53 | // ------------------------ 54 | // Team 55 | // ------------------------ 56 | joinOrCreateTeam: function(code){ 57 | return $http.put(base + Session.getUserId() + '/team', { 58 | code: code 59 | }); 60 | }, 61 | 62 | leaveTeam: function(){ 63 | return $http.delete(base + Session.getUserId() + '/team'); 64 | }, 65 | 66 | getMyTeammates: function(){ 67 | return $http.get(base + Session.getUserId() + '/team'); 68 | }, 69 | 70 | // ------------------------- 71 | // Admin Only 72 | // ------------------------- 73 | 74 | getStats: function(){ 75 | return $http.get(base + 'stats'); 76 | }, 77 | 78 | admitUser: function(id){ 79 | return $http.post(base + id + '/admit'); 80 | }, 81 | 82 | checkIn: function(id){ 83 | return $http.post(base + id + '/checkin'); 84 | }, 85 | 86 | checkOut: function(id){ 87 | return $http.post(base + id + '/checkout'); 88 | }, 89 | 90 | makeAdmin: function(id){ 91 | return $http.post(base + id + '/makeadmin'); 92 | }, 93 | 94 | removeAdmin: function(id){ 95 | return $http.post(base + id + '/removeadmin'); 96 | }, 97 | }; 98 | } 99 | ]); 100 | -------------------------------------------------------------------------------- /app/client/stylesheets/_admin.scss: -------------------------------------------------------------------------------- 1 | #admin { 2 | .label { 3 | margin-bottom: 6px; 4 | } 5 | 6 | .page.button { 7 | margin-bottom: 6px; 8 | } 9 | 10 | #table-container { 11 | overflow-x: auto; 12 | } 13 | 14 | table.users { 15 | 16 | tr { 17 | cursor: pointer; 18 | 19 | &.admin { 20 | background: lighten($brand-secondary, 50%) !important; 21 | &:hover { 22 | background: lighten($brand-secondary, 45%) !important; 23 | } 24 | } 25 | } 26 | 27 | } 28 | } -------------------------------------------------------------------------------- /app/client/stylesheets/_animation.scss: -------------------------------------------------------------------------------- 1 | #root-view { 2 | } 3 | 4 | #sidebar, #login .segment{ 5 | 6 | } 7 | 8 | #root-view.ng-enter { 9 | } 10 | 11 | #root-view.ng-enter-active { 12 | } 13 | 14 | #root-view.ng-leave { 15 | } 16 | 17 | #root-view.ng-leave-active { 18 | } -------------------------------------------------------------------------------- /app/client/stylesheets/_application.scss: -------------------------------------------------------------------------------- 1 | #application { 2 | #school .title { 3 | text-align: left; 4 | text-transform: none; 5 | letter-spacing: 0; 6 | } 7 | } -------------------------------------------------------------------------------- /app/client/stylesheets/_base.scss: -------------------------------------------------------------------------------- 1 | #base { 2 | background: $sidebar-color; 3 | width: 100%; 4 | height: 100%; 5 | 6 | > .content { 7 | width: 100%; 8 | min-height: 100%; 9 | padding-left: $sidebar-width; 10 | background: white; 11 | box-shadow: 0 0 8px rgba(0,0,0,.4); 12 | 13 | transition: padding-left $sidebar-transition-time; 14 | 15 | > .container { 16 | padding: 36px; 17 | } 18 | 19 | } 20 | } 21 | 22 | @media only screen and (max-width: 768px) { 23 | #base > .content { 24 | padding-left: 0; 25 | > .container { 26 | padding: 0; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/client/stylesheets/_branding.scss: -------------------------------------------------------------------------------- 1 | /** BRANDING COLORS 2 | * primary (default pink): sidebar color, admitted dashboard status 3 | * secondary (default purple): header text, submitted/confirmed/checked-in dashboard status 4 | * tertiary (default blue): waitlist/declined dashboard status 5 | */ 6 | 7 | $primary: #3cc05d; 8 | $secondary: #473899; 9 | $tertiary: #4c82cc; 10 | 11 | /** ASSET URLS 12 | * login-bg: image for login splash page 13 | */ 14 | 15 | $login-bg: url('http://bsnscb.com/data/out/40/39910212-coding-wallpapers.jpg'); 16 | -------------------------------------------------------------------------------- /app/client/stylesheets/_custom.scss: -------------------------------------------------------------------------------- 1 | // Branding overrides 2 | // 3 | // Copy variables from `_branding.scss` to this file to override default values. 4 | -------------------------------------------------------------------------------- /app/client/stylesheets/_dashboard.scss: -------------------------------------------------------------------------------- 1 | #dashboard { 2 | .status { 3 | text-align: center; 4 | p { 5 | text-align: left; 6 | } 7 | .button { 8 | margin-top: 12px; 9 | } 10 | .header { 11 | font-size: 1.2em; 12 | } 13 | .state { 14 | text-align: center; 15 | padding: 12px 24px; 16 | margin-bottom: 1em; 17 | color: white; 18 | font-size: 1.3em; 19 | text-transform: uppercase; 20 | 21 | &.unverified { 22 | background: $status-unverified-color; 23 | } 24 | 25 | &.incomplete { 26 | background: $status-incomplete-color; 27 | color: black; 28 | } 29 | 30 | &.submitted { 31 | background: $status-submitted-color; 32 | } 33 | 34 | &.waitlist { 35 | background: $status-waitlist-color; 36 | } 37 | 38 | &.admitted { 39 | background: $status-admitted-color; 40 | } 41 | 42 | &.confirmed { 43 | background: $status-confirmed-color; 44 | } 45 | 46 | &.checked.in { 47 | background: $status-checkedin-color; 48 | } 49 | 50 | &.declined { 51 | background: $status-declined-color; 52 | } 53 | } 54 | } 55 | 56 | .segment { 57 | padding: 36px 36px 12px 36px; 58 | } 59 | 60 | .description { 61 | margin-bottom: 24px; 62 | .markdown { 63 | text-align: left; 64 | margin-bottom: 24px; 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /app/client/stylesheets/_login.scss: -------------------------------------------------------------------------------- 1 | #login { 2 | height: 100%; 3 | width: 100%; 4 | background-color: $login-bg-color; 5 | background: $login-bg; 6 | background-size: cover; 7 | 8 | > .container { 9 | position: relative; 10 | top: 50%; 11 | 12 | -webkit-transform: translateY(-50%); 13 | -ms-transform: translateY(-50%); 14 | transform: translateY(-50%); 15 | 16 | > .content { 17 | max-width: 320px; 18 | margin: 0 auto; 19 | 20 | .logo { 21 | max-width: 100px; 22 | margin: .5rem auto 1rem auto; 23 | img { 24 | width: 100px; 25 | height: 100px; 26 | } 27 | } 28 | 29 | .button { 30 | color: white; 31 | &.login { 32 | background: $login-color; 33 | } 34 | &.register { 35 | background: $register-color 36 | } 37 | } 38 | 39 | .forgot { 40 | text-align: center; 41 | } 42 | 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /app/client/stylesheets/_page.scss: -------------------------------------------------------------------------------- 1 | .page { 2 | padding-top: 2em; 3 | padding-bottom: 4em; 4 | 5 | .title { 6 | font-family: $font-header; 7 | text-transform: uppercase; 8 | font-size: 2.5em; 9 | text-align: center; 10 | color: $page-header-color; 11 | letter-spacing: 8px; 12 | font-weight: 700; 13 | 14 | &.divided:after { 15 | content: " "; 16 | width: 200px; 17 | display: block; 18 | margin: 1em auto 2em; 19 | border-bottom: 4px solid $page-header-color; 20 | } 21 | 22 | &.small { 23 | font-size: 1em; 24 | letter-spacing: 4px; 25 | 26 | &:after { 27 | border-width: 2px; 28 | } 29 | } 30 | } 31 | 32 | .logo { 33 | text-align: center; 34 | margin: 1rem; 35 | 36 | img { 37 | height: 5rem; 38 | width: 5rem; 39 | } 40 | } 41 | 42 | .form { 43 | .title { 44 | font-size: 1em; 45 | letter-spacing: 4px; 46 | 47 | &:after { 48 | border-width: 2px; 49 | } 50 | } 51 | } 52 | 53 | .subheader { 54 | font-size: 1.2em; 55 | text-align: center; 56 | } 57 | 58 | } 59 | 60 | .about-page { 61 | padding: 3.6rem; 62 | } 63 | 64 | @media only screen and (max-width: 768px) { 65 | .page { 66 | .title { 67 | font-size: 1.25em; 68 | } 69 | 70 | .logo { 71 | text-align: center; 72 | 73 | img { 74 | margin-right: 0px; 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /app/client/stylesheets/_sidebar.scss: -------------------------------------------------------------------------------- 1 | #sidebar { 2 | height: 100%; 3 | position: fixed; 4 | background: $sidebar-color; 5 | color: white; 6 | width: $sidebar-width; 7 | z-index: 999; 8 | overflow: auto; 9 | 10 | @include transition-duration($sidebar-transition-time); 11 | 12 | .item { 13 | width: 100%; 14 | padding: 16px; 15 | transition-duration: .1s; 16 | transition: border-left, .5s, linear; 17 | border-bottom: 1px solid lighten($sidebar-color, 3%); 18 | opacity: .7; 19 | border-left: 0px solid transparent; 20 | 21 | &.active { 22 | background: lighten($sidebar-color, 4%); 23 | opacity: 1; 24 | border-left: 8px solid lighten($sidebar-color, 10%); 25 | } 26 | &:hover { 27 | cursor: pointer; 28 | background: darken($sidebar-color, 2%); 29 | opacity: 1; 30 | } 31 | &:active { 32 | background: darken($sidebar-color, 5%); 33 | opacity: 1; 34 | } 35 | 36 | &.logo { 37 | opacity: 1; 38 | padding: 48px; 39 | } 40 | 41 | } 42 | 43 | .note { 44 | font-size: .8em; 45 | text-align: center; 46 | padding: 16px; 47 | position: absolute; 48 | left: 0; 49 | right: 0; 50 | bottom: 16px; 51 | 52 | } 53 | 54 | // Tab is the sibling of the sidebar 55 | +.tab { 56 | position: fixed; 57 | width: $sidebar-tab-size; 58 | height: $sidebar-tab-size; 59 | background: $sidebar-color; 60 | top: 12px; 61 | 62 | z-index: 9999; 63 | 64 | // Is not visible unless mobile. 65 | visibility: 'hidden'; 66 | opacity: 0; 67 | 68 | @include transform(translate3d($sidebar-width, 0,0)); 69 | @include transition-duration($sidebar-transition-time); 70 | 71 | cursor: pointer; 72 | 73 | .close { 74 | color: white; 75 | font-size: 2em; 76 | } 77 | } 78 | 79 | } 80 | 81 | @media only screen and (max-width: 768px) { 82 | #sidebar { 83 | @include transform(translate3d(-$sidebar-width, 0, 0)); 84 | 85 | // When the sidebar is open 86 | &.open { 87 | // Transform the sidebar to original position 88 | @include transform(translate3d(0,0,0)); 89 | +.tab { 90 | // Transform the tab to the side of the sidebar 91 | @include transform(translate3d($sidebar-width, 0, 0)); 92 | 93 | // Hack to get the X lined up 94 | line-height: $sidebar-tab-size; 95 | text-align: center; 96 | padding: 0; 97 | } 98 | } 99 | // When the tab is closed. 100 | +.tab { 101 | visibility: 'visible'; 102 | opacity: 1; 103 | @include transform(translate3d(0,0,0)); 104 | padding: 12px; 105 | } 106 | .note { 107 | display: none; 108 | } 109 | 110 | } 111 | } -------------------------------------------------------------------------------- /app/client/stylesheets/_team.scss: -------------------------------------------------------------------------------- 1 | #team { 2 | text-align: center; 3 | .title { 4 | padding-top: 24px; 5 | } 6 | p { 7 | text-align: center; 8 | margin: 12px; 9 | } 10 | .subheader { 11 | word-wrap: break-word; 12 | } 13 | } -------------------------------------------------------------------------------- /app/client/stylesheets/_verify.scss: -------------------------------------------------------------------------------- 1 | .verify.page { 2 | padding-top: 4em; 3 | .segment { 4 | padding: 36px; 5 | padding-top: 70px; 6 | max-width: 300px; 7 | margin: 0 auto; 8 | text-align: center; 9 | .icon { 10 | font-size: 5em; 11 | margin-bottom: 36px; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /app/client/stylesheets/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.1 | MIT License | git.io/normalize */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS text size adjust after orientation change, without disabling 6 | * user zoom. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; /* 1 */ 11 | -ms-text-size-adjust: 100%; /* 2 */ 12 | -webkit-text-size-adjust: 100%; /* 2 */ 13 | } 14 | 15 | /** 16 | * Remove default margin. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | } 22 | 23 | /* HTML5 display definitions 24 | ========================================================================== */ 25 | 26 | /** 27 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 and Firefox. 29 | * Correct `block` display not defined for `main` in IE 11. 30 | */ 31 | 32 | article, 33 | aside, 34 | details, 35 | figcaption, 36 | figure, 37 | footer, 38 | header, 39 | hgroup, 40 | main, 41 | nav, 42 | section, 43 | summary { 44 | display: block; 45 | } 46 | 47 | /** 48 | * 1. Correct `inline-block` display not defined in IE 8/9. 49 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 50 | */ 51 | 52 | audio, 53 | canvas, 54 | progress, 55 | video { 56 | display: inline-block; /* 1 */ 57 | vertical-align: baseline; /* 2 */ 58 | } 59 | 60 | /** 61 | * Prevent modern browsers from displaying `audio` without controls. 62 | * Remove excess height in iOS 5 devices. 63 | */ 64 | 65 | audio:not([controls]) { 66 | display: none; 67 | height: 0; 68 | } 69 | 70 | /** 71 | * Address `[hidden]` styling not present in IE 8/9/10. 72 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. 73 | */ 74 | 75 | [hidden], 76 | template { 77 | display: none; 78 | } 79 | 80 | /* Links 81 | ========================================================================== */ 82 | 83 | /** 84 | * Remove the gray background color from active links in IE 10. 85 | */ 86 | 87 | a { 88 | background: transparent; 89 | } 90 | 91 | /** 92 | * Improve readability when focused and also mouse hovered in all browsers. 93 | */ 94 | 95 | a:active, 96 | a:hover { 97 | outline: 0; 98 | } 99 | 100 | /* Text-level semantics 101 | ========================================================================== */ 102 | 103 | /** 104 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 105 | */ 106 | 107 | abbr[title] { 108 | border-bottom: 1px dotted; 109 | } 110 | 111 | /** 112 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 113 | */ 114 | 115 | b, 116 | strong { 117 | font-weight: bold; 118 | } 119 | 120 | /** 121 | * Address styling not present in Safari and Chrome. 122 | */ 123 | 124 | dfn { 125 | font-style: italic; 126 | } 127 | 128 | /** 129 | * Address variable `h1` font-size and margin within `section` and `article` 130 | * contexts in Firefox 4+, Safari, and Chrome. 131 | */ 132 | 133 | h1 { 134 | font-size: 2em; 135 | margin: 0.67em 0; 136 | } 137 | 138 | /** 139 | * Address styling not present in IE 8/9. 140 | */ 141 | 142 | mark { 143 | background: #ff0; 144 | color: #000; 145 | } 146 | 147 | /** 148 | * Address inconsistent and variable font size in all browsers. 149 | */ 150 | 151 | small { 152 | font-size: 80%; 153 | } 154 | 155 | /** 156 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 157 | */ 158 | 159 | sub, 160 | sup { 161 | font-size: 75%; 162 | line-height: 0; 163 | position: relative; 164 | vertical-align: baseline; 165 | } 166 | 167 | sup { 168 | top: -0.5em; 169 | } 170 | 171 | sub { 172 | bottom: -0.25em; 173 | } 174 | 175 | /* Embedded content 176 | ========================================================================== */ 177 | 178 | /** 179 | * Remove border when inside `a` element in IE 8/9/10. 180 | */ 181 | 182 | img { 183 | border: 0; 184 | } 185 | 186 | /** 187 | * Correct overflow not hidden in IE 9/10/11. 188 | */ 189 | 190 | svg:not(:root) { 191 | overflow: hidden; 192 | } 193 | 194 | /* Grouping content 195 | ========================================================================== */ 196 | 197 | /** 198 | * Address margin not present in IE 8/9 and Safari. 199 | */ 200 | 201 | figure { 202 | margin: 1em 40px; 203 | } 204 | 205 | /** 206 | * Address differences between Firefox and other browsers. 207 | */ 208 | 209 | hr { 210 | -moz-box-sizing: content-box; 211 | box-sizing: content-box; 212 | height: 0; 213 | } 214 | 215 | /** 216 | * Contain overflow in all browsers. 217 | */ 218 | 219 | pre { 220 | overflow: auto; 221 | } 222 | 223 | /** 224 | * Address odd `em`-unit font size rendering in all browsers. 225 | */ 226 | 227 | code, 228 | kbd, 229 | pre, 230 | samp { 231 | font-family: monospace, monospace; 232 | font-size: 1em; 233 | } 234 | 235 | /* Forms 236 | ========================================================================== */ 237 | 238 | /** 239 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 240 | * styling of `select`, unless a `border` property is set. 241 | */ 242 | 243 | /** 244 | * 1. Correct color not being inherited. 245 | * Known issue: affects color of disabled elements. 246 | * 2. Correct font properties not being inherited. 247 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 248 | */ 249 | 250 | button, 251 | input, 252 | optgroup, 253 | select, 254 | textarea { 255 | color: inherit; /* 1 */ 256 | font: inherit; /* 2 */ 257 | margin: 0; /* 3 */ 258 | } 259 | 260 | /** 261 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 262 | */ 263 | 264 | button { 265 | overflow: visible; 266 | } 267 | 268 | /** 269 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 270 | * All other form control elements do not inherit `text-transform` values. 271 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 272 | * Correct `select` style inheritance in Firefox. 273 | */ 274 | 275 | button, 276 | select { 277 | text-transform: none; 278 | } 279 | 280 | /** 281 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 282 | * and `video` controls. 283 | * 2. Correct inability to style clickable `input` types in iOS. 284 | * 3. Improve usability and consistency of cursor style between image-type 285 | * `input` and others. 286 | */ 287 | 288 | button, 289 | html input[type="button"], /* 1 */ 290 | input[type="reset"], 291 | input[type="submit"] { 292 | -webkit-appearance: button; /* 2 */ 293 | cursor: pointer; /* 3 */ 294 | } 295 | 296 | /** 297 | * Re-set default cursor for disabled elements. 298 | */ 299 | 300 | button[disabled], 301 | html input[disabled] { 302 | cursor: default; 303 | } 304 | 305 | /** 306 | * Remove inner padding and border in Firefox 4+. 307 | */ 308 | 309 | button::-moz-focus-inner, 310 | input::-moz-focus-inner { 311 | border: 0; 312 | padding: 0; 313 | } 314 | 315 | /** 316 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 317 | * the UA stylesheet. 318 | */ 319 | 320 | input { 321 | line-height: normal; 322 | } 323 | 324 | /** 325 | * It's recommended that you don't attempt to style these elements. 326 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 327 | * 328 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 329 | * 2. Remove excess padding in IE 8/9/10. 330 | */ 331 | 332 | input[type="checkbox"], 333 | input[type="radio"] { 334 | box-sizing: border-box; /* 1 */ 335 | padding: 0; /* 2 */ 336 | } 337 | 338 | /** 339 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 340 | * `font-size` values of the `input`, it causes the cursor style of the 341 | * decrement button to change from `default` to `text`. 342 | */ 343 | 344 | input[type="number"]::-webkit-inner-spin-button, 345 | input[type="number"]::-webkit-outer-spin-button { 346 | height: auto; 347 | } 348 | 349 | /** 350 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 351 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome 352 | * (include `-moz` to future-proof). 353 | */ 354 | 355 | input[type="search"] { 356 | -webkit-appearance: textfield; /* 1 */ 357 | -moz-box-sizing: content-box; 358 | -webkit-box-sizing: content-box; /* 2 */ 359 | box-sizing: content-box; 360 | } 361 | 362 | /** 363 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 364 | * Safari (but not Chrome) clips the cancel button when the search input has 365 | * padding (and `textfield` appearance). 366 | */ 367 | 368 | input[type="search"]::-webkit-search-cancel-button, 369 | input[type="search"]::-webkit-search-decoration { 370 | -webkit-appearance: none; 371 | } 372 | 373 | /** 374 | * Define consistent border, margin, and padding. 375 | */ 376 | 377 | fieldset { 378 | border: 1px solid #c0c0c0; 379 | margin: 0 2px; 380 | padding: 0.35em 0.625em 0.75em; 381 | } 382 | 383 | /** 384 | * 1. Correct `color` not being inherited in IE 8/9/10/11. 385 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 386 | */ 387 | 388 | legend { 389 | border: 0; /* 1 */ 390 | padding: 0; /* 2 */ 391 | } 392 | 393 | /** 394 | * Remove default vertical scrollbar in IE 8/9/10/11. 395 | */ 396 | 397 | textarea { 398 | overflow: auto; 399 | } 400 | 401 | /** 402 | * Don't inherit the `font-weight` (applied by a rule above). 403 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 404 | */ 405 | 406 | optgroup { 407 | font-weight: bold; 408 | } 409 | 410 | /* Tables 411 | ========================================================================== */ 412 | 413 | /** 414 | * Remove most spacing between table cells. 415 | */ 416 | 417 | table { 418 | border-collapse: collapse; 419 | border-spacing: 0; 420 | } 421 | 422 | td, 423 | th { 424 | padding: 0; 425 | } 426 | -------------------------------------------------------------------------------- /app/client/stylesheets/site.scss: -------------------------------------------------------------------------------- 1 | @import 'branding'; 2 | @import 'custom'; 3 | $font-body: 'Lato', 4 | 'Helvetica Neue', 5 | 'Helvetica', 6 | sans-serif; 7 | $font-header: 'Montserrat', 8 | 'Helvetica Neue', 9 | 'Helvetica', 10 | sans-serif; 11 | 12 | // Login screen variables 13 | $login-bg-color: $secondary; 14 | $login-color: $primary; 15 | $register-color: $tertiary; 16 | 17 | $status-unverified-color: rgb(82, 82, 82); 18 | $status-incomplete-color: #F5F5F5; 19 | $status-submitted-color: lighten($secondary, 20%); 20 | $status-admitted-color: $primary; 21 | $status-confirmed-color: $secondary; 22 | $status-checkedin-color: $secondary; 23 | $status-waitlist-color: $tertiary; 24 | $status-declined-color: $tertiary; 25 | $status-rejected-color: red; // Not used 26 | 27 | // Sidebar variables 28 | $sidebar-color: #597f9b; 29 | $sidebar-width: 265px; 30 | $sidebar-transition-time: .5s; 31 | $sidebar-tab-size: 55px; 32 | 33 | // Page variables 34 | $page-header-color: $secondary; 35 | 36 | $brand-primary: $primary; 37 | $brand-secondary: $secondary; 38 | $brand-tertiary: $tertiary; 39 | 40 | @mixin transform($transforms) { 41 | -webkit-transform: $transforms; 42 | transform: $transforms; 43 | } 44 | 45 | @mixin transition-duration($duration) { 46 | -webkit-transition-duration: $duration; 47 | transition-duration: $duration; 48 | } 49 | 50 | @import 'login'; 51 | @import 'base'; 52 | @import 'sidebar'; 53 | @import 'page'; 54 | @import 'dashboard'; 55 | @import 'application'; 56 | @import 'verify'; 57 | @import 'team'; 58 | @import 'admin'; 59 | 60 | html, 61 | body { 62 | height: 100%; 63 | width: 100%; 64 | font-size: 1.02em; 65 | } 66 | 67 | body { 68 | margin: 0; 69 | font-family: 'Lato', 'Helvetica Neue', 'Helvetica', sans-serif; 70 | font-weight: 300; 71 | 72 | >.container { 73 | width: 100%; 74 | height: 100%; 75 | } 76 | } 77 | 78 | /** 79 | * SweetAlert Overrides 80 | */ 81 | .sweet-alert { 82 | font-family: $font-body; 83 | } 84 | 85 | /** 86 | * Semantic-UI Overrides for squareishness 87 | */ 88 | .ui.segment { 89 | border-radius: 0; 90 | box-shadow: none; 91 | } 92 | 93 | input { 94 | border-radius: 0 !important; 95 | } 96 | 97 | .ui.button { 98 | border-radius: 0; 99 | 100 | &.primary { 101 | background: $primary; 102 | 103 | &:hover { 104 | background: lighten($primary, 3%); 105 | } 106 | 107 | &:active { 108 | background: darken($primary, 3%); 109 | } 110 | } 111 | 112 | &.secondary { 113 | background: $secondary; 114 | 115 | &:hover { 116 | background: lighten($secondary, 3%); 117 | } 118 | 119 | &:active { 120 | background: darken($secondary, 3%); 121 | } 122 | 123 | &:focus { 124 | background: $secondary; 125 | } 126 | } 127 | } 128 | 129 | .ui.form select { 130 | height: 2.5em; 131 | } 132 | 133 | fieldset { 134 | padding: 0; 135 | border: none; 136 | } 137 | 138 | .ui.search>.results { 139 | width: 100%; 140 | } 141 | 142 | // @import 'animation'; 143 | 144 | ::-webkit-scrollbar { 145 | width: 10px; 146 | 147 | @media screen and (max-width: 930px) { 148 | width: 7px; 149 | } 150 | } 151 | 152 | ::-webkit-scrollbar-track { 153 | background: #fff; 154 | } 155 | 156 | ::-webkit-scrollbar-thumb { 157 | background: #597f9b; 158 | } 159 | 160 | ::-webkit-scrollbar-thumb:hover { 161 | background: #18335C; 162 | } -------------------------------------------------------------------------------- /app/client/views/404.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |

Oops. Looks like this doesn't exist.

7 |
8 | -------------------------------------------------------------------------------- /app/client/views/about/about.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

What is Hackathon / HackIIITV?

4 |
5 |

A Hackathon is any event of any duration where people come together to solve problems. 6 | There is given a set of Problems to the team. Team has to give solution of the problem. it may be idea of the 7 | problem or implementation like app, web based etc. within the duration. HackIIITV is not just a coding contest 8 | rather, its notion is to get the most effective solutions to real-life problems. It is a team-based event in 9 | which 10 | each team is given a problem set consisting of real-life problems and the teams have to come up with a practical 11 | solution for the same. The teams have to fabricate programs to implement their ideas & also present it, however 12 | complex it is, to the judges. All of this has to be done within a period of 24 hours, so your management, 13 | team-work, 14 | and planning skills matter here. As you can see HackIIITV is a blend of programming, creative thinking, time 15 | management and presentation skills,

16 |
17 |

Rules and Regulations

18 |
19 |
    20 |
  • A team must contain 5 members.
  • 21 |
  • All work on the project must be done at the venue.
  • 22 |
  • The project must follow the theme.
  • 23 |
  • Every team must submit at-least one pull-request every hour on 24 | GitHub, during the “Hack” time.
  • 25 |
  • Each team much present their ideas formally (PowerPoint 26 | presentation not necessarily required) to the judges after the 24 27 | hour hack time.
  • 28 |
  • The presentation time must not exceed 6 minutes.
  • 29 |
  • Teams must stop hacking once the time is up. However, teams are 30 | allowed to debug and make small fixes to their programs after time 31 | is up. e.g. If during demonstrating your hack, you find a bug that 32 | breaks your application and the fix is only a few lines of code, it's 33 | okay to fix that.
  • 34 |
  • If any team is found to have plagiarised their hack, the team shall 35 | be disqualified immediately & will not be allowed to participate in 36 | any technical event of institute in academic year 2020-21.
  • 37 |
  • If any team is found to have made major changes to their work 38 | product after submission then the team will be disqualified 39 | immediately.
  • 40 |
  • Projects containing any kind of harassment, abuse or profanity will 41 | be disqualified immediately.
  • 42 |
  • Any team who fails to submit their work product will be 43 | disqualified immediately.
  • 44 |
  • In any case of conflict, the decision of the organizers will be final.
  • 45 |
46 |
47 |

Venue

48 |
49 |
50 |
Sabar Hostel premises
51 | 52 |
53 |
54 |

Time

55 |
56 |
57 |
Date: 5th October 2020 (Monday) 10:00 AM to 6th October 2020 (Tuesday) 10:00 58 | AM 59 |
60 |
61 |
62 |

In Association with

63 |
64 | 67 | 68 |
69 |
70 |

Code of Conduct

71 |
72 |
73 |
74 |

To view Code of Conduct

76 |
77 | 78 | 82 |
-------------------------------------------------------------------------------- /app/client/views/admin/admin.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | GOD MODE 4 |
5 | 6 | 28 | 29 | 30 |
31 | -------------------------------------------------------------------------------- /app/client/views/admin/adminCtrl.js: -------------------------------------------------------------------------------- 1 | angular.module('reg') 2 | .controller('AdminCtrl', [ 3 | '$scope', 4 | 'UserService', 5 | function($scope, UserService){ 6 | $scope.loading = true; 7 | }]); -------------------------------------------------------------------------------- /app/client/views/admin/settings/adminSettingsCtrl.js: -------------------------------------------------------------------------------- 1 | angular.module('reg') 2 | .controller('AdminSettingsCtrl', [ 3 | '$scope', 4 | '$sce', 5 | 'SettingsService', 6 | function($scope, $sce, SettingsService){ 7 | 8 | $scope.settings = {}; 9 | SettingsService 10 | .getPublicSettings() 11 | .success(function(settings){ 12 | updateSettings(settings); 13 | }); 14 | 15 | function updateSettings(settings){ 16 | $scope.loading = false; 17 | // Format the dates in settings. 18 | settings.timeOpen = new Date(settings.timeOpen); 19 | settings.timeClose = new Date(settings.timeClose); 20 | settings.timeConfirm = new Date(settings.timeConfirm); 21 | 22 | $scope.settings = settings; 23 | } 24 | 25 | // Additional Options -------------------------------------- 26 | 27 | $scope.updateAllowMinors = function () { 28 | SettingsService 29 | .updateAllowMinors($scope.settings.allowMinors) 30 | .success(function (data) { 31 | $scope.settings.allowMinors = data.allowMinors; 32 | const successText = $scope.settings.allowMinors ? 33 | "Minors are now allowed to register." : 34 | "Minors are no longer allowed to register." 35 | swal("Looks good!", successText, "success"); 36 | }); 37 | }; 38 | 39 | // Whitelist -------------------------------------- 40 | 41 | SettingsService 42 | .getWhitelistedEmails() 43 | .success(function(emails){ 44 | $scope.whitelist = emails.join(", "); 45 | }); 46 | 47 | $scope.updateWhitelist = function(){ 48 | SettingsService 49 | .updateWhitelistedEmails($scope.whitelist.replace(/ /g, '').split(',')) 50 | .success(function(settings){ 51 | swal('Whitelist updated.'); 52 | $scope.whitelist = settings.whitelistedEmails.join(", "); 53 | }); 54 | }; 55 | 56 | // Registration Times ----------------------------- 57 | 58 | $scope.formatDate = function(date){ 59 | if (!date){ 60 | return "Invalid Date"; 61 | } 62 | 63 | // Hack for timezone 64 | return moment(date).format('dddd, MMMM Do YYYY, h:mm a') + 65 | " " + date.toTimeString().split(' ')[2]; 66 | }; 67 | 68 | // Take a date and remove the seconds. 69 | function cleanDate(date){ 70 | return new Date( 71 | date.getFullYear(), 72 | date.getMonth(), 73 | date.getDate(), 74 | date.getHours(), 75 | date.getMinutes() 76 | ); 77 | } 78 | 79 | $scope.updateRegistrationTimes = function(){ 80 | // Clean the dates and turn them to ms. 81 | var open = cleanDate($scope.settings.timeOpen).getTime(); 82 | var close = cleanDate($scope.settings.timeClose).getTime(); 83 | 84 | if (open < 0 || close < 0 || open === undefined || close === undefined){ 85 | return swal('Oops...', 'You need to enter valid times.', 'error'); 86 | } 87 | if (open >= close){ 88 | swal('Oops...', 'Registration cannot open after it closes.', 'error'); 89 | return; 90 | } 91 | 92 | SettingsService 93 | .updateRegistrationTimes(open, close) 94 | .success(function(settings){ 95 | updateSettings(settings); 96 | swal("Looks good!", "Registration Times Updated", "success"); 97 | }); 98 | }; 99 | 100 | // Confirmation Time ----------------------------- 101 | 102 | $scope.updateConfirmationTime = function(){ 103 | var confirmBy = cleanDate($scope.settings.timeConfirm).getTime(); 104 | 105 | SettingsService 106 | .updateConfirmationTime(confirmBy) 107 | .success(function(settings){ 108 | updateSettings(settings); 109 | swal("Sounds good!", "Confirmation Date Updated", "success"); 110 | }); 111 | }; 112 | 113 | // Acceptance / Confirmation Text ---------------- 114 | 115 | var converter = new showdown.Converter(); 116 | 117 | $scope.markdownPreview = function(text){ 118 | return $sce.trustAsHtml(converter.makeHtml(text)); 119 | }; 120 | 121 | $scope.updateWaitlistText = function(){ 122 | var text = $scope.settings.waitlistText; 123 | SettingsService 124 | .updateWaitlistText(text) 125 | .success(function(data){ 126 | swal("Looks good!", "Waitlist Text Updated", "success"); 127 | updateSettings(data); 128 | }); 129 | }; 130 | 131 | $scope.updateAcceptanceText = function(){ 132 | var text = $scope.settings.acceptanceText; 133 | SettingsService 134 | .updateAcceptanceText(text) 135 | .success(function(data){ 136 | swal("Looks good!", "Acceptance Text Updated", "success"); 137 | updateSettings(data); 138 | }); 139 | }; 140 | 141 | $scope.updateConfirmationText = function(){ 142 | var text = $scope.settings.confirmationText; 143 | SettingsService 144 | .updateConfirmationText(text) 145 | .success(function(data){ 146 | swal("Looks good!", "Confirmation Text Updated", "success"); 147 | updateSettings(data); 148 | }); 149 | }; 150 | 151 | }]); -------------------------------------------------------------------------------- /app/client/views/admin/settings/settings.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
8 |
9 | 10 |
11 | Open/Close Registration 12 |
13 | 14 |

15 | Users will be able to register new accounts within the time period specified. 16 |

17 | 18 |
19 | 22 | 23 |
24 | 25 |
26 | 29 | 30 |
31 | 32 |
33 |
36 | Update 37 |
38 |
39 |
40 |
41 | 42 |
43 |
44 | 45 | 46 |
49 |
50 | 51 |
52 | Confirmation Date 53 |
54 | 55 |

56 | Any users that are accepted will have to confirm by the date selected. 57 |

58 | 59 |
60 | 63 | 64 |
65 | 66 |
67 |
70 | Update 71 |
72 |
73 |
74 |
75 | 76 |
77 |
78 | 79 | 80 |
83 | 84 |
85 | Additional Options 86 |
87 | 88 |
89 |
90 |
91 |
92 |
93 | 99 | 100 |
101 |
102 |
103 |
104 |
105 |
106 | 107 | 108 |
111 | 112 |
113 | Whitelisted Emails 114 |
115 | 116 |
117 |
120 | {{email}} 121 |
122 |
123 |
124 |
125 | 127 |
128 |
129 |
132 | Update 133 |
134 |
135 |
136 |
137 | 138 | 139 |
142 |
143 | Waitlist Text 144 |
145 |
146 |
147 |
148 |
149 | 154 |
155 |
156 |
159 | Update 160 |
161 |
162 |
163 |
164 |
165 |
168 |
169 |
170 |
171 | 172 |
173 | 174 | 175 |
178 |
179 | Acceptance Text 180 |
181 |
182 |
183 |
184 |
185 | 190 |
191 |
192 |
195 | Update 196 |
197 |
198 |
199 |
200 |
201 |
204 |
205 |
206 |
207 | 208 |
209 | 210 |
213 |
214 | Confirmation Text 215 |
216 |
217 |
218 |
219 |
220 | 225 |
226 |
227 |
230 | Update 231 |
232 |
233 |
234 |
235 |
236 |
239 |
240 |
241 |
242 |
243 | -------------------------------------------------------------------------------- /app/client/views/admin/stats/adminStatsCtrl.js: -------------------------------------------------------------------------------- 1 | angular.module('reg') 2 | .controller('AdminStatsCtrl',[ 3 | '$scope', 4 | 'UserService', 5 | function($scope, UserService){ 6 | 7 | UserService 8 | .getStats() 9 | .success(function(stats){ 10 | $scope.stats = stats; 11 | $scope.loading = false; 12 | }); 13 | 14 | $scope.fromNow = function(date){ 15 | return moment(date).fromNow(); 16 | }; 17 | 18 | }]); -------------------------------------------------------------------------------- /app/client/views/admin/stats/stats.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
6 |
7 | Stats 8 |
9 |
10 | Last updated: {{fromNow(stats.lastUpdated)}} 11 |
12 |
14 | 15 |
16 | 17 | Total Users 18 | : 19 | {{stats.total}} 20 |
21 |
22 |
24 | 25 |
26 | 27 | Verified Users 28 | : 29 | {{stats.verified}} ({{stats.verified / stats.total * 100 | number: 1}}%) 30 |
31 |
32 |
34 | 35 |
36 | 37 | Submitted Users 38 | : 39 | {{stats.submitted}} ({{stats.submitted / stats.total * 100 | number: 1}}%) 40 |
41 |
42 |
43 |
45 | 46 |
47 | 48 | Admitted 49 | : 50 | {{stats.admitted}} 51 |
52 |
53 |
55 | 56 |
57 | 58 | Confirmed (Total) 59 | : 60 | {{stats.confirmed}} 61 |
62 |
63 |
65 | 66 |
67 | 68 | Confirmed (IIITV) 69 | : 70 | {{stats.confirmedMit}} 71 |
72 |
73 |
74 | 75 |
76 | 77 | Declined 78 | : 79 | {{stats.declined}} 80 |
81 |
82 | 83 |
84 | 85 |
86 | 87 | Checked In 88 | : 89 | {{stats.checkedIn}} 90 |
91 |
92 | 93 |
94 | 95 |
96 | 97 |
98 | 99 | Unisex shirt sizes: 100 | : 101 | XS ({{stats.shirtSizes['XS']}}) 102 | S ({{stats.shirtSizes['S']}}) 103 | M ({{stats.shirtSizes['M']}}) 104 | L ({{stats.shirtSizes['L']}}) 105 | XL ({{stats.shirtSizes['XL']}}) 106 | XXL ({{stats.shirtSizes['XXL']}}) 107 |
108 |
109 | 110 |
111 | 112 |
113 | 114 | Women's shirt sizes: 115 | : 116 | XS ({{stats.shirtSizes['WXS']}}) 117 | S ({{stats.shirtSizes['WS']}}) 118 | M ({{stats.shirtSizes['WM']}}) 119 | L ({{stats.shirtSizes['WL']}}) 120 | XL ({{stats.shirtSizes['WXL']}}) 121 | XXL ({{stats.shirtSizes['WXXL']}}) 122 |
123 |
124 | 125 |
126 | 127 |
128 | 129 |
130 | 131 | Total Reimbursements: 132 | {{stats.reimbursementTotal}} 133 |
134 |
135 | 136 |
137 | 138 |
139 | 140 | Still Need Reimbursement: 141 | {{stats.reimbursementMissing}} 142 |
143 |
144 | 145 |
146 | 147 |
148 | 149 |
150 | 151 | Need/Want Hardware: 152 | {{stats.wantsHardware}} 153 |
154 |
155 | 156 |
157 | 158 |
159 | 160 |
161 | 162 | Dietary Restrictions: 163 | 164 | 166 | {{restriction.name}} ({{restriction.count}}) 167 | 168 |
169 |
170 | 171 |
172 |
173 |
174 | 175 |
178 |
179 | Demographics 180 |
181 |
182 |
183 | 184 |
185 | Total users: {{stats.total}} 186 |
187 |
188 |
189 | 190 |
191 | Female: {{stats.demo.gender['F']}} ({{stats.demo.gender['F'] / stats.total * 100 | number:1}}%) 192 |
193 |
194 |
195 | 196 |
197 | Male: {{stats.demo.gender['M']}} ({{stats.demo.gender['M'] / stats.total * 100 | number:1}}%) 198 |
199 |
200 |
201 | 202 |
203 | Other: {{stats.demo.gender['O']}} ({{stats.demo.gender['O'] / stats.total * 100 | number:1}}%) 204 |
205 |
206 |
207 | 208 |
209 | Did not respond: {{stats.demo.gender['N']}} ({{stats.demo.gender['N'] / stats.total * 100 | number:1 }}%) 210 |
211 |
212 |
213 |
214 |
215 |
216 | 1st Year: {{stats.demo.year['2022']}} ({{stats.demo.year['2022'] / stats.total * 100 | number: 1}}%) 217 |
218 |
219 | 2nd Year: {{stats.demo.year['2021']}} ({{stats.demo.year['2021'] / stats.total * 100 | number: 1}}%) 220 |
221 |
222 | 3rd Year: {{stats.demo.year['2020']}} ({{stats.demo.year['2020'] / stats.total * 100 | number: 1}}%) 223 |
224 |
225 | Final Year: {{stats.demo.year['2019']}} ({{stats.demo.year['2019'] / stats.total * 100 | number: 1}}%) 226 |
227 |
228 |
229 |
230 |
233 |
234 | {{school.email}} : {{school.count}} ({{school.count / stats.total * 100 | number: 1}}%) 235 |

236 |

237 | Submitted: {{school.stats.submitted}} 238 |
239 |
240 | Admitted: {{school.stats.admitted}} 241 |
242 |
243 | Confirmed: {{school.stats.confirmed}} 244 |
245 |
246 | Declined: {{school.stats.declined}} 247 |
248 |

249 |
250 |
251 |
252 |
253 |
254 | 255 | 272 | 273 |
274 | -------------------------------------------------------------------------------- /app/client/views/admin/user/adminUserCtrl.js: -------------------------------------------------------------------------------- 1 | angular.module('reg') 2 | .controller('AdminUserCtrl',[ 3 | '$scope', 4 | '$http', 5 | 'user', 6 | 'UserService', 7 | function($scope, $http, User, UserService){ 8 | $scope.selectedUser = User.data; 9 | 10 | // Populate the school dropdown 11 | populateSchools(); 12 | 13 | /** 14 | * TODO: JANK WARNING 15 | */ 16 | function populateSchools(){ 17 | 18 | $http 19 | .get('/assets/schools.json') 20 | .then(function(res){ 21 | var schools = res.data; 22 | var email = $scope.selectedUser.email.split('@')[1]; 23 | 24 | if (schools[email]){ 25 | $scope.selectedUser.profile.school = schools[email].school; 26 | $scope.autoFilledSchool = true; 27 | } 28 | 29 | }); 30 | } 31 | 32 | 33 | $scope.updateProfile = function(){ 34 | UserService 35 | .updateProfile($scope.selectedUser._id, $scope.selectedUser.profile) 36 | .success(function(data){ 37 | $selectedUser = data; 38 | swal("Updated!", "Profile updated.", "success"); 39 | }) 40 | .error(function(){ 41 | swal("Oops, you forgot something."); 42 | }); 43 | }; 44 | 45 | }]); -------------------------------------------------------------------------------- /app/client/views/admin/users/users.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 | Search 7 |
8 |
9 |
10 |
11 | 15 | 16 |
17 |
18 |
19 | 20 |
21 | 22 | 30 |
31 |
32 | 33 |
34 |
35 | Users 36 |
37 | 90 | 91 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 112 | 119 | 120 | 121 | 125 | 126 | 178 | 179 | 180 | 199 | 200 | 213 | 214 | 222 | 223 | 261 | 262 | 263 |
NameE-mail addressGradTeamV/S/A/C
113 | 114 | {{user.profile.name}} 115 |   116 | 117 | 118 | {{user.email}} 123 | {{user.profile.graduationYear}} 124 | {{user.teamCode}} 128 | 129 | 130 | 133 | 134 | 137 | 138 | 139 | 140 | 143 | 144 | 147 | 148 | 149 | 150 | 153 | 154 | 157 | 158 | 159 | 160 | 163 | 164 | 167 | 168 | 171 | 172 | 175 | 176 | 177 | 216 | 221 | 225 | 226 | 231 | 232 | 245 | 246 | 259 | 260 |
264 | 265 |
266 |
267 | 268 |
-------------------------------------------------------------------------------- /app/client/views/application/application.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Application 4 |
5 | 6 |
7 |
8 |
10 | 11 |
Basic Information
12 | 13 |
14 | 15 |
16 | 17 | 21 |
22 | 23 |
24 | 25 | 28 |
29 | 30 |
31 | 32 | 36 |
37 | 38 |
39 | 40 | 46 |
47 | 48 |
49 | 50 | 59 |
60 | 61 |
62 | 63 | 72 |
73 | 74 |
75 | 76 | 79 |
80 | 81 |
82 | 85 | 87 |
88 | 89 |
91 |

92 | Because of limitations imposed by IIITV, we are not legally allowed to host 93 | non-IIITV minors (those under 18) for HackIIITV 2020. Checking the box below affirms that you are or will be 18 years or older by October 6th, 2020. 94 |

95 |

96 | 97 | We will be checking ID. If you are a non-IIITVian minor, you will be turned away at the door. 98 | 99 |

100 |
101 | 102 | 103 |
104 |
105 | 106 |
107 | 108 |
109 | 114 |
115 | 116 |
117 |
118 |
119 |
120 |
121 | -------------------------------------------------------------------------------- /app/client/views/application/applicationCtrl.js: -------------------------------------------------------------------------------- 1 | angular.module('reg') 2 | .controller('ApplicationCtrl', [ 3 | '$scope', 4 | '$rootScope', 5 | '$state', 6 | '$http', 7 | 'currentUser', 8 | 'settings', 9 | 'Session', 10 | 'UserService', 11 | function($scope, $rootScope, $state, $http, currentUser, Settings, Session, UserService){ 12 | 13 | // Set up the user 14 | $scope.user = currentUser.data; 15 | 16 | // Is the student from IIITV? 17 | $scope.isIIITStudent = $scope.user.email.split('@')[1] == 'iiitv.ac.in' || $scope.user.email.split('@')[1] == 'iiitvadodara.ac.in'; 18 | 19 | // If so, default them to adult: true 20 | if ($scope.isIIITStudent){ 21 | $scope.user.profile.adult = true; 22 | } 23 | 24 | // Populate the school dropdown 25 | populateSchools(); 26 | _setupForm(); 27 | 28 | $scope.regIsClosed = Date.now() > Settings.data.timeClose; 29 | 30 | /** 31 | * TODO: JANK WARNING 32 | */ 33 | function populateSchools(){ 34 | $http 35 | .get('/assets/schools.json') 36 | .then(function(res){ 37 | var schools = res.data; 38 | var email = $scope.user.email.split('@')[1]; 39 | 40 | if (schools[email]){ 41 | $scope.user.profile.school = schools[email].school; 42 | $scope.autoFilledSchool = true; 43 | } 44 | }); 45 | 46 | $http 47 | .get('/assets/schools.csv') 48 | .then(function(res){ 49 | $scope.schools = res.data.split('\n'); 50 | $scope.schools.push('Other'); 51 | 52 | var content = []; 53 | 54 | for(i = 0; i < $scope.schools.length; i++) { 55 | $scope.schools[i] = $scope.schools[i].trim(); 56 | content.push({title: $scope.schools[i]}) 57 | } 58 | 59 | $('#school.ui.search') 60 | .search({ 61 | source: content, 62 | cache: true, 63 | onSelect: function(result, response) { 64 | $scope.user.profile.school = result.title.trim(); 65 | } 66 | }) 67 | }); 68 | } 69 | 70 | function _updateUser(e){ 71 | UserService 72 | .updateProfile(Session.getUserId(), $scope.user.profile) 73 | .success(function(data){ 74 | sweetAlert({ 75 | title: "Awesome!", 76 | text: "Your application has been saved.", 77 | type: "success", 78 | confirmButtonColor: "#e76482" 79 | }, function(){ 80 | $state.go('app.dashboard'); 81 | }); 82 | }) 83 | .error(function(res){ 84 | sweetAlert("Uh oh!", "Something went wrong.", "error"); 85 | }); 86 | } 87 | 88 | function isMinor() { 89 | return !$scope.user.profile.adult; 90 | } 91 | 92 | function minorsAreAllowed() { 93 | return Settings.data.allowMinors; 94 | } 95 | 96 | function minorsValidation() { 97 | // Are minors allowed to register? 98 | if (isMinor() && !minorsAreAllowed()) { 99 | return false; 100 | } 101 | return true; 102 | } 103 | 104 | function _setupForm(){ 105 | // Custom minors validation rule 106 | $.fn.form.settings.rules.allowMinors = function (value) { 107 | return minorsValidation(); 108 | }; 109 | 110 | // Semantic-UI form validation 111 | $('.ui.form').form({ 112 | inline: true, 113 | fields: { 114 | name: { 115 | identifier: 'name', 116 | rules: [ 117 | { 118 | type: 'empty', 119 | prompt: 'Please enter your name.' 120 | } 121 | ] 122 | }, 123 | school: { 124 | identifier: 'school', 125 | rules: [ 126 | { 127 | type: 'empty', 128 | prompt: 'Please enter your school name.' 129 | } 130 | ] 131 | }, 132 | year: { 133 | identifier: 'year', 134 | rules: [ 135 | { 136 | type: 'empty', 137 | prompt: 'Please select your graduation year.' 138 | } 139 | ] 140 | }, 141 | gender: { 142 | identifier: 'gender', 143 | rules: [ 144 | { 145 | type: 'empty', 146 | prompt: 'Please select a gender.' 147 | } 148 | ] 149 | }, 150 | adult: { 151 | identifier: 'adult', 152 | rules: [ 153 | { 154 | type: 'allowMinors', 155 | prompt: 'You must be an adult, or an IIITV student.' 156 | } 157 | ] 158 | } 159 | } 160 | }); 161 | } 162 | 163 | $scope.submitForm = function(){ 164 | if ($('.ui.form').form('is valid')){ 165 | _updateUser(); 166 | } 167 | else{ 168 | sweetAlert("Uh oh!", "Please Fill The Required Fields", "error"); 169 | } 170 | }; 171 | 172 | }]); 173 | -------------------------------------------------------------------------------- /app/client/views/base.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 |
7 |
8 |
9 |
10 | 11 |
12 | -------------------------------------------------------------------------------- /app/client/views/confirmation/confirmationCtrl.js: -------------------------------------------------------------------------------- 1 | angular.module('reg') 2 | .controller('ConfirmationCtrl', [ 3 | '$scope', 4 | '$rootScope', 5 | '$state', 6 | 'currentUser', 7 | 'Utils', 8 | 'UserService', 9 | function($scope, $rootScope, $state, currentUser, Utils, UserService){ 10 | 11 | // Set up the user 12 | var user = currentUser.data; 13 | $scope.user = user; 14 | 15 | $scope.pastConfirmation = Date.now() > user.status.confirmBy; 16 | 17 | $scope.formatTime = Utils.formatTime; 18 | 19 | _setupForm(); 20 | 21 | $scope.fileName = user._id + "_" + user.profile.name.split(" ").join("_"); 22 | 23 | // ------------------------------- 24 | // All this just for dietary restriction checkboxes fml 25 | 26 | var dietaryRestrictions = { 27 | 'Vegetarian': false, 28 | 'Vegan': false, 29 | 'Halal': false, 30 | 'Kosher': false, 31 | 'Nut Allergy': false 32 | }; 33 | 34 | if (user.confirmation.dietaryRestrictions){ 35 | user.confirmation.dietaryRestrictions.forEach(function(restriction){ 36 | if (restriction in dietaryRestrictions){ 37 | dietaryRestrictions[restriction] = true; 38 | } 39 | }); 40 | } 41 | 42 | $scope.dietaryRestrictions = dietaryRestrictions; 43 | 44 | // ------------------------------- 45 | 46 | function _updateUser(e){ 47 | var confirmation = $scope.user.confirmation; 48 | // Get the dietary restrictions as an array 49 | var drs = []; 50 | Object.keys($scope.dietaryRestrictions).forEach(function(key){ 51 | if ($scope.dietaryRestrictions[key]){ 52 | drs.push(key); 53 | } 54 | }); 55 | confirmation.dietaryRestrictions = drs; 56 | 57 | UserService 58 | .updateConfirmation(user._id, confirmation) 59 | .success(function(data){ 60 | sweetAlert({ 61 | title: "Woo!", 62 | text: "You're confirmed!", 63 | type: "success", 64 | confirmButtonColor: "#e76482" 65 | }, function(){ 66 | $state.go('app.dashboard'); 67 | }); 68 | }) 69 | .error(function(res){ 70 | sweetAlert("Uh oh!", "Something went wrong.", "error"); 71 | }); 72 | } 73 | 74 | function _setupForm(){ 75 | // Semantic-UI form validation 76 | $('.ui.form').form({ 77 | fields: { 78 | shirt: { 79 | identifier: 'shirt', 80 | rules: [ 81 | { 82 | type: 'empty', 83 | prompt: 'Please give us a shirt size!' 84 | } 85 | ] 86 | }, 87 | phone: { 88 | identifier: 'phone', 89 | rules: [ 90 | { 91 | type: 'empty', 92 | prompt: 'Please enter a phone number.' 93 | } 94 | ] 95 | }, 96 | signatureLiability: { 97 | identifier: 'signatureLiabilityWaiver', 98 | rules: [ 99 | { 100 | type: 'empty', 101 | prompt: 'Please type your digital signature.' 102 | } 103 | ] 104 | }, 105 | signaturePhotoRelease: { 106 | identifier: 'signaturePhotoRelease', 107 | rules: [ 108 | { 109 | type: 'empty', 110 | prompt: 'Please type your digital signature.' 111 | } 112 | ] 113 | }, 114 | signatureCodeOfConduct: { 115 | identifier: 'signatureCodeOfConduct', 116 | rules: [ 117 | { 118 | type: 'empty', 119 | prompt: 'Please type your digital signature.' 120 | } 121 | ] 122 | }, 123 | } 124 | }); 125 | } 126 | 127 | $scope.submitForm = function(){ 128 | if ($('.ui.form').form('is valid')){ 129 | _updateUser(); 130 | } 131 | }; 132 | 133 | }]); -------------------------------------------------------------------------------- /app/client/views/dashboard/dashboard.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Dashboard 4 |
5 | 6 |
7 |
8 |
9 |
10 | Your status: 11 |
12 |
13 | 14 |
18 | {{user.status.name}} 19 |
20 | 21 |
24 | Expired Admission 25 |
26 | 27 | 28 |
31 | On waitlist 32 |
33 | 34 |
35 | 36 |
37 |

38 | 39 | Welcome back, {{user.profile.name}}! 40 | 41 |

42 |

43 |

44 |
45 | 46 |
49 |

50 | 51 | Your email address ({{user.email}}) is not yet verified. 52 | 53 |

54 |

55 | {{ DASHBOARD.UNVERIFIED }} 56 |

57 |
58 | Resend verification email 59 |
60 |
61 | 62 |
65 | 66 | Registration Deadline: 67 | 68 | {{ timeClose }}) 69 |
70 | 71 |
74 | 75 | Confirmation Deadline: 76 | 77 | {{ timeConfirm }}) 78 |
79 |
80 |
83 |

84 | 85 | {{ DASHBOARD.INCOMPLETE_TITLE }} 86 | 87 |

88 |

89 | {{ DASHBOARD.INCOMPLETE }} 90 |

91 | 92 |
94 | Complete your application 95 |
96 | 97 |
98 | 99 |
102 |

103 | 104 | {{ DASHBOARD.SUBMITTED_TITLE }} 105 | 106 |

107 |

108 | {{ DASHBOARD.SUBMITTED }} 109 |

110 |
112 | Edit your application 113 |
114 | 115 |
119 | Create or join a team 120 |
121 | 122 |
126 | View your Team 127 |
128 | 129 |
130 | 131 |
133 |

134 | 135 | {{ DASHBOARD.CLOSED_AND_INCOMPLETE_TITLE }} 136 | 137 |

138 |

139 | {{ DASHBOARD.CLOSED_AND_INCOMPLETE }} 140 |

141 |
142 | 143 |
146 | 147 |
150 |
151 | 152 |
154 | View your application 155 |
156 | 157 |
161 | View your Team 162 |
163 |
164 | 165 |
168 | 169 |
170 |

171 | {{ DASHBOARD.ADMITTED_AND_CAN_CONFIRM_TITLE }} 172 |

173 |
174 | 175 |
178 |
179 | 180 |
181 |

182 | {{ DASHBOARD.ADMITTED_AND_CAN_CONFIRM }} 183 |

184 |
185 | 186 |
188 | Confirm your spot 189 |
190 | 191 |
193 | Sorry, I can't make it 194 |
195 | 196 |
197 | 198 |
201 | 202 |
203 |

204 | {{ DASHBOARD.ADMITTED_AND_CANNOT_CONFIRM_TITLE }} 205 |

206 | 207 |

208 | {{ DASHBOARD.ADMITTED_AND_CANNOT_CONFIRM }} 209 |

210 |
211 | 212 |
213 | 214 |
217 | 218 |
221 |

222 | {{ DASHBOARD.CONFIRMED_NOT_PAST_TITLE }} 223 |

224 |
225 | 226 |
229 |
230 | 231 |
233 | {{pastConfirmation ? 'View' : 'Edit'}} your confirmation information 234 |
235 | 236 |
238 | Sorry, I can't make it 239 |
240 | 241 |
242 | 243 |
246 | 247 |

248 | {{ DASHBOARD.DECLINED }} 249 |

250 |
251 | 252 |
253 | 254 |
255 |
256 |
257 | -------------------------------------------------------------------------------- /app/client/views/dashboard/dashboardCtrl.js: -------------------------------------------------------------------------------- 1 | angular.module('reg') 2 | .controller('DashboardCtrl', [ 3 | '$rootScope', 4 | '$scope', 5 | '$sce', 6 | 'currentUser', 7 | 'settings', 8 | 'Utils', 9 | 'AuthService', 10 | 'UserService', 11 | 'EVENT_INFO', 12 | 'DASHBOARD', 13 | function($rootScope, $scope, $sce, currentUser, settings, Utils, AuthService, UserService, EVENT_INFO, DASHBOARD){ 14 | var Settings = settings.data; 15 | var user = currentUser.data; 16 | $scope.user = user; 17 | $scope.timeClose = Utils.formatTime(Settings.timeClose); 18 | $scope.timeConfirm = Utils.formatTime(Settings.timeConfirm); 19 | 20 | $scope.DASHBOARD = DASHBOARD; 21 | 22 | for (var msg in $scope.DASHBOARD) { 23 | if ($scope.DASHBOARD[msg].includes('[APP_DEADLINE]')) { 24 | $scope.DASHBOARD[msg] = $scope.DASHBOARD[msg].replace('[APP_DEADLINE]', Utils.formatTime(Settings.timeClose)); 25 | } 26 | if ($scope.DASHBOARD[msg].includes('[CONFIRM_DEADLINE]')) { 27 | $scope.DASHBOARD[msg] = $scope.DASHBOARD[msg].replace('[CONFIRM_DEADLINE]', Utils.formatTime(user.status.confirmBy)); 28 | } 29 | } 30 | 31 | // Is registration open? 32 | var regIsOpen = $scope.regIsOpen = Utils.isRegOpen(Settings); 33 | 34 | // Is it past the user's confirmation time? 35 | var pastConfirmation = $scope.pastConfirmation = Utils.isAfter(user.status.confirmBy); 36 | 37 | $scope.dashState = function(status){ 38 | var user = $scope.user; 39 | switch (status) { 40 | case 'unverified': 41 | return !user.verified; 42 | case 'openAndIncomplete': 43 | return regIsOpen && user.verified && !user.status.completedProfile; 44 | case 'openAndSubmitted': 45 | return regIsOpen && user.status.completedProfile && !user.status.admitted; 46 | case 'closedAndIncomplete': 47 | return !regIsOpen && !user.status.completedProfile && !user.status.admitted; 48 | case 'closedAndSubmitted': // Waitlisted State 49 | return !regIsOpen && user.status.completedProfile && !user.status.admitted; 50 | case 'admittedAndCanConfirm': 51 | return !pastConfirmation && 52 | user.status.admitted && 53 | !user.status.confirmed && 54 | !user.status.declined; 55 | case 'admittedAndCannotConfirm': 56 | return pastConfirmation && 57 | user.status.admitted && 58 | !user.status.confirmed && 59 | !user.status.declined; 60 | case 'confirmed': 61 | return user.status.admitted && user.status.confirmed && !user.status.declined; 62 | case 'declined': 63 | return user.status.declined; 64 | } 65 | return false; 66 | }; 67 | 68 | $scope.showWaitlist = !regIsOpen && user.status.completedProfile && !user.status.admitted; 69 | 70 | $scope.resendEmail = function(){ 71 | AuthService 72 | .resendVerificationEmail() 73 | .then(function(){ 74 | sweetAlert('Your email has been sent.'); 75 | }); 76 | }; 77 | 78 | 79 | // ----------------------------------------------------- 80 | // Text! 81 | // ----------------------------------------------------- 82 | var converter = new showdown.Converter(); 83 | $scope.acceptanceText = $sce.trustAsHtml(converter.makeHtml(Settings.acceptanceText)); 84 | $scope.confirmationText = $sce.trustAsHtml(converter.makeHtml(Settings.confirmationText)); 85 | $scope.waitlistText = $sce.trustAsHtml(converter.makeHtml(Settings.waitlistText)); 86 | 87 | $scope.declineAdmission = function(){ 88 | 89 | swal({ 90 | title: "Whoa!", 91 | text: "Are you sure you would like to decline your admission? \n\n You can't go back!", 92 | type: "warning", 93 | showCancelButton: true, 94 | confirmButtonColor: "#DD6B55", 95 | confirmButtonText: "Yes, I can't make it.", 96 | closeOnConfirm: true 97 | }, function(){ 98 | 99 | UserService 100 | .declineAdmission(user._id) 101 | .success(function(user){ 102 | $rootScope.currentUser = user; 103 | $scope.user = user; 104 | }); 105 | }); 106 | }; 107 | 108 | }]); 109 | -------------------------------------------------------------------------------- /app/client/views/login/login.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | 9 | 10 |
11 | 12 |
14 | {{error}} 15 |
16 | 17 |
18 | 43 | 44 |
45 |
46 |
47 | 53 |
54 |
55 |
56 | 57 |
58 |
59 |
60 |
61 | 62 | 66 |
67 |
68 | 73 |
74 |
75 |
76 |
77 | 78 |
79 | 80 | 85 | 86 | 91 | 92 | 93 |
94 |
95 | 96 |
97 |
98 | -------------------------------------------------------------------------------- /app/client/views/login/loginCtrl.js: -------------------------------------------------------------------------------- 1 | angular.module('reg') 2 | .controller('LoginCtrl', [ 3 | '$scope', 4 | '$http', 5 | '$state', 6 | 'settings', 7 | 'Utils', 8 | 'AuthService', 9 | function($scope, $http, $state, settings, Utils, AuthService){ 10 | 11 | // Is registration open? 12 | var Settings = settings.data; 13 | $scope.regIsOpen = Utils.isRegOpen(Settings); 14 | 15 | // Start state for login 16 | $scope.loginState = 'login'; 17 | 18 | function onSuccess() { 19 | $state.go('app.dashboard'); 20 | } 21 | 22 | function onError(data){ 23 | $scope.error = data.message; 24 | } 25 | 26 | function resetError(){ 27 | $scope.error = null; 28 | } 29 | 30 | $scope.login = function(){ 31 | resetError(); 32 | AuthService.loginWithPassword( 33 | $scope.email, $scope.password, onSuccess, onError); 34 | }; 35 | 36 | $scope.register = function(){ 37 | resetError(); 38 | AuthService.register( 39 | $scope.email, $scope.password, onSuccess, onError); 40 | }; 41 | 42 | $scope.setLoginState = function(state) { 43 | $scope.loginState = state; 44 | }; 45 | 46 | $scope.sendResetEmail = function() { 47 | var email = $scope.email; 48 | AuthService.sendResetEmail(email); 49 | sweetAlert({ 50 | title: "Don't Sweat!", 51 | text: "An email should be sent to you shortly.", 52 | type: "success", 53 | confirmButtonColor: "#e76482" 54 | }); 55 | }; 56 | 57 | } 58 | ]); 59 | -------------------------------------------------------------------------------- /app/client/views/reset/reset.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | 9 | 10 |
11 | 12 |
14 | {{error}} 15 |
16 | 17 | 43 | 44 |
45 |
46 | 47 |
48 |
-------------------------------------------------------------------------------- /app/client/views/reset/resetCtrl.js: -------------------------------------------------------------------------------- 1 | angular.module('reg') 2 | .controller('ResetCtrl', [ 3 | '$scope', 4 | '$stateParams', 5 | '$state', 6 | 'AuthService', 7 | function($scope, $stateParams, $state, AuthService){ 8 | var token = $stateParams.token; 9 | 10 | $scope.loading = true; 11 | 12 | $scope.changePassword = function(){ 13 | var password = $scope.password; 14 | var confirm = $scope.confirm; 15 | 16 | if (password !== confirm){ 17 | $scope.error = "Passwords don't match!"; 18 | $scope.confirm = ""; 19 | return; 20 | } 21 | 22 | AuthService.resetPassword( 23 | token, 24 | $scope.password, 25 | function(message){ 26 | sweetAlert({ 27 | title: "Neato!", 28 | text: "Your password has been changed!", 29 | type: "success", 30 | confirmButtonColor: "#e76482" 31 | }, function(){ 32 | $state.go('login'); 33 | }); 34 | }, 35 | function(data){ 36 | $scope.error = data.message; 37 | $scope.loading = false; 38 | }); 39 | }; 40 | 41 | }]); -------------------------------------------------------------------------------- /app/client/views/sidebar/sidebar.html: -------------------------------------------------------------------------------- 1 | 43 | 44 |
45 | 46 | 47 |
-------------------------------------------------------------------------------- /app/client/views/sidebar/sidebarCtrl.js: -------------------------------------------------------------------------------- 1 | angular.module('reg') 2 | .controller('SidebarCtrl', [ 3 | '$rootScope', 4 | '$scope', 5 | 'settings', 6 | 'Utils', 7 | 'AuthService', 8 | 'Session', 9 | 'EVENT_INFO', 10 | function($rootScope, $scope, Settings, Utils, AuthService, Session, EVENT_INFO){ 11 | 12 | var settings = Settings.data; 13 | var user = $rootScope.currentUser; 14 | 15 | $scope.EVENT_INFO = EVENT_INFO; 16 | 17 | $scope.pastConfirmation = Utils.isAfter(user.status.confirmBy); 18 | 19 | $scope.logout = function(){ 20 | AuthService.logout(); 21 | }; 22 | 23 | $scope.showSidebar = false; 24 | $scope.toggleSidebar = function(){ 25 | $scope.showSidebar = !$scope.showSidebar; 26 | }; 27 | 28 | // oh god jQuery hack 29 | $('.item').on('click', function(){ 30 | $scope.showSidebar = false; 31 | }); 32 | 33 | }]); 34 | -------------------------------------------------------------------------------- /app/client/views/team/team.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Team 4 |
5 |
6 |
7 |
10 | {{error}} 11 |
12 | 13 |
14 |
16 |

17 | {{ TEAM.NO_TEAM_REG_CLOSED }} 18 |

19 |
20 |
23 | 24 |
25 | Join or Create a Team 26 |
27 | 28 |
29 | 30 |
31 | 36 |
37 | 38 |
39 | 44 |
45 | 46 |
47 | 48 |
50 |
51 |
52 | Your Team Name: 53 |
54 |
55 |
56 |
57 | {{user.teamCode}} 58 |
59 | 60 |
61 | 62 |
63 | 64 |
65 |
66 | Your Team: 67 |
68 |
69 | 70 |

73 | {{user.profile.name || 'Somebody (has not filled out their profile!)'}} 74 |

75 | 76 |
78 |
79 | 80 | 83 |
84 | 85 |
86 | 87 |
88 |
89 |
90 | 91 |
92 | -------------------------------------------------------------------------------- /app/client/views/team/teamCtrl.js: -------------------------------------------------------------------------------- 1 | angular.module('reg') 2 | .controller('TeamCtrl', [ 3 | '$scope', 4 | 'currentUser', 5 | 'settings', 6 | 'Utils', 7 | 'UserService', 8 | 'TEAM', 9 | function($scope, currentUser, settings, Utils, UserService, TEAM){ 10 | // Get the current user's most recent data. 11 | var Settings = settings.data; 12 | 13 | $scope.regIsOpen = Utils.isRegOpen(Settings); 14 | 15 | $scope.user = currentUser.data; 16 | 17 | $scope.TEAM = TEAM; 18 | 19 | function _populateTeammates() { 20 | UserService 21 | .getMyTeammates() 22 | .success(function(users){ 23 | $scope.error = null; 24 | $scope.teammates = users; 25 | }); 26 | } 27 | 28 | if ($scope.user.teamCode){ 29 | _populateTeammates(); 30 | } 31 | 32 | $scope.joinTeam = function(){ 33 | UserService 34 | .joinOrCreateTeam($scope.code) 35 | .success(function(user){ 36 | $scope.error = null; 37 | $scope.user = user; 38 | _populateTeammates(); 39 | }) 40 | .error(function(res){ 41 | $scope.error = res.message; 42 | }); 43 | }; 44 | 45 | $scope.leaveTeam = function(){ 46 | UserService 47 | .leaveTeam() 48 | .success(function(user){ 49 | $scope.error = null; 50 | $scope.user = user; 51 | $scope.teammates = []; 52 | }) 53 | .error(function(res){ 54 | $scope.error = res.data.message; 55 | }); 56 | }; 57 | 58 | }]); 59 | -------------------------------------------------------------------------------- /app/client/views/verify/verify.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | EMAIL VERIFICATION 4 |
5 |
6 |
7 | 8 |

9 | Your email has been verified! 10 |

11 |
12 |
14 | Continue to Dashboard 15 |
16 |
17 |
18 | 19 |

20 | This token is invalid. 21 |

22 |
23 |
24 |
25 | -------------------------------------------------------------------------------- /app/client/views/verify/verifyCtrl.js: -------------------------------------------------------------------------------- 1 | angular.module('reg') 2 | .controller('VerifyCtrl', [ 3 | '$scope', 4 | '$stateParams', 5 | 'AuthService', 6 | function($scope, $stateParams, AuthService){ 7 | var token = $stateParams.token; 8 | 9 | $scope.loading = true; 10 | 11 | if (token){ 12 | AuthService.verify(token, 13 | function(user){ 14 | $scope.success = true; 15 | 16 | $scope.loading = false; 17 | }, 18 | function(err){ 19 | 20 | $scope.loading = false; 21 | }); 22 | } 23 | 24 | }]); -------------------------------------------------------------------------------- /app/server/controllers/SettingsController.js: -------------------------------------------------------------------------------- 1 | var Settings = require('../models/Settings'); 2 | 3 | var SettingsController = {}; 4 | 5 | /** 6 | * Update any field in the settings. 7 | * @param {String} field Name of the field 8 | * @param {Any} value Value to replace it to 9 | * @param {Function} callback args(err, settings) 10 | */ 11 | SettingsController.updateField = function(field, value, callback){ 12 | var update = {}; 13 | update[field] = value; 14 | Settings 15 | .findOneAndUpdate({},{ 16 | $set: update 17 | }, {new: true}, callback); 18 | }; 19 | 20 | /** 21 | * Update the list of whitelisted emails and email extensions. 22 | * @param {[type]} emails [description] 23 | * @param {Function} callback args(err, settings) 24 | */ 25 | SettingsController.updateWhitelistedEmails = function(emails, callback){ 26 | Settings 27 | .findOneAndUpdate({},{ 28 | $set: { 29 | whitelistedEmails: emails 30 | } 31 | }, {new: true}) 32 | .select('whitelistedEmails') 33 | .exec(callback); 34 | }; 35 | 36 | /** 37 | * Get the list of whitelisted emails. 38 | * Whitelist emails are by default not included in settings. 39 | * @param {Function} callback args(err, emails) 40 | */ 41 | SettingsController.getWhitelistedEmails = function(callback){ 42 | Settings.getWhitelistedEmails(callback); 43 | }; 44 | 45 | /** 46 | * Set the time window for registrations. 47 | * If either open or close are null, do not change that time. 48 | * @param {Number} open Open time in ms 49 | * @param {Number} close Close time in ms 50 | * @param {Function} callback args(err, settings) 51 | */ 52 | SettingsController.updateRegistrationTimes = function(open, close, callback){ 53 | var updatedTimes = {}; 54 | 55 | if (close <= open){ 56 | return callback({ 57 | message: "Registration cannot close before or at exactly the same time it opens." 58 | }); 59 | } 60 | 61 | if (open){ 62 | updatedTimes.timeOpen = open; 63 | } 64 | 65 | if (close){ 66 | updatedTimes.timeClose = close; 67 | } 68 | 69 | Settings 70 | .findOneAndUpdate({},{ 71 | $set: updatedTimes 72 | }, {new: true}, callback); 73 | }; 74 | 75 | /** 76 | * Get the open and close time for registration. 77 | * @param {Function} callback args(err, times : {timeOpen, timeClose}) 78 | */ 79 | SettingsController.getRegistrationTimes = function(callback){ 80 | Settings.getRegistrationTimes(callback); 81 | }; 82 | 83 | /** 84 | * Get all public settings. 85 | * @param {Function} callback [description] 86 | * @return {[type]} [description] 87 | */ 88 | SettingsController.getPublicSettings = function(callback){ 89 | Settings.getPublicSettings(callback); 90 | }; 91 | 92 | module.exports = SettingsController; -------------------------------------------------------------------------------- /app/server/models/Settings.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var validator = require('validator'); 3 | 4 | /** 5 | * Settings Schema! 6 | * 7 | * Fields with select: false are not public. 8 | * These can be retrieved in controller methods. 9 | * 10 | * @type {mongoose} 11 | */ 12 | var schema = new mongoose.Schema({ 13 | status: String, 14 | timeOpen: { 15 | type: Number, 16 | default: 0 17 | }, 18 | timeClose: { 19 | type: Number, 20 | default: Date.now() + 31104000000 // Add a year from now. 21 | }, 22 | timeConfirm: { 23 | type: Number, 24 | default: 604800000 // Date of confirmation 25 | }, 26 | whitelistedEmails: { 27 | type: [String], 28 | select: false, 29 | default: ['.edu', '.ac.in'], 30 | }, 31 | waitlistText: { 32 | type: String 33 | }, 34 | acceptanceText: { 35 | type: String, 36 | }, 37 | confirmationText: { 38 | type: String 39 | }, 40 | allowMinors: { 41 | type: Boolean 42 | } 43 | }); 44 | 45 | /** 46 | * Get the list of whitelisted emails. 47 | * Whitelist emails are by default not included in settings. 48 | * @param {Function} callback args(err, emails) 49 | */ 50 | schema.statics.getWhitelistedEmails = function(callback){ 51 | this 52 | .findOne({}) 53 | .select('whitelistedEmails') 54 | .exec(function(err, settings){ 55 | return callback(err, settings.whitelistedEmails); 56 | }); 57 | }; 58 | 59 | /** 60 | * Get the open and close time for registration. 61 | * @param {Function} callback args(err, times : {timeOpen, timeClose, timeConfirm}) 62 | */ 63 | schema.statics.getRegistrationTimes = function(callback){ 64 | this 65 | .findOne({}) 66 | .select('timeOpen timeClose timeConfirm') 67 | .exec(function(err, settings){ 68 | callback(err, { 69 | timeOpen: settings.timeOpen, 70 | timeClose: settings.timeClose, 71 | timeConfirm: settings.timeConfirm 72 | }); 73 | }); 74 | }; 75 | 76 | schema.statics.getPublicSettings = function(callback){ 77 | this 78 | .findOne({}) 79 | .exec(callback); 80 | }; 81 | 82 | module.exports = mongoose.model('Settings', schema); -------------------------------------------------------------------------------- /app/server/models/User.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'), 2 | bcrypt = require('bcrypt-nodejs'), 3 | validator = require('validator'), 4 | jwt = require('jsonwebtoken'); 5 | JWT_SECRET = process.env.JWT_SECRET; 6 | 7 | var profile = { 8 | 9 | // Basic info 10 | name: { 11 | type: String, 12 | min: 1, 13 | max: 100, 14 | }, 15 | 16 | adult: { 17 | type: Boolean, 18 | required: true, 19 | default: false, 20 | }, 21 | 22 | school: { 23 | type: String, 24 | min: 1, 25 | max: 150, 26 | }, 27 | 28 | graduationYear: { 29 | type: String, 30 | enum: { 31 | values: '2019 2020 2021 2022'.split(' '), 32 | } 33 | }, 34 | 35 | description: { 36 | type: String, 37 | min: 0, 38 | max: 300 39 | }, 40 | 41 | essay: { 42 | type: String, 43 | min: 0, 44 | max: 1500 45 | }, 46 | 47 | // Optional info for demographics 48 | gender: { 49 | type: String, 50 | enum : { 51 | values: 'M F O N'.split(' ') 52 | } 53 | }, 54 | 55 | }; 56 | 57 | // Only after confirmed 58 | var confirmation = { 59 | phoneNumber: String, 60 | dietaryRestrictions: [String], 61 | shirtSize: { 62 | type: String, 63 | enum: { 64 | values: 'XS S M L XL XXL WXS WS WM WL WXL WXXL'.split(' ') 65 | } 66 | }, 67 | wantsHardware: Boolean, 68 | hardware: String, 69 | 70 | major: String, 71 | github: String, 72 | twitter: String, 73 | website: String, 74 | resume: String, 75 | 76 | needsReimbursement: Boolean, 77 | address: { 78 | name: String, 79 | line1: String, 80 | line2: String, 81 | city: String, 82 | state: String, 83 | zip: String, 84 | country: String 85 | }, 86 | receipt: String, 87 | 88 | hostNeededFri: Boolean, 89 | hostNeededSat: Boolean, 90 | genderNeutral: Boolean, 91 | catFriendly: Boolean, 92 | smokingFriendly: Boolean, 93 | hostNotes: String, 94 | 95 | notes: String, 96 | 97 | signatureLiability: String, 98 | signaturePhotoRelease: String, 99 | signatureCodeOfConduct: String, 100 | }; 101 | 102 | var status = { 103 | /** 104 | * Whether or not the user's profile has been completed. 105 | * @type {Object} 106 | */ 107 | completedProfile: { 108 | type: Boolean, 109 | required: true, 110 | default: false, 111 | }, 112 | admitted: { 113 | type: Boolean, 114 | required: true, 115 | default: false, 116 | }, 117 | admittedBy: { 118 | type: String, 119 | validate: [ 120 | validator.isEmail, 121 | 'Invalid Email', 122 | ], 123 | select: false 124 | }, 125 | confirmed: { 126 | type: Boolean, 127 | required: true, 128 | default: false, 129 | }, 130 | declined: { 131 | type: Boolean, 132 | required: true, 133 | default: false, 134 | }, 135 | checkedIn: { 136 | type: Boolean, 137 | required: true, 138 | default: false, 139 | }, 140 | checkInTime: { 141 | type: Number, 142 | }, 143 | confirmBy: { 144 | type: Number 145 | }, 146 | reimbursementGiven: { 147 | type: Boolean, 148 | default: false 149 | } 150 | }; 151 | 152 | // define the schema for our admin model 153 | var schema = new mongoose.Schema({ 154 | 155 | email: { 156 | type: String, 157 | required: true, 158 | unique: true, 159 | validate: [ 160 | validator.isEmail, 161 | 'Invalid Email', 162 | ] 163 | }, 164 | 165 | password: { 166 | type: String, 167 | required: true, 168 | select: false 169 | }, 170 | 171 | admin: { 172 | type: Boolean, 173 | required: true, 174 | default: false, 175 | }, 176 | 177 | timestamp: { 178 | type: Number, 179 | required: true, 180 | default: Date.now(), 181 | }, 182 | 183 | lastUpdated: { 184 | type: Number, 185 | default: Date.now(), 186 | }, 187 | 188 | teamCode: { 189 | type: String, 190 | min: 0, 191 | max: 140, 192 | }, 193 | 194 | verified: { 195 | type: Boolean, 196 | required: true, 197 | default: false 198 | }, 199 | 200 | salt: { 201 | type: Number, 202 | required: true, 203 | default: Date.now(), 204 | select: false 205 | }, 206 | 207 | /** 208 | * User Profile. 209 | * 210 | * This is the only part of the user that the user can edit. 211 | * 212 | * Profile validation will exist here. 213 | */ 214 | profile: profile, 215 | 216 | /** 217 | * Confirmation information 218 | * 219 | * Extension of the user model, but can only be edited after acceptance. 220 | */ 221 | confirmation: confirmation, 222 | 223 | status: status, 224 | 225 | }); 226 | 227 | schema.set('toJSON', { 228 | virtuals: true 229 | }); 230 | 231 | schema.set('toObject', { 232 | virtuals: true 233 | }); 234 | 235 | //========================================= 236 | // Instance Methods 237 | //========================================= 238 | 239 | // checking if this password matches 240 | schema.methods.checkPassword = function(password) { 241 | return bcrypt.compareSync(password, this.password); 242 | }; 243 | 244 | // Token stuff 245 | schema.methods.generateEmailVerificationToken = function(){ 246 | return jwt.sign(this.email, JWT_SECRET); 247 | }; 248 | 249 | schema.methods.generateAuthToken = function(){ 250 | return jwt.sign(this._id, JWT_SECRET); 251 | }; 252 | 253 | /** 254 | * Generate a temporary authentication token (for changing passwords) 255 | * @return JWT 256 | * payload: { 257 | * id: userId 258 | * iat: issued at ms 259 | * exp: expiration ms 260 | * } 261 | */ 262 | schema.methods.generateTempAuthToken = function(){ 263 | return jwt.sign({ 264 | id: this._id 265 | }, JWT_SECRET, { 266 | expiresInMinutes: 60, 267 | }); 268 | }; 269 | 270 | //========================================= 271 | // Static Methods 272 | //========================================= 273 | 274 | schema.statics.generateHash = function(password) { 275 | return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null); 276 | }; 277 | 278 | /** 279 | * Verify an an email verification token. 280 | * @param {[type]} token token 281 | * @param {Function} cb args(err, email) 282 | */ 283 | schema.statics.verifyEmailVerificationToken = function(token, callback){ 284 | jwt.verify(token, JWT_SECRET, function(err, email){ 285 | return callback(err, email); 286 | }); 287 | }; 288 | 289 | /** 290 | * Verify a temporary authentication token. 291 | * @param {[type]} token temporary auth token 292 | * @param {Function} callback args(err, id) 293 | */ 294 | schema.statics.verifyTempAuthToken = function(token, callback){ 295 | jwt.verify(token, JWT_SECRET, function(err, payload){ 296 | 297 | if (err || !payload){ 298 | return callback(err); 299 | } 300 | 301 | if (!payload.exp || Date.now() >= payload.exp * 1000){ 302 | return callback({ 303 | message: 'Token has expired.' 304 | }); 305 | } 306 | 307 | return callback(null, payload.id); 308 | }); 309 | }; 310 | 311 | schema.statics.findOneByEmail = function(email){ 312 | return this.findOne({ 313 | email: email.toLowerCase() 314 | }); 315 | }; 316 | 317 | /** 318 | * Get a single user using a signed token. 319 | * @param {String} token User's authentication token. 320 | * @param {Function} callback args(err, user) 321 | */ 322 | schema.statics.getByToken = function(token, callback){ 323 | jwt.verify(token, JWT_SECRET, function(err, id){ 324 | if (err) { 325 | return callback(err); 326 | } 327 | this.findOne({_id: id}, callback); 328 | }.bind(this)); 329 | }; 330 | 331 | schema.statics.validateProfile = function(profile, cb){ 332 | return cb(!( 333 | profile.name.length > 0 && 334 | profile.adult && 335 | profile.school.length > 0 && 336 | ['2019', '2020', '2021', '2022'].indexOf(profile.graduationYear) > -1 && 337 | ['M', 'F', 'O', 'N'].indexOf(profile.gender) > -1 338 | )); 339 | }; 340 | 341 | //========================================= 342 | // Virtuals 343 | //========================================= 344 | 345 | /** 346 | * Has the user completed their profile? 347 | * This provides a verbose explanation of their furthest state. 348 | */ 349 | schema.virtual('status.name').get(function(){ 350 | 351 | if (this.status.checkedIn) { 352 | return 'checked in'; 353 | } 354 | 355 | if (this.status.declined) { 356 | return "declined"; 357 | } 358 | 359 | if (this.status.confirmed) { 360 | return "confirmed"; 361 | } 362 | 363 | if (this.status.admitted) { 364 | return "admitted"; 365 | } 366 | 367 | if (this.status.completedProfile){ 368 | return "submitted"; 369 | } 370 | 371 | if (!this.verified){ 372 | return "unverified"; 373 | } 374 | 375 | return "incomplete"; 376 | 377 | }); 378 | 379 | module.exports = mongoose.model('User', schema); 380 | -------------------------------------------------------------------------------- /app/server/routes.js: -------------------------------------------------------------------------------- 1 | var User = require('./models/User'); 2 | 3 | module.exports = function(app) { 4 | 5 | // Application ------------------------------------------ 6 | app.get('/', function(req, res){ 7 | res.sendfile('./app/client/index.html'); 8 | }); 9 | 10 | // Wildcard all other GET requests to the angular app 11 | app.get('*', function(req, res){ 12 | res.sendfile('./app/client/index.html'); 13 | }); 14 | 15 | }; 16 | -------------------------------------------------------------------------------- /app/server/routes/api.js: -------------------------------------------------------------------------------- 1 | var UserController = require('../controllers/UserController'); 2 | var SettingsController = require('../controllers/SettingsController'); 3 | 4 | var request = require('request'); 5 | 6 | module.exports = function(router) { 7 | 8 | function getToken(req){ 9 | return req.headers['x-access-token']; 10 | } 11 | 12 | /** 13 | * Using the access token provided, check to make sure that 14 | * you are, indeed, an admin. 15 | */ 16 | function isAdmin(req, res, next){ 17 | 18 | var token = getToken(req); 19 | 20 | UserController.getByToken(token, function(err, user){ 21 | 22 | if (err) { 23 | return res.status(500).send(err); 24 | } 25 | 26 | if (user && user.admin){ 27 | req.user = user; 28 | return next(); 29 | } 30 | 31 | return res.status(401).send({ 32 | message: 'Get outta here, punk!' 33 | }); 34 | 35 | }); 36 | } 37 | 38 | /** 39 | * [Users API Only] 40 | * 41 | * Check that the id param matches the id encoded in the 42 | * access token provided. 43 | * 44 | * That, or you're the admin, so you can do whatever you 45 | * want I suppose! 46 | */ 47 | function isOwnerOrAdmin(req, res, next){ 48 | var token = getToken(req); 49 | var userId = req.params.id; 50 | 51 | UserController.getByToken(token, function(err, user){ 52 | 53 | if (err || !user) { 54 | return res.status(500).send(err); 55 | } 56 | 57 | if (user._id == userId || user.admin){ 58 | return next(); 59 | } 60 | return res.status(400).send({ 61 | message: 'Token does not match user id.' 62 | }); 63 | }); 64 | } 65 | 66 | /** 67 | * Default response to send an error and the data. 68 | * @param {[type]} res [description] 69 | * @return {[type]} [description] 70 | */ 71 | function defaultResponse(req, res){ 72 | return function(err, data){ 73 | if (err){ 74 | // SLACK ALERT! 75 | if (process.env.NODE_ENV === 'production'){ 76 | request 77 | .post(process.env.SLACK_HOOK, 78 | { 79 | form: { 80 | payload: JSON.stringify({ 81 | "text": 82 | "``` \n" + 83 | "Request: \n " + 84 | req.method + ' ' + req.url + 85 | "\n ------------------------------------ \n" + 86 | "Body: \n " + 87 | JSON.stringify(req.body, null, 2) + 88 | "\n ------------------------------------ \n" + 89 | "\nError:\n" + 90 | JSON.stringify(err, null, 2) + 91 | "``` \n" 92 | }) 93 | } 94 | }, 95 | function (error, response, body) { 96 | return res.status(500).send({ 97 | message: "Your error has been recorded, we'll get right on it!" 98 | }); 99 | } 100 | ); 101 | } else { 102 | return res.status(500).send(err); 103 | } 104 | } else { 105 | 106 | return res.json(data); 107 | } 108 | }; 109 | } 110 | 111 | /** 112 | * API! 113 | */ 114 | 115 | // --------------------------------------------- 116 | // Users 117 | // --------------------------------------------- 118 | 119 | /** 120 | * [ADMIN ONLY] 121 | * 122 | * GET - Get all users, or a page at a time. 123 | * ex. Paginate with ?page=0&size=100 124 | */ 125 | router.get('/users', isAdmin, function(req, res){ 126 | var query = req.query; 127 | 128 | if (query.page && query.size){ 129 | 130 | UserController.getPage(query, defaultResponse(req, res)); 131 | 132 | } else { 133 | 134 | UserController.getAll(defaultResponse(req, res)); 135 | 136 | } 137 | }); 138 | 139 | /** 140 | * [ADMIN ONLY] 141 | */ 142 | router.get('/users/stats', isAdmin, function(req, res){ 143 | UserController.getStats(defaultResponse(req, res)); 144 | }); 145 | 146 | /** 147 | * [OWNER/ADMIN] 148 | * 149 | * GET - Get a specific user. 150 | */ 151 | router.get('/users/:id', isOwnerOrAdmin, function(req, res){ 152 | UserController.getById(req.params.id, defaultResponse(req, res)); 153 | }); 154 | 155 | /** 156 | * [OWNER/ADMIN] 157 | * 158 | * PUT - Update a specific user's profile. 159 | */ 160 | router.put('/users/:id/profile', isOwnerOrAdmin, function(req, res){ 161 | var profile = req.body.profile; 162 | var id = req.params.id; 163 | 164 | UserController.updateProfileById(id, profile , defaultResponse(req, res)); 165 | }); 166 | 167 | /** 168 | * [OWNER/ADMIN] 169 | * 170 | * PUT - Update a specific user's confirmation information. 171 | */ 172 | router.put('/users/:id/confirm', isOwnerOrAdmin, function(req, res){ 173 | var confirmation = req.body.confirmation; 174 | var id = req.params.id; 175 | 176 | UserController.updateConfirmationById(id, confirmation, defaultResponse(req, res)); 177 | }); 178 | 179 | /** 180 | * [OWNER/ADMIN] 181 | * 182 | * POST - Decline an acceptance. 183 | */ 184 | router.post('/users/:id/decline', isOwnerOrAdmin, function(req, res){ 185 | var confirmation = req.body.confirmation; 186 | var id = req.params.id; 187 | 188 | UserController.declineById(id, defaultResponse(req, res)); 189 | }); 190 | 191 | /** 192 | * Get a user's team member's names. Uses the code associated 193 | * with the user making the request. 194 | */ 195 | router.get('/users/:id/team', isOwnerOrAdmin, function(req, res){ 196 | var id = req.params.id; 197 | UserController.getTeammates(id, defaultResponse(req, res)); 198 | }); 199 | 200 | /** 201 | * Update a teamcode. Join/Create a team here. 202 | * { 203 | * code: STRING 204 | * } 205 | */ 206 | router.put('/users/:id/team', isOwnerOrAdmin, function(req, res){ 207 | var code = req.body.code; 208 | var id = req.params.id; 209 | 210 | UserController.createOrJoinTeam(id, code, defaultResponse(req, res)); 211 | 212 | }); 213 | 214 | /** 215 | * Remove a user from a team. 216 | */ 217 | router.delete('/users/:id/team', isOwnerOrAdmin, function(req, res){ 218 | var id = req.params.id; 219 | 220 | UserController.leaveTeam(id, defaultResponse(req, res)); 221 | }); 222 | 223 | /** 224 | * Update a user's password. 225 | * { 226 | * oldPassword: STRING, 227 | * newPassword: STRING 228 | * } 229 | */ 230 | router.put('/users/:id/password', isOwnerOrAdmin, function(req, res){ 231 | return res.status(304).send(); 232 | // Currently disable. 233 | // var id = req.params.id; 234 | // var old = req.body.oldPassword; 235 | // var pass = req.body.newPassword; 236 | 237 | // UserController.changePassword(id, old, pass, function(err, user){ 238 | // if (err || !user){ 239 | // return res.status(400).send(err); 240 | // } 241 | // return res.json(user); 242 | // }); 243 | }); 244 | 245 | /** 246 | * Admit a user. ADMIN ONLY, DUH 247 | * 248 | * Also attaches the user who did the admitting, for liabaility. 249 | */ 250 | router.post('/users/:id/admit', isAdmin, function(req, res){ 251 | // Accept the hacker. Admin only 252 | var id = req.params.id; 253 | var user = req.user; 254 | UserController.admitUser(id, user, defaultResponse(req, res)); 255 | }); 256 | 257 | /** 258 | * Check in a user. ADMIN ONLY, DUH 259 | */ 260 | router.post('/users/:id/checkin', isAdmin, function(req, res){ 261 | var id = req.params.id; 262 | var user = req.user; 263 | UserController.checkInById(id, user, defaultResponse(req, res)); 264 | }); 265 | 266 | /** 267 | * Check in a user. ADMIN ONLY, DUH 268 | */ 269 | router.post('/users/:id/checkout', isAdmin, function(req, res){ 270 | var id = req.params.id; 271 | var user = req.user; 272 | UserController.checkOutById(id, user, defaultResponse(req, res)); 273 | }); 274 | 275 | /** 276 | * Make user an admin 277 | */ 278 | router.post('/users/:id/makeadmin', isAdmin, function(req, res){ 279 | var id = req.params.id; 280 | var user = req.user; 281 | UserController.makeAdminById(id, user, defaultResponse(req, res)); 282 | }); 283 | 284 | /** 285 | * Demote user 286 | */ 287 | router.post('/users/:id/removeadmin', isAdmin, function(req, res){ 288 | var id = req.params.id; 289 | var user = req.user; 290 | UserController.removeAdminById(id, user, defaultResponse(req, res)); 291 | }); 292 | 293 | 294 | // --------------------------------------------- 295 | // Settings [ADMIN ONLY!] 296 | // --------------------------------------------- 297 | 298 | /** 299 | * Get the public settings. 300 | * res: { 301 | * timeOpen: Number, 302 | * timeClose: Number, 303 | * timeToConfirm: Number, 304 | * acceptanceText: String, 305 | * confirmationText: String, 306 | * allowMinors: Boolean 307 | * } 308 | */ 309 | router.get('/settings', function(req, res){ 310 | SettingsController.getPublicSettings(defaultResponse(req, res)); 311 | }); 312 | 313 | /** 314 | * Update the acceptance text. 315 | * body: { 316 | * text: String 317 | * } 318 | */ 319 | router.put('/settings/waitlist', isAdmin, function(req, res){ 320 | var text = req.body.text; 321 | SettingsController.updateField('waitlistText', text, defaultResponse(req, res)); 322 | }); 323 | 324 | /** 325 | * Update the acceptance text. 326 | * body: { 327 | * text: String 328 | * } 329 | */ 330 | router.put('/settings/acceptance', isAdmin, function(req, res){ 331 | var text = req.body.text; 332 | SettingsController.updateField('acceptanceText', text, defaultResponse(req, res)); 333 | }); 334 | 335 | /** 336 | * Update the confirmation text. 337 | * body: { 338 | * text: String 339 | * } 340 | */ 341 | router.put('/settings/confirmation', isAdmin, function(req, res){ 342 | var text = req.body.text; 343 | SettingsController.updateField('confirmationText', text, defaultResponse(req, res)); 344 | }); 345 | 346 | /** 347 | * Update the confirmation date. 348 | * body: { 349 | * time: Number 350 | * } 351 | */ 352 | router.put('/settings/confirm-by', isAdmin, function(req, res){ 353 | var time = req.body.time; 354 | SettingsController.updateField('timeConfirm', time, defaultResponse(req, res)); 355 | }); 356 | 357 | /** 358 | * Set the registration open and close times. 359 | * body : { 360 | * timeOpen: Number, 361 | * timeClose: Number 362 | * } 363 | */ 364 | router.put('/settings/times', isAdmin, function(req, res){ 365 | var open = req.body.timeOpen; 366 | var close = req.body.timeClose; 367 | SettingsController.updateRegistrationTimes(open, close, defaultResponse(req, res)); 368 | }); 369 | 370 | /** 371 | * Get the whitelisted emails. 372 | * 373 | * res: { 374 | * emails: [String] 375 | * } 376 | */ 377 | router.get('/settings/whitelist', isAdmin, function(req, res){ 378 | SettingsController.getWhitelistedEmails(defaultResponse(req, res)); 379 | }); 380 | 381 | /** 382 | * [ADMIN ONLY] 383 | * { 384 | * emails: [String] 385 | * } 386 | * res: Settings 387 | * 388 | */ 389 | router.put('/settings/whitelist', isAdmin, function(req, res){ 390 | var emails = req.body.emails; 391 | SettingsController.updateWhitelistedEmails(emails, defaultResponse(req, res)); 392 | }); 393 | 394 | /** 395 | * [ADMIN ONLY] 396 | * { 397 | * allowMinors: Boolean 398 | * } 399 | * res: Settings 400 | * 401 | */ 402 | router.put('/settings/minors', isAdmin, function(req, res){ 403 | var allowMinors = req.body.allowMinors; 404 | SettingsController.updateField('allowMinors', allowMinors, defaultResponse(req, res)); 405 | }); 406 | 407 | }; 408 | -------------------------------------------------------------------------------- /app/server/routes/auth.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | var jwt = require('jsonwebtoken'); 3 | var validator = require('validator'); 4 | 5 | var SettingsController = require('../controllers/SettingsController'); 6 | var UserController = require('../controllers/UserController'); 7 | 8 | module.exports = function(router){ 9 | 10 | // --------------------------------------------- 11 | // AUTHENTICATION 12 | // --------------------------------------------- 13 | 14 | /** 15 | * Login a user with a username (email) and password. 16 | * Find em', check em'. 17 | * Pass them an authentication token on success. 18 | * Otherwise, 401. You fucked up. 19 | * 20 | * body { 21 | * email: email, 22 | * password: password 23 | * token: ? 24 | * } 25 | * 26 | */ 27 | router.post('/login', 28 | function(req, res, next){ 29 | var email = req.body.email; 30 | var password = req.body.password; 31 | var token = req.body.token; 32 | 33 | if (token) { 34 | UserController.loginWithToken(token, 35 | function(err, token, user){ 36 | if (err || !user) { 37 | return res.status(400).send(err); 38 | } 39 | return res.json({ 40 | token: token, 41 | user: user 42 | }); 43 | }); 44 | } else { 45 | UserController.loginWithPassword(email, password, 46 | function(err, token, user){ 47 | if (err || !user) { 48 | return res.status(400).send(err); 49 | } 50 | return res.json({ 51 | token: token, 52 | user: user 53 | }); 54 | }); 55 | } 56 | 57 | }); 58 | 59 | /** 60 | * Register a user with a username (email) and password. 61 | * If it already exists, then don't register, duh. 62 | * 63 | * body { 64 | * email: email, 65 | * password: password 66 | * } 67 | * 68 | */ 69 | router.post('/register', 70 | function(req, res, next){ 71 | // Register with an email and password 72 | var email = req.body.email; 73 | var password = req.body.password; 74 | 75 | UserController.createUser(email, password, 76 | function(err, user){ 77 | if (err){ 78 | return res.status(400).send(err); 79 | } 80 | return res.json(user); 81 | }); 82 | }); 83 | 84 | router.post('/reset', 85 | function(req, res, next){ 86 | var email = req.body.email; 87 | if (!email){ 88 | return res.status(400).send(); 89 | } 90 | 91 | UserController.sendPasswordResetEmail(email, function(err){ 92 | if(err){ 93 | return res.status(400).send(err); 94 | } 95 | return res.json({ 96 | message: 'Email Sent' 97 | }); 98 | }); 99 | }); 100 | 101 | /** 102 | * Reset user's password. 103 | * { 104 | * token: STRING 105 | * password: STRING, 106 | * } 107 | */ 108 | router.post('/reset/password', function(req, res){ 109 | var pass = req.body.password; 110 | var token = req.body.token; 111 | 112 | UserController.resetPassword(token, pass, function(err, user){ 113 | if (err || !user){ 114 | return res.status(400).send(err); 115 | } 116 | return res.json(user); 117 | }); 118 | }); 119 | 120 | /** 121 | * Resend a password verification email for this user. 122 | * 123 | * body { 124 | * id: user id 125 | * } 126 | */ 127 | router.post('/verify/resend', 128 | function(req, res, next){ 129 | var id = req.body.id; 130 | if (id){ 131 | UserController.sendVerificationEmailById(id, function(err, user){ 132 | if (err || !user){ 133 | return res.status(400).send(); 134 | } 135 | return res.status(200).send(); 136 | }); 137 | } else { 138 | return res.status(400).send(); 139 | } 140 | }); 141 | 142 | /** 143 | * Verify a user with a given token. 144 | */ 145 | router.get('/verify/:token', 146 | function(req, res, next){ 147 | var token = req.params.token; 148 | UserController.verifyByToken(token, function(err, user){ 149 | 150 | if (err || !user){ 151 | return res.status(400).send(err); 152 | } 153 | 154 | return res.json(user); 155 | 156 | }); 157 | }); 158 | 159 | }; -------------------------------------------------------------------------------- /app/server/services/email.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var nodemailer = require('nodemailer'); 3 | var smtpTransport = require('nodemailer-smtp-transport'); 4 | 5 | var templatesDir = path.join(__dirname, '../templates'); 6 | var emailTemplates = require('email-templates'); 7 | 8 | var ROOT_URL = process.env.ROOT_URL; 9 | 10 | var HACKATHON_NAME = process.env.HACKATHON_NAME; 11 | var EMAIL_ADDRESS = process.env.EMAIL_ADDRESS; 12 | var TWITTER_HANDLE = process.env.TWITTER_HANDLE; 13 | var FACEBOOK_HANDLE = process.env.FACEBOOK_HANDLE; 14 | 15 | var EMAIL_HOST = process.env.EMAIL_HOST; 16 | var EMAIL_USER = process.env.EMAIL_USER; 17 | var EMAIL_PASS = process.env.EMAIL_PASS; 18 | var EMAIL_PORT = process.env.EMAIL_PORT; 19 | var EMAIL_CONTACT = process.env.EMAIL_CONTACT; 20 | var EMAIL_HEADER_IMAGE = process.env.EMAIL_HEADER_IMAGE; 21 | if(EMAIL_HEADER_IMAGE.indexOf("https") == -1){ 22 | EMAIL_HEADER_IMAGE = ROOT_URL + EMAIL_HEADER_IMAGE; 23 | } 24 | 25 | var NODE_ENV = process.env.NODE_ENV; 26 | 27 | var options = { 28 | host: EMAIL_HOST, 29 | port: EMAIL_PORT, 30 | secure: true, 31 | auth: { 32 | user: EMAIL_USER, 33 | pass: EMAIL_PASS 34 | } 35 | }; 36 | 37 | var transporter = nodemailer.createTransport(smtpTransport(options)); 38 | 39 | var controller = {}; 40 | 41 | controller.transporter = transporter; 42 | 43 | function sendOne(templateName, options, data, callback){ 44 | 45 | if (NODE_ENV === "dev") { 46 | console.log(templateName); 47 | console.log(JSON.stringify(data, "", 2)); 48 | } 49 | 50 | emailTemplates(templatesDir, function(err, template){ 51 | if (err) { 52 | return callback(err); 53 | } 54 | 55 | data.emailHeaderImage = EMAIL_HEADER_IMAGE; 56 | data.emailAddress = EMAIL_ADDRESS; 57 | data.hackathonName = HACKATHON_NAME; 58 | data.twitterHandle = TWITTER_HANDLE; 59 | data.facebookHandle = FACEBOOK_HANDLE; 60 | template(templateName, data, function(err, html, text){ 61 | if (err) { 62 | return callback(err); 63 | } 64 | 65 | transporter.sendMail({ 66 | from: EMAIL_CONTACT, 67 | to: options.to, 68 | subject: options.subject, 69 | html: html, 70 | text: text 71 | }, function(err, info){ 72 | if(callback){ 73 | callback(err, info); 74 | } 75 | }); 76 | }); 77 | }); 78 | } 79 | 80 | /** 81 | * Send a verification email to a user, with a verification token to enter. 82 | * @param {[type]} email [description] 83 | * @param {[type]} token [description] 84 | * @param {Function} callback [description] 85 | * @return {[type]} [description] 86 | */ 87 | controller.sendVerificationEmail = function(email, token, callback) { 88 | 89 | var options = { 90 | to: email, 91 | subject: "["+HACKATHON_NAME+"] - Verify your email" 92 | }; 93 | 94 | var locals = { 95 | verifyUrl: ROOT_URL + '/verify/' + token 96 | }; 97 | 98 | /** 99 | * Eamil-verify takes a few template values: 100 | * { 101 | * verifyUrl: the url that the user must visit to verify their account 102 | * } 103 | */ 104 | sendOne('email-verify', options, locals, function(err, info){ 105 | if (err){ 106 | console.log(err); 107 | } 108 | if (info){ 109 | console.log(info.message); 110 | } 111 | if (callback){ 112 | callback(err, info); 113 | } 114 | }); 115 | 116 | }; 117 | 118 | /** 119 | * Send a password recovery email. 120 | * @param {[type]} email [description] 121 | * @param {[type]} token [description] 122 | * @param {Function} callback [description] 123 | */ 124 | controller.sendPasswordResetEmail = function(email, token, callback) { 125 | 126 | var options = { 127 | to: email, 128 | subject: "["+HACKATHON_NAME+"] - Password reset requested!" 129 | }; 130 | 131 | var locals = { 132 | title: 'Password Reset Request', 133 | subtitle: '', 134 | description: 'Somebody (hopefully you!) has requested that your password be reset. If ' + 135 | 'this was not you, feel free to disregard this email. This link will expire in one hour.', 136 | actionUrl: ROOT_URL + '/reset/' + token, 137 | actionName: "Reset Password" 138 | }; 139 | 140 | /** 141 | * Eamil-verify takes a few template values: 142 | * { 143 | * verifyUrl: the url that the user must visit to verify their account 144 | * } 145 | */ 146 | sendOne('email-link-action', options, locals, function(err, info){ 147 | if (err){ 148 | console.log(err); 149 | } 150 | if (info){ 151 | console.log(info.message); 152 | } 153 | if (callback){ 154 | callback(err, info); 155 | } 156 | }); 157 | 158 | }; 159 | 160 | /** 161 | * Send a password recovery email. 162 | * @param {[type]} email [description] 163 | * @param {Function} callback [description] 164 | */ 165 | controller.sendPasswordChangedEmail = function(email, callback){ 166 | 167 | var options = { 168 | to: email, 169 | subject: "["+HACKATHON_NAME+"] - Your password has been changed!" 170 | }; 171 | 172 | var locals = { 173 | title: 'Password Updated', 174 | body: 'Somebody (hopefully you!) has successfully changed your password.', 175 | }; 176 | 177 | /** 178 | * Eamil-verify takes a few template values: 179 | * { 180 | * verifyUrl: the url that the user must visit to verify their account 181 | * } 182 | */ 183 | sendOne('email-basic', options, locals, function(err, info){ 184 | if (err){ 185 | console.log(err); 186 | } 187 | if (info){ 188 | console.log(info.message); 189 | } 190 | if (callback){ 191 | callback(err, info); 192 | } 193 | }); 194 | 195 | }; 196 | 197 | module.exports = controller; 198 | -------------------------------------------------------------------------------- /app/server/services/stats.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | var async = require('async'); 3 | var User = require('../models/User'); 4 | 5 | // In memory stats. 6 | var stats = {}; 7 | function calculateStats(){ 8 | console.log('Calculating stats...'); 9 | var newStats = { 10 | lastUpdated: 0, 11 | 12 | total: 0, 13 | demo: { 14 | gender: { 15 | M: 0, 16 | F: 0, 17 | O: 0, 18 | N: 0 19 | }, 20 | schools: {}, 21 | year: { 22 | '2019': 0, 23 | '2020': 0, 24 | '2021': 0, 25 | '2022': 0, 26 | } 27 | }, 28 | 29 | teams: {}, 30 | verified: 0, 31 | submitted: 0, 32 | admitted: 0, 33 | confirmed: 0, 34 | confirmedMit: 0, 35 | declined: 0, 36 | 37 | confirmedFemale: 0, 38 | confirmedMale: 0, 39 | confirmedOther: 0, 40 | confirmedNone: 0, 41 | 42 | shirtSizes: { 43 | 'XS': 0, 44 | 'S': 0, 45 | 'M': 0, 46 | 'L': 0, 47 | 'XL': 0, 48 | 'XXL': 0, 49 | 'WXS': 0, 50 | 'WS': 0, 51 | 'WM': 0, 52 | 'WL': 0, 53 | 'WXL': 0, 54 | 'WXXL': 0, 55 | 'None': 0 56 | }, 57 | 58 | dietaryRestrictions: {}, 59 | 60 | hostNeededFri: 0, 61 | hostNeededSat: 0, 62 | hostNeededUnique: 0, 63 | 64 | hostNeededFemale: 0, 65 | hostNeededMale: 0, 66 | hostNeededOther: 0, 67 | hostNeededNone: 0, 68 | 69 | reimbursementTotal: 0, 70 | reimbursementMissing: 0, 71 | 72 | wantsHardware: 0, 73 | 74 | checkedIn: 0 75 | }; 76 | 77 | User 78 | .find({}) 79 | .exec(function(err, users){ 80 | if (err || !users){ 81 | throw err; 82 | } 83 | 84 | newStats.total = users.length; 85 | 86 | async.each(users, function(user, callback){ 87 | 88 | // Grab the email extension 89 | var email = user.email.split('@')[1]; 90 | 91 | // Add to the gender 92 | newStats.demo.gender[user.profile.gender] += 1; 93 | 94 | // Count verified 95 | newStats.verified += user.verified ? 1 : 0; 96 | 97 | // Count submitted 98 | newStats.submitted += user.status.completedProfile ? 1 : 0; 99 | 100 | // Count accepted 101 | newStats.admitted += user.status.admitted ? 1 : 0; 102 | 103 | // Count confirmed 104 | newStats.confirmed += user.status.confirmed ? 1 : 0; 105 | 106 | // Count confirmed that are mit 107 | newStats.confirmedMit += user.status.confirmed && email === "iiitv.ac.in" ? 1 : 0 + user.status.confirmed && email === "iiitvadodara.ac.in" ? 1 : 0; 108 | 109 | newStats.confirmedFemale += user.status.confirmed && user.profile.gender == "F" ? 1 : 0; 110 | newStats.confirmedMale += user.status.confirmed && user.profile.gender == "M" ? 1 : 0; 111 | newStats.confirmedOther += user.status.confirmed && user.profile.gender == "O" ? 1 : 0; 112 | newStats.confirmedNone += user.status.confirmed && user.profile.gender == "N" ? 1 : 0; 113 | 114 | // Count declined 115 | newStats.declined += user.status.declined ? 1 : 0; 116 | 117 | // Count the number of people who need reimbursements 118 | newStats.reimbursementTotal += user.confirmation.needsReimbursement ? 1 : 0; 119 | 120 | // Count the number of people who still need to be reimbursed 121 | newStats.reimbursementMissing += user.confirmation.needsReimbursement && 122 | !user.status.reimbursementGiven ? 1 : 0; 123 | 124 | // Count the number of people who want hardware 125 | newStats.wantsHardware += user.confirmation.wantsHardware ? 1 : 0; 126 | 127 | // Count schools 128 | if (!newStats.demo.schools[email]){ 129 | newStats.demo.schools[email] = { 130 | submitted: 0, 131 | admitted: 0, 132 | confirmed: 0, 133 | declined: 0, 134 | }; 135 | } 136 | newStats.demo.schools[email].submitted += user.status.completedProfile ? 1 : 0; 137 | newStats.demo.schools[email].admitted += user.status.admitted ? 1 : 0; 138 | newStats.demo.schools[email].confirmed += user.status.confirmed ? 1 : 0; 139 | newStats.demo.schools[email].declined += user.status.declined ? 1 : 0; 140 | 141 | // Count graduation years 142 | if (user.profile.graduationYear){ 143 | newStats.demo.year[user.profile.graduationYear] += 1; 144 | } 145 | 146 | // Grab the team name if there is one 147 | // if (user.teamCode && user.teamCode.length > 0){ 148 | // if (!newStats.teams[user.teamCode]){ 149 | // newStats.teams[user.teamCode] = []; 150 | // } 151 | // newStats.teams[user.teamCode].push(user.profile.name); 152 | // } 153 | 154 | // Count shirt sizes 155 | if (user.confirmation.shirtSize in newStats.shirtSizes){ 156 | newStats.shirtSizes[user.confirmation.shirtSize] += 1; 157 | } 158 | 159 | // Host needed counts 160 | newStats.hostNeededFri += user.confirmation.hostNeededFri ? 1 : 0; 161 | newStats.hostNeededSat += user.confirmation.hostNeededSat ? 1 : 0; 162 | newStats.hostNeededUnique += user.confirmation.hostNeededFri || user.confirmation.hostNeededSat ? 1 : 0; 163 | 164 | newStats.hostNeededFemale 165 | += (user.confirmation.hostNeededFri || user.confirmation.hostNeededSat) && user.profile.gender == "F" ? 1 : 0; 166 | newStats.hostNeededMale 167 | += (user.confirmation.hostNeededFri || user.confirmation.hostNeededSat) && user.profile.gender == "M" ? 1 : 0; 168 | newStats.hostNeededOther 169 | += (user.confirmation.hostNeededFri || user.confirmation.hostNeededSat) && user.profile.gender == "O" ? 1 : 0; 170 | newStats.hostNeededNone 171 | += (user.confirmation.hostNeededFri || user.confirmation.hostNeededSat) && user.profile.gender == "N" ? 1 : 0; 172 | 173 | // Dietary restrictions 174 | if (user.confirmation.dietaryRestrictions){ 175 | user.confirmation.dietaryRestrictions.forEach(function(restriction){ 176 | if (!newStats.dietaryRestrictions[restriction]){ 177 | newStats.dietaryRestrictions[restriction] = 0; 178 | } 179 | newStats.dietaryRestrictions[restriction] += 1; 180 | }); 181 | } 182 | 183 | // Count checked in 184 | newStats.checkedIn += user.status.checkedIn ? 1 : 0; 185 | 186 | callback(); // let async know we've finished 187 | }, function() { 188 | // Transform dietary restrictions into a series of objects 189 | var restrictions = []; 190 | _.keys(newStats.dietaryRestrictions) 191 | .forEach(function(key){ 192 | restrictions.push({ 193 | name: key, 194 | count: newStats.dietaryRestrictions[key], 195 | }); 196 | }); 197 | newStats.dietaryRestrictions = restrictions; 198 | 199 | // Transform schools into an array of objects 200 | var schools = []; 201 | _.keys(newStats.demo.schools) 202 | .forEach(function(key){ 203 | schools.push({ 204 | email: key, 205 | count: newStats.demo.schools[key].submitted, 206 | stats: newStats.demo.schools[key] 207 | }); 208 | }); 209 | newStats.demo.schools = schools; 210 | 211 | // Likewise, transform the teams into an array of objects 212 | // var teams = []; 213 | // _.keys(newStats.teams) 214 | // .forEach(function(key){ 215 | // teams.push({ 216 | // name: key, 217 | // users: newStats.teams[key] 218 | // }); 219 | // }); 220 | // newStats.teams = teams; 221 | 222 | console.log('Stats updated!'); 223 | newStats.lastUpdated = new Date(); 224 | stats = newStats; 225 | }); 226 | }); 227 | 228 | } 229 | 230 | // Calculate once every five minutes. 231 | calculateStats(); 232 | setInterval(calculateStats, 300000); 233 | 234 | var Stats = {}; 235 | 236 | Stats.getUserStats = function(){ 237 | return stats; 238 | }; 239 | 240 | module.exports = Stats; 241 | -------------------------------------------------------------------------------- /app/server/templates/email-basic/html.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | *|MC:SUBJECT|* 6 | 7 | 8 |
9 | 10 | 11 | 74 | 75 |
12 | 13 | 14 | 15 | 26 | 27 | 28 | 50 | 51 | 52 | 70 | 71 |
16 | 17 | 18 | 19 | 22 | 23 |
20 | 21 |
24 | 25 |
29 | 30 | 31 | 32 | 37 | 38 | 39 | 44 | 45 | 46 | 47 |
33 |

{{title}}

34 |

{{subtitle}}

35 | {{body}} 36 |
40 | Thanks, 41 |
42 | The {{ hackathonName }} Team 43 |
48 | 49 |
53 | 54 | 55 | 56 | 60 | 61 | 62 | 66 | 67 |
57 | Follow on Twitter   Like on Facebook    58 | Email Us 59 |
63 | Copyright © {{ hackathonName }} 2020, All rights reserved. 64 |
65 |
68 | 69 |
72 | 73 |
76 |
77 | 78 | 79 | -------------------------------------------------------------------------------- /app/server/templates/email-basic/text.hbs: -------------------------------------------------------------------------------- 1 | {{title}} 2 | 3 | {{subtitle}} 4 | 5 | {{body}} 6 | 7 | Thanks, 8 | HackIIITV Team -------------------------------------------------------------------------------- /app/server/templates/email-link-action/html.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | *|MC:SUBJECT|* 6 | 7 | 8 |
9 | 10 | 11 | 80 | 81 |
12 | 13 | 14 | 15 | 26 | 27 | 28 | 56 | 57 | 58 | 76 | 77 |
16 | 17 | 18 | 19 | 22 | 23 |
20 | 21 |
24 | 25 |
29 | 30 | 31 | 32 | 43 | 44 | 45 | 50 | 51 | 52 | 53 |
33 |

{{title}}

34 |

{{subtitle}}

35 | {{description}} 36 |
37 |
38 |
39 | 40 | {{actionName}} 41 | 42 |
46 | Thanks, 47 |
48 | The {{ hackathonName }} Team 49 |
54 | 55 |
59 | 60 | 61 | 62 | 66 | 67 | 68 | 72 | 73 |
63 | Follow on Twitter   Like on Facebook    64 | Email Us 65 |
69 | Copyright © {{ hackathonName }} 2020, All rights reserved. 70 |
71 |
74 | 75 |
78 | 79 |
82 |
83 | 84 | 85 | -------------------------------------------------------------------------------- /app/server/templates/email-link-action/text.hbs: -------------------------------------------------------------------------------- 1 | Hi! Thanks for signing up for HackIIITV 2020. 2 | To verify your email, follow this link: {{verifyUrl}} 3 | 4 | Thanks, 5 | HackIIITV Team -------------------------------------------------------------------------------- /app/server/templates/email-verify/html.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | *|MC:SUBJECT|* 6 | 7 | 8 |
9 | 10 | 11 | 80 | 81 |
12 | 13 | 14 | 15 | 26 | 27 | 28 | 56 | 57 | 58 | 76 | 77 |
16 | 17 | 18 | 19 | 22 | 23 |
20 | 21 |
24 | 25 |
29 | 30 | 31 | 32 | 43 | 44 | 45 | 50 | 51 | 52 | 53 |
33 |

Verify Your Email

34 |

Thanks for signing up for {{ hackathonName }}!

35 | To verify your email, click the button below. 36 |
37 |
38 |
39 | 40 | Verify Me 41 | 42 |
46 | Thanks, 47 |
48 | The {{ hackathonName }} Team 49 |
54 | 55 |
59 | 60 | 61 | 62 | 66 | 67 | 68 | 72 | 73 |
63 | Follow on Twitter   Like on Facebook    64 | Email Us 65 |
69 | Copyright © {{ hackathonName }} 2020, All rights reserved. 70 |
71 |
74 | 75 |
78 | 79 |
82 |
83 | 84 | 85 | -------------------------------------------------------------------------------- /app/server/templates/email-verify/text.hbs: -------------------------------------------------------------------------------- 1 | Hi! Thanks for signing up for HackIIITV 2020. 2 | To verify your email, follow this link: {{verifyUrl}} 3 | 4 | Thanks, 5 | HackIIITV Team -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jafrs", 3 | "version": "0.0.0", 4 | "homepage": "https://github.com/techx/hackmit-registration", 5 | "authors": [ 6 | "Edwin Zhang " 7 | ], 8 | "description": "just another frickin reg system", 9 | "license": "MIT", 10 | "ignore": [ 11 | "**/.*", 12 | "node_modules", 13 | "bower_components", 14 | "app/client/plugins", 15 | "test", 16 | "tests" 17 | ], 18 | "dependencies": { 19 | "semantic-ui": "~2.0.0", 20 | "jquery": "~2.1.4", 21 | "animate.css": "~3.3.0", 22 | "angular": "~1.4.1", 23 | "angular-ui-router": "~0.2.15", 24 | "angular-animate": "~1.4.1", 25 | "sweetalert": "~1.0.1", 26 | "moment": "~2.10.3", 27 | "showdown": "~1.2.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /config/admin.js: -------------------------------------------------------------------------------- 1 | ADMIN_EMAIL = process.env.ADMIN_EMAIL; 2 | ADMIN_PASSWORD = process.env.ADMIN_PASS; 3 | 4 | // Create a default admin user. 5 | var User = require('../app/server/models/User'); 6 | 7 | // If there is already a user 8 | User 9 | .findOne({ 10 | email: ADMIN_EMAIL 11 | }) 12 | .exec(function(err, user){ 13 | if (!user){ 14 | var u = new User(); 15 | u.email = ADMIN_EMAIL; 16 | u.password = User.generateHash(ADMIN_PASSWORD); 17 | u.admin = true; 18 | u.verified = true; 19 | u.save(function(err){ 20 | if (err){ 21 | console.log(err); 22 | } 23 | }); 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /config/settings.js: -------------------------------------------------------------------------------- 1 | var Settings = require('../app/server/models/Settings'); 2 | 3 | Settings 4 | .findOne({}) 5 | .exec(function(err, settings){ 6 | if (!settings){ 7 | var settings = new Settings(); 8 | settings.save(); 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /docs/images/screenshots/admin-users.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iiitv/hackiiitv/9cd90dd5140bc617c1613516aa788885446f86b6/docs/images/screenshots/admin-users.png -------------------------------------------------------------------------------- /docs/images/screenshots/application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iiitv/hackiiitv/9cd90dd5140bc617c1613516aa788885446f86b6/docs/images/screenshots/application.png -------------------------------------------------------------------------------- /docs/images/screenshots/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iiitv/hackiiitv/9cd90dd5140bc617c1613516aa788885446f86b6/docs/images/screenshots/dashboard.png -------------------------------------------------------------------------------- /docs/images/screenshots/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iiitv/hackiiitv/9cd90dd5140bc617c1613516aa788885446f86b6/docs/images/screenshots/login.png -------------------------------------------------------------------------------- /docs/images/screenshots/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iiitv/hackiiitv/9cd90dd5140bc617c1613516aa788885446f86b6/docs/images/screenshots/settings.png -------------------------------------------------------------------------------- /docs/images/screenshots/stats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iiitv/hackiiitv/9cd90dd5140bc617c1613516aa788885446f86b6/docs/images/screenshots/stats.png -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | require('dotenv').load({silent: true}); 2 | 3 | var gulp = require('gulp'); 4 | var sass = require('gulp-sass'); 5 | var minifyCss = require('gulp-minify-css'); 6 | var concat = require('gulp-concat'); 7 | var sourcemaps = require('gulp-sourcemaps'); 8 | var uglify = require('gulp-uglify'); 9 | var ngAnnotate = require('gulp-ng-annotate'); 10 | 11 | var environment = process.env.NODE_ENV; 12 | 13 | var nodemon = require('gulp-nodemon'); 14 | 15 | function swallowError (error) { 16 | //If you want details of the error in the console 17 | console.log(error.toString()); 18 | this.emit('end'); 19 | } 20 | 21 | gulp.task('default', function(){ 22 | console.log('yo. use gulp watch or something'); 23 | }); 24 | 25 | gulp.task('js', function () { 26 | if (environment !== 'dev'){ 27 | // Minify for non-development 28 | gulp.src(['app/client/src/**/*.js', 'app/client/views/**/*.js']) 29 | .pipe(sourcemaps.init()) 30 | .pipe(concat('app.js')) 31 | .pipe(ngAnnotate()) 32 | .on('error', swallowError) 33 | .pipe(uglify()) 34 | .pipe(gulp.dest('app/client/build')); 35 | } else { 36 | gulp.src(['app/client/src/**/*.js', 'app/client/views/**/*.js']) 37 | .pipe(sourcemaps.init()) 38 | .pipe(concat('app.js')) 39 | .pipe(ngAnnotate()) 40 | .on('error', swallowError) 41 | .pipe(sourcemaps.write()) 42 | .pipe(gulp.dest('app/client/build')); 43 | } 44 | 45 | }); 46 | 47 | gulp.task('sass', function () { 48 | gulp.src('app/client/stylesheets/site.scss') 49 | .pipe(sass()) 50 | .on('error', sass.logError) 51 | .pipe(minifyCss()) 52 | .pipe(gulp.dest('app/client/build')); 53 | }); 54 | 55 | gulp.task('build', ['js', 'sass'], function(){ 56 | // Yup, build the js and sass. 57 | }); 58 | 59 | gulp.task('watch', ['js', 'sass'], function () { 60 | gulp 61 | .watch('app/client/src/**/*.js', ['js']); 62 | gulp 63 | .watch('app/client/views/**/*.js', ['js']); 64 | gulp 65 | .watch('app/client/stylesheets/**/*.scss', ['sass']); 66 | }); 67 | 68 | gulp.task('server', ['watch'], function(){ 69 | nodemon({ 70 | script: 'app.js', 71 | env: { 'NODE_ENV': process.env.NODE_ENV || 'DEV' }, 72 | watch: [ 73 | 'app/server' 74 | ] 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hackiiitv-reg", 3 | "description": "mean stack registration system", 4 | "version": "0.0.1", 5 | "private": "true", 6 | "dependencies": { 7 | "angular-particle": "^1.0.4", 8 | "async": "^2.6.4", 9 | "bcrypt-nodejs": "0.0.3", 10 | "body-parser": "^1.20.1", 11 | "bower": "^1.8.8", 12 | "dotenv": "^1.2.0", 13 | "email-templates": "^2.0.1", 14 | "express": "^4.18.2", 15 | "gulp-concat": "^2.6.1", 16 | "gulp-minify-css": "^1.2.0", 17 | "gulp-ng-annotate": "^1.1.0", 18 | "gulp-nodemon": "^2.5.0", 19 | "gulp-sass": "3.0.0", 20 | "gulp-sourcemaps": "^1.12.0", 21 | "gulp-uglify": "^1.5.4", 22 | "handlebars": "^4.7.7", 23 | "jsonwebtoken": "9.0.0", 24 | "method-override": "^2.3.5", 25 | "moment": "^2.29.4", 26 | "mongoose": "^5.13.20", 27 | "morgan": "^1.9.1", 28 | "nodemailer": "^6.9.9", 29 | "nodemailer-smtp-transport": "^1.0.3", 30 | "particles.js": "^2.0.0", 31 | "passport-local": "^1.0.0", 32 | "request": "^2.60.0", 33 | "underscore": "^1.12.1", 34 | "validator": "^13.7.0" 35 | }, 36 | "scripts": { 37 | "mongo": "mongod --dbpath db", 38 | "start": "node app.js", 39 | "dev": "nodemon app.js", 40 | "watch": "gulp server", 41 | "prod": "gulp build && node app.js", 42 | "config": "cp .env.config .env", 43 | "postinstall": "bower install && gulp build" 44 | }, 45 | "devDependencies": { 46 | "gulp": "^4.0.2", 47 | "gulp-browserify": "^0.5.1", 48 | "gulp-concat": "^2.6.0", 49 | "gulp-ng-annotate": "^1.0.0", 50 | "gulp-nodemon": "^2.5.0", 51 | "gulp-sass": "^2.0.1", 52 | "gulp-sourcemaps": "^1.5.2", 53 | "gulp-uglify": "^1.2.0", 54 | "nodemon": "^1.2.1" 55 | }, 56 | "engines": { 57 | "node": "7.4.0" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /scripts/acceptUsers.js: -------------------------------------------------------------------------------- 1 | require('dotenv').load(); 2 | var mongoose = require('mongoose'); 3 | var database = process.env.DATABASE || "mongodb://localhost:27017"; 4 | var jwt = require('jsonwebtoken'); 5 | mongoose.connect(database); 6 | 7 | var UserController = require('../app/server/controllers/UserController'); 8 | 9 | var user = { email: process.env.ADMIN_EMAIL }; 10 | 11 | var userArray = require('fs').readFileSync('accepted.txt').toString().split('\n'); 12 | var count = 0; 13 | userArray.forEach(function (id) { 14 | UserController.admitUser( id, user, function() { 15 | count += 1; 16 | if (count == userArray.length) { 17 | console.log("Done"); 18 | } 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /scripts/createHellaUsers.js: -------------------------------------------------------------------------------- 1 | // Connect to mongodb 2 | var mongoose = require('mongoose'); 3 | var database = process.env.DATABASE || { url: "mongodb://localhost:27017"}; 4 | mongoose.connect(database.url); 5 | 6 | var UserController = require('../app/server/controllers/UserController'); 7 | 8 | var users = 1000; 9 | var username = 'hacker'; 10 | 11 | for (var i = 0; i < users; i++){ 12 | console.log(username, i); 13 | UserController 14 | .createUser(username + i + '@school.edu', 'foobar', function(){ 15 | console.log(i); 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /scripts/getVerifyToken.js: -------------------------------------------------------------------------------- 1 | require('dotenv').load(); 2 | var mongoose = require('mongoose'); 3 | var database = process.env.DATABASE || { url: "mongodb://localhost:27017"}; 4 | var jwt = require('jsonwebtoken'); 5 | mongoose.connect(database.url); 6 | 7 | var User = require('../app/server/models/User'); 8 | 9 | var email = 'hacker@school.ac.in'; 10 | 11 | User.findOne({ 12 | email: email 13 | }, function(err, user){ 14 | console.log(user.generateEmailVerificationToken()); 15 | console.log(user.generateAuthToken()); 16 | 17 | var temp = user.generateTempAuthToken(); 18 | console.log(temp); 19 | 20 | console.log(jwt.verify(temp, process.env.JWT_SECRET)); 21 | }); 22 | -------------------------------------------------------------------------------- /scripts/testChangePassword.js: -------------------------------------------------------------------------------- 1 | require('dotenv').load(); 2 | var mongoose = require('mongoose'); 3 | var database = process.env.DATABASE || { url: "mongodb://localhost:27017"}; 4 | var jwt = require('jsonwebtoken'); 5 | mongoose.connect(database.url); 6 | 7 | var User = require('../app/server/models/User'); 8 | var UserController = require('../app/server/controllers/UserController'); 9 | 10 | var email = 'hacker@school.ac.in'; 11 | 12 | User.findOne({ 13 | email: email 14 | }, function(err, user){ 15 | var id = user._id; 16 | 17 | /* Change with old password */ 18 | UserController.changePassword( 19 | id, 20 | 'foobar', 21 | 'hunter123', 22 | function (err, something){ 23 | console.log(!err ? 'Successfuly changed' : err); 24 | } 25 | ); 26 | 27 | /* Change with auth token */ 28 | // var token = user.generateTempAuthToken(); 29 | 30 | // UserController.resetPassword( 31 | // id, 32 | // token, 33 | // 'hunter123', 34 | // function (err, something){ 35 | // console.log(!err ? 'Successfully changed' : err); 36 | // } 37 | // ); 38 | 39 | }); 40 | --------------------------------------------------------------------------------