1
0
mirror of https://github.com/mgerb/mywebsite synced 2026-01-10 09:52:51 +00:00

server set up with app engine

This commit is contained in:
2016-08-22 00:01:53 -05:00
parent 6dac5aeb06
commit 34b463cd44
18 changed files with 827 additions and 33 deletions

5
.gitignore vendored
View File

@@ -1,7 +1,6 @@
\.c9 \.c9
\.DS_Store \.DS_Store
config.json config.json
mywebsite
mywebsite.exe
node_modules node_modules
dist dist
public

6
app.yaml Normal file
View File

@@ -0,0 +1,6 @@
runtime: go
api_version: go1
handlers:
- url: /.*
script: _go_app

View File

@@ -11,13 +11,13 @@ function initPreview(posts){
//in this case we can send the http request here rather in the react component //in this case we can send the http request here rather in the react component
export function fetchPreview(){ export function fetchPreview(){
return (dispatch) => { return (dispatch) => {
return fetch('/metadata.json') return fetch('/public/metadata.json')
.then(response => response.json()) .then(response => response.json())
.then(json => { .then(json => {
dispatch(initPreview(json)); dispatch(initPreview(json));
}) })
.catch(error => { .catch(error => {
console.log(error); console.log(error);
}); });
} }
} }

9
config.template.json Normal file
View File

@@ -0,0 +1,9 @@
{
"database": {
"url": "",
"database": ""
},
"api": {
"key": ""
}
}

View File

@@ -1,21 +1,31 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="Mitchell Gerber"> <meta name="author" content="Mitchell Gerber">
<link href="https://fonts.googleapis.com/css?family=Roboto+Slab:300,400" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Roboto+Slab:300,400" rel="stylesheet">
<title>mitchel.io</title> <title>mitchel.io</title>
</head> </head>
<body> <body>
<script> <script>
(function(i, s, o, g, r, a, m) {i['GoogleAnalyticsObject'] = r;i[r] = i[r] || function() { (function(i, s, o, g, r, a, m) {
(i[r].q = i[r].q || []).push(arguments)}, i[r].l = 1 * new Date(); i['GoogleAnalyticsObject'] = r;
a = s.createElement(o),m = s.getElementsByTagName(o)[0];a.async = 1;a.src = g;m.parentNode.insertBefore(a, m)}) i[r] = i[r] || function() {
(i[r].q = i[r].q || []).push(arguments)
}, i[r].l = 1 * new Date();
a = s.createElement(o), m = s.getElementsByTagName(o)[0];
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m)
})
(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga'); (window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
</script> </script>
<!--it's happening!!!--> <!--it's happening!!!-->
<div id="app"></div> <div id="app"></div>
</body> </body>
</html> </html>

View File

@@ -3,7 +3,7 @@
folder/files within posts are scanned recursively folder/files within posts are scanned recursively
each post is contained within category, which is supplied by the direct parent folder each post is contained within category, which is supplied by the direct parent folder
Posts are sorted by date 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 Client uses metadata to display posts on preview page
*/ */
@@ -27,10 +27,10 @@ const json = {
//do everything synchronously to keep posts ordered //do everything synchronously to keep posts ordered
function parse_dir(dir, folder_name){ function parse_dir(dir, folder_name){
const posts = fs.readdirSync(dir); const posts = fs.readdirSync(dir);
for(let post of posts){ for(let post of posts){
const stats = fs.statSync(dir + post); const stats = fs.statSync(dir + post);
if(stats.isDirectory()){ if(stats.isDirectory()){
parse_dir(dir + post + '/', post); parse_dir(dir + post + '/', post);
} else { } else {
@@ -58,15 +58,15 @@ json.posts.sort((a, b) => {
}); });
//output to public path //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; if (err) throw err;
console.log("Saved metadata.json"); console.log("Saved metadata.json");
}) })
//copy posts folder to dist //copy posts folder to public
ncp('./posts', './dist/posts', (err) => { ncp('./posts', './public/posts', (err) => {
if (err) { if (err) {
return console.error(err); return console.error(err);
} }
console.log('copied'); console.log('copied');
}); });

View File

@@ -5,8 +5,8 @@
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"build": "webpack && babel-node metadata.js", "build": "webpack && babel-node metadata.js",
"c9": "webpack-dev-server --port $PORT --host $IP --hot --content-base dist --history-api-fallback", "c9": "webpack-dev-server --port $PORT --host $IP --hot --content-base public --history-api-fallback",
"dev": "webpack-dev-server --content-base dist --inline --hot --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": "export NODE_ENV=production && webpack -p && babel-node metadata.js",
"prod-win": "set NODE_ENV=production && webpack -p && babel-node metadata.js", "prod-win": "set NODE_ENV=production && webpack -p && babel-node metadata.js",
"deploy": "goapp deploy", "deploy": "goapp deploy",

View File

@@ -6,4 +6,14 @@
`npm run build` - builds the application using webpack and runs metadata.js `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 - 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 - 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

23
server.go Normal file
View File

@@ -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)
}

View File

@@ -0,0 +1,13 @@
package api
import ()
var Api ApiInfo
type ApiInfo struct {
Key string `json:"key"`
}
func Configure(a ApiInfo) {
Api = a
}

View File

@@ -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)
}

View File

@@ -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)
}

54
server/db/db.go Normal file
View File

@@ -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
}

View File

@@ -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")
}
}

View File

@@ -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(),
}
**************************/

54
server/route/route.go Normal file
View File

@@ -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)
}
}

44
server/utils/config.go Normal file
View File

@@ -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
}

View File

@@ -33,8 +33,8 @@ module.exports = {
] ]
}, },
output: { output: {
path: __dirname + "/dist/", path: __dirname + "/public/",
publicPath: "/dist/", publicPath: "/public/",
filename: "client.min.js" filename: "client.min.js"
}, },
plugins: debug ? [] : [ plugins: debug ? [] : [
@@ -48,6 +48,6 @@ module.exports = {
template: 'index.html', template: 'index.html',
inject: 'body', inject: 'body',
hash: true hash: true
}) })
] ]
}; };