From 34b463cd446ad3cafaca69bb34c30a9b59aec3d2 Mon Sep 17 00:00:00 2001 From: Mitchell Date: Mon, 22 Aug 2016 00:01:53 -0500 Subject: [PATCH] server set up with app engine --- .gitignore | 5 +- app.yaml | 6 + client/js/redux/actions.js | 6 +- config.template.json | 9 + index.html | 36 ++-- metadata.js | 14 +- package.json | 4 +- readme.md | 12 +- server.go | 23 +++ server/controller/api/config.go | 13 ++ server/controller/api/sensor.go | 211 ++++++++++++++++++++++ server/controller/redirect.go | 22 +++ server/db/db.go | 54 ++++++ server/model/daily_sensor/daily_sensor.go | 200 ++++++++++++++++++++ server/model/raw_sensor/raw_sensor.go | 139 ++++++++++++++ server/route/route.go | 54 ++++++ server/utils/config.go | 44 +++++ webpack.config.js | 8 +- 18 files changed, 827 insertions(+), 33 deletions(-) create mode 100644 app.yaml create mode 100644 config.template.json create mode 100644 server.go create mode 100644 server/controller/api/config.go create mode 100644 server/controller/api/sensor.go create mode 100644 server/controller/redirect.go create mode 100644 server/db/db.go create mode 100644 server/model/daily_sensor/daily_sensor.go create mode 100644 server/model/raw_sensor/raw_sensor.go create mode 100644 server/route/route.go create mode 100644 server/utils/config.go diff --git a/.gitignore b/.gitignore index c81003e..0ef7373 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ \.c9 \.DS_Store config.json -mywebsite -mywebsite.exe node_modules -dist \ No newline at end of file +dist +public diff --git a/app.yaml b/app.yaml new file mode 100644 index 0000000..51946a9 --- /dev/null +++ b/app.yaml @@ -0,0 +1,6 @@ +runtime: go +api_version: go1 + +handlers: +- url: /.* + script: _go_app diff --git a/client/js/redux/actions.js b/client/js/redux/actions.js index 4e2309c..c28ab2c 100644 --- a/client/js/redux/actions.js +++ b/client/js/redux/actions.js @@ -11,13 +11,13 @@ function initPreview(posts){ //in this case we can send the http request here rather in the react component export function fetchPreview(){ return (dispatch) => { - return fetch('/metadata.json') + return fetch('/public/metadata.json') .then(response => response.json()) .then(json => { - dispatch(initPreview(json)); + dispatch(initPreview(json)); }) .catch(error => { console.log(error); }); } -} \ No newline at end of file +} diff --git a/config.template.json b/config.template.json new file mode 100644 index 0000000..ea73a1e --- /dev/null +++ b/config.template.json @@ -0,0 +1,9 @@ +{ + "database": { + "url": "", + "database": "" + }, + "api": { + "key": "" + } +} diff --git a/index.html b/index.html index 8b4412c..dcfec5c 100644 --- a/index.html +++ b/index.html @@ -1,21 +1,31 @@ + - - - - - - mitchel.io + + + + + + mitchel.io + - - -
+ + +
+ diff --git a/metadata.js b/metadata.js index cb433bd..34f556e 100644 --- a/metadata.js +++ b/metadata.js @@ -3,7 +3,7 @@ folder/files within posts are scanned recursively each post is contained within category, which is supplied by the direct parent folder Posts are sorted by date - Stores all metadata in ./dist/metadata.json + Stores all metadata in ./public/metadata.json Client uses metadata to display posts on preview page */ @@ -27,10 +27,10 @@ const json = { //do everything synchronously to keep posts ordered function parse_dir(dir, folder_name){ const posts = fs.readdirSync(dir); - + for(let post of posts){ const stats = fs.statSync(dir + post); - + if(stats.isDirectory()){ parse_dir(dir + post + '/', post); } else { @@ -58,15 +58,15 @@ json.posts.sort((a, b) => { }); //output to public path -fs.writeFile('./dist/metadata.json', JSON.stringify(json,null,4), (err) => { +fs.writeFile('./public/metadata.json', JSON.stringify(json,null,4), (err) => { if (err) throw err; console.log("Saved metadata.json"); }) -//copy posts folder to dist -ncp('./posts', './dist/posts', (err) => { +//copy posts folder to public +ncp('./posts', './public/posts', (err) => { if (err) { return console.error(err); } console.log('copied'); -}); \ No newline at end of file +}); diff --git a/package.json b/package.json index d01d889..0452f12 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "main": "index.js", "scripts": { "build": "webpack && babel-node metadata.js", - "c9": "webpack-dev-server --port $PORT --host $IP --hot --content-base dist --history-api-fallback", - "dev": "webpack-dev-server --content-base dist --inline --hot --history-api-fallback", + "c9": "webpack-dev-server --port $PORT --host $IP --hot --content-base public --history-api-fallback", + "dev": "webpack-dev-server --content-base public --inline --hot --history-api-fallback", "prod": "export NODE_ENV=production && webpack -p && babel-node metadata.js", "prod-win": "set NODE_ENV=production && webpack -p && babel-node metadata.js", "deploy": "goapp deploy", diff --git a/readme.md b/readme.md index 439f9f7..dab1958 100644 --- a/readme.md +++ b/readme.md @@ -6,4 +6,14 @@ `npm run build` - builds the application using webpack and runs metadata.js - metadata.js recursivly scans the posts folder for markdown files and then parses each into into a json object -- the posts folder is then copied into the dist folder \ No newline at end of file +- the posts folder is then copied into the dist folder + +## TODO +- fix goapp serve and webpack-dev-server so paths work correctly +- posts page +- add sensor page +- finally do writeups on my projects +- clean go code up +- host on app engine (run mongodb on compute engine???) +- pull everything off digital ocean +- add paging diff --git a/server.go b/server.go new file mode 100644 index 0000000..59b6825 --- /dev/null +++ b/server.go @@ -0,0 +1,23 @@ +package main + +import ( + "net/http" + + "mywebsite/server/controller/api" + "mywebsite/server/db" + "mywebsite/server/route" + "mywebsite/server/utils" +) + +func init() { + configurations := utils.ReadConfig() + + db.Configure(configurations.Database) + api.Configure(configurations.Api) + + db.Mongo.Connect() + + router := route.Routes() + + http.Handle("/", router) +} diff --git a/server/controller/api/config.go b/server/controller/api/config.go new file mode 100644 index 0000000..f9b34fc --- /dev/null +++ b/server/controller/api/config.go @@ -0,0 +1,13 @@ +package api + +import () + +var Api ApiInfo + +type ApiInfo struct { + Key string `json:"key"` +} + +func Configure(a ApiInfo) { + Api = a +} diff --git a/server/controller/api/sensor.go b/server/controller/api/sensor.go new file mode 100644 index 0000000..5dd87be --- /dev/null +++ b/server/controller/api/sensor.go @@ -0,0 +1,211 @@ +package api + +import ( + "encoding/json" + "fmt" + "github.com/julienschmidt/httprouter" + "log" + "net/http" + "strconv" + "time" + + "mywebsite/server/model/daily_sensor" + "mywebsite/server/model/raw_sensor" +) + +// handle http request from sensors +func HandleSensorRequest(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + + //store data from sensor in raw_sensor collection + //********************************************************************************** + key := r.URL.Query().Get("key") + w.Header().Set("Content-Type", "application/json") + + var message string + + if key == Api.Key { + + //get request parameters - convert temp to float64 + temperature, _ := strconv.ParseFloat(r.URL.Query().Get("temperature"), 64) + location := r.URL.Query().Get("location") + t := time.Now() + + store := raw_sensor.Data{"", temperature, location, t} + + err := store.StoreData() + + if err != nil { + message = "Failed to insert into database" + } else { + message = "Data inserted into database" + } + + //compare current readings with dialy_sensor readings + //update daily_sensor readings if out of bounds + //********************************************************************************** + + storedData, err := daily_sensor.GetDailySensorInfo(location) + + if err != nil { + log.Println(err) + } + + //store data if nothing exists for the day + if storedData.Location == "" { + + storedData.ID = "" + storedData.Location = location + storedData.MaxTemp = temperature + storedData.MinTemp = temperature + storedData.Day = t.Day() + storedData.Month = int(t.Month()) + storedData.MonthName = t.Month().String() + storedData.Year = t.Year() + + err := storedData.StoreData() + + if err != nil { + log.Println(err) + } + + } else { + + performUpdate := false + + //check if values exceed max or min + if temperature > storedData.MaxTemp { + storedData.MaxTemp = temperature + performUpdate = true + } + if temperature < storedData.MinTemp { + storedData.MinTemp = temperature + performUpdate = true + } + + //store or update information if values have been changed + if performUpdate == true { + + err := storedData.UpdateData() + + if err != nil { + log.Println(err) + } + + } + } + } else { + message = "Incorrect api key" + } + + //send response back + fmt.Fprint(w, "{ message : \""+message+"\"}") +} + +func HandleAllSensors(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + + w.Header().Set("Content-Type", "application/json") + + s, err := raw_sensor.GetAllSensors() + + var response string + + if err != nil { + log.Println(err) + response = "{message : \"Error loading data from database\"" + } else { + js, err := json.MarshalIndent(s, "", " ") + + if err != nil { + log.Println(err) + response = "{message : \"Error loading data from database\"" + } else { + response = string(js) + } + } + + fmt.Fprint(w, response) +} + +func HandleSensorByLocation(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + + location := ps.ByName("location") + + w.Header().Set("Content-Type", "application/json") + + s, err := daily_sensor.GetAllSensorInfo(location) + + var response string + + if err != nil { + log.Println(err) + response = "{message : \"Error loading data from database\"" + } else { + js, err := json.MarshalIndent(s, "", " ") + + if err != nil { + log.Println(err) + response = "{message : \"Error loading data from database\"" + } else { + response = string(js) + } + } + + fmt.Fprint(w, response) +} + +func HandleSensorByLocationYear(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + + location := ps.ByName("location") + year, _ := strconv.Atoi(ps.ByName("year")) + + w.Header().Set("Content-Type", "application/json") + + s, err := daily_sensor.GetAllSensorInfoByYear(location, year) + + var response string + + if err != nil { + log.Println(err) + response = "{message : \"Error loading data from database\"" + } else { + js, err := json.MarshalIndent(s, "", " ") + + if err != nil { + log.Println(err) + response = "{message : \"Error loading data from database\"" + } else { + response = string(js) + } + } + + fmt.Fprint(w, response) +} + +func HandleSensorByLocationMonth(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + + location := ps.ByName("location") + year, _ := strconv.Atoi(ps.ByName("year")) + monthname := ps.ByName("monthname") + + w.Header().Set("Content-Type", "application/json") + + s, err := daily_sensor.GetAllSensorInfoByMonth(location, year, monthname) + + var response string + + if err != nil { + log.Println(err) + response = "{message : \"Error loading data from database\"" + } else { + js, err := json.MarshalIndent(s, "", " ") + + if err != nil { + log.Println(err) + response = "{message : \"Error loading data from database\"" + } else { + response = string(js) + } + } + + fmt.Fprint(w, response) +} diff --git a/server/controller/redirect.go b/server/controller/redirect.go new file mode 100644 index 0000000..0b78f0f --- /dev/null +++ b/server/controller/redirect.go @@ -0,0 +1,22 @@ +package controller + +import ( + "github.com/julienschmidt/httprouter" + "net/http" +) + +// redirects for personal use +// Redirect to discord +func DiscordRedirect(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + http.Redirect(w, r, "https://discordapp.com/invite/0Z2tzxKECEj2BHwj", 301) +} + +// Redirect to discord +func VPNRedirect(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + http.Redirect(w, r, "https://mitchel.io:943", 301) +} + +// Redirect to security cameras +func CameraRedirect(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + http.Redirect(w, r, "http://24.118.44.161:8080/html/", 301) +} diff --git a/server/db/db.go b/server/db/db.go new file mode 100644 index 0000000..c5c877b --- /dev/null +++ b/server/db/db.go @@ -0,0 +1,54 @@ +package db + +import ( + "gopkg.in/mgo.v2" + "log" + "time" +) + +var Mongo Driver + +type Driver struct { + Session *mgo.Session + Info DatabaseInfo +} + +type DatabaseInfo struct { + URL string `json:"url"` + Database string `json:"database"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` +} + +func Configure(d DatabaseInfo) { + Mongo.Info = d +} + +func (d *Driver) Connect() { + // Connect to MongoDB + s, err := mgo.DialWithTimeout(d.Info.URL, 5*time.Second) + + if err != nil { + log.Println("MongoDB Driver Error", err) + return + } + + d.Session = s + + // Prevents these errors: read tcp 127.0.0.1:27017: i/o timeout + d.Session.SetSocketTimeout(10 * time.Second) + + // Check if is alive + if err = d.Session.Ping(); err != nil { + log.Println("Database Error", err) + } + + log.Println("Connected to database") +} + +func (d *Driver) Connected() bool { + if d.Session != nil { + return true + } + return false +} diff --git a/server/model/daily_sensor/daily_sensor.go b/server/model/daily_sensor/daily_sensor.go new file mode 100644 index 0000000..99837bb --- /dev/null +++ b/server/model/daily_sensor/daily_sensor.go @@ -0,0 +1,200 @@ +package daily_sensor + +import ( + "encoding/json" + "errors" + "gopkg.in/mgo.v2/bson" + "log" + "time" + + "mywebsite/server/db" +) + +const ( + collection = "daily_sensor" +) + +type Data struct { + ID bson.ObjectId `bson:"_id,omitempty"` + MaxTemp float64 `json:"maxtemp" bson:"maxtemp"` + MinTemp float64 `json:"mintemp" bson:"mintemp"` + Location string `json:"location" bson:"location"` + Month int `json:"month" bson:"month"` + MonthName string `json:"monthname" bson:"monthname"` + Day int `json:"day" bson:"day"` + Year int `json:"year" bson:"year"` +} + +//convert struct to json string +func (s *Data) toJson() string { + + b, err := json.MarshalIndent(s, "", " ") + + if err != nil { + log.Println(err.Error) + } + + return string(b) + +} + +func (s *Data) StoreData() error { + + if db.Mongo.Connected() { + + log.Println("Inserting data into " + collection) + + session := db.Mongo.Session.Copy() + defer session.Close() + + c := session.DB(db.Mongo.Info.Database).C(collection) + + // Insert Datas + err := c.Insert(s) + + if err != nil { + return err + } + } + + return nil +} + +//function to update the daily temperature max or min for a location +func (s *Data) UpdateData() error { + + if db.Mongo.Connected() { + + log.Println("Updating data") + + session := db.Mongo.Session.Copy() + defer session.Close() + + c := session.DB(db.Mongo.Info.Database).C(collection) + + colQuerier := bson.M{"location": s.Location, "month": s.Month, "monthname": s.MonthName, "day": s.Day, "year": s.Year} + change := bson.M{"$set": bson.M{"maxtemp": s.MaxTemp, "mintemp": s.MinTemp}} + + err := c.Update(colQuerier, change) + + if err != nil { + return err + } + } + + return nil + +} + +//get the current daily sensor reading to compare +//if the max or min temp needs to be updated +func GetDailySensorInfo(sensor_location string) (Data, error) { + + d := Data{} + t := time.Now() + + day := t.Day() + month := int(t.Month()) + monthname := t.Month().String() + year := t.Year() + + if db.Mongo.Connected() == true { + + session := db.Mongo.Session.Copy() + defer session.Close() + + c := session.DB(db.Mongo.Info.Database).C(collection) + + err := c.Find(bson.M{"location": sensor_location, "day": day, "month": month, "monthname": monthname, "year": year}).One(&d) + + if err != nil { + log.Println(err) + return d, nil + } + + return d, nil + + } else { + return d, errors.New("Query failed") + } + +} + +func GetAllSensorInfo(sensor_location string) ([]Data, error) { + d := []Data{} + + if db.Mongo.Connected() == true { + + session := db.Mongo.Session.Copy() + defer session.Close() + + c := session.DB(db.Mongo.Info.Database).C(collection) + + //err := c.Find(bson.M{"location": sensor_location}).Sort("-year, -month").All(&d) + err := c.Pipe([]bson.M{{"$match": bson.M{"location": sensor_location}}, + {"$sort": bson.M{"year": -1, "month": 1}}}).All(&d) + + if err != nil { + log.Println(err) + return d, nil + } + + return d, nil + + } else { + return d, errors.New("Query failed") + } +} + +//return all daily temperature readings per year for a location +//sort by most recent year and oldest month +func GetAllSensorInfoByYear(sensor_location string, year int) ([]Data, error) { + d := []Data{} + + if db.Mongo.Connected() == true { + + session := db.Mongo.Session.Copy() + defer session.Close() + + c := session.DB(db.Mongo.Info.Database).C(collection) + + err := c.Pipe([]bson.M{{"$match": bson.M{"location": sensor_location, "year": year}}, + {"$sort": bson.M{"year": -1, "month": 1}}}).All(&d) + + if err != nil { + log.Println(err) + return d, nil + } + + return d, nil + + } else { + return d, errors.New("Query failed") + } +} + +//return all temperature readings for a specific month and location +func GetAllSensorInfoByMonth(sensor_location string, year int, monthname string) ([]Data, error) { + d := []Data{} + + if db.Mongo.Connected() == true { + + session := db.Mongo.Session.Copy() + defer session.Close() + + c := session.DB(db.Mongo.Info.Database).C(collection) + + err := c.Pipe([]bson.M{{"$match": bson.M{"location": sensor_location, "year": year, "monthname": monthname}}, + {"$sort": bson.M{"year": -1, "month": 1}}}).All(&d) + + if err != nil { + log.Println(err) + return d, nil + } + + return d, nil + + } else { + return d, errors.New("Query failed") + } +} diff --git a/server/model/raw_sensor/raw_sensor.go b/server/model/raw_sensor/raw_sensor.go new file mode 100644 index 0000000..c329a09 --- /dev/null +++ b/server/model/raw_sensor/raw_sensor.go @@ -0,0 +1,139 @@ +package raw_sensor + +import ( + "encoding/json" + "errors" + "gopkg.in/mgo.v2/bson" + "log" + "time" + + "mywebsite/server/db" +) + +const ( + collection = "raw_sensor" +) + +type Data struct { + ID bson.ObjectId `bson:"_id,omitempty"` + Temperature float64 `json:"temperature" bson:"temperature"` + Location string `json:"location" bson:"location"` + Updated time.Time `json:"updated" bson:"updated"` +} + +//convert struct to json string +func (s *Data) toJson() string { + + b, err := json.MarshalIndent(s, "", " ") + + if err != nil { + log.Println(err.Error) + } + + return string(b) + +} + +func (s *Data) StoreData() error { + + if db.Mongo.Connected() { + + log.Println("Inserting data into " + collection) + + session := db.Mongo.Session.Copy() + defer session.Close() + + c := session.DB(db.Mongo.Info.Database).C(collection) + + // Insert Datas + err := c.Insert(s) + + if err != nil { + return err + } + } + + return nil +} + +//handle queries for all sensors page +//******************************************************************************** +type DataStore_AllSensors struct { + ID string `json:"location" bson:"_id"` + Temperature float64 `json:"temperature" bson:"temperature"` + Updated time.Time `json:"updated" bson:"updated"` +} + +func GetAllSensors() ([]DataStore_AllSensors, error) { + + s := []DataStore_AllSensors{} + + if db.Mongo.Connected() == true { + + session := db.Mongo.Session.Copy() + defer session.Close() + + c := session.DB(db.Mongo.Info.Database).C(collection) + + err := c.Pipe([]bson.M{{"$group": bson.M{"_id": "$location", "temperature": bson.M{"$last": "$temperature"}, + "updated": bson.M{"$last": "$updated"}}}, + bson.M{"$sort": bson.M{"_id": 1}}}).All(&s) + + if err != nil { + return s, nil + } + + return s, nil + + } else { + return s, errors.New("Query failed") + } +} + +//******************************************************************************** + +//get sensor information by location +//******************************************************************************** +type DataStore_SensorByLocation struct { + Id sensorByLocation `json:"_id" bson:"_id"` +} + +type sensorByLocation struct { + Year int `json:"year" bson:"year"` + Month int `json:"month" bson:"month"` + Location string `json:"location" bson:"location"` +} + +/* +func GetSensorInfoByLocation(sensor_location string) ([]DataStore_SensorByLocation, error) { + s := []DataStore_SensorByLocation{} + if db.Mongo.Connected() == true { + session := db.Mongo.Session.Copy() + defer session.Close() + c := session.DB(db.Mongo.Info.Database).C(collection) + err := c.Pipe([]bson.M{{"$project": bson.M{"location": "$location", "year": bson.M{"$year": "$updated"}, "month": bson.M{"$month": "$updated"}}}, + bson.M{"$match": bson.M{"location": sensor_location}}, + bson.M{"$group": bson.M{"_id": bson.M{"year": "$year", "month": "$month", "location": "$location"}}}, + bson.M{"$sort": bson.M{"_id.year": -1, "_id.month": -1}}}).All(&s) + if err != nil { + log.Println(err) + return s, nil + } + return s, nil + } else { + return s, errors.New("Query failed") + } +} +*/ + +//******************************************************************************** + +/************************* +testStore := model.SensorData{ + ID: bson.NewObjectId(), + Temperature: 34.2, + Humidity: 33.22, + Location: "Grand Meadow", + Updated: time.Now(), +} +**************************/ diff --git a/server/route/route.go b/server/route/route.go new file mode 100644 index 0000000..8d7b9a6 --- /dev/null +++ b/server/route/route.go @@ -0,0 +1,54 @@ +package route + +import ( + "github.com/julienschmidt/httprouter" + "log" + "net/http" + + "mywebsite/server/controller" + "mywebsite/server/controller/api" +) + +func Routes() *httprouter.Router { + + log.Println("Server Started") + + r := httprouter.New() + + r.GET("/api/storedata", api.HandleSensorRequest) + r.GET("/api/allsensors", api.HandleAllSensors) + r.GET("/api/sensor/:location", api.HandleSensorByLocation) + r.GET("/api/sensor/:location/:year", api.HandleSensorByLocationYear) + r.GET("/api/sensor/:location/:year/:monthname", api.HandleSensorByLocationMonth) + + r.GET("/discord", controller.DiscordRedirect) + r.GET("/vpn", controller.VPNRedirect) + r.GET("/camera", controller.CameraRedirect) + + //set up public folder path + r.ServeFiles("/public/*filepath", http.Dir("./public/")) + + //route every invalid request to template file + //routing is all handled on the client side with angular + r.NotFound = http.HandlerFunc(fileHandler("./public/index.html")) + + return r +} + +//route requests to static files +func routerFileHandler(path string) httprouter.Handle { + return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { + + http.ServeFile(w, r, path) + + } +} + +//function to serve files with standard net/http library +func fileHandler(path string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + http.ServeFile(w, r, path) + + } +} diff --git a/server/utils/config.go b/server/utils/config.go new file mode 100644 index 0000000..1e7e22d --- /dev/null +++ b/server/utils/config.go @@ -0,0 +1,44 @@ +package utils + +import ( + "encoding/json" + "io/ioutil" + "log" + "os" + + "mywebsite/server/controller/api" + "mywebsite/server/db" +) + +//structure for application configurations +type Config struct { + Database db.DatabaseInfo `json:"database"` + Api api.ApiInfo `json:"api"` +} + +//read the config file and return JsonObject struct +func ReadConfig() Config { + + log.Println("Reading config file...") + + file, e := ioutil.ReadFile("./config.json") + + if e != nil { + log.Printf("File error: %v\n", e) + os.Exit(1) + } + + log.Printf("%s\n", string(file)) + + //m := new(Dispatch) + //var m interface{} + var result Config + + err := json.Unmarshal(file, &result) + + if err != nil { + log.Println(err) + } + + return result +} diff --git a/webpack.config.js b/webpack.config.js index 67b9ad1..6201d9d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -33,8 +33,8 @@ module.exports = { ] }, output: { - path: __dirname + "/dist/", - publicPath: "/dist/", + path: __dirname + "/public/", + publicPath: "/public/", filename: "client.min.js" }, plugins: debug ? [] : [ @@ -48,6 +48,6 @@ module.exports = { template: 'index.html', inject: 'body', hash: true - }) + }) ] -}; \ No newline at end of file +};