diff --git a/private/api/methods/TestMethod.js b/private/api/methods/TestMethod.js deleted file mode 100644 index e9dc51f..0000000 --- a/private/api/methods/TestMethod.js +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2018 Dominic Masters -// -// MIT License -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -const APIHandler = require('./../APIHandler'); - -class TestMethod extends APIHandler { - constructor(api) { - super(api, 'GET', '/test'); - } - - handle(request) { - return { - ok: true, - data: "Hello World" - }; - } -} - -module.exports = TestMethod; diff --git a/private/app/App.js b/private/app/App.js index 489e7af..ead0814 100644 --- a/private/app/App.js +++ b/private/app/App.js @@ -25,27 +25,21 @@ const Configuration = require('./../config/Configuration'), DatabaseConnection = require('./../database/DatabaseConnection'), - Server = require('./../server/Server'), - Email = require('./../email/Email') + Server = require('./../server/Server') ; -//Constants -const PUBLIC_PATH = path.join(__dirname, '..', '..', 'dist'); - class App { constructor() { this.config = new Configuration(this); this.database = new DatabaseConnection(this); this.server = new Server(this); - this.email = new Email(this); } getConfig() { return this.config; } + getDiscord() { return this.discord; } getDatabase() { return this.database; } - getServer() { return this.server; } - getEmail() {return this.email;} + getPalaise() { return this.palaise; } - //Primary Functions async init() { this.log('Starting App...'); @@ -69,26 +63,17 @@ class App { return; } - //Connect to our SMTP Host (For sending mail) + //Start the server + this.log('Starting Server...'); try { - this.log('Connecting to SMTP Server'); - await this.email.connect(); - console.log('...Done'); + await this.server.init(); } catch(e) { - console.error("Failed to setup emails!"); - throw new Error(e); + this.error('Failed to start server!'); + this.error(e); + return; } - //Now we need to start the server. This provides both a nice interface, as - //well as our API Handler (including 2auth callback urls) - try { - this.log("Starting Server..."); - await this.server.start(); - console.log("...Done!"); - } catch(e) { - console.error("Failed to start the server!"); - throw new Error(e); - } + this.log('App ready'); } // Logging Functions // diff --git a/private/configuration/Configuration.js b/private/config/Configuration.js similarity index 93% rename from private/configuration/Configuration.js rename to private/config/Configuration.js index 725b54a..787402f 100644 --- a/private/configuration/Configuration.js +++ b/private/config/Configuration.js @@ -36,14 +36,10 @@ class Configuration { async loadConfig(path) { //First we need to check if this is Heroku or not... let processVariabels = process.env; - this.isHeroku = false; - if( - processVariabels !== typeof undefined && - typeof processVariabels.NODE_HOME !== typeof undefined && + this.isHeroku = ( + processVariabels && processVariabels.NODE_HOME && processVariabels.NODE_HOME.indexOf("heroku") !== -1 - ) { - this.isHeroku = true; - } + ); //Read config data if(this.isHeroku) { diff --git a/private/database/DatabaseConnection.js b/private/database/DatabaseConnection.js index 485eacf..bc9b4ea 100644 --- a/private/database/DatabaseConnection.js +++ b/private/database/DatabaseConnection.js @@ -44,17 +44,16 @@ class DatabaseConnection { //Load queries into cache let queries = {}; - let types = fs.readdirSync(__dirname + '/' + QUERIES_DIRECTORY); + let queryDir = `${__dirname}/${QUERIES_DIRECTORY}` + let types = fs.readdirSync(queryDir); for(let i = 0; i < types.length; i++) { //Now Scan each file in this directory - let dir = __dirname + '/' + QUERIES_DIRECTORY + '/' + types[i]; + let dir = `${queryDir}/${types[i]}`; let dirContents = fs.readdirSync(dir); for(let x = 0; x < dirContents.length; x++) { //Now read each file within this dir.. - let filePath = dir + '/' + dirContents[x]; - console.log(filePath); + let filePath = `${dir}/${dirContents[x]}`; let query = fs.readFileSync(filePath, 'utf8'); - //Now Save our query as filename minus extension. queries[dirContents[x].split('.')[0]] = query; } diff --git a/private/database/queries/create/CreateFormatsTable.sql b/private/database/queries/create/CreateFormatsTable.sql new file mode 100644 index 0000000..b20b63a --- /dev/null +++ b/private/database/queries/create/CreateFormatsTable.sql @@ -0,0 +1,5 @@ +CREATE TABLE IF NOT EXISTS "Formats" ( + "id" BIGSERIAL NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "gameId" BIGSERIAL NOT NULL +); diff --git a/private/database/queries/create/CreateGamesTable.sql b/private/database/queries/create/CreateGamesTable.sql new file mode 100644 index 0000000..c7a7d1b --- /dev/null +++ b/private/database/queries/create/CreateGamesTable.sql @@ -0,0 +1,4 @@ +CREATE TABLE IF NOT EXISTS "Games" ( + "id" BIGSERIAL NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL UNIQUE +); diff --git a/private/database/queries/create/CreateSeasonsTable.sql b/private/database/queries/create/CreateSeasonsTable.sql new file mode 100644 index 0000000..f145a2c --- /dev/null +++ b/private/database/queries/create/CreateSeasonsTable.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS "Seasons" ( + "id" BIGSERIAL NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "formatId" BIGSERIAL NOT NULL, + "startDate" TIMESTAMP NOT NULL, + "endDate" TIMESTAMP NOT NULL +); diff --git a/private/database/queries/create/CreateTeamUsersSeasonsTable.sql b/private/database/queries/create/CreateTeamUsersSeasonsTable.sql new file mode 100644 index 0000000..d4e020f --- /dev/null +++ b/private/database/queries/create/CreateTeamUsersSeasonsTable.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS "TeamUsersSeasons" ( + "teamId" BIGSERIAL NOT NULL, + "userId" BIGSERIAL NOT NULL, + "seasonId" BIGSERIAL NOT NULL, + "registered" TIMESTAMP NOT NULL, + PRIMARY KEY("teamId", "userId", "seasonId") +); diff --git a/private/database/queries/create/CreateTeamsTable.sql b/private/database/queries/create/CreateTeamsTable.sql new file mode 100644 index 0000000..de63a4a --- /dev/null +++ b/private/database/queries/create/CreateTeamsTable.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS "Teams" ( + "id" BIGSERIAL NOT NULL PRIMARY KEY, + "name" varchar(32) NOT NULL UNIQUE, + "motto" text NULL, + "image" text NULL, + "registered" TIMESTAMP NOT NULL +); diff --git a/private/database/queries/create/CreateUsersTable.sql b/private/database/queries/create/CreateUsersTable.sql new file mode 100644 index 0000000..7d950b2 --- /dev/null +++ b/private/database/queries/create/CreateUsersTable.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS "Users" ( + "id" BIGSERIAL NOT NULL PRIMARY KEY, + "discordId" text NULL UNIQUE, + "steamId" text NULL UNIQUE, + "email" text NULL UNIQUE +); diff --git a/private/database/queries/format/AddFormat.sql b/private/database/queries/format/AddFormat.sql new file mode 100644 index 0000000..56a4b0b --- /dev/null +++ b/private/database/queries/format/AddFormat.sql @@ -0,0 +1 @@ +INSERT INTO "Formats" ("name", "gameId") VALUES (${name}, ${gameId}) RETURNING *; diff --git a/private/database/queries/format/GetFormatsByGame.sql b/private/database/queries/format/GetFormatsByGame.sql new file mode 100644 index 0000000..a7a8f52 --- /dev/null +++ b/private/database/queries/format/GetFormatsByGame.sql @@ -0,0 +1 @@ +SELECT * FROM "Formats" WHERE "gameId"=${gameId}; diff --git a/private/database/queries/game/AddGame.sql b/private/database/queries/game/AddGame.sql new file mode 100644 index 0000000..942d60d --- /dev/null +++ b/private/database/queries/game/AddGame.sql @@ -0,0 +1 @@ +INSERT INTO "Games" (name) VALUES (${name}) RETURNING *; diff --git a/private/database/queries/game/GetGameByName.sql b/private/database/queries/game/GetGameByName.sql new file mode 100644 index 0000000..0ac18b1 --- /dev/null +++ b/private/database/queries/game/GetGameByName.sql @@ -0,0 +1 @@ +SELECT * FROM "Games" WHERE LOWER("name") = LOWER(${name}) LIMIT 1; diff --git a/private/database/queries/season/GetSeasonById.sql b/private/database/queries/season/GetSeasonById.sql new file mode 100644 index 0000000..be98c37 --- /dev/null +++ b/private/database/queries/season/GetSeasonById.sql @@ -0,0 +1 @@ +SELECT * FROM "Season" WHERE id = ${id} LIMIT 1; diff --git a/private/database/queries/season/GetSeasonForDate.sql b/private/database/queries/season/GetSeasonForDate.sql new file mode 100644 index 0000000..9da30a5 --- /dev/null +++ b/private/database/queries/season/GetSeasonForDate.sql @@ -0,0 +1,5 @@ +SELECT * FROM "Seasons" +WHERE + "startDate" <= ${date} AND + "endDate" >= ${date} +LIMIT 1; diff --git a/private/database/queries/team/AddTeam.sql b/private/database/queries/team/AddTeam.sql new file mode 100644 index 0000000..752b91c --- /dev/null +++ b/private/database/queries/team/AddTeam.sql @@ -0,0 +1,5 @@ +INSERT INTO "Teams" ( + "name", "motto", "image", "registered" +) VALUES ( + ${name}, ${motto}, ${image}, ${registered} +) RETURNING *; diff --git a/private/database/queries/team/AddTeamUserSeason.sql b/private/database/queries/team/AddTeamUserSeason.sql new file mode 100644 index 0000000..4e57fe6 --- /dev/null +++ b/private/database/queries/team/AddTeamUserSeason.sql @@ -0,0 +1,5 @@ +INSERT INTO "TeamUsersSeasons" ( + "teamId", "userId", "seasonId", "registered" +) VALUES ( + ${teamId}, ${userId}, ${seasonId}, ${registered} +) RETURNING *; diff --git a/private/database/queries/team/GetTeamById.sql b/private/database/queries/team/GetTeamById.sql new file mode 100644 index 0000000..ce14847 --- /dev/null +++ b/private/database/queries/team/GetTeamById.sql @@ -0,0 +1 @@ +SELECT * FROM "Teams" WHERE "id"=${id} LIMIT 1; diff --git a/private/database/queries/team/GetTeamByName.sql b/private/database/queries/team/GetTeamByName.sql new file mode 100644 index 0000000..d8087ea --- /dev/null +++ b/private/database/queries/team/GetTeamByName.sql @@ -0,0 +1 @@ +SELECT * FROM "Teams" WHERE LOWER("name") = LOWER(${name}) LIMIT 1; diff --git a/private/database/queries/team/GetTeamBySeasonAndUser.sql b/private/database/queries/team/GetTeamBySeasonAndUser.sql new file mode 100644 index 0000000..73fdce5 --- /dev/null +++ b/private/database/queries/team/GetTeamBySeasonAndUser.sql @@ -0,0 +1,10 @@ +SELECT + * +FROM + "Teams" +INNER JOIN + "TeamUsersSeasons" ON "TeamUsersSeasons"."teamId" = "Teams"."id" +WHERE + "TeamUsersSeasons"."userId" = ${userId} AND + "TeamUsersSeasons"."seasonId" = ${seasonId} +LIMIT 1; diff --git a/private/database/queries/team/GetTeamUsersBySeason.sql b/private/database/queries/team/GetTeamUsersBySeason.sql new file mode 100644 index 0000000..0aebb44 --- /dev/null +++ b/private/database/queries/team/GetTeamUsersBySeason.sql @@ -0,0 +1 @@ +SELECT * FROM "TeamUsersSeasons" WHERE "teamId" = ${teamId}; diff --git a/private/database/queries/team/GetTeamsBySeason.sql b/private/database/queries/team/GetTeamsBySeason.sql new file mode 100644 index 0000000..8043e6c --- /dev/null +++ b/private/database/queries/team/GetTeamsBySeason.sql @@ -0,0 +1,11 @@ +SELECT + "Teams".* +FROM + "Teams" +INNER JOIN + "TeamUsersSeasons" ON "TeamUsersSeasons"."teamId" = "Teams"."id" +WHERE + "TeamUsersSeasons"."seasonId" = ${seasonId} +GROUP BY + "Teams"."id" +; diff --git a/private/database/queries/user/AddUser.sql b/private/database/queries/user/AddUser.sql new file mode 100644 index 0000000..6e3041a --- /dev/null +++ b/private/database/queries/user/AddUser.sql @@ -0,0 +1,9 @@ +INSERT INTO "Users" ( + "discordId", + "steamId", + "email" +) VALUES ( + ${discordId}, + ${steamId}, + ${email} +) RETURNING *; diff --git a/private/database/queries/user/GetUserByDiscordId.sql b/private/database/queries/user/GetUserByDiscordId.sql new file mode 100644 index 0000000..c704ad2 --- /dev/null +++ b/private/database/queries/user/GetUserByDiscordId.sql @@ -0,0 +1 @@ +SELECT * FROM "Users" WHERE "discordId" = ${discordId} LIMIT 1; diff --git a/private/database/queries/user/GetUserById.sql b/private/database/queries/user/GetUserById.sql new file mode 100644 index 0000000..df0afa1 --- /dev/null +++ b/private/database/queries/user/GetUserById.sql @@ -0,0 +1 @@ +SELECT * FROM "Users" WHERE "id"=${id} LIMIT 1; diff --git a/private/index.js b/private/index.js index 2edea32..47fb6f6 100644 --- a/private/index.js +++ b/private/index.js @@ -21,8 +21,6 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -'use strict'; - const App = require('./app/App'); //Attempt to make a global "Async Handler" for the app itself diff --git a/private/server/Server.js b/private/server/Server.js index b4e0196..7198d8f 100644 --- a/private/server/Server.js +++ b/private/server/Server.js @@ -21,21 +21,18 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -//Imports const http = require('http'), - https = require('https'), express = require('express'), bodyParser = require('body-parser'), fs = require('fs'), path = require('path'), webpack = require('webpack'), - CompilerOptions = require('./WebpackCompilerOptions'), - API = require('./../api/API') + WebpackCompiler = require('./../webpack/WebpackCompiler'), + API = require('./api/API') ; -//Constants -const LANDING_FILE = 'index.html'; +const SERVE_FOLDER = path.resolve(`${__dirname}/../../dist`); class Server { constructor(app) { @@ -43,48 +40,22 @@ class Server { //Server settings this.ip = - app.getConfig().getValueOf("IP") || - app.getConfig().getValueOf("ip") || - app.getConfig().getValueOf("server.ip") || - app.getConfig().ip || + app.getConfig().get("IP") || + app.getConfig().get("server.ip") || process.env.ip || process.env.IP || null ; + this.port = - app.getConfig().getValueOf("PORT") || - app.getConfig().getValueOf("port") || - app.getConfig().getValueOf("server.port") || - app.getConfig().port || + app.getConfig().get("PORT") || + app.getConfig().get("port") || + app.getConfig().get("server.port") || process.env.port || process.env.PORT || 80 ; - this.useHTTPS = app.getConfig().getValueOf("ssl") && app.getConfig().getValueOf("ssl.enable") - if(this.useHTTPS) { - this.portHTTPS = this.config.ssl.port || 443; - if(!this.config.ssl.key) { - throw new Error("Invalid SSL Key in Server Configuration"); - } - if(!this.config.ssl.cert) { - throw new Error("Invalid SSL Cert in Server Configuration"); - } - - //TODO: Clean this up, don't use static files (use path.join etc) and should these be flat files? - let keyFile = __dirname+'./../'+this.config.ssl.key; - let certFile = __dirname+'./../'+this.config.ssl.cert; - if(!fs.existsSync(keyFile)) { - throw new Error("Key file \"" + keyFile + "\" doesn't exist!"); - } - if(!fs.existsSync(certFile)) { - throw new Error("Key file \"" + certFile + "\" doesn't exist!"); - } - - this.key = fs.readFileSync(keyFile, 'utf8'); - this.cert = fs.readFileSync(certFile, 'utf8'); - } - //Setup the express wrapper. this.express = express(); @@ -92,160 +63,65 @@ class Server { this.express.use(bodyParser.json({ type:'application/json' // to support JSON-encoded bodies })); + this.express.use(bodyParser.urlencoded({ extended: true })); //Serve Static Files - this.express.use(express.static('./dist')); + this.express.use(express.static(SERVE_FOLDER)); - //API Handler + //Register API Handlers this.api = new API(this); this.api.loadHandlers(); - //Finally our catcher for all other enquiries - this.express.get('*', this.onGetRequest.bind(this)); + //Setup fallback GET request + this.express.get('*', (req,res) => this.onGetRequest(req,res)); + //Setup our webpack compiler + this.compiler = webpack(WebpackCompiler()); + } + + getExpress() {return this.express;} + getApp() {return this.app;} + getHTTP() {return this.http;} + + async init() { //Create our HTTP and (if needed HTTPS) server(s) this.http = http.createServer(this.express); - this.http.on('error', this.onServerError.bind(this)); + this.http.on('error', e => this.onServerError(e)); - if(this.isHTTPS()) { - if(!this.key) throw new Error("Can't start server, missing SSL Key"); - if(!this.cert) throw new Error("Can't start server, missing SSL Cert"); + //Start the compiler watching + this.watcher = this.compiler.watch({}, (e,s) => this.onWatchChange(e,s)); - this.https = https.createServer({ - key: this.key, - cert: this.cert - }, this.express); - this.https.on('error', this.onServerError.bind(this)); - } - - //Create our bundler - this.compiler = webpack(CompilerOptions(this, this.app)); - } - - getConfig() {return this.config;} - getIP() {return this.ip; } - getPort() {return this.port;} - isHTTPS() {return this.useHTTPS;} - getHTTPSPort() {return this.portHTTPS;} - getKey() {return this.key;} - getCertificate() {return this.cert;} - getLandingFile() {return path.join(this.app.getPublicDirectory(), LANDING_FILE);} - getExpress() {return this.express;} - getAPI() {return this.api;} - getApp() {return this.app;} - - isRunning() { - if(typeof this.http !== typeof undefined) { - return this.http.listening; - } - return false; - } - - async start() { - if(typeof this.startPromise !== typeof undefined) { - await this.startPromise(); - return; - } - this.startPromise = new Promise(this.startServerPromise.bind(this));//Lazy Programming FTW - await this.startPromise; - } - - startServerPromise(resolve, reject) { - this.startResolve = resolve; - this.startReject = reject; - - let options = { + //Start Listening + this.http.listen({ host: this.ip, port: this.port - }; - - //Create our webpack watcher - this.watcher = this.compiler.watch({ - - }, this.onWatchChange.bind(this)); - - //Start the HTTP Server - this.http.listen(options, this.onServerStart.bind(this)); - - //HTTPS? - if(this.https) { - this.https.listen(options, this.portHTTPS); - } + }, e => this.onServerStart(e)); } - onServerStart() { - this.bound = this.http.address(); - this.startResolve(this); + //Events + onServerStart(e) { + this.boundAddress = this.http.address(); } onServerError(e) { - console.log("A Server Error occured!"); - this.startReject(e); - this.stop(); - throw new Error(e); - } - - - async stop() { - if(typeof this.stopPromise !== typeof undefined) { - await this.stopPromise; - return; - } - this.stopPromise = new Promse(this.stopPromise.bind(this)); - await this.stopPromise; - delete this.http; - delete this.https; - delete this.stopPromise; - delete this.watcher; - } - - stopPromise(resolve, reject) { - this.stopResolve = resolve; - this.stopReject = reject; - - if(typeof this.watcher !== typeof undefined) { - this.watcher.close(() => { - }); - } - - try { - this.http.close(this.onHTTPClosed.bind(this)); - } catch(e) { - this.stopReject(e); - } - } - - onHTTPClosed() { - if(typeof this.https === typeof undefined) { - this.resolve(); - return; - } - - try { - this.https.close(this.onHTTPSClosed.bind(this)); - } catch(e) { - this.stopReject(e); - } - } - - onHTTPSClosed() { - this.resolve(); - } - - onWatchChange(error, stats) { - if(error || (stats.compilation.errors && stats.compilation.errors.length)) { - console.error(error || stats.compilation.errors); - } else { - console.log("Server compiled!"); - } + console.error('Error'); + console.error(e); } onGetRequest(req, res) { - //Used as our "catch all get requests" - res.sendFile(this.getLandingFile()); + let file = path.resolve(`${SERVE_FOLDER}/index.html`); + res.sendFile(file); + } + + onWatchChange(error,stats) { + if(error || (stats.compilation.errors && stats.compilation.errors.length)) { + return this.app.error(error || stats.compilation.errors); + } + + this.app.log("Server Compiled!"); } } diff --git a/private/server/WebpackCompilerOptions.js b/private/server/WebpackCompilerOptions.js deleted file mode 100644 index 7e20116..0000000 --- a/private/server/WebpackCompilerOptions.js +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (c) 2018 Dominic Masters -// -// MIT License -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -//Includes -const - path = require('path'), - webpack = require('webpack'), - HtmlWebpackPlugin = require('html-webpack-plugin'), - CompressionPlugin = require("compression-webpack-plugin"), - UglifyJsPlugin = require('uglifyjs-webpack-plugin'), - MiniCssExtractPlugin = require("mini-css-extract-plugin"), - OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"), - SharpLoader = require('responsive-loader/sharp') -; - -//Constants -const SOURCE_DIRECTORY = './public'; -const ENTRY_FILE = 'index.jsx'; -const ENTRY_WRAPPER = 'index.html'; - -module.exports = function(server, app) { - //Create our dirs - let entryDir = path.join(app.getPublicDirectory(), '..', 'public'); - - //Create our output - let output = {}; - - //Set the entry point - output.entry = path.join(entryDir, ENTRY_FILE); - - //Set the output - output.output = { - path: app.getPublicDirectory(), - filename: "app.js" - } - - //Set Resolves - output.resolve = { - modules: ['node_modules', SOURCE_DIRECTORY], - extensions: ['.js', '.jsx', '.css', '.scss' ] - }; - - //Setup Modules - output.module = { - rules: [ - { - test: /\.jsx?$|\.js?$/, - exclude: /node_modules/, - use: { - loader: 'babel-loader' - } - }, - { - test: /\.scss$|\.css$/i, - use: [ - MiniCssExtractPlugin.loader, - "css-loader", - 'sass-loader', - ] - }, - - { - test: /\.svg|\.webm|\.mp4$/i, - use: [{ - loader: "file-loader", - options: { - name: "[path][name].[ext]", - context: 'public' - } - }] - }, - - { - test: /\.jpe?g$|\.gif$|\.png$/i, - use: [{ - loader: "responsive-loader", - options: { - adapter: SharpLoader, - sizes: [250, 500, 1000, 1500, 2000, 2500], - name: "[path][name]_[width]x.[ext]", - context: 'public' - } - }] - }, - - { - test: /\.(eot|ttf|woff(2)?)(\?v=\d+\.\d+\.\d+)?/, - loader: 'url-loader' - } - ] - }; - - //Setup the Plugins - let HTMLWebpackPluginConfig = new HtmlWebpackPlugin({ - template: path.join(SOURCE_DIRECTORY, ENTRY_WRAPPER), - filename: ENTRY_WRAPPER, - inject: true - }); - - let UglifyPluginConfig = new UglifyJsPlugin({ - test: /\.js($|\?)/i - }); - - let MiniCssExtractConfig = new MiniCssExtractPlugin({ - filename: "[name].css", - chunkFilename: "[id].css" - }) - - //Set the plugins - output.plugins = [ - new webpack.DefinePlugin({ - 'process.env.NODE_ENV': JSON.stringify('production') - }), - MiniCssExtractConfig, - HTMLWebpackPluginConfig - ]; - - //Minimization - output.optimization = { - minimize: true, - minimizer: [ - UglifyPluginConfig, - new OptimizeCSSAssetsPlugin({}) - ] - }; - - //Now setup the production values - output.devtool = 'source-map'; - - return output; -} diff --git a/private/api/API.js b/private/server/api/API.js similarity index 77% rename from private/api/API.js rename to private/server/api/API.js index 2eb6a92..4658182 100644 --- a/private/api/API.js +++ b/private/server/api/API.js @@ -26,9 +26,10 @@ const fs = require('fs') ; -const API_BASE = '/api'; +const API_BASE = path.resolve(__dirname, 'methods'); +const API_URL_BASE = '/api'; -class API { +module.exports = class API { constructor(server) { this.server = server; this.handlers = []; @@ -44,49 +45,42 @@ class API { addHandler(handler) {this.handlers.push(handler);} registerHandlers() { - for(let i = 0; i < this.handlers.length; i++) { - let handler = this.handlers[i]; + this.handlers.forEach(handler => { + handler.getMethods().forEach(method => { + method = method.toLowerCase(); - //Now we need to register each of the paths to each of the methods! - for(let x = 0; x < handler.getMethods().length; x++) { - let method = handler.getMethods()[x].toLowerCase(); //For each method, there's perhaps multiple paths (e.g. post /test, get /test, post /ayy, get /ayy) - for(let y = 0; y < handler.getPaths().length; y++) { - let path = handler.getPaths()[y]; - let url = API_BASE; + handler.getPaths().forEach(path => { + let url = API_URL_BASE; if(!path.startsWith('/')) url += '/'; url += path; this.getExpress()[method](url, handler.onMethod.bind(handler)); console.log('Registering ' + url + '...'); - } - } - } + }); + }); + }); } loadHandlers() { - let dir = path.join(__dirname, 'methods'); - this.loadHandlersInDirectory(dir); + this.loadHandlersInDirectory(API_BASE); this.registerHandlers(); } loadHandlersInDirectory(dir) { let assets = fs.readdirSync(dir); - for(let i = 0; i < assets.length; i++) { - let asset = assets[i]; + assets.forEach(asset => { let assetPath = path.join(dir, asset); let stats = fs.statSync(assetPath); if(stats.isDirectory()) { this.loadHandlersInDirectory(assetPath ); - continue; + return; } let method = require(assetPath); let instance = new method(this); this.addHandler(instance); - } + }); } } - -module.exports = API; diff --git a/private/api/APIHandler.js b/private/server/api/APIHandler.js similarity index 100% rename from private/api/APIHandler.js rename to private/server/api/APIHandler.js diff --git a/private/api/APIRequest.js b/private/server/api/APIRequest.js similarity index 99% rename from private/api/APIRequest.js rename to private/server/api/APIRequest.js index c607fec..ace8097 100644 --- a/private/api/APIRequest.js +++ b/private/server/api/APIRequest.js @@ -21,7 +21,7 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -const Forms = require('./../../common/Forms'); +const Forms = require('./../../../common/Forms.js'); class APIRequest { constructor(handler, req, res) { diff --git a/private/api/methods/contact/send.js b/private/server/api/methods/contact/send.js similarity index 98% rename from private/api/methods/contact/send.js rename to private/server/api/methods/contact/send.js index 47d9119..283cf83 100644 --- a/private/api/methods/contact/send.js +++ b/private/server/api/methods/contact/send.js @@ -35,7 +35,7 @@ const ERRORS = { module.exports = class Send extends APIHandler { constructor(api) { - super(api, ['POST'], '/contact/send'); + super(api, ['GET', 'POST'], '/contact/send'); } async handle(request) { diff --git a/private/webpack/WebpackCompiler.js b/private/webpack/WebpackCompiler.js new file mode 100644 index 0000000..f9f48da --- /dev/null +++ b/private/webpack/WebpackCompiler.js @@ -0,0 +1,203 @@ +// Copyright (c) 2018 Dominic Masters +// +// MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// Copyright (c) 2018 Dominic Masters +// +// MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +const + path = require('path'), + fs = require('fs'), + + webpack = require('webpack'), + SharpLoader = require('responsive-loader/sharp'), + HtmlWebpackPlugin = require('html-webpack-plugin'), + CompressionPlugin = require("compression-webpack-plugin"), + UglifyJsPlugin = require('uglifyjs-webpack-plugin'), + MiniCssExtractPlugin = require("mini-css-extract-plugin"), + OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin") +; + +const base = path.resolve(`${__dirname}/../..`); + +// export webpack config +module.exports = (isDev) => { + if(typeof isDev === typeof undefined) isDev = false; + + //Base (Production) Configuration. + let config = { + devtool: 'source-map', + entry: [ `${base}/public/index.jsx` ], + output: { path: `${base}/dist`, filename: "app.js" }, + mode: isDev ? 'development' : 'production', + resolve: { + modules: [`${base}/node_modules`, `${base}/public`], + extensions: ['.js', '.jsx', '.css', '.scss' ], + alias: { + '@public': `${base}/public`, + '@objects': `${base}/public/objects`, + '@components': `${base}/public/components`, + '@assets': `${base}/public/assets`, + '@pages': `${base}/public/pages`, + '@common': `${base}/common`, + '@styles': `${base}/public/styles` + } + }, + + module: { + rules: [ + { + test: /\.jsx?$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + options: { + presets: [ + [ + "@babel/preset-env", + { + "targets": { + "node": "current", + "browsers": [ "Chrome >= 41", "FireFox >= 44", "Safari >= 7", "Explorer 11", "last 4 Edge versions" ] + }, + "useBuiltIns": false + } + ], + "@babel/preset-react" + ], + "plugins": [ '@babel/plugin-syntax-dynamic-import' ] + } + } + }, + + { + test: /\.scss$|\.css$/i, + use: isDev ? ( + [ "style-loader", "css-loader", 'sass-loader' ] + ) : ( + [ MiniCssExtractPlugin.loader, "css-loader", 'sass-loader' ] + ) + }, + + { + test: /\.svg$|\.webm$|\.mp4$/i, + use: [{ + loader: "file-loader", + options: { name: "[path][name].[ext]", context: 'public' } + }] + }, + + { + test: /\.jpe?g$|\.gif$|\.png$/i, + use: [{ + loader: "responsive-loader", + options: { + adapter: SharpLoader, + sizes: [250, 500, 1000, 1500, 2000, 2500], + name: "[path][name]_[width]x.[ext]", + context: 'public' + } + }] + }, + + { + test: /\.(eot|ttf|woff(2)?)(\?v=\d+\.\d+\.\d+)?/, + loader: 'url-loader' + } + ] + }, + + plugins: [] + }; + + //Setup the Plugins + let HTMLWebpackPluginConfig = new HtmlWebpackPlugin({ + template: `${base}/public/index.html`, + filename: 'index.html', + inject: true + }); + + config.plugins = [ + ...config.plugins, + HTMLWebpackPluginConfig + ] + + //Dev Setting Overrides + if(isDev) { + config = { + ...config, + devtool: 'cheap-module-eval-source-map', + plugins: [ + ...config.plugins, + new webpack.HotModuleReplacementPlugin() + ] + }; + } else { + let UglifyPluginConfig = new UglifyJsPlugin({ + test: /\.js($|\?)/i + }); + + let MiniCssExtractConfig = new MiniCssExtractPlugin({ + filename: "[name].css", + chunkFilename: "[id].css" + }); + + config = { + ...config, + plugins: [ + ...config.plugins, + MiniCssExtractConfig, + HTMLWebpackPluginConfig + ], + optimization: { + minimize: true, + minimizer: [ + UglifyPluginConfig, + MiniCssExtractConfig, + new OptimizeCSSAssetsPlugin({}), + ] + } + }; + } + + return config; +}; diff --git a/public/components/App.jsx b/public/components/App.jsx index cc21bea..c860aa3 100644 --- a/public/components/App.jsx +++ b/public/components/App.jsx @@ -72,6 +72,7 @@ class App extends React.Component { //For testing you can switch the router type let RouterType = BrowserRouter; + console.log(process.env.NODE_ENV); if(true) RouterType = HashRouter; return ( diff --git a/webpack.config.js b/webpack.config.js index e77536c..3834ad1 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,113 +1,26 @@ -const - webpack = require('webpack'), - HtmlWebpackPlugin = require('html-webpack-plugin'), - SharpLoader = require('responsive-loader/sharp'), - path = require('path') -; +// Copyright (c) 2018 Dominic Masters +// +// MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -const HTMLWebpackPluginConfig = new HtmlWebpackPlugin({ - template: __dirname + '/public/index.html', - filename: 'index.html', - inject: 'body' -}); +const WebpackCompiler = require('./private/webpack/WebpackCompiler.js'); -// export webpack config -module.exports = { - devtool: 'cheap-module-eval-source-map', - entry: [ - './public/index.jsx' - ], - - output: { - path: '/dist', - filename: "app.js" - }, - - mode: 'development', - - resolve: { - modules: ['node_modules', './public'], - extensions: ['.js', '.jsx', '.css', '.scss' ], - alias: { - '@public': path.resolve(__dirname, './public'), - '@objects': path.resolve(__dirname, './public/objects'), - '@components': path.resolve(__dirname, './public/components'), - '@assets': path.resolve(__dirname, './public/assets'), - '@pages': path.resolve(__dirname, './public/pages'), - '@common': path.resolve(__dirname, './common/'), - '@styles': path.resolve(__dirname, './public/styles') - } - }, - - // declare loaders to be used in webpack - module: { - rules: [ - { - test: /\.jsx?$/, - exclude: /node_modules/, - use: { - loader: 'babel-loader', - options: { - presets: [ - [ - "@babel/preset-env", - { - "targets": { - "node": "current", - "browsers": [ "Chrome >= 41", "FireFox >= 44", "Safari >= 7", "Explorer 11", "last 4 Edge versions" ] - }, - "useBuiltIns": false - } - ], - "@babel/preset-react" - ], - "plugins": [ '@babel/plugin-syntax-dynamic-import' ] - } - } - }, - - { - test: /\.scss$|\.css$/i, - loaders: ["style-loader", "css-loader", "sass-loader"] - }, - - { - test: /\.svg$|\.webm$|\.mp4$/i, - use: [{ - loader: "file-loader", - options: { - name: "[path][name].[ext]", - context: 'public' - } - }] - }, - - { - test: /\.jpe?g$|\.gif$|\.png$/i, - use: [{ - loader: "responsive-loader", - options: { - adapter: SharpLoader, - sizes: [250, 500, 1000, 1500, 2000, 2500], - name: "[path][name]_[width]x.[ext]", - context: 'public' - } - }] - }, - - { - test: /\.(eot|ttf|woff(2)?)(\?v=\d+\.\d+\.\d+)?/, - loader: 'url-loader' - } - ] - }, - - // initialize the added webpack plugins - plugins: [ - HTMLWebpackPluginConfig, - new webpack.HotModuleReplacementPlugin(), - new webpack.DefinePlugin({ - DEVELOPMENT: JSON.stringify(true) - }) - ] -}; +module.exports = WebpackCompiler(true);