diff --git a/client/assets/scss/Content.scss b/client/assets/scss/Content.scss index 9c41fdf..e854a2b 100644 --- a/client/assets/scss/Content.scss +++ b/client/assets/scss/Content.scss @@ -2,6 +2,7 @@ flex: 1; flex-wrap: wrap; min-width: 0; + min-height: 600px; .post+ .post { margin-top: 2em; } @@ -14,6 +15,7 @@ width: 7em; text-align: right; margin-left: -8em; + line-height: 2.5em; } } .intro { diff --git a/client/assets/scss/Footer.scss b/client/assets/scss/Footer.scss index 061b49d..4075b42 100644 --- a/client/assets/scss/Footer.scss +++ b/client/assets/scss/Footer.scss @@ -7,4 +7,13 @@ border-color: #DADADA; font-weight: 300; margin-top: 1em; + + display: flex; + padding-right: calc(50% - 997px / 2); + padding-left: calc(50% - 997px / 2); + &:after, + &:before { + content: " "; + width: 1em; + } } diff --git a/client/assets/scss/Header.scss b/client/assets/scss/Header.scss new file mode 100644 index 0000000..cad250d --- /dev/null +++ b/client/assets/scss/Header.scss @@ -0,0 +1,12 @@ +.Header { + width: 100%; + background: url("../images/header.jpg"); + background-size: cover; + height: 30em; + border-bottom: solid; + border-width: 1px; + border-color: #DADADA; + h1 { + text-align: center; + } +} \ No newline at end of file diff --git a/client/assets/scss/main.scss b/client/assets/scss/main.scss index d90879e..d9994ca 100644 --- a/client/assets/scss/main.scss +++ b/client/assets/scss/main.scss @@ -25,7 +25,6 @@ h5, h6 { margin: 0; font-weight: 400; - line-height: 1em; } p { @@ -55,7 +54,19 @@ hr { border-top: 1px solid #eee; } -.Footer, +select{ + background-color: #fff; + border: 1px solid #ccc; + border-radius: 0.2em; + height: 35px; + color: #555; + &:focus{ + border-color: #66afe9; + outline: 0; + box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6); + } +} + .Main { display: flex; padding-right: calc(50% - 997px / 2); @@ -71,23 +82,11 @@ hr { padding-top: 1em; } -.Header { - width: 100%; - background: url("../images/header.jpg"); - background-size: cover; - height: 30em; - border-bottom: solid; - border-width: 1px; - border-color: #DADADA; - h1 { - text-align: center; - } -} - .Loading { display: flex; flex: 1; justify-content: center; + align-items: center; } .btn { diff --git a/client/js/app.js b/client/js/app.js index 9560bed..7a19d31 100644 --- a/client/js/app.js +++ b/client/js/app.js @@ -38,8 +38,8 @@ ReactDOM.render(( - - + + diff --git a/client/js/components/Footer.js b/client/js/components/Footer.js index 068eea4..f56f106 100644 --- a/client/js/components/Footer.js +++ b/client/js/components/Footer.js @@ -7,7 +7,7 @@ export default class Footer extends React.Component { render() { return ( ); } diff --git a/client/js/components/Header.js b/client/js/components/Header.js index 491e357..0257a8b 100644 --- a/client/js/components/Header.js +++ b/client/js/components/Header.js @@ -1,6 +1,8 @@ import React from 'react'; import {bubble} from '../../assets/js/bubble'; +import '../../assets/scss/Header.scss'; + export default class Header extends React.Component { componentDidMount() { bubble(); diff --git a/client/js/components/Post.js b/client/js/components/Post.js index 188153b..d340785 100644 --- a/client/js/components/Post.js +++ b/client/js/components/Post.js @@ -21,7 +21,7 @@ export default class Post extends React.Component { componentDidMount() { const params = this.props.params; - this.props.appActions.fetchPost(params.category, params.post); + this.props.appActions.fetchPost(params.post, params.category); } render() { diff --git a/client/js/components/sensors/SensorInfo.js b/client/js/components/sensors/SensorInfo.js index dcd0f3b..f1e35f7 100644 --- a/client/js/components/sensors/SensorInfo.js +++ b/client/js/components/sensors/SensorInfo.js @@ -1,14 +1,108 @@ import React from 'react'; +import {Link} from 'react-router'; -export default class SensorInfo extends React.Component{ - - componentDidMount(){ - this.props.sensorActions.fetchSensorInfoYear('Grand Meadow', '2016'); - this.props.sensorActions.fetchSensorInfoMonth('Grand Meadow', '2016', 'May'); +import Loading from '../utils/Loading'; +import _chartjs from 'chart.js'; +import Chart from 'react-chartjs'; +import { + ChartOptions, + DataTemplate +} +from './chartOptions'; + +import './SensorInfo.scss'; + +const LineChart = Chart.Line; + +export default class SensorInfo extends React.Component { + + componentWillMount() { + let location = this.props.params.location; + this.props.sensorActions.fetchUniqueDates(location); } - render(){ - return( -
Test123
- ); + + componentWillUpdate(nextProps) { + let currentLocation = this.props.params.location, + nextLocation = nextProps.params.location; + + if (currentLocation !== nextLocation){ + this.props.sensorActions.fetchUniqueDates(nextLocation); + } + } + + loadYearOptions = (date, index) => { + return ( + + ); + } + + loadMonthOptions = (date, index) => { + return ( + + ); + } + + onChange(event, type) { + let location = this.props.params.location, + sensor = this.props.sensor, + actions = this.props.sensorActions, + yearIndex = sensor.selectedYearIndex, + monthIndex = sensor.selectedMonthIndex; + + if (type === 'year') { + yearIndex = parseInt(event.target.value); + monthIndex = 0; + } + else if (type === 'month') { + monthIndex = parseInt(event.target.value); + } + + let year = sensor.uniqueDates[yearIndex].year; + let monthname = sensor.uniqueDates[yearIndex].months[monthIndex].monthname; + + actions.setSelectedMonthIndex(monthIndex); + actions.setSelectedYearIndex(yearIndex); + this.props.sensorActions.fetchSensorInfoMonth(location, year, monthname); + } + + filterData(data) { + let temp = JSON.parse(JSON.stringify(DataTemplate)); + + for (let d of data) { + let label = `${d.month}/${d.day}`; + temp.labels.push(label); + temp.datasets[0].data.push(d.maxtemp); + temp.datasets[1].data.push(d.mintemp); + } + + return temp; + } + + render() { + let sensor = this.props.sensor; + let data = this.filterData(sensor.info); + return ( +
+ {sensor.fetchedUniqueDates ? +
+

{this.props.params.location}

+ + + +
+ : } + + {sensor.fetchedUniqueDates && sensor.fetchedInfo + ? + : null} + {sensor.fetchedUniqueDates && sensor.fetchedInfo + ?
Home
+ : null} +
+ ); } } \ No newline at end of file diff --git a/client/js/components/sensors/SensorInfo.scss b/client/js/components/sensors/SensorInfo.scss new file mode 100644 index 0000000..9045ded --- /dev/null +++ b/client/js/components/sensors/SensorInfo.scss @@ -0,0 +1,20 @@ +.SensorInfo{ + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + min-width: 0; + min-height: 600px; + width: 80%; + .selector-row{ + display: flex; + justify-content: center; + margin-bottom: 2em; + select{ + margin-left: 5px; + } + } + .home{ + margin-top: 1em; + } +} \ No newline at end of file diff --git a/client/js/components/sensors/SensorList.js b/client/js/components/sensors/SensorList.js index c1dfa1f..b83029c 100644 --- a/client/js/components/sensors/SensorList.js +++ b/client/js/components/sensors/SensorList.js @@ -20,8 +20,8 @@ export default class SensorList extends React.Component { this.openLink = this.openLink.bind(this); } - openLink(){ - browserHistory.push("/"); + openLink(location){ + browserHistory.push(`/sensor/${location}`); this.props.toggleOff(); } @@ -29,13 +29,18 @@ export default class SensorList extends React.Component { const date = new Date(sensor.updated); return ( -
-
+
{this.openLink(sensor.location)}}> +

{sensor.temperature}°f

-
+

{sensor.location}

- Updated: {date.toLocaleString('en-us', options)} + Updated: {date.toLocaleString('en-us', options)} + {Date.now() - date < 420000 + ? Connected + : Disconnected + } +
); diff --git a/client/js/components/sensors/SensorList.scss b/client/js/components/sensors/SensorList.scss index 893884f..ba5f99d 100644 --- a/client/js/components/sensors/SensorList.scss +++ b/client/js/components/sensors/SensorList.scss @@ -9,11 +9,16 @@ padding: 1em; &:hover{ - background-color: gray; + background-color: #D1D1D1; } - .item + .item{ + .temperature{ + flex: .5; + } + + .info{ margin-left: 1em; + flex: 1; } h2{ @@ -25,4 +30,11 @@ } } + .connected{ + color: green; + } + .disconnected{ + color: red; + } + } \ No newline at end of file diff --git a/client/js/components/sensors/chartOptions.js b/client/js/components/sensors/chartOptions.js new file mode 100644 index 0000000..f2bac62 --- /dev/null +++ b/client/js/components/sensors/chartOptions.js @@ -0,0 +1,30 @@ +export const ChartOptions = { + responsive: true, + // scaleOverride: true, + //scaleSteps: 20, + // scaleStartValue: 0, + //scaleStepWidth: 5 +}; + +export let DataTemplate = { + labels: [], + datasets: [{ + label: "Max Temperature °F", + fillColor: "rgba(220,220,220,0.2)", + strokeColor: "rgba(220,220,220,1)", + pointColor: "rgba(220,220,220,1)", + pointStrokeColor: "#fff", + pointHighlightFill: "#fff", + pointHighlightStroke: "rgba(220,220,220,1)", + data: [] + }, { + label: "Min Temperature °F", + fillColor: "rgba(151,187,205,0)", + strokeColor: "rgba(151,187,205,1)", + pointColor: "rgba(151,187,205,1)", + pointStrokeColor: "#fff", + pointHighlightFill: "#fff", + pointHighlightStroke: "rgba(151,187,205,1)", + data: [] + }] +}; \ No newline at end of file diff --git a/client/js/components/utils/AboutMe.js b/client/js/components/utils/AboutMe.js index f9dd82d..fd4588f 100644 --- a/client/js/components/utils/AboutMe.js +++ b/client/js/components/utils/AboutMe.js @@ -1,4 +1,5 @@ import React from 'react'; +import {Link} from 'react-router'; import me from '../../../assets/images/me.jpg'; @@ -12,7 +13,10 @@ export default class AboutMe extends React.Component{

My name is Mitchell and I have a passion for software development. I am currently a software engineer and enjoy working on personal projects in my free time.

- +

+ + Home +

eMail @@ -25,10 +29,12 @@ export default class AboutMe extends React.Component{ GitHub

+ {/*

Resume

+ */}
); } diff --git a/client/js/redux/actions/app.js b/client/js/redux/actions/app.js index b1c9018..5759223 100644 --- a/client/js/redux/actions/app.js +++ b/client/js/redux/actions/app.js @@ -44,10 +44,14 @@ export function fetchPreview() { } } -export function fetchPost(category, post) { +//adjust url according to parameters +//mainly used to load md files in the parent directory within /posts +export function fetchPost(post, category = null) { + let url; return (dispatch) => { dispatch(fetching()); - return fetch(`/public/posts/${category}/${post}.md`) + url = category !== null || typeof category === 'undefined' ? `/public/posts/${category}/${post}.md` : `/public/posts/${post}.md`; + return fetch(url) .then(response => response.text()) .then(response => { dispatch(loadPost(response)); diff --git a/client/js/redux/actions/sensor.js b/client/js/redux/actions/sensor.js index e356e99..b97c5f2 100644 --- a/client/js/redux/actions/sensor.js +++ b/client/js/redux/actions/sensor.js @@ -8,17 +8,31 @@ function loadSensorList(sensor_list){ } } -function loadSensorInfoYear(sensor_info){ +function loadSensorInfo(sensor_info){ return{ - type: types.LOAD_SENSOR_INFO_YEAR, + type: types.LOAD_SENSOR_INFO, sensor_info } } -function loadSensorInfoMonth(sensor_info){ +function loadUniqueDates(dates){ return{ - type: types.LOAD_SENSOR_INFO_MONTH, - sensor_info + type: types.LOAD_UNIQUE_DATES, + dates + } +} + +export function setSelectedYearIndex(index){ + return{ + type: types.SET_SELECTED_YEAR_INDEX, + index + } +} + +export function setSelectedMonthIndex(index){ + return{ + type: types.SET_SELECTED_MONTH_INDEX, + index } } @@ -28,16 +42,16 @@ function fetchingList(){ } } -function fetchingInfoYear(){ +function fetchingInfo(){ return { - type: types.FETCHING_INFO_YEAR + type: types.FETCHING_INFO } } -function fetchingInfoMonth(){ +function fetchingUniqueDates(){ return { - type: types.FETCHING_INFO_MONTH - } + type: types.FETCHING_UNIQUE_DATES + } } export function fetchSensorList(){ @@ -56,11 +70,11 @@ export function fetchSensorList(){ export function fetchSensorInfoYear(location, year){ return (dispatch) => { - dispatch(fetchingInfoYear()); + dispatch(fetchingInfo()); return fetch(`/api/sensor/${location}/${year}`) .then(response => response.json()) .then(json => { - dispatch(loadSensorInfoYear(json)); + dispatch(loadSensorInfo(json)); }) .catch(error => { console.log(error); @@ -70,14 +84,38 @@ export function fetchSensorInfoYear(location, year){ export function fetchSensorInfoMonth(location, year, month){ return (dispatch) => { - dispatch(fetchingInfoMonth()); + dispatch(fetchingInfo()); return fetch(`/api/sensor/${location}/${year}/${month}`) .then(response => response.json()) .then(json => { - dispatch(loadSensorInfoMonth(json)); + dispatch(loadSensorInfo(json)); }) .catch(error => { console.log(error); }); } -} \ No newline at end of file +} + +//this is called to initialize the sensor info page +//reloads unique dates and resets indexes +//then fetches new data for chart +export function fetchUniqueDates(location){ + return (dispatch) => { + dispatch(fetchingUniqueDates()); + dispatch(setSelectedMonthIndex(0)); + dispatch(setSelectedYearIndex(0)); + return fetch(`/api/uniquedates/${location}`) + .then(response => response.json()) + .then(json => { + dispatch(loadUniqueDates(json)); + if(json.length > 0){ + let year = json[0].year; + let month = json[0].months[0].monthname; + dispatch(fetchSensorInfoMonth(location, year, month)); + } + }) + .catch(error => { + console.log(error); + }); + } +} diff --git a/client/js/redux/constants/sensor.js b/client/js/redux/constants/sensor.js index ae93469..a23112b 100644 --- a/client/js/redux/constants/sensor.js +++ b/client/js/redux/constants/sensor.js @@ -1,10 +1,13 @@ //constants export const LOAD_SENSOR_LIST = 'LOAD_SENSOR_LIST'; -export const LOAD_SENSOR_INFO_YEAR = 'LOAD_SENSOR_INFO_YEAR'; -export const LOAD_SENSOR_INFO_MONTH = 'LOAD_SENSOR_INFO_MONTH'; - +export const LOAD_SENSOR_INFO = 'LOAD_SENSOR_INFO'; +export const LOAD_UNIQUE_DATES = 'LOAD_UNIQUE_DATES'; //fetching export const FETCHING_LIST = 'FETCHING_LIST'; -export const FETCHING_INFO_YEAR = 'FETCHING_INFO_YEAR'; -export const FETCHING_INFO_MONTH = 'FETCHING_INFO_MONTH'; \ No newline at end of file +export const FETCHING_INFO = 'FETCHING_INFO_YEAR'; +export const FETCHING_UNIQUE_DATES = 'FETCHING_UNIQUE_DATES'; + +//indexes +export const SET_SELECTED_YEAR_INDEX = 'SET_SELECTED_YEAR_INDEX'; +export const SET_SELECTED_MONTH_INDEX = 'SET_SELECTED_MONTH_INDEX'; \ No newline at end of file diff --git a/client/js/redux/reducers/sensor.js b/client/js/redux/reducers/sensor.js index 8d5a4af..bb32411 100644 --- a/client/js/redux/reducers/sensor.js +++ b/client/js/redux/reducers/sensor.js @@ -4,54 +4,69 @@ import * as types from '../constants/sensor'; //defaults - const defaultState = { list : [], - infoMonth: [], - infoYear: [], + info: [], + uniqueDates: {}, + + selectedYearIndex: 0, + selectedMonthIndex: 0, fetchingList: false, - fetchingInfoMonth: false, - fetchingInfoYear: false, + fetchingInfo: false, + fetchingUniqueDates: false, fetchedList: false, - fetchedInfoMonth: false, - fetchedInfoYear: false + fetchedInfo: false, + fetchedUniqueDates: false }; //default reducer export default function app(state = defaultState, action) { switch(action.type){ + //fetching functions - we use a fetching state to display loading images case types.FETCHING_LIST: return Object.assign({}, state, { fetchingList: true, fetchedList: false }); - case types.FETCHING_INFO_MONTH: + case types.FETCHING_INFO: return Object.assign({}, state, { - fetchingInfoMonth: true, - fetchedInfoMonth: false + fetchingInfo: true, + fetchedInfo: false }); - case types.FETCHING_INFO_YEAR: + case types.FETCHING_UNIQUE_DATES: return Object.assign({}, state, { - fetchingInfoYear: true, - fetchedInfoYear: false + fetchingUniqueDates: true, + fetchedUniqueDates: false }); + //other functions case types.LOAD_SENSOR_LIST: return Object.assign({}, state, { list: action.sensor_list, fetchingList: false, fetchedList: true }); - case types.LOAD_SENSOR_INFO_MONTH: + case types.LOAD_SENSOR_INFO: return Object.assign({}, state, { - infoMonth: action.sensor_info, - fetchingInfoMonth: false, - fetchedInfoMonth: true + info: action.sensor_info, + fetchingInfo: false, + fetchedInfo: true }); - case types.LOAD_SENSOR_INFO_YEAR: + case types.LOAD_UNIQUE_DATES: return Object.assign({}, state, { - infoYear: action.sensor_info, - fetchingInfoYear: false, - fetchedInfoYear: true + uniqueDates: action.dates, + fetchingUniqueDates: false, + fetchedUniqueDates: true + }); + + //indexes + case types.SET_SELECTED_YEAR_INDEX: + return Object.assign({}, state, { + selectedYearIndex: action.index + }); + case types.SET_SELECTED_MONTH_INDEX: + return Object.assign({}, state, { + selectedMonthIndex: action.index }); } //return present state if no actions get called diff --git a/client/js/redux/store.js b/client/js/redux/store.js index b28f24c..3e62523 100644 --- a/client/js/redux/store.js +++ b/client/js/redux/store.js @@ -6,7 +6,10 @@ import {syncHistoryWithStore} from 'react-router-redux'; import reducers from './reducers/reducers'; -const middleware = applyMiddleware(thunk, logger()); +const debug = process.env.NODE_ENV !== "production"; + +//run redux logger if we are in dev mode +const middleware = debug ? applyMiddleware(thunk, logger()) : applyMiddleware(thunk); //create the new store with default state as an empty object const store = createStore(reducers, {}, middleware); diff --git a/metadata.js b/metadata.js index fa3bce8..476a068 100644 --- a/metadata.js +++ b/metadata.js @@ -19,13 +19,15 @@ marked.setOptions({ } }); -const dir = './posts/'; +const rootDirectory = './posts/'; const json = { posts: [] }; //do everything synchronously to keep posts ordered -function parse_dir(dir, folder_name){ +//we are not worried about execution time since this script only runs once when building +//ignores files that are not in a directory +function parse_dir(dir, folder_name = null){ const posts = fs.readdirSync(dir); for(let post of posts){ @@ -33,7 +35,7 @@ function parse_dir(dir, folder_name){ if(stats.isDirectory()){ parse_dir(dir + post + '/', post); - } else { + } else if(folder_name !== null){ const file = fs.readFileSync(dir+post, 'utf8'); const tokens = marked.lexer(file, null); const temp = { @@ -49,7 +51,8 @@ function parse_dir(dir, folder_name){ } //recursively parse posts directory for all markdown files -parse_dir(dir, 'posts'); +//folder defaults to null and immediate child files are not added to json +parse_dir(rootDirectory); //sort posts by date json.posts.sort((a, b) => { diff --git a/package.json b/package.json index 72b6bf9..d9969dd 100644 --- a/package.json +++ b/package.json @@ -7,11 +7,12 @@ "build": "webpack && babel-node metadata.js", "c9": "webpack-dev-server --port $PORT --host $IP --hot --content-base dist --history-api-fallback", "check_gzip_size": "gzip -9 -c ./public/client.min.js | wc -c | numfmt --to=iec-i --suffix=B --padding=10", - "deploy" : "npm run get_dependencies && npm run prod && ./mywebsite", + "deploy": "npm run get_dependencies && npm run prod && ./mywebsite", "dev": "webpack-dev-server --content-base public --inline --hot --history-api-fallback", "get_dependencies": "go get ./server && npm install", - "prod": "export NODE_ENV=production && webpack -p && babel-node metadata.js && go build ./server/mywebsite.go", - "prod-win": "set NODE_ENV=production && webpack -p && babel-node metadata.js && go build ./server/mywebsite.go" + "prod": "export NODE_ENV=production && webpack -p --define process.env.NODE_ENV='\"production\"' --progress --colors && babel-node metadata.js && go build ./server/mywebsite.go", + "prod-win": "set NODE_ENV=production && webpack -p --define process.env.NODE_ENV='\"production\"' --progress --colors && babel-node metadata.js && go build ./server/mywebsite.go", + "watch": "webpack --watch --colors --progress" }, "repository": { "type": "git", @@ -24,6 +25,7 @@ }, "homepage": "https://github.com/mgerb/mywebsite#readme", "dependencies": { + "autoprefixer": "^6.4.1", "babel": "^6.5.2", "babel-cli": "^6.11.4", "babel-core": "^6.13.2", @@ -35,6 +37,7 @@ "babel-preset-es2015": "^6.13.2", "babel-preset-react": "^6.11.1", "babel-preset-stage-0": "^6.5.0", + "chart.js": "^1.1.1", "css-loader": "^0.23.1", "exports-loader": "^0.6.3", "file-loader": "^0.9.0", @@ -44,7 +47,10 @@ "marked": "^0.3.6", "ncp": "^2.0.0", "node-sass": "^3.8.0", + "postcss-loader": "^0.13.0", "react": "^15.3.0", + "react-addons-transition-group": "^15.3.1", + "react-chartjs": "^0.8.0", "react-dom": "^15.3.0", "react-redux": "^4.4.5", "react-router": "^2.6.1", diff --git a/server/controller/api/sensor.go b/server/controller/api/sensor.go index 5dd87be..5fefa2d 100644 --- a/server/controller/api/sensor.go +++ b/server/controller/api/sensor.go @@ -61,6 +61,7 @@ func HandleSensorRequest(w http.ResponseWriter, r *http.Request, ps httprouter.P storedData.Month = int(t.Month()) storedData.MonthName = t.Month().String() storedData.Year = t.Year() + storedData.Updated = t err := storedData.StoreData() @@ -134,21 +135,7 @@ func HandleSensorByLocation(w http.ResponseWriter, r *http.Request, ps httproute 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) - } - } + response := createResponse(s, err) fmt.Fprint(w, response) } @@ -162,21 +149,7 @@ func HandleSensorByLocationYear(w http.ResponseWriter, r *http.Request, ps httpr 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) - } - } + response := createResponse(s, err) fmt.Fprint(w, response) } @@ -191,6 +164,19 @@ func HandleSensorByLocationMonth(w http.ResponseWriter, r *http.Request, ps http s, err := daily_sensor.GetAllSensorInfoByMonth(location, year, monthname) + response := createResponse(s, err) + + fmt.Fprint(w, response) +} + +func HandleUniqueDates(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + + location := ps.ByName("location") + + w.Header().Set("Content-Type", "application/json") + + s, err := daily_sensor.GetUniqueSensorDates(location) + var response string if err != nil { @@ -209,3 +195,23 @@ func HandleSensorByLocationMonth(w http.ResponseWriter, r *http.Request, ps http fmt.Fprint(w, response) } + +func createResponse(s []daily_sensor.Data, err error) string{ + 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) + } + } + + return response +} \ No newline at end of file diff --git a/server/model/daily_sensor/daily_sensor.go b/server/model/daily_sensor/daily_sensor.go index 99837bb..0a80db1 100644 --- a/server/model/daily_sensor/daily_sensor.go +++ b/server/model/daily_sensor/daily_sensor.go @@ -16,22 +16,24 @@ const ( 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"` + MaxTemp float64 `json:"maxtemp,omitempty" bson:"maxtemp"` + MinTemp float64 `json:"mintemp,omitempty" bson:"mintemp"` + Location string `json:"location,omitempty" bson:"location"` + Month int `json:"month,omitempty" bson:"month"` + MonthName string `json:"monthname,omitempty" bson:"monthname"` + Day int `json:"day,omitempty" bson:"day"` + Year int `json:"year,omitempty" bson:"year"` + Updated time.Time `json:"updated,omitempty" bson:"updated"` } //convert struct to json string -func (s *Data) toJson() string { +func (s *Data) ToJson() string { b, err := json.MarshalIndent(s, "", " ") if err != nil { - log.Println(err.Error) + log.Println(err) + return "{message : \"Error loading data from database\"" } return string(b) @@ -73,7 +75,7 @@ func (s *Data) UpdateData() error { 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}} + change := bson.M{"$set": bson.M{"maxtemp": s.MaxTemp, "mintemp": s.MinTemp, "updated": time.Now()}} err := c.Update(colQuerier, change) @@ -130,7 +132,6 @@ func GetAllSensorInfo(sensor_location string) ([]Data, error) { 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) @@ -198,3 +199,55 @@ func GetAllSensorInfoByMonth(sensor_location string, year int, monthname string) return d, errors.New("Query failed") } } + +//I need to find a better implementation of this +//MongoDB $addToSet creates an array of objecta +//this ends up being a different type than the Data struct above +//it would be nice to make all MongoDB queries load directly into a Data struct +/* +type UniqueDates struct{ + Dates []Data `json:"dates" bson:"dates"` +} +*/ + +type Years struct { + Year int `json:"year", bson:"year"` + Months []Month `json:"months", bson:"months"` +} + +type Month struct { + Month int `json:"month", bson:"month"` + MonthName string `json:"monthname", bson:"monthname"` +} + +func GetUniqueSensorDates(sensor_location string) ([]Years, error){ + d := []Years{} + + 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{bson.M{"$match": bson.M{"location": sensor_location}}, + bson.M{"$group": bson.M{"_id": "$year", "months": bson.M{"$addToSet": bson.M{"month": "$month", "monthname": "$monthname"}}}}, + bson.M{"$project": bson.M{"year": "$_id", "months": "$months"}}, + bson.M{"$unwind": "$months"}, + bson.M{"$sort": bson.M{"months.month": -1}}, + bson.M{"$group": bson.M{"_id": "$year", "months": bson.M{"$push": "$months"}}}, + bson.M{"$project": bson.M{"year": "$_id", "months": "$months"}}, + + }).All(&d) + + if err != nil { + log.Println(err) + return d, nil + } + + return d, nil + + } else { + return d, errors.New("Query failed") + } +} \ No newline at end of file diff --git a/server/model/raw_sensor/raw_sensor.go b/server/model/raw_sensor/raw_sensor.go index 9b91b54..560fda8 100644 --- a/server/model/raw_sensor/raw_sensor.go +++ b/server/model/raw_sensor/raw_sensor.go @@ -59,9 +59,10 @@ func (s *Data) StoreData() error { //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"` + ID bson.ObjectId `bson:"_id,omitempty"` + Location string `json:"location", bson:"location"` + Temperature float64 `json:"temperature" bson:"temperature"` + Updated time.Time `json:"updated" bson:"updated"` } //get latest update from each unique sensor @@ -78,7 +79,9 @@ func GetAllSensors() ([]DataStore_AllSensors, error) { 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) + bson.M{"$sort": bson.M{"_id": 1}}, + bson.M{"$project": bson.M{"location": "$_id", "temperature": "$temperature", "updated": "$updated"}}, + }).All(&s) if err != nil { return s, nil diff --git a/server/route/route.go b/server/route/route.go index 8d7b9a6..90a6efe 100644 --- a/server/route/route.go +++ b/server/route/route.go @@ -20,7 +20,8 @@ func Routes() *httprouter.Router { 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("/api/uniquedates/:location", api.HandleUniqueDates) + r.GET("/discord", controller.DiscordRedirect) r.GET("/vpn", controller.VPNRedirect) r.GET("/camera", controller.CameraRedirect) diff --git a/webpack.config.js b/webpack.config.js index 3b6726a..447595f 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -2,6 +2,7 @@ var debug = process.env.NODE_ENV !== "production"; var webpack = require('webpack'); var path = require('path'); var HtmlWebpackPlugin = require('html-webpack-plugin'); +var autoprefixer = require('autoprefixer'); module.exports = { devtool: debug ? "inline-sourcemap" : null, @@ -17,7 +18,7 @@ module.exports = { plugins: ['react-html-attrs', 'transform-class-properties', 'transform-decorators-legacy'], } }, - { test: /\.scss$/, loader: "style-loader!css-loader!sass-loader"}, + { test: /\.scss$/, loader: "style-loader!css-loader!postcss-loader!sass-loader"}, { test: /\.css$/, loader: "style-loader!css-loader" }, { test: /\.png$/, loader: "url-loader?limit=100000&name=images/[hash].[ext]" }, { test: /\.jpg$/, loader: "url-loader?limit=100000&name=images/[hash].[ext]" }, @@ -32,6 +33,7 @@ module.exports = { } ] }, + postcss: function(){ return [autoprefixer]}, output: { path: __dirname + "/public/", publicPath: "/public/",