diff --git a/client/getFromFirebase.js b/client/getFromFirebase.js
new file mode 100644
index 0000000..12f4a45
--- /dev/null
+++ b/client/getFromFirebase.js
@@ -0,0 +1,300 @@
+//
+// Get info from frebase and show it on the datatable
+//
+
+
+
+var database = firebase.database();
+var arrayStocksHistory = [];
+
+var resultadoClone;
+
+var lastUpdate;
+
+var comparadores = {
+ patrLiq: { value: 2000000000, checked: 1 },
+ liqCorr: { value: 1.5, checked: 1 },
+ roe: { value: 20, checked: 1 },
+ divPat: { value: 50, checked: 1 },
+ cresc: { value: 5, checked: 1 },
+ pvp: { value: 1.5, checked: 1 },
+ pl: { value: 15, checked: 1 },
+ dy: { value: 2.5, checked: 1 },
+ plxpvp: { value: 22.5, checked: 1 },
+}
+
+initInputs();
+
+
+// *******************************
+// Logic functions
+// *******************************
+database.ref().child('stocks').once('value').then(function(snapshot){
+ // console.log(snapshot.val())
+
+ Object.keys(snapshot.val()).forEach(function(key){
+ // console.log(snapshot.val()[key])
+ var object = JSON.parse(snapshot.val()[key]);
+ arrayStocksHistory.push(object)
+ // console.log(object);
+ })
+
+ // Sort by date, the first one will be the newest
+ arrayStocksHistory.sort(function(a,b){
+ var date1 = new Date(a.date);
+ var date2 = new Date(b.date);
+ return date2 - date1;
+ })
+
+ // console.log(arrayStocksHistory);
+ buildTable(arrayStocksHistory[0]);
+ hideLoading();
+
+}, function(error){
+ console.log("deu erro");
+})
+
+// Get last update
+database.ref().child('last_date').once('value').then(function(snapshot){
+
+ lastUpdate = Object.keys(snapshot.val())
+ .map(function(key){
+ return new Date(snapshot.val()[key]);
+ })
+ .sort(function(a, b) {
+ console.log(new Date(a), new Date(b), new Date(a) > new Date(b));
+ return new Date(b) > new Date(a) ? 1 : -1;
+ })[0];
+
+ $("#lastUpdate").append(lastUpdate);
+
+}, function(error){
+ console.log("deu erro");
+})
+
+
+
+var buildTable = function(data){
+ // console.log('vamos montar a tabela', data);
+
+ // Put the objects in an array
+ arrayObjects = [];
+ Object.keys(data).forEach(function(key){
+ arrayObjects.push(data[key])
+ });
+
+ arrayObjects = calculateScores(arrayObjects);
+
+ var resultArray = [];
+ arrayObjects
+ .forEach(function(element) {
+ var innerObject = {};
+
+ if(Object.keys(element).pop().length > 3) {
+ Object.keys(element).map(function(innerKey) {
+ innerObject = element[innerKey];
+ innerObject['papel'] = innerKey;
+ });
+
+ var myArray = [];
+
+ myArray.push(innerObject["papel"]);
+ myArray.push(innerObject["nota"]);
+ myArray.push(innerObject["cotacao"]);
+ myArray.push(innerObject["Pat.Liq"]);
+ myArray.push(innerObject["Liq.Corr."]);
+ myArray.push(innerObject["ROE"]);
+ myArray.push(innerObject["Div.Brut/Pat."]);
+ myArray.push(innerObject["Cresc.5a"]);
+ myArray.push(innerObject["P/VP"]);
+ myArray.push(innerObject["P/L"]);
+ myArray.push(innerObject["DY"]);
+ myArray.push(innerObject["PSR"]);
+ myArray.push(innerObject["P/Ativo"]);
+ myArray.push(innerObject["P/Cap.Giro"]);
+ myArray.push(innerObject["P/EBIT"]);
+ myArray.push(innerObject["P/Ativ.Circ.Liq."]);
+ myArray.push(innerObject["EV/EBIT"]);
+ myArray.push(innerObject["EBITDA"]);
+ myArray.push(innerObject["Mrg.Liq."]);
+ myArray.push(innerObject["ROIC"]);
+ myArray.push(innerObject["Liq.2m."]);
+ resultArray.push(myArray);
+
+ }
+
+ });
+ $('#resultado').DataTable({
+ 'data': resultArray,
+ 'order' : [[ 1, "desc" ]], // Order by score
+ "pageLength": 500, // All stocks in the page
+ "createdRow": function( row, data, dataIndex ) {
+ if(data[1] >= 7.5) {
+ $(row).css('background-color', '#cde0cd')
+ }
+ }
+ });
+
+ // var txt = "";
+ // for(var i = 0; i < arrayObjects.length; i++) {
+ // if(typeof(arrayObjects[i]) == "object"){
+
+ // Object.keys(arrayObjects[i]).forEach(function(stock){
+
+ // var greenLine = " class='green-line'";
+ // // If we see a stock with score below 7.5 we turnoff green
+ // if(parseFloat(arrayObjects[i][stock]["nota"]) < 7.5)
+ // greenLine = '';
+
+ // var stockLine = "
" +
+ // "" + stock + " " +
+ // "" + arrayObjects[i][stock]["nota"] + " " +
+ // "" + arrayObjects[i][stock]["cotacao"] + " " +
+ // "" + arrayObjects[i][stock]["Pat.Liq"] + " " +
+ // "" + arrayObjects[i][stock]["Liq.Corr."] + " " +
+ // "" + arrayObjects[i][stock]["ROE"] + " " +
+ // "" + arrayObjects[i][stock]["Div.Brut/Pat."] + " " +
+ // "" + arrayObjects[i][stock]["Cresc.5a"] + " " +
+ // "" + arrayObjects[i][stock]["P/VP"] + " " +
+ // "" + arrayObjects[i][stock]["P/L"] + " " +
+ // "" + arrayObjects[i][stock]["DY"] + " " +
+ // "" + arrayObjects[i][stock]["PSR"] + " " +
+ // "" + arrayObjects[i][stock]["P/Ativo"] + " " +
+ // "" + arrayObjects[i][stock]["P/Cap.Giro"] + " " +
+ // "" + arrayObjects[i][stock]["P/EBIT"] + " " +
+ // "" + arrayObjects[i][stock]["P/Ativ.Circ.Liq."] + " " +
+ // "" + arrayObjects[i][stock]["EV/EBIT"] + " " +
+ // "" + arrayObjects[i][stock]["EBITDA"] + " " +
+ // "" + arrayObjects[i][stock]["Mrg.Liq."] + " " +
+ // "" + arrayObjects[i][stock]["ROIC"] + " " +
+ // "" + arrayObjects[i][stock]["Liq.2m."] + " " +
+ // " ";
+
+ // txt += stockLine;
+ // })
+ // }
+ // }
+ // $(function(){
+ // $(".table-body").append(txt);
+ // console.log('estou aqui');
+ // // $("#resultado").append('dsajdhayasyusadd');
+ // })
+}
+
+var calculateScores = function(stockArray){
+ console.log('calculando socres');
+ let dividePor = 0;
+ Object.keys(comparadores).forEach(function(elem){
+ if(comparadores[elem].checked) dividePor += 1;
+ })
+ console.log('dividePor', dividePor);
+
+ for(var i = 0; i < stockArray.length; i++) {
+ if(typeof(stockArray[i]) == "object"){
+ Object.keys(stockArray[i]).forEach(function(stock){
+ var nota = 0.0;
+
+ var patrLiq = parseFloat(stockArray[i][stock]["Pat.Liq"].replace(/\./g, '').replace(/\,/g, '.'));
+ if(comparadores.patrLiq.checked && patrLiq > parseFloat(comparadores.patrLiq.value))
+ nota = nota + 1
+ var liqCorr = parseFloat(stockArray[i][stock]["Liq.Corr."].replace(/\./g, '').replace(/,/g, '.'));
+ if(comparadores.liqCorr.checked && liqCorr > parseFloat(comparadores.liqCorr.value))
+ nota = nota + 1
+ var roe = parseFloat(stockArray[i][stock]["ROE"].replace(/\./g, '').replace(/\,/g, '.').replace(/%/g, ''));
+ if(comparadores.roe.checked && roe > parseFloat(comparadores.roe.value))
+ nota = nota + 1
+ var divPat = parseFloat(stockArray[i][stock]["Div.Brut/Pat."].replace(/\./g, '').replace(/\,/g, '.').replace(/%/g, ''));
+ if(comparadores.divPat.checked && divPat * 100 < parseFloat(comparadores.divPat.value) && divPat > 0)
+ nota = nota + 1
+ var cresc = parseFloat(stockArray[i][stock]["Cresc.5a"].replace(/\./g, '').replace(/\,/g, '.').replace(/%/g, ''));
+ if(comparadores.cresc.checked && cresc > parseFloat(comparadores.cresc.value))
+ nota = nota + 1
+ var pvp = parseFloat(stockArray[i][stock]["P/VP"].replace(/\./g, '').replace(/\,/g, '.').replace(/%/g, ''));
+ if(comparadores.pvp.checked && pvp < parseFloat(comparadores.pvp.value) && pvp > 0)
+ nota = nota + 1
+ var pl = parseFloat(stockArray[i][stock]["P/L"].replace(/\./g, '').replace(/\,/g, '.').replace(/%/g, ''));
+ if(comparadores.pl.checked && pl < parseFloat(comparadores.pl.value) && pl > 0)
+ nota = nota + 1
+ var dy = parseFloat(stockArray[i][stock]["DY"].replace(/\./g, '').replace(/\,/g, '.').replace(/%/g, ''));
+ if(comparadores.dy.checked && dy > parseFloat(comparadores.dy.value))
+ nota = nota + 1
+ if(comparadores.plxpvp.checked && pl * pvp < parseFloat(comparadores.plxpvp.value))
+ nota = nota + 1;
+
+ stockArray[i][stock]["nota"] = (parseFloat(nota) / dividePor * 10.0).toFixed(2);
+
+ // console.log([stock, stockArray[i][stock]["nota"], patrLiq, liqCorr, roe, divPat, cresc, pvp, pl, dy])
+
+ });
+ }
+ }
+
+ return stockArray;
+}
+
+
+
+// Clears the table
+var resetTable = function() {
+ if ( $.fn.DataTable.isDataTable('#resultado') ) {
+ $('#resultado').DataTable().destroy();
+ }
+ console.log("reseta tabela", $(".table-body").html())
+}
+
+
+// *******************************
+// View state functions
+// *******************************
+var hideLoading = function(){
+ $('#loading-whell').addClass("hidden");
+ $('#info').removeClass("hidden");
+}
+
+
+function initInputs(){
+ console.log("chamei");
+ $(function() {
+ $('#patrLiqInput').val(comparadores.patrLiq.value);
+ $('#liqCorrInput').val(comparadores.liqCorr.value);
+ $('#divPatInput').val(comparadores.divPat.value);
+ $('#dyInput').val(comparadores.dy.value);
+ $('#crescInput').val(comparadores.cresc.value);
+ $('#roeInput').val(comparadores.roe.value);
+ $('#pvpInput').val(comparadores.pvp.value);
+ $('#plInput').val(comparadores.pl.value);
+ $('#plxpvpInput').val(comparadores.plxpvp.value);
+
+ resultadoClone = $(".table-body").clone();
+ })
+}
+
+// Save rules
+$(document).on('click', '#saveRules', function(){
+
+ // Get if checkbox is checked
+ comparadores.patrLiq.checked = $('#patrLiqCheckbox:checked').length;
+ comparadores.liqCorr.checked = $('#liqCorrCheckbox:checked').length;
+ comparadores.roe.checked = $('#roeCheckbox:checked').length;
+ comparadores.divPat.checked = $('#divPatCheckbox:checked').length;
+ comparadores.cresc.checked = $('#crescCheckbox:checked').length;
+ comparadores.pvp.checked = $('#pvpCheckbox:checked').length;
+ comparadores.pl.checked = $('#plCheckbox:checked').length;
+ comparadores.dy.checked = $('#dyCheckbox:checked').length;
+ comparadores.plxpvp.checked = $('#plxpvpCheckbox:checked').length;
+
+ // get new values
+ comparadores.patrLiq.value = $('#patrLiqInput').val();
+ comparadores.liqCorr.value = $('#liqCorrInput').val();
+ comparadores.roe.value = $('#roeInput').val();
+ comparadores.divPat.value = $('#divPatInput').val();
+ comparadores.cresc.value = $('#crescInput').val();
+ comparadores.pvp.value = $('#pvpInput').val();
+ comparadores.pl.value = $('#plInput').val();
+ comparadores.dy.value = $('#dyInput').val();
+ comparadores.plxpvp.value = $('#plxpvpInput').val();
+
+ resetTable();
+ buildTable(arrayStocksHistory[0]);
+
+})
\ No newline at end of file
diff --git a/client/styles.css b/client/styles.css
new file mode 100644
index 0000000..1ba218f
--- /dev/null
+++ b/client/styles.css
@@ -0,0 +1,61 @@
+@keyframes App-logo-spin {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+}
+
+th {
+ background: white;
+ position: sticky;
+ top: 0;
+ z-index: 10;
+ background: #dddddd;
+ cursor: pointer;
+}
+
+.hidden{display:none;}
+
+.screen {
+ position: relative;
+ width: 100%;
+ height: 200px;
+}
+
+
+.loading-wrapper {
+ position: relative;
+}
+
+.loading-wrapper .loading-screen {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 10;
+ background-color: rgba(250,250,250,0.7);
+}
+
+.loading-wheel {
+ z-index: 11;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ margin-top: -50px;
+ margin-left: -50px;
+ border: 8px solid #e3e3e3;
+ border-radius: 50%;
+ border-top: 8px solid #4e8df9;
+ width: 100px;
+ height: 100px;
+ transform: translateY(-50%);
+ -webkit-animation: App-logo-spin 1.5s linear infinite;
+ animation: App-logo-spin 1.5s linear infinite;
+}
+
+.stock {
+ color: #0062ff;
+}
+
+.green-line {
+ background-color: #cde0cd;
+}
\ No newline at end of file
diff --git a/functions/.gitignore b/functions/.gitignore
new file mode 100644
index 0000000..7fbb8b4
--- /dev/null
+++ b/functions/.gitignore
@@ -0,0 +1,8 @@
+## Compiled JavaScript files
+**/*.js
+**/*.js.map
+
+# Typescript v1 declaration files
+typings/
+
+node_modules/
\ No newline at end of file
diff --git a/functions/package.json b/functions/package.json
new file mode 100644
index 0000000..d9e8a8f
--- /dev/null
+++ b/functions/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "functions",
+ "scripts": {
+ "lint": "tslint --project tsconfig.json",
+ "build": "tsc",
+ "serve": "npm run build && firebase serve --only functions",
+ "shell": "npm run build && firebase functions:shell",
+ "start": "npm run shell",
+ "deploy": "firebase deploy --only functions",
+ "logs": "firebase functions:log"
+ },
+ "engines": {
+ "node": "8"
+ },
+ "main": "lib/index.js",
+ "dependencies": {
+ "firebase-admin": "^8.0.0",
+ "firebase-functions": "^3.0.0",
+ "mysql": "^2.17.1"
+ },
+ "devDependencies": {
+ "@types/mysql": "2.15.6",
+ "tslint": "^5.12.0",
+ "typescript": "^3.2.2"
+ },
+ "private": true
+}
diff --git a/functions/src/index.ts b/functions/src/index.ts
new file mode 100644
index 0000000..e41e8bb
--- /dev/null
+++ b/functions/src/index.ts
@@ -0,0 +1,132 @@
+import { Change } from 'firebase-functions';
+import { DataSnapshot } from 'firebase-functions/lib/providers/database';
+import * as Mysql from 'mysql';
+
+// // Start writing Firebase Functions
+// // https://firebase.google.com/docs/functions/typescript
+//
+// export const helloWorld = functions.https.onRequest((request, response) => {
+// response.send("Hello from Firebase!");
+// });
+
+export const saveStockHistory = (change: Change, context = null) => {
+ // export const saveStockHistory = functions.database.ref('/stocks')
+ // .onWrite((change: Change, context) => {
+
+ const arrayStocksHistory = [];
+
+ const val = change.after.val;
+
+
+ Object.keys(val).forEach((key) => {
+ // console.log(snapshot.val()[key])
+ var object = JSON.parse(val[key]);
+ arrayStocksHistory.push(object)
+ // console.log(object);
+ })
+
+ // Sort by date, the first one will be the newest
+ arrayStocksHistory.sort((a,b) => {
+ var date1: any = new Date(a.date);
+ var date2 : any= new Date(b.date);
+ return date2 - date1;
+ })
+
+
+
+ const conn: Mysql.Connection = Mysql.createConnection({
+ host: process.env.DB_ENDPOINT,
+ user: process.env.DB_USER,
+ password: process.env.DB_PASSWORD,
+ database: process.env.DATABASE_NAME,
+ });
+
+
+ query(conn, "show tables", {}).then((res) => {
+ console.log(res);
+
+ conn.destroy();
+ })
+ .catch(err => conn.destroy());
+
+
+ // Grab the current value of what was written to the Realtime Database.
+ console.log('Payload:', change, "Context", context);
+ return ['Uppercasing', context, change];
+ // const uppercase = original.toUpperCase();
+ // You must return a Promise when performing asynchronous tasks inside a Functions such as
+ // writing to the Firebase Realtime Database.
+ // Setting an "uppercase" sibling in the Realtime Database returns a Promise.
+ // return snapshot.ref.parent.child('uppercase').set(uppercase);
+ // });
+};
+
+
+async function query(conn: Mysql.Connection, queryString: string, substituionValues: Object): Promise {
+ const queryOptions: Mysql.QueryOptions = transformIntoMySQLString(queryString, substituionValues);
+
+ return new Promise((resolve, reject) => {
+ conn.query(
+ queryOptions,
+ (err: Mysql.MysqlError | null, results?: any, fields?: Mysql.FieldInfo[]) => {
+ if (err) reject(err);
+ else {
+ console.info('Salvo objeto com sucesso no DB');
+ resolve(results);
+ }
+ }
+ );
+ });
+}
+
+function transformIntoMySQLString(queryString: string, substituionValues: any): Mysql.QueryOptions {
+
+ // Get all keys (they have a ':' in front of the name)
+ const allKeys: string[] = queryString.split(' ')
+ .filter((word: string) => word.indexOf(':') == 0);
+
+ const allValuesInOrder: Array = allKeys
+ .map((key: string) => substituionValues[key]);
+
+ // In case of error
+ if (allKeys.length != allValuesInOrder.length)
+ throw (
+ { 'queryString': queryString, subsValues: substituionValues }
+ );
+
+
+ // Overwrite the keys with '?'
+ let MysqlQueryString: string = queryString;
+ for (const key of allKeys) {
+ MysqlQueryString = MysqlQueryString.replace(key, '?');
+ }
+
+ return {
+ sql: MysqlQueryString,
+ values: allValuesInOrder
+ };
+}
+
+
+const change: any = null;
+// {
+// before:
+// DataSnapshot: {
+// app:
+// FirebaseApp: {
+// firebaseInternals_: [],
+// services_: { },
+// isDeleted_: false,
+// name_: '__admin__',
+// options_: [],
+// INTERNAL: [] },
+// instance: 'https://bovespastockratings.firebaseio.com',
+// _path: '/stocks',
+// _data:
+// {
+// '-LiAHa0uatNefaIDZ250': ' {\n "0": {\n "GBIO33": {\n "Cresc.5a": "0,00%",\n "DY": "0,00%",\n "Div.Brut/Pat.": "0,30",\n "EBITDA": "23,44%",\n "EV/EBIT": "0,00",\n "Liq.2m.": "1.360.630,00",\n "Liq.Corr.": "2,03",\n "Mrg.Liq.": "7,73%",\n "P/Ativ.Circ.Liq.": "0,00",\n "P/Ativo": "0,000",\n "P/Cap.Giro": "0,00",\n "P/EBIT": "0,00",\n "P/L": "0,00",\n "P/VP": "0,00",\n "PSR": "0,000",\n "Pat.Liq": "729.236.000,00",\n "ROE": "8,70%",\n "ROIC": "19,69%",\n "cotacao": "7,25"\n }\n },\n "1": {\n "STBP3": {\n "Cresc.5a": "-2,25%",\n "DY": "0,10%",\n "Div.Brut/Pat.": "0,16",\n "EBITDA": "5,32%",\n "EV/EBIT": "58,74",\n "Liq.2m.": "6.607.860,00",\n "Liq.Corr.": "1,23",\n "Mrg.Liq.": "-0,01%",\n "P/Ativ.Circ.Liq.": "-2,58",\n "P/Ativo": "1,024",\n "P/Cap.Giro": "39,43",\n "P/EBIT": "59,37",\n "P/L": "-26.388,80",\n "P/VP": "2,21",\n "PSR": "3,161",\n "Pat.Liq": "1.337.190.000,00",\n "ROE": "-0,01%",\n "ROIC": "1,93%",\n "cotacao": "4,43"\n }\n }'
+// }
+// }
+// }
+
+saveStockHistory(change);
\ No newline at end of file
diff --git a/functions/tsconfig.json b/functions/tsconfig.json
new file mode 100644
index 0000000..7ce05d0
--- /dev/null
+++ b/functions/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "noImplicitReturns": true,
+ "noUnusedLocals": true,
+ "outDir": "lib",
+ "sourceMap": true,
+ "strict": true,
+ "target": "es2017"
+ },
+ "compileOnSave": true,
+ "include": [
+ "src"
+ ]
+}
diff --git a/functions/tslint.json b/functions/tslint.json
new file mode 100644
index 0000000..98b2bfd
--- /dev/null
+++ b/functions/tslint.json
@@ -0,0 +1,115 @@
+{
+ "rules": {
+ // -- Strict errors --
+ // These lint rules are likely always a good idea.
+
+ // Force function overloads to be declared together. This ensures readers understand APIs.
+ "adjacent-overload-signatures": true,
+
+ // Do not allow the subtle/obscure comma operator.
+ "ban-comma-operator": true,
+
+ // Do not allow internal modules or namespaces . These are deprecated in favor of ES6 modules.
+ "no-namespace": true,
+
+ // Do not allow parameters to be reassigned. To avoid bugs, developers should instead assign new values to new vars.
+ "no-parameter-reassignment": true,
+
+ // Force the use of ES6-style imports instead of /// imports.
+ "no-reference": true,
+
+ // Do not allow type assertions that do nothing. This is a big warning that the developer may not understand the
+ // code currently being edited (they may be incorrectly handling a different type case that does not exist).
+ "no-unnecessary-type-assertion": true,
+
+ // Disallow nonsensical label usage.
+ "label-position": true,
+
+ // Disallows the (often typo) syntax if (var1 = var2). Replace with if (var2) { var1 = var2 }.
+ "no-conditional-assignment": true,
+
+ // Disallows constructors for primitive types (e.g. new Number('123'), though Number('123') is still allowed).
+ "no-construct": true,
+
+ // Do not allow super() to be called twice in a constructor.
+ "no-duplicate-super": true,
+
+ // Do not allow the same case to appear more than once in a switch block.
+ "no-duplicate-switch-case": true,
+
+ // Do not allow a variable to be declared more than once in the same block. Consider function parameters in this
+ // rule.
+ "no-duplicate-variable": [true, "check-parameters"],
+
+ // Disallows a variable definition in an inner scope from shadowing a variable in an outer scope. Developers should
+ // instead use a separate variable name.
+ "no-shadowed-variable": true,
+
+ // Empty blocks are almost never needed. Allow the one general exception: empty catch blocks.
+ "no-empty": [true, "allow-empty-catch"],
+
+ // Functions must either be handled directly (e.g. with a catch() handler) or returned to another function.
+ // This is a major source of errors in Cloud Functions and the team strongly recommends leaving this rule on.
+ "no-floating-promises": true,
+
+ // Do not allow any imports for modules that are not in package.json. These will almost certainly fail when
+ // deployed.
+ "no-implicit-dependencies": true,
+
+ // The 'this' keyword can only be used inside of classes.
+ "no-invalid-this": true,
+
+ // Do not allow strings to be thrown because they will not include stack traces. Throw Errors instead.
+ "no-string-throw": true,
+
+ // Disallow control flow statements, such as return, continue, break, and throw in finally blocks.
+ "no-unsafe-finally": true,
+
+ // Expressions must always return a value. Avoids common errors like const myValue = functionReturningVoid();
+ "no-void-expression": [true, "ignore-arrow-function-shorthand"],
+
+ // Disallow duplicate imports in the same file.
+ "no-duplicate-imports": true,
+
+
+ // -- Strong Warnings --
+ // These rules should almost never be needed, but may be included due to legacy code.
+ // They are left as a warning to avoid frustration with blocked deploys when the developer
+ // understand the warning and wants to deploy anyway.
+
+ // Warn when an empty interface is defined. These are generally not useful.
+ "no-empty-interface": {"severity": "warning"},
+
+ // Warn when an import will have side effects.
+ "no-import-side-effect": {"severity": "warning"},
+
+ // Warn when variables are defined with var. Var has subtle meaning that can lead to bugs. Strongly prefer const for
+ // most values and let for values that will change.
+ "no-var-keyword": {"severity": "warning"},
+
+ // Prefer === and !== over == and !=. The latter operators support overloads that are often accidental.
+ "triple-equals": {"severity": "warning"},
+
+ // Warn when using deprecated APIs.
+ "deprecation": {"severity": "warning"},
+
+ // -- Light Warnings --
+ // These rules are intended to help developers use better style. Simpler code has fewer bugs. These would be "info"
+ // if TSLint supported such a level.
+
+ // prefer for( ... of ... ) to an index loop when the index is only used to fetch an object from an array.
+ // (Even better: check out utils like .map if transforming an array!)
+ "prefer-for-of": {"severity": "warning"},
+
+ // Warns if function overloads could be unified into a single function with optional or rest parameters.
+ "unified-signatures": {"severity": "warning"},
+
+ // Prefer const for values that will not change. This better documents code.
+ "prefer-const": {"severity": "warning"},
+
+ // Multi-line object literals and function calls should have a trailing comma. This helps avoid merge conflicts.
+ "trailing-comma": {"severity": "warning"}
+ },
+
+ "defaultSeverity": "error"
+}
diff --git a/gettingstarted/static/humans.txt b/gettingstarted/static/humans.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gettingstarted/urls.py b/gettingstarted/urls.py
new file mode 100644
index 0000000..36492ed
--- /dev/null
+++ b/gettingstarted/urls.py
@@ -0,0 +1,17 @@
+from django.conf.urls import include, url
+from django.urls import path
+
+from django.contrib import admin
+admin.autodiscover()
+
+import hello.views
+
+# Examples:
+# url(r'^$', 'gettingstarted.views.home', name='home'),
+# url(r'^blog/', include('blog.urls')),
+
+urlpatterns = [
+ url(r'^$', hello.views.index, name='index'),
+ url(r'^db', hello.views.db, name='db'),
+ path('admin/', admin.site.urls),
+]
diff --git a/gettingstarted/wsgi.py b/gettingstarted/wsgi.py
new file mode 100644
index 0000000..feb0af4
--- /dev/null
+++ b/gettingstarted/wsgi.py
@@ -0,0 +1,15 @@
+"""
+WSGI config for gettingstarted project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/
+"""
+
+import os
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gettingstarted.settings")
+
+from django.core.wsgi import get_wsgi_application
+
+application = get_wsgi_application()
diff --git a/hello/__init__.py b/hello/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/hello/admin.py b/hello/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/hello/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/hello/migrations/0001_initial.py b/hello/migrations/0001_initial.py
new file mode 100644
index 0000000..99c4a82
--- /dev/null
+++ b/hello/migrations/0001_initial.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.1 on 2016-01-27 21:54
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Greeting',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('when', models.DateTimeField(auto_now_add=True, verbose_name=b'date created')),
+ ],
+ ),
+ ]
diff --git a/hello/migrations/__init__.py b/hello/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/hello/models.py b/hello/models.py
new file mode 100644
index 0000000..89d76fd
--- /dev/null
+++ b/hello/models.py
@@ -0,0 +1,5 @@
+from django.db import models
+
+# Create your models here.
+class Greeting(models.Model):
+ when = models.DateTimeField('date created', auto_now_add=True)
diff --git a/hello/static/lang-logo.png b/hello/static/lang-logo.png
new file mode 100644
index 0000000..f04aff1
Binary files /dev/null and b/hello/static/lang-logo.png differ
diff --git a/hello/templates/base.html b/hello/templates/base.html
new file mode 100644
index 0000000..97ec073
--- /dev/null
+++ b/hello/templates/base.html
@@ -0,0 +1,83 @@
+
+
+
+Python Getting Started on Heroku
+
+
+
+
+
+
+
+
+
+
+{% block content %}{% endblock %}
+
+
+
diff --git a/hello/templates/db.html b/hello/templates/db.html
new file mode 100644
index 0000000..e0e03d4
--- /dev/null
+++ b/hello/templates/db.html
@@ -0,0 +1,21 @@
+{% extends "base.html" %}
+{% load staticfiles %}
+
+{% block content %}
+
+
+
+
Page View Report
+
+
+
+
+{% for greeting in greetings %}
+ {{ greeting.when }}
+{% endfor %}
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/hello/templates/index.html b/hello/templates/index.html
new file mode 100644
index 0000000..8af2902
--- /dev/null
+++ b/hello/templates/index.html
@@ -0,0 +1,58 @@
+{% extends "base.html" %}
+{% load staticfiles %}
+
+{% block content %}
+
+
+
+
+
+
+
Getting Started with Python on Heroku
+
This is a sample Python application deployed to Heroku. It's a reasonably simple app - but a good foundation for understanding how to get the most out of the Heroku platform.
+
Getting Started with Python
+
Source on GitHub
+
+
+
+
+ To deploy your own copy, and learn the fundamentals of the Heroku platform, head over to the
Getting Started with Python on Heroku tutorial.
+
+
+
+
+
How this sample app works
+
+ This app was deployed to Heroku, either using Git or by using Heroku Button on the repository.
+
+ When Heroku received the source code, it fetched all the dependencies in the Pipfile , creating a deployable slug.
+ The platform then spins up a dyno, a lightweight container that provides an isolated environment in which the slug can be mounted and executed.
+ You can scale your app, manage it, and deploy over 150 add-on services , from the Dashboard or CLI.
+
+
+
+
Next Steps
+
+ If you are following the Getting Started guide, then please head back to the tutorial and follow the next steps!
+ If you deployed this app by deploying the Heroku Button, then in a command line shell, run:
+
+ git clone https://github.com/heroku/python-getting-started.git
- this will create a local copy of the source code for the app
+ cd python-getting-started
- change directory into the local source code repository
+ heroku git:remote -a <your-app-name>
- associate the Heroku app with the repository
+ You'll now be set up to run the app locally, or deploy changes to Heroku
+
+
+
Helpful Links
+
+
+
+
+ Please do work through the Getting Started guide, even if you do know how to build such an application. The guide covers the basics of working with Heroku, and will familiarize you with all the concepts you need in order to build and deploy your own apps.
+
+
+{% endblock %}
diff --git a/hello/tests.py b/hello/tests.py
new file mode 100644
index 0000000..1d69113
--- /dev/null
+++ b/hello/tests.py
@@ -0,0 +1,18 @@
+from django.contrib.auth.models import AnonymousUser, User
+from django.test import TestCase, RequestFactory
+
+from .views import index
+
+class SimpleTest(TestCase):
+ def setUp(self):
+ # Every test needs access to the request factory.
+ self.factory = RequestFactory()
+
+ def test_details(self):
+ # Create an instance of a GET request.
+ request = self.factory.get('/')
+ request.user = AnonymousUser()
+
+ # Test my_view() as if it were deployed at /customer/details
+ response = index(request)
+ self.assertEqual(response.status_code, 200)
\ No newline at end of file
diff --git a/hello/views.py b/hello/views.py
new file mode 100644
index 0000000..8558d84
--- /dev/null
+++ b/hello/views.py
@@ -0,0 +1,20 @@
+from django.shortcuts import render
+from django.http import HttpResponse
+
+from .models import Greeting
+
+# Create your views here.
+def index(request):
+ # return HttpResponse('Hello from Python!')
+ return render(request, 'index.html')
+
+
+def db(request):
+
+ greeting = Greeting()
+ greeting.save()
+
+ greetings = Greeting.objects.all()
+
+ return render(request, 'db.html', {'greetings': greetings})
+