Alan Colon 7 gadi atpakaļ
vecāks
revīzija
ade113b4cd
6 mainītis faili ar 370 papildinājumiem un 5 dzēšanām
  1. 8 1
      config.js
  2. 267 0
      driverstatsframe.html
  3. 39 2
      lib/server.js
  4. 2 0
      package.json
  5. 11 2
      src/index.html
  6. 43 0
      yarn.lock

+ 8 - 1
config.js

@@ -1,3 +1,10 @@
 module.exports = {
-  port: process.env.NODE_PORT
+  port: process.env.NODE_PORT,
+  ftp: {
+    host: 'waws-prod-bay-001.ftp.azurewebsites.windows.net',
+    user: 'driftstats\\$driftstats',
+    password: 'taDa2wff6HS4S26x0fsfLKvpNlRrzWmoN5u4cKhM3tmt9mWY1E3xS85T6BbT',
+    secure: false
+  }
+
 }

+ 267 - 0
driverstatsframe.html

@@ -0,0 +1,267 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+    <title></title>
+    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.12/angular.min.js"></script>
+    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
+    <script src="/Scripts/jquery-fontspy.js"></script>
+<!--    <link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet" />-->
+    <link href="/css/fonts.css" rel="stylesheet" />
+    <style>
+        table {
+            width: 100%;
+            border-collapse: collapse;
+        }
+        h1 {
+            font-family: veneer;
+            font-size: 20px; 
+            font-weight: normal;
+            background-color: rgb(235,37,42);
+            color: white;
+            margin: 0 0 0 0;
+            padding: 5px 5px 5px 5px;
+        }
+        footer {
+            margin-top: 30px;
+            background-color: black;
+            color: white;
+            padding: 5px 5px 5px 5px;
+        }
+        footer a {
+            color: rgb(70, 181, 255);
+        }
+        th {
+            text-align: left;
+            font-family: function-pro-demi;
+            font-weight: normal;
+        }
+        .table-striped > tbody > tr:nth-child(even) > td {
+            background-color: rgb(227, 227, 227);
+        }
+        .table-striped > tbody > tr:nth-child(odd) > td {
+            background-color: rgb(214, 214, 214);
+        }
+        .content {
+            overflow-y: auto;
+            position: absolute;
+        }
+        header, footer{
+            position: absolute;
+        }
+        body, select, option {
+            font-family: function-pro-book;
+        }
+        body {
+            overflow: hidden;
+            -webkit-font-smoothing: antialiased;
+        }
+        @media screen and (max-width: 300px) {
+            thead select {
+                max-width: 112px;
+            }
+        }
+        @media (min-width: 301px) {
+            td:first-child, th:first-child {
+                padding: 0 0 0 5px;
+            }
+        }
+    </style>
+    <script>
+        $(function () {
+            function onresize() {
+                var header = $("header");
+                var footer = $("footer");
+                var content = $(".content");
+                var w = $(window).width();
+                var h = $(window).height();
+                header.offset({ left: 0, top: 0 });
+                header.width(w);
+                footer.offset({ left: 0, top: h - footer.outerHeight() });
+                footer.width(w);
+                content.offset({ left: 0, top: header.outerHeight() });
+                content.width(w);
+                content.height(h - footer.outerHeight() - header.outerHeight());
+            };
+            $(window).resize(onresize);
+            fontSpy('veneer', { success: onresize });
+            fontSpy('function-pro-demi', { success: onresize });
+            fontSpy('function-pro-book', { success: onresize });
+            onresize();
+        });
+
+    </script>
+    <script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
+    <script>
+        var serieses = [
+            { id: 1, name: "Formula Drift USA PRO" },
+            { id: 3, name: "Formula Drift USA PRO2" },
+            { id: 2, name: "Formula Drift Asia" }
+        ];
+
+        var statTypes = [
+            { procedure: "record_highesteventwinpercentage.wins"                   , name: "Event Win Count"        },
+            { procedure: "record_highestpodiumcount.podiums"                       , name: "Podium Count"           },
+            { procedure: "record_highestpodiumpercentage.percentage"               , name: "Podium Percentage"      },
+            { procedure: "record_highestresultaverage.averageresult"               , name: "Result Average"         },
+            { procedure: "record_highestmatchwincount.wins"                        , name: "Match Win Count"        },
+            { procedure: "record_highestmatchwinpercentage.percentage"             , name: "Match Win Percentage"   },
+            { procedure: "record_highestqualifyingaverage.averagequalify"          , name: "Qualifying Average"     },
+            { procedure: "record_top4count.topcount"                               , name: "Top 4 Count"            },
+            { procedure: "record_top8count.topcount"                               , name: "Top 8 Count"            },
+            { procedure: "record_top16count.topcount"                              , name: "Top 16 Count"           },
+            { procedure: "record_top32count.topcount"                              , name: "Top 32 Count"           }
+        ];
+
+        function queryObj() {
+            var result = {}, queryString = location.search.slice(1),
+                re = /([^&=]+)=([^&]*)/g, m;
+
+            while (m = re.exec(queryString)) {
+                result[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
+            }
+
+            return result;
+        }
+
+
+
+        function driverStatsFrame($scope, $http) {
+            $scope.statType = statTypes[0].procedure;
+            var query = queryObj();
+            var mySeries = serieses.slice(0);
+            var qs = [];
+            if (query.seriesid) {
+                qs = query.seriesid.split(',').map(function(s){ return +s; });
+                var newSeries = [];
+                for (var i = 0; i < mySeries.length; i++) {
+                    if (qs.indexOf(mySeries[i].id) != -1) {
+                        newSeries.push(mySeries[i]);
+                    }
+                }
+                mySeries = newSeries;
+            }
+            if (query.seriesid) $scope.seriesid = qs[0];
+            if (query.stattypeid) $scope.statType = statTypes[+query.stattypeid - 1].procedure;
+            $scope.serieses = mySeries;
+            $scope.statTypes = statTypes;
+            window.$scope = $scope;
+            $scope.year = null;
+            $scope.hash = function () {
+                return [$scope.seriesid, $scope.statType, $scope.year].join(".");
+            };
+            $scope.$watch("seriesid", function () {
+                if ($scope.seriesid) {
+                    $http.post("/Service.asmx/GetSeries", {
+                        series: +$scope.seriesid
+                    }).success(function (res) {
+                        $scope.championshipData = res.d;
+                        $scope.championshipData.Seasons.forEach(function (season) {
+                            season.Label = season.Year.toString();
+                        });
+                        $scope.championshipData.Seasons.unshift({
+                            Label: "Lifetime",
+                            Year: null
+                        });
+                        var validYears = $scope.championshipData.Seasons.map(function (season) {
+                            return season.Year;
+                        });
+                        if ($scope.year && validYears.indexOf($scope.year) == -1) {
+                            $scope.year = null;
+                        }
+                    });
+                }
+            });
+            $scope.$watch("hash()", function () {
+                if ($scope.seriesid && $scope.statType) {
+                    var words = $scope.statType.split("."),
+                        procedure = words[0],
+                        column = $scope.column = words[1];
+                    $scope.data = null;
+                    $http.post("/Service.asmx/QueryRecords", {
+                        procedure: procedure,
+                        arguments: [+$scope.seriesid, $scope.year]
+                    })
+                    .success(function (dataset) {
+                        $scope.dataset = dataset;
+                        window.dataset = dataset;
+                        $scope.data = window.data = dataset.d.Tables[0].Data.map(function(row) {
+                            return row.reduce(function (pv, cv, i, a) {
+                                var column = dataset.d.Tables[0].Columns[i];
+                                pv[column] = column == "percentage" ? 
+                                    (Math.floor(cv * 10000) / 100) + "%"
+                                    : isFinite(cv) ? (Math.floor(cv * 100) / 100) :
+                                    cv
+                                return pv;
+                            }, {});
+                        });
+                    });
+                }
+            });
+        }
+    </script>
+
+</head>
+<body ng-app ng-controller="driverStatsFrame">
+    <header>
+        <h1>Driver Stats</h1>
+        <table>
+            <thead>
+                <tr>
+                    <th>Championship</th>
+                    <th>Year</th>
+                    <th>Stat Type</th>
+                </tr>
+                <tr>
+                    <td>
+                        <select ng-model="seriesid" ng-options="series.id as series.name for series in serieses"></select>
+                    </td>
+                    <td>
+                        <select ng-model="year" ng-options="season.Year as season.Label for season in championshipData.Seasons">
+                        
+                        </select>
+                    </td>
+                    <td>
+                        <select ng-model="statType" ng-options="st.procedure as st.name for st in statTypes"></select>
+    <!--                    <select ng-model="statType">
+                            <option value="record_highesteventwinpercentage.wins"                   >Event Win Count</option>
+                            <option value="record_highestpodiumcount.podiums"                       >Podium Count</option>
+                            <option value="record_highestpodiumpercentage.percentage"               >Podium Percentage</option>
+                            <option value="record_highestresultaverage.averageresult"               >Result Average</option>
+                            <option value="record_highestmatchwincount.wins"                        >Match Win Count</option>
+                            <option value="record_highestmatchwinpercentage.percentage"             >Match Win Percentage</option>
+                            <option value="record_highestqualifyingaverage.averagequalify"          >Qualifying Average</option>
+                            <option value="record_top4count.topcount"                               >Top 4 Count</option>
+                            <option value="record_top8count.topcount"                               >Top 8 Count</option>
+                            <option value="record_top16count.topcount"                              >Top 16 Count</option>
+                            <option value="record_top32count.topcount"                              >Top 32 Count</option>
+                        </select>-->
+                    </td>
+                </tr>
+            </thead>
+        </table>
+    </header>
+    <div class="content">
+        <table>
+            <tbody>
+                <tr>
+                    <td colspan="3">
+                        <table class="table-striped">
+                            <tbody>
+                                <tr ng-repeat="row in data">
+                                    <td width="66%">{{row.drivername}}</td>
+                                    <td width="34%">{{row[column]}}</td>
+                                </tr>
+                            </tbody>
+                        </table>
+
+                    </td>
+                </tr>
+            </tbody>
+        </table>
+    </div>
+    <footer>
+        Powered by <a target="_blank" href="http://www.driftstats.com">DriftStats.com</a>
+    </footer>
+</body>
+</html>
+

+ 39 - 2
lib/server.js

@@ -1,6 +1,8 @@
 const app = require('./app')
-
-/* TODO: Add handlers */
+const fileUpload = require('express-fileupload')
+const ftp = require('basic-ftp')
+const config = require('../config')
+const { Readable } = require('stream')
 
 const connections = []
 
@@ -19,4 +21,39 @@ app.ws.on('connection', ws => {
 app.listen().catch((err) => {
   console.log(err.toString())
   process.exit(1)
+})
+
+
+app.post('/upload', fileUpload(), async (req, res) => {
+  try {
+    const files = Array.isArray(req.files.files)
+    ? req.files.files
+    : [req.files.files]
+
+    for (let file of files) {
+      if (file.name.includes('..') || file.name.includes('\\') || file.name.includes('//')) {
+        throw new Error('Illegal characters: ' + file.name)
+      }
+      const allowed = ['.html', '.css', '.png', '.gif', '.jpg', '.svg', '.js']
+      const isAllowed = allowed.find(ext => file.name.endsWith(ext))
+      if (!isAllowed) throw new Error('Illegal extension: ' + file.name)
+    }
+    const client = new ftp.Client()
+    client.ftp.verbose = true
+    await client.access(config.ftp)
+    await client.ensureDir('site/wwwroot/formuladrift')
+    console.log(await client.list())
+
+    for (let file of files) {
+      const stream = new Readable()
+      stream.push(file.data)
+      stream.push(null)
+      await client.upload(stream, file.name)
+    }
+    client.close()
+    res.status(200).send('Files uploaded')
+  } catch (err) {
+    console.error(err)
+    res.status(500).send('Internal server error')
+  }
 })

+ 2 - 0
package.json

@@ -34,9 +34,11 @@
     "webpack-livereload-plugin": "^2.2.0"
   },
   "dependencies": {
+    "basic-ftp": "^3.4.4",
     "body-parser": "^1.18.3",
     "chalk": "^2.4.2",
     "express": "^4.16.4",
+    "express-fileupload": "^1.1.3-alpha.2",
     "morgan": "^1.9.1",
     "ws": "^6.1.2"
   }

+ 11 - 2
src/index.html

@@ -1,8 +1,17 @@
 <html>
   <head>
-    <title>CHANGEME</title>
+    <title>DriftStats Uploader</title>
   </head>
   <body>
-    <h1>CHANGEME</h1>
+    <h1>DriftStats Uploader</h1>
+
+    <p>Select files to upload to driftstats.com</p>
+    <p>Valid file extensions are: .html, .css, .png, .gif, .jpg, .svg, .js</p>
+    <p>Any file you upload will be available at http://www.driftstats.com/formuladrift/<i>filename</i></filename></p>
+
+    <form action="upload" method="POST" enctype="multipart/form-data">
+      <input type="file" name="files" multiple />
+      <button type="submit">Upload</button>
+    </form>
   </body>
 </html>

+ 43 - 0
yarn.lock

@@ -418,6 +418,11 @@ basic-auth@~2.0.0:
   dependencies:
     safe-buffer "5.1.2"
 
+basic-ftp@^3.4.4:
+  version "3.4.4"
+  resolved "https://registry.yarnpkg.com/basic-ftp/-/basic-ftp-3.4.4.tgz#371513cceaf2a5e13c37fd3124c321cc4c2fab5c"
+  integrity sha512-aU9nwsw6og9IajVjVW/lhJVHUawObSIHoUIEoxSgX9jXKpQmVvTqwH1ut6mstygVE1pUSW2KEU4Y2zKzKy9whQ==
+
 batch@0.6.1:
   version "0.6.1"
   resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16"
@@ -625,6 +630,14 @@ builtin-status-codes@^3.0.0:
   resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
   integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=
 
+busboy@^0.2.14:
+  version "0.2.14"
+  resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.2.14.tgz#6c2a622efcf47c57bbbe1e2a9c37ad36c7925453"
+  integrity sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=
+  dependencies:
+    dicer "0.2.5"
+    readable-stream "1.1.x"
+
 bytes@1:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/bytes/-/bytes-1.0.0.tgz#3569ede8ba34315fab99c3e92cb04c7220de1fa8"
@@ -1235,6 +1248,14 @@ detect-node@^2.0.4:
   resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c"
   integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==
 
+dicer@0.2.5:
+  version "0.2.5"
+  resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.2.5.tgz#5996c086bb33218c812c090bddc09cd12facb70f"
+  integrity sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=
+  dependencies:
+    readable-stream "1.1.x"
+    streamsearch "0.1.2"
+
 diff@3.5.0:
   version "3.5.0"
   resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
@@ -1552,6 +1573,13 @@ expand-brackets@^2.1.4:
     snapdragon "^0.8.1"
     to-regex "^3.0.1"
 
+express-fileupload@^1.1.3-alpha.2:
+  version "1.1.3-alpha.2"
+  resolved "https://registry.yarnpkg.com/express-fileupload/-/express-fileupload-1.1.3-alpha.2.tgz#91ed0a44a42c7b14c01cb01649aee96f6386a208"
+  integrity sha512-askIbniNmGzLBsmzDzfy9aR3vOFCUgNBOKesplC8XAYT85rOOTlgK0gdJMwgDKQ8tw4sdfgYpAnAbuPbYoyQKg==
+  dependencies:
+    busboy "^0.2.14"
+
 express@^4.16.2, express@^4.16.4:
   version "4.16.4"
   resolved "https://registry.yarnpkg.com/express/-/express-4.16.4.tgz#fddef61926109e24c515ea97fd2f1bdbf62df12e"
@@ -3753,6 +3781,16 @@ readable-stream@1.0:
     isarray "0.0.1"
     string_decoder "~0.10.x"
 
+readable-stream@1.1.x:
+  version "1.1.14"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
+  integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk=
+  dependencies:
+    core-util-is "~1.0.0"
+    inherits "~2.0.1"
+    isarray "0.0.1"
+    string_decoder "~0.10.x"
+
 readable-stream@^3.0.6:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.1.1.tgz#ed6bbc6c5ba58b090039ff18ce670515795aeb06"
@@ -4331,6 +4369,11 @@ stream-shift@^1.0.0:
   resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
   integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=
 
+streamsearch@0.1.2:
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
+  integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
+
 string-template@~0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add"