Commit f5ef664e authored by Kunal Sarkhel's avatar Kunal Sarkhel

Begin port to Node and TypeScript

parent b02cfb9a
VARNAME=value
# Created by https://www.gitignore.io/api/node
# Edit at https://www.gitignore.io/?templates=node
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
# FuseBox cache
.fusebox/
# End of https://www.gitignore.io/api/node
{
"recommendations": [
"eg2.tslint",
"ms-azuretools.vscode-cosmosdb",
"streetsidesoftware.code-spell-checker"
]
}
{
// Use IntelliSense to learn about possible Node.js debug attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "Attach by Process ID",
"processId": "${command:PickProcess}",
"protocol": "inspector"
},
{
"type": "node",
"request": "launch",
"name": "Launch in Docker",
"preLaunchTask": "tsc-watch",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run",
"docker-debug"
],
"port": 9222,
"restart": true,
"timeout": 60000,
"localRoot": "${workspaceFolder}",
"remoteRoot": "/server",
"outFiles": [
"${workspaceFolder}/dist/**/*.js"
],
"skipFiles": [
"<node_internals>/**/*.js"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
}
]
}
// Place your settings in this file to overwrite default and user settings.
{
"search.exclude": {
"**/node_modules": true,
"**/bower_components": true,
"**/dist": true,
"**/coverge": true
},
"typescript.referencesCodeLens.enabled": true,
"tslint.ignoreDefinitionFiles": false,
"tslint.autoFixOnSave": true,
"tslint.exclude": "**/node_modules/**/*",
"cSpell.words": [
"csrf",
"definitelytyped",
"promisified"
]
}
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "build",
"group": {
"kind": "build",
"isDefault": true
}
},
{
"version": "0.1.0",
"tasks": [
{
"taskName": "tsc-watch",
"command": "npm",
"args": [
"run",
"watch"
],
"isShellCommand": true,
"isBackground": true,
"isBuildCommand": true,
"problemMatcher": "$tsc-watch",
"showOutput": "always"
}
]
}
FROM node:10-slim
WORKDIR /server
COPY . /server
RUN npm install
RUN npm run build
EXPOSE 3000
CMD [ "npm", "start" ]
This diff is collapsed.
......@@ -5,3 +5,14 @@ Map out points of interest at Mason, like water fountains, bathrooms, and study
Ideas/Planning: [https://go.gmu.edu/where-ideas](https://go.gmu.edu/where-ideas)
For updates, join #where on Slack.
# Running
First run `npm install` or `yarn`. Then run `npm run build` to build then project, and then `npm run start`. You should then be able to view the site [in your browser](http://localhost:3000/)
Alternatively, you can use Docker Compose by running `docker-compose up`. You can build and run the Docker container on it's own by executing the following
```
docker build -t where .
docker run -p 3000:3000 where
```
import * as shell from "shelljs";
shell.cp("-R", "src/public/js/lib", "dist/public/js/");
shell.cp("-R", "src/public/fonts", "dist/public/");
shell.cp("-R", "src/public/images", "dist/public/");
version: "2"
services:
web:
build: .
command: npm run debug
volumes:
- ./dist:/server/dist
ports:
- "3000:3000"
- "9222:9222"
module.exports = {
globals: {
'ts-jest': {
tsConfigFile: 'tsconfig.json'
}
},
moduleFileExtensions: [
'ts',
'js'
],
transform: {
'^.+\\.(ts|tsx)$': './node_modules/ts-jest/preprocessor.js'
},
testMatch: [
'**/test/**/*.test.(ts|js)'
],
testEnvironment: 'node'
};
\ No newline at end of file
venv/
*.pyc
*.egg-info/
dist/
settings.cfg
*.log*
recursive-include map_mason/templates *
recursive-include map_mason/static *
all: run
clean:
rm -rf venv && rm -rf *.egg-info && rm -rf dist && rm -rf *.log*
venv:
virtualenv --python=python3 venv && venv/bin/python setup.py develop
run: venv
FLASK_APP=map_mason MAP_MASON_SETTINGS=../settings.cfg venv/bin/flask run
test: venv
MAP_MASON_SETTINGS=../settings.cfg venv/bin/python -m unittest discover -s tests
sdist: venv test
venv/bin/python setup.py sdist
# MapMason
MapMason description
## Quick Start
Run the application:
make run
And open it in the browser at [http://127.0.0.1:5000/](http://127.0.0.1:5000/)
## Prerequisites
This is built to be used with Python 3. Update `Makefile` to switch to Python 2 if needed.
Some Flask dependencies are compiled during installation, so `gcc` and Python header files need to be present.
For example, on Ubuntu:
apt install build-essential python3-dev
## Development environment and release process
- create virtualenv with Flask and MapMason installed into it (latter is installed in
[develop mode](http://setuptools.readthedocs.io/en/latest/setuptools.html#development-mode) which allows
modifying source code directly without a need to re-install the app): `make venv`
- run development server in debug mode: `make run`; Flask will restart if source code is modified
- run tests: `make test` (see also: [Testing Flask Applications](http://flask.pocoo.org/docs/0.12/testing/))
- create source distribution: `make sdist` (will run tests first)
- to remove virtualenv and built distributions: `make clean`
- to add more python dependencies: add to `install_requires` in `setup.py`
- to modify configuration in development environment: edit file `settings.cfg`; this is a local configuration file
and it is *ignored* by Git - make sure to put a proper configuration file to a production environment when
deploying
## Deployment
If you are interested in an out-of-the-box deployment automation, check out accompanying
[`cookiecutter-flask-ansible`](https://github.com/candidtim/cookiecutter-flask-ansible).
Or, check out [Deploying with Fabric](http://flask.pocoo.org/docs/0.12/patterns/fabric/#fabric-deployment) on one of the
possible ways to automate the deployment.
In either case, generally the idea is to build a package (`make sdist`), deliver it to a server (`scp ...`),
install it (`pip install map_mason.tar.gz`), ensure that configuration file exists and
`MAP_MASON_SETTINGS` environment variable points to it, ensure that user has access to the
working directory to create and write log files in it, and finally run a
[WSGI container](http://flask.pocoo.org/docs/0.12/deploying/wsgi-standalone/) with the application.
And, most likely, it will also run behind a
[reverse proxy](http://flask.pocoo.org/docs/0.12/deploying/wsgi-standalone/#proxy-setups).
import os
from flask import Flask
app = Flask(__name__)
app.config.from_object('map_mason.default_settings')
app.config.from_envvar('MAP_MASON_SETTINGS')
if not app.debug:
import logging
from logging.handlers import TimedRotatingFileHandler
# https://docs.python.org/3.6/library/logging.handlers.html#timedrotatingfilehandler
file_handler = TimedRotatingFileHandler(os.path.join(app.config['LOG_DIR'], 'map_mason.log'), 'midnight')
file_handler.setLevel(logging.WARNING)
file_handler.setFormatter(logging.Formatter('<%(asctime)s> <%(levelname)s> %(message)s'))
app.logger.addHandler(file_handler)
import map_mason.views
DEBUG = False # make sure DEBUG is off unless enabled explicitly otherwise
LOG_DIR = '.' # create log files in current working directory
This diff is collapsed.
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link href="https://srct.gmu.io/masonstrap/css/masonstrap.min.css" rel="stylesheet">
{% block extra_head %}
{% endblock %}
<title>{% block title %}{% endblock %} - MapMason</title>
</head>
<body>
{% block content %}{% endblock %}
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://srct.gmu.io/masonstrap/js/masonstrap.min.js"></script>
{% block extra_js %}{% endblock %}
</body>
</html>
\ No newline at end of file
{% extends 'base.html' %}
{% block extra_head %}
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.1/dist/leaflet.css"
integrity="sha512-Rksm5RenBEKSKFjgI3a41vrjkw4EVPlJ3+OiI65vTjIdo9brlAacEuKOiQ5OFh7cOI1bkDwLqdLw3Zg0cRJAAQ=="
crossorigin=""/>
<link rel="stylesheet" href="{{ url_for('static', filename='titatoggle-dist-min.css') }}">
<style>
#map {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
}
html, body {
height: 100%;
width: 100%;
}
#page-content {
height: 100%;
}
#map-container {
padding: 0;
margin: 0;
}
</style>
{% endblock %}
{% block extra_js %}
<script src="https://unpkg.com/leaflet@1.3.1/dist/leaflet.js"
integrity="sha512-/Nsx9X4HebavoBvEBuyp3I7od5tA0UzAxs+j83KgC8PU0kgB4XiK4Lfe4y4cgBtaRJQEIFCW+oC506aPT2L1zw=="
crossorigin=""></script>
<script type="text/javascript">
// GMU Decimal Latitude and Decimal Longitude
const gmuLatitude = 38.8322871;
const gmuLongitude = -77.3080912;
const defaultZoom = 16;
var map = L.map('map', {
minZoom: 0,
maxZoom: 20,
zoomSnap: 0,
zoomDelta: 0.25
});
var cartodbAttribution = '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="http://cartodb.com/attributions">CartoDB</a>';
var tileUrl = 'http://{s}.tile.osm.org/{z}/{x}/{y}.png';
// 'http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png'
var positron = L.tileLayer(tileUrl, {
attribution: cartodbAttribution
}).addTo(map);
map.setView([gmuLatitude, gmuLongitude], defaultZoom);
</script>
{% endblock %}
{% block content %}
<div class="container-fluid" id="page-content">
<div class="row" style="height: 100%;">
<nav class="col-sm-3 col-md-2 d-none d-sm-block navbar-inverse navbar-dark bg-dark sidebar">
<h1 class="navbar-brand navbar-dark bg-dark">MapMason</h1>
<p>
<h3 class="text-primary">Search</h3>
<form class="form-inline mt-2 mt-md-0">
<small class="form-text text-light">Search via Name or Keyword</small>
<input class="form-control mr-sm-2" type="text" placeholder="Search" aria-label="Name or Keyword">
</form>
</p>
<p>
<h3 class="text-primary">Filter Locations</h3>
<div class="nav flex-column text-light">
<div class="form-check checkbox-slider--b-flat">
<label>
<input type="checkbox"><span>Parking</span>
</label>
</div>
<div class="form-check checkbox-slider--b-flat">
<label>
<input type="checkbox"><span>Residence Halls</span>
</label>
</div>
<div class="form-check checkbox-slider--b-flat">
<label>
<input type="checkbox"><span>Dining Halls</span>
</label>
</div>
</div>
</p>
<p class="text-light">Having problems? <a href="mailto:srct@gmu.edu">Submit feedback</a></p>
<p class="text-muted">A service of <a href="http://srct.gmu.edu">Mason SRCT</a>. Licensed under the <a href="http://opensource.org/licenses/Apache-2.0">Apache License, Version 2.0</a>.</p>
</nav>
<main role="main" class="col-sm-9 ml-sm-auto col-md-10" id="map-container">
<div id="map"></div>
</main>
</div>
</div>
{% endblock %}
from flask import render_template
from map_mason import app
@app.route('/')
def index():
# app.logger.warning('Hit index')
return render_template('index.html')
from setuptools import setup, find_packages
setup(
name='map_mason',
version='1.0',
long_description=__doc__,
packages=find_packages(),
include_package_data=True,
zip_safe=False,
install_requires=[
'flask',
],
)
import unittest
import map_mason
class Map_masonTestCase(unittest.TestCase):
def setUp(self):
self.app = map_mason.app.test_client()
def test_index(self):
rv = self.app.get('/')
self.assertIn('Welcome to MapMason', rv.data.decode())
if __name__ == '__main__':
unittest.main()
{
"name": "map_mason",
"version": "0.1.0",
"description": "Map mason",
"repository": {
"type": "git",
"url": "https://git.gmu.edu/srct/where/"
},
"author": "Kunal Sarkhel",
"license": "Apache-2.0",
"scripts": {
"start": "npm run serve",
"build": "npm run build-sass && npm run build-ts && npm run tslint && npm run copy-static-assets",
"serve": "node dist/server.js",
"watch-node": "nodemon dist/server.js",
"watch": "concurrently -k -p \"[{name}]\" -n \"Sass,TypeScript,Node\" -c \"yellow.bold,cyan.bold,green.bold\" \"npm run watch-sass\" \"npm run watch-ts\" \"npm run watch-node\"",
"test": "jest --forceExit --coverage --verbose",
"watch-test": "npm run test -- --watchAll",
"build-ts": "tsc",
"watch-ts": "tsc -w",
"build-sass": "node-sass src/public/css/main.scss dist/public/css/main.css",
"watch-sass": "node-sass -w src/public/css/main.scss dist/public/css/main.css",
"tslint": "tslint -c tslint.json -p tsconfig.json",
"copy-static-assets": "ts-node copyStaticAssets.ts",
"debug": "npm run build && npm run watch-debug",
"serve-debug": "nodemon --inspect dist/server.js",
"watch-debug": "concurrently -k -p \"[{name}]\" -n \"Sass,TypeScript,Node\" -c \"yellow.bold,cyan.bold,green.bold\" \"npm run watch-sass\" \"npm run watch-ts\" \"npm run serve-debug\""
},
"dependencies": {
"async": "^2.6.0",
"bcrypt-nodejs": "^0.0.3",
"bluebird": "^3.5.1",
"body-parser": "^1.18.2",
"compression": "^1.7.1",
"dotenv": "^4.0.0",
"errorhandler": "^1.5.0",
"express": "^4.16.2",
"express-validator": "^4.3.0",
"fbgraph": "^1.4.1",
"lodash": "^4.17.5",
"lusca": "^1.5.2",
"nodemailer": "^4.4.1",
"pug": "^2.0.0-rc.4",
"request": "^2.83.0",
"request-promise": "^4.2.2",
"titatoggle": "^2.1.2",
"winston": "^2.4.0"
},
"devDependencies": {
"@types/async": "^2.0.45",
"@types/bcrypt-nodejs": "^0.0.30",
"@types/bluebird": "^3.5.20",
"@types/body-parser": "^1.16.8",
"@types/compression": "^0.0.35",
"@types/dotenv": "^4.0.3",
"@types/errorhandler": "^0.0.32",
"@types/express": "^4.11.1",
"@types/jest": "^22.1.3",
"@types/jquery": "^3.2.17",
"@types/lodash": "^4.14.91",
"@types/lusca": "^1.5.0",
"@types/morgan": "^1.7.35",
"@types/node": "^9.4.6",
"@types/nodemailer": "^4.3.4",
"@types/request": "^2.47.0",
"@types/shelljs": "^0.7.8",
"@types/supertest": "^2.0.4",
"chai": "^4.1.2",
"concurrently": "^3.5.1",
"jest": "^22.0.4",
"node-sass": "^4.7.2",
"nodemon": "^1.13.0",
"shelljs": "^0.8.1",
"supertest": "^3.0.0",
"ts-jest": "^22.0.4",
"ts-node": "^5.0.0",
"tslint": "^5.9.1",
"typescript": "^2.7.2"
}
}
import express from "express";
import compression from "compression"; // compresses requests
import bodyParser from "body-parser";
import logger from "./util/logger";
import lusca from "lusca";
import dotenv from "dotenv";
import path from "path";
import expressValidator from "express-validator";
import bluebird from "bluebird";
// Load environment variables from .env file, where API keys and passwords are configured
dotenv.config({path: ".env.example"});
// Controllers (route handlers)
import * as homeController from "./controllers/home";
import * as apiController from "./controllers/api";
// Create Express server
const app = express();
// Express configuration
app.set("port", process.env.PORT || 3000);
app.set("views", path.join(__dirname, "../views"));
app.set("view engine", "pug");
app.use(compression());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.use(expressValidator());
app.use(lusca.xframe("SAMEORIGIN"));
app.use(lusca.xssProtection(true));
app.use(
express.static(path.join(__dirname, "public"), {maxAge: 31557600000})
);
/**
* Primary app routes.
*/
app.get("/", homeController.index);
/**
* API examples routes.
*/
app.get("/api", apiController.getApi);
export default app;
"use strict";
import async from "async";
import request from "request";
import { Response, Request, NextFunction } from "express";
/**
* GET /api
* List of API examples.
*/
export let getApi = (req: Request, res: Response) => {
res.render("api/index", {
title: "API Examples"
});
};
import { Request, Response } from "express";
/**
* GET /
* Home page.
*/
export let index = (req: Request, res: Response) => {
res.render("home", {
title: "Home"
});
};