diff --git a/.env.example b/.env.example index d0e87bc0..3767c1f4 100644 --- a/.env.example +++ b/.env.example @@ -6,6 +6,7 @@ ADDRESS= #front-end server FRONTEND_ADDRESS_DEV=localhost:1337 FRONTEND_ADDRESS_DEPLOY=app.mchacks.ca +FRONTEND_ADDRESS_BETA=develop--mchacks-dashboard.netlify.com #The info for the deployment database DB_ADDRESS_DEPLOY= @@ -51,4 +52,4 @@ CLIENT_ID= AUTH_URI= TOKEN_URI= AUTH_PROVIDER_X509_CERT_URL= -CLIENT_X509_CERT_URL= \ No newline at end of file +CLIENT_X509_CERT_URL= diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..0a382569 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +assets/ +docs/ +node_modules/ diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..bc137efb --- /dev/null +++ b/.eslintrc @@ -0,0 +1,19 @@ +{ + "env": { + "browser": true, + "es6": true, + "node": true, + "mocha": true + }, + "parserOptions": { + "ecmaVersion": 2017, + "sourceType": "module" + }, + "plugins": [ + "prettier" + ], + "extends": [ + "eslint:recommended", + "prettier" + ] +} diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md new file mode 100644 index 00000000..f58a2e7a --- /dev/null +++ b/.github/CHANGELOG.md @@ -0,0 +1,38 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [2.0.0](https://github.com/hackmcgill/hackerapi/tree/2.0.0) - 2019-12-17 + +### Added + +- Send accounts back to account confirmation on email change where confirmed is false +- Add Travis notifications to Slack +- Add code formatting with ESLint and Prettier +- Add `declined` hacker status +- Add accept hacker route +- Create changelog + +### Changed + +- Match npm scripts with dashboard +- Update application fields +- Modify account creation/profile fields +- Move deployment from GCP to Heroku +- Change `cancelled` hacker status to `withdrawn` +- Update pre-acceptance email templates: none hacker status, applied hacker status, account invitation, account confirmation, password reset +- Group application enums +- Change job interest enums + +### Fixed + +- Fix duplicate email bug + +### Security + +- Fix vulnerabilities +- Update packages +- Bump node version to 10.17.0 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..635db9f5 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,47 @@ +### Tickets: + +- HCK- + +### List of changes: + +Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. + +- + +## Type of change + +Please delete options that are not relevant. + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] New release +- [ ] This change requires a documentation update + +### How has this been tested? + +Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration + +- [ ] Test A +- [ ] Test B + +**Test Configuration**: + +**Firmware version:** +**Hardware:** +**Toolchain:** +**SDK:** + +### Questions for code reviewers? + +### Checklist: + +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] My changes generate no new warnings +- [ ] Listed change(s) in the Changelog +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] I have made corresponding changes to the documentation +- [ ] Any dependent changes have been merged and published in downstream modules diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index cfbc9e06..00000000 --- a/.jshintrc +++ /dev/null @@ -1,91 +0,0 @@ -{ - // JSHint Default Configuration File (as on JSHint website) - // See http://jshint.com/docs/ for more details - - "maxerr": 50, // {int} Maximum error before stopping - - // Enforcing - "bitwise": true, // true: Prohibit bitwise operators (&, |, ^, etc.) - "camelcase": true, // true: Identifiers must be in camelCase - "curly": true, // true: Require {} for every new block or scope - "eqeqeq": true, // true: Require triple equals (===) for comparison - "forin": true, // true: Require filtering for..in loops with obj.hasOwnProperty() - "freeze": true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc. - "immed": false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` - "latedef": false, // true: Require variables/functions to be defined before being used - "newcap": false, // true: Require capitalization of all constructor functions e.g. `new F()` - "noarg": true, // true: Prohibit use of `arguments.caller` and `arguments.callee` - "noempty": true, // true: Prohibit use of empty blocks - "nonbsp": true, // true: Prohibit "non-breaking whitespace" characters. - "nonew": false, // true: Prohibit use of constructors for side-effects (without assignment) - "plusplus": false, // true: Prohibit use of `++` and `--` - "quotmark": true, // Quotation mark consistency: - // false : do nothing (default) - // true : ensure whatever is used is consistent - // "single" : require single quotes - // "double" : require double quotes - "undef": true, // true: Require all non-global variables to be declared (prevents global leaks) - "unused": true, // Unused variables: - // true : all variables, last function parameter - // "vars" : all variables only - // "strict" : all variables, all function parameters - "strict": true, // true: Requires all functions run in ES5 Strict Mode - "maxparams": false, // {int} Max number of formal params allowed per function - "maxdepth": false, // {int} Max depth of nested blocks (within functions) - "maxstatements": false, // {int} Max number statements per function - "maxcomplexity": false, // {int} Max cyclomatic complexity per function - "maxlen": false, // {int} Max number of characters per line - "varstmt": false, // true: Disallow any var statements. Only `let` and `const` are allowed. - - // Relaxing - "asi": false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) - "boss": false, // true: Tolerate assignments where comparisons would be expected - "debug": false, // true: Allow debugger statements e.g. browser breakpoints. - "eqnull": false, // true: Tolerate use of `== null` - "esversion": 9, // {int} Specify the ECMAScript version to which the code must adhere. - "moz": false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) - // (ex: `for each`, multiple try/catch, function expression…) - "evil": false, // true: Tolerate use of `eval` and `new Function()` - "expr": false, // true: Tolerate `ExpressionStatement` as Programs - "funcscope": false, // true: Tolerate defining variables inside control statements - "globalstrict": true, // true: Allow global "use strict" (also enables 'strict') - "iterator": false, // true: Tolerate using the `__iterator__` property - "lastsemic": false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block - "laxbreak": false, // true: Tolerate possibly unsafe line breakings - "laxcomma": false, // true: Tolerate comma-first style coding - "loopfunc": false, // true: Tolerate functions being defined in loops - "multistr": false, // true: Tolerate multi-line strings - "noyield": false, // true: Tolerate generator functions with no yield statement in them. - "notypeof": false, // true: Tolerate invalid typeof operator values - "proto": false, // true: Tolerate using the `__proto__` property - "scripturl": false, // true: Tolerate script-targeted URLs - "shadow": false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` - "sub": false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation - "supernew": false, // true: Tolerate `new function () { ... };` and `new Object;` - "validthis": true, // true: Tolerate using this in a non-constructor function - - // Environments - "browser": true, // Web Browser (window, document, etc) - "browserify": false, // Browserify (node.js code in the browser) - "couch": false, // CouchDB - "devel": true, // Development/debugging (alert, confirm, etc) - "dojo": false, // Dojo Toolkit - "jasmine": false, // Jasmine - "jquery": false, // jQuery - "mocha": true, // Mocha - "mootools": false, // MooTools - "node": true, // Node.js - "nonstandard": false, // Widely adopted globals (escape, unescape, etc) - "phantom": false, // PhantomJS - "prototypejs": false, // Prototype and Scriptaculous - "qunit": false, // QUnit - "rhino": false, // Rhino - "shelljs": false, // ShellJS - "typed": false, // Globals for typed array constructions - "worker": false, // Web Workers - "wsh": false, // Windows Scripting Host - "yui": false, // Yahoo User Interface - - // Custom Globals - "globals": {} // additional predefined global variables -} \ No newline at end of file diff --git a/.nvmrc b/.nvmrc index 4d0ffae7..73bffb03 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -9.3.0 \ No newline at end of file +10.17.0 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..0a382569 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +assets/ +docs/ +node_modules/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..6577547b --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "tabWidth": 4, + "useTabs": false, + "singleQuote": false, + "semi": true, + "arrowParens": "always", + "jsxBracketSameLine": false +} diff --git a/.travis.yml b/.travis.yml index 80b04f0d..9e3ca997 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: node_js dist: trusty sudo: false -services: +services: - mongodb before_install: - npm i -g npm@6.4.0 @@ -17,4 +17,4 @@ branches: - develop notifications: - slack: hackboard6:4baCeRXUOQmvy4ZBAHQclN6K + slack: hackboard7:Yt0nW0DyaF85eTOxceBLptU1 diff --git a/.vscode/launch.json b/.vscode/launch.json index b8ba1518..ccaa15e7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,7 +12,7 @@ "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ "-u", - "tdd", + "bdd", "--timeout", "999999", "--colors", @@ -28,7 +28,7 @@ "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ "-u", - "tdd", + "bdd", "--timeout", "999999", "--colors", @@ -43,7 +43,7 @@ "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ "-u", - "tdd", + "bdd", "--timeout", "999999", "--colors", @@ -59,7 +59,7 @@ "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ "-u", - "tdd", + "bdd", "--timeout", "999999", "--colors", @@ -75,7 +75,7 @@ "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ "-u", - "tdd", + "bdd", "--timeout", "999999", "--colors", @@ -91,7 +91,7 @@ "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ "-u", - "tdd", + "bdd", "--timeout", "999999", "--colors", @@ -107,7 +107,7 @@ "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ "-u", - "tdd", + "bdd", "--timeout", "999999", "--colors", @@ -123,7 +123,7 @@ "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ "-u", - "tdd", + "bdd", "--timeout", "999999", "--colors", @@ -139,7 +139,7 @@ "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ "-u", - "tdd", + "bdd", "--timeout", "999999", "--colors", @@ -155,7 +155,7 @@ "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ "-u", - "tdd", + "bdd", "--timeout", "999999", "--colors", @@ -171,7 +171,7 @@ "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ "-u", - "tdd", + "bdd", "--timeout", "999999", "--colors", @@ -187,7 +187,7 @@ "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ "-u", - "tdd", + "bdd", "--timeout", "999999", "--colors", @@ -203,7 +203,7 @@ "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ "-u", - "tdd", + "bdd", "--timeout", "999999", "--colors", @@ -213,4 +213,4 @@ "internalConsoleOptions": "openOnSessionStart" } ] -} \ No newline at end of file +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 74276cc5..aee26f6c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,10 +1,7 @@ { - "jslint.version": "es6", - "jslint.options": { - "node":true, - "this":false, - "bad_property":false - }, "search.usePCRE2": true, - "editor.formatOnSave": true -} \ No newline at end of file + "editor.formatOnSave": true, + "files.insertFinalNewline": true, + "files.trimFinalNewlines": true, + "editor.tabSize": 4 +} diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 0720d9ee..00000000 --- a/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,38 +0,0 @@ -# Description - -Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. - -Fixes # (issue) - -## Type of change - -Please delete options that are not relevant. - -- [ ] Bug fix (non-breaking change which fixes an issue) -- [ ] New feature (non-breaking change which adds functionality) -- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) -- [ ] This change requires a documentation update - -# How Has This Been Tested? - -Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration - -- [ ] Test A -- [ ] Test B - -**Test Configuration**: -* Firmware version: -* Hardware: -* Toolchain: -* SDK: - -# Checklist: - -- [ ] My code follows the style guidelines of this project -- [ ] I have performed a self-review of my own code -- [ ] I have commented my code, particularly in hard-to-understand areas -- [ ] I have made corresponding changes to the documentation -- [ ] My changes generate no new warnings -- [ ] I have added tests that prove my fix is effective or that my feature works -- [ ] New and existing unit tests pass locally with my changes -- [ ] Any dependent changes have been merged and published in downstream modules diff --git a/Procfile b/Procfile new file mode 100644 index 00000000..e44ffb20 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: npm run deploy diff --git a/README.md b/README.md index 7e873a2c..88f32e86 100755 --- a/README.md +++ b/README.md @@ -15,3 +15,7 @@ API for registration, live-site ## How to use and contribute See documentation here: + +## Deploy + +[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) diff --git a/VERSION b/VERSION index fdd3be6d..227cea21 100755 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.6.2 +2.0.0 diff --git a/app.js b/app.js index 875b13fc..4de92284 100755 --- a/app.js +++ b/app.js @@ -44,7 +44,11 @@ if (!Services.env.isProduction()) { } else { // TODO: change this when necessary corsOptions = { - origin: [`https://${process.env.FRONTEND_ADDRESS_DEPLOY}`, `https://docs.mchacks.ca`], + origin: [ + `https://${process.env.FRONTEND_ADDRESS_DEPLOY}`, + `https://${process.env.FRONTEND_ADDRESS_BETA}`, + `https://docs.mchacks.ca` + ], credentials: true }; } @@ -53,17 +57,21 @@ app.use(cors(corsOptions)); app.use(Services.log.requestLogger); app.use(Services.log.errorLogger); app.use(express.json()); -app.use(express.urlencoded({ - extended: false -})); +app.use( + express.urlencoded({ + extended: false + }) +); app.use(cookieParser()); //Cookie-based session tracking -app.use(cookieSession({ - name: "session", - keys: [process.env.COOKIE_SECRET], - // Cookie Options - maxAge: 48 * 60 * 60 * 1000 //Logged in for 48 hours -})); +app.use( + cookieSession({ + name: "session", + keys: [process.env.COOKIE_SECRET], + // Cookie Options + maxAge: 48 * 60 * 60 * 1000 //Logged in for 48 hours + }) +); app.use(passport.initialize()); app.use(passport.session()); //persistent login session @@ -98,8 +106,8 @@ app.use("/api", apiRouter); //Custom error handler app.use((err, req, res, next) => { // log the error... - const status = (err.status) ? err.status : 500; - const message = (err.message) ? err.message : "Internal Server Error"; + const status = err.status ? err.status : 500; + const message = err.message ? err.message : "Internal Server Error"; //Only show bad error when we're not in deployment let errorContents; if (status === 500 && Services.env.isProduction) { @@ -118,5 +126,5 @@ app.use((err, req, res, next) => { }); module.exports = { - app: app, -}; \ No newline at end of file + app: app +}; diff --git a/app.json b/app.json new file mode 100644 index 00000000..34439148 --- /dev/null +++ b/app.json @@ -0,0 +1,77 @@ +{ + "name": "HackMcGill HackerAPI", + "description": "API for registration, live-site", + "repository": "https://github.com/hackmcgill/hackerAPI", + "keywords": ["node", "express", "mongo", "react", "hackathon"], + "env": { + "FRONTEND_ADDRESS_DEPLOY": { + "description": "URL for application front end" + }, + "DB_ADDRESS_DEPLOY": { + "description": "Connection string for MongoDB" + }, + "DB_USER_DEPLOY": { + "description": "DB Username" + }, + "DB_PASS_DEPLOY": { + "description": "DB Password" + }, + "JWT_INVITE_SECRET": { + "generator": "secret" + }, + "JWT_RESET_PWD_SECRET": { + "generator": "secret" + }, + "JWT_CONFIRM_ACC_SECRET": { + "generator": "secret" + }, + "SENDGRID_API_KEY": { + "description": "Sendgrid API Key" + }, + "NO_REPLY_EMAIL": { + "description": "Noreply email address" + }, + "NO_REPLY_EMAIL": { + "description": "GCP Storage Bucket Name" + }, + "TYPE": { + "description": "GCP Account Type" + }, + "PROJECT_ID": { + "description": "GCP Project ID" + }, + "PRIVATE_KEY_ID": { + "description": "GCP Private Key ID" + }, + "PRIVATE_KEY": { + "description": "GCP Private Key" + }, + "CLIENT_EMAIL": { + "description": "GCP Client Email" + }, + "AUTH_URI": { + "description": "GCP Auth URI" + }, + "TOKEN_URI": { + "description": "GCP TOken URI" + }, + "AUTH_PROVIDER_X509_CERT_URL": { + "description": "GCP Provider Cert URL" + }, + "CLIENT_X509_CERT_URL": { + "description": "GCP Cert URL" + } + }, + "formation": { + "web": { + "quantity": 1, + "size": "free" + } + }, + "image": "heroku/nodejs", + "buildpacks": [ + { + "url": "https://github.com/heroku/heroku-buildpack-nodejs" + } + ] +} diff --git a/assets/email/AccountConfirmation.hbs b/assets/email/AccountConfirmation.hbs index 5e5337f1..a390b3f8 100644 --- a/assets/email/AccountConfirmation.hbs +++ b/assets/email/AccountConfirmation.hbs @@ -3,6 +3,7 @@ @@ -342,22 +338,30 @@ We've got your application! - +
- +
-
+ - +
- -
+ - +
-
- Logo + + Logo
@@ -365,35 +369,51 @@
+ - +
- @@ -403,7 +423,8 @@ -
-

Create Your Account

-

- Hey there! +

+ +

+ Hey there! 👋

- You've been invited to create an account on our hacker dashboard. + You've been invited to create an account on our hacker + dashboard.

Click the button below to get things started!

-
- - Create my account + -

- In the meantime, follow us on Facebook, - Twitter, and Instagram - for important updates and news about McHacks 6! If you have any - questions, feel free to reach out at contact@mchacks.ca. +

+ In the meantime, follow us on Facebook, + Twitter, + and Instagram + for important updates and news about McHacks! If you have any + questions, feel free to reach out at contact@mchacks.ca.

-

+


McHacks Team
- mchacks.ca + mchacks.ca

+

- Hey there! + Hey there! 👋

Looks like you forgot your password. That's okay, it happens now and then! @@ -381,8 +382,8 @@ a page where you can reset your password.

@@ -426,4 +427,4 @@ - \ No newline at end of file + diff --git a/assets/email/statusEmail/Applied.hbs b/assets/email/statusEmail/Applied.hbs index 542a39aa..998ed902 100644 --- a/assets/email/statusEmail/Applied.hbs +++ b/assets/email/statusEmail/Applied.hbs @@ -1,14 +1,15 @@ - + @@ -342,22 +341,30 @@ We've got your application! - +

- +
-
+ - +
- -
+ - +
-
- Logo + + Logo
@@ -365,27 +372,49 @@
+ - +
- @@ -395,7 +424,8 @@ - + +
-

Application Received

-

- Hi {{firstName}}, +

+

+

+

+ Hey there, {{firstName}}! 👋

- We've received your application to McHacks 6! + Your application for McHacks has been safely received! 🎉

- You can view your application on our hacker dashboard. + You can view and edit your application on our hacker + dashboard until the deadline on January + 1st at + 12:00pm ET.

- In the meantime, follow us on Facebook, Twitter, and Instagram for important updates and news about McHacks 6! If you have any questions, feel free to reach out at contact@mchacks.ca. + In the meantime, follow us on Facebook, + Twitter, + and Instagram + for important updates and news about McHacks! If you have any questions, + feel free to reach out at contact@mchacks.ca.

-

+


McHacks Team
- mchacks.ca + mchacks.ca

+ + + + + + + + + + + +
+ + + + + +
+ Logo +
+ +
+ + + + + +
+

+ McHacks 6 Decision

+

+ Hi {{firstName}}, +

+ Thank you for applying to McHacks 6. We received a ton of great + applicants this year and are unfortunately unable to offer you a place + at McHacks 6 this year. +

+

+ Until next year, follow us on Facebook, + Twitter, + and Instagram + for important updates and news! If you have any + questions, feel free to reach out at contact@mchacks.ca. +

+

+
+ McHacks Team +
+ mchacks.ca +

+ +
+ +
+ + + +
+ +
+ + + + \ No newline at end of file diff --git a/assets/email/statusEmail/None.hbs b/assets/email/statusEmail/None.hbs index a7a9e064..add6b4bd 100644 --- a/assets/email/statusEmail/None.hbs +++ b/assets/email/statusEmail/None.hbs @@ -1,14 +1,15 @@ - + @@ -342,22 +340,30 @@ We've got your application! - +
- +
-
+ - +
- -
+ - +
-
- Logo + + Logo
@@ -365,26 +371,52 @@
+ - +
- @@ -394,7 +426,8 @@ -
-

Complete Your Application

-

- Thanks for starting your application, {{firstName}}! +

+

+

+

+ Hey {{firstName}}! 👋

- You're one step closer to an epic weekend at McHacks, Canada's favourite hackathon! You can sign in to app.mchacks.ca to edit and submit your application for review. + You're one step closer to an epic weekend at McHacks, Canada's favourite + hackathon! Ready to start your application? Sign in to our hacker + dashboard to get started! +

+ Applications close on January + 1st at + 12:00pm ET so + be sure to + click submit before then to be considered. +

+ In the meantime, follow us on Facebook, + Twitter, + and Instagram + for important updates and news about McHacks! If you have any questions, + feel free to reach out at contact@mchacks.ca.

-

- In the meantime, follow us on Facebook, Twitter, and Instagram for important updates and news about McHacks 6! If you have any questions, feel free to reach out at contact@mchacks.ca. -

-

+


McHacks Team
- mchacks.ca + mchacks.ca

+