diff --git a/app/config/timezones.json b/app/config/timezones.json new file mode 100644 index 0000000..558c0aa --- /dev/null +++ b/app/config/timezones.json @@ -0,0 +1,421 @@ +{ +"LocationNames": [ +"America/New_York", +"America/Chicago", +"America/Denver", +"America/Phoenix", +"America/Los_Angeles", +"America/Anchorage", +"Pacific/Honolulu", +"", +"Africa/Abidjan", +"Africa/Accra", +"Africa/Addis_Ababa", +"Africa/Algiers", +"Africa/Asmara", +"Africa/Bamako", +"Africa/Bangui", +"Africa/Banjul", +"Africa/Bissau", +"Africa/Blantyre", +"Africa/Brazzaville", +"Africa/Bujumbura", +"Africa/Cairo", +"Africa/Casablanca", +"Africa/Ceuta", +"Africa/Conakry", +"Africa/Dakar", +"Africa/Dar_es_Salaam", +"Africa/Djibouti", +"Africa/Douala", +"Africa/El_Aaiun", +"Africa/Freetown", +"Africa/Gaborone", +"Africa/Harare", +"Africa/Johannesburg", +"Africa/Juba", +"Africa/Kampala", +"Africa/Khartoum", +"Africa/Kigali", +"Africa/Kinshasa", +"Africa/Lagos", +"Africa/Libreville", +"Africa/Lome", +"Africa/Luanda", +"Africa/Lubumbashi", +"Africa/Lusaka", +"Africa/Malabo", +"Africa/Maputo", +"Africa/Maseru", +"Africa/Mbabane", +"Africa/Mogadishu", +"Africa/Monrovia", +"Africa/Nairobi", +"Africa/Ndjamena", +"Africa/Niamey", +"Africa/Nouakchott", +"Africa/Ouagadougou", +"Africa/Porto-Novo", +"Africa/Sao_Tome", +"Africa/Tripoli", +"Africa/Tunis", +"Africa/Windhoek", +"America/Adak", +"America/Anguilla", +"America/Antigua", +"America/Araguaina", +"America/Argentina/Buenos_Aires", +"America/Argentina/Catamarca", +"America/Argentina/Cordoba", +"America/Argentina/Jujuy", +"America/Argentina/La_Rioja", +"America/Argentina/Mendoza", +"America/Argentina/Rio_Gallegos", +"America/Argentina/Salta", +"America/Argentina/San_Juan", +"America/Argentina/San_Luis", +"America/Argentina/Tucuman", +"America/Argentina/Ushuaia", +"America/Aruba", +"America/Asuncion", +"America/Atikokan", +"America/Bahia", +"America/Bahia_Banderas", +"America/Barbados", +"America/Belem", +"America/Belize", +"America/Blanc-Sablon", +"America/Boa_Vista", +"America/Bogota", +"America/Boise", +"America/Cambridge_Bay", +"America/Campo_Grande", +"America/Cancun", +"America/Caracas", +"America/Cayenne", +"America/Cayman", +"America/Chihuahua", +"America/Costa_Rica", +"America/Creston", +"America/Cuiaba", +"America/Curacao", +"America/Danmarkshavn", +"America/Dawson", +"America/Dawson_Creek", +"America/Detroit", +"America/Dominica", +"America/Edmonton", +"America/Eirunepe", +"America/El_Salvador", +"America/Fortaleza", +"America/Glace_Bay", +"America/Godthab", +"America/Goose_Bay", +"America/Grand_Turk", +"America/Grenada", +"America/Guadeloupe", +"America/Guatemala", +"America/Guayaquil", +"America/Guyana", +"America/Halifax", +"America/Havana", +"America/Hermosillo", +"America/Indiana/Indianapolis", +"America/Indiana/Knox", +"America/Indiana/Marengo", +"America/Indiana/Petersburg", +"America/Indiana/Tell_City", +"America/Indiana/Vevay", +"America/Indiana/Vincennes", +"America/Indiana/Winamac", +"America/Inuvik", +"America/Iqaluit", +"America/Jamaica", +"America/Juneau", +"America/Kentucky/Louisville", +"America/Kentucky/Monticello", +"America/Kralendijk", +"America/La_Paz", +"America/Lima", +"America/Lower_Princes", +"America/Maceio", +"America/Managua", +"America/Manaus", +"America/Marigot", +"America/Martinique", +"America/Matamoros", +"America/Mazatlan", +"America/Menominee", +"America/Merida", +"America/Metlakatla", +"America/Mexico_City", +"America/Miquelon", +"America/Moncton", +"America/Monterrey", +"America/Montevideo", +"America/Montserrat", +"America/Nassau", +"America/Nipigon", +"America/Nome", +"America/Noronha", +"America/North_Dakota/Beulah", +"America/North_Dakota/Center", +"America/North_Dakota/New_Salem", +"America/Ojinaga", +"America/Panama", +"America/Pangnirtung", +"America/Paramaribo", +"America/Port_of_Spain", +"America/Port-au-Prince", +"America/Porto_Velho", +"America/Puerto_Rico", +"America/Rainy_River", +"America/Rankin_Inlet", +"America/Recife", +"America/Regina", +"America/Resolute", +"America/Rio_Branco", +"America/Santa_Isabel", +"America/Santarem", +"America/Santiago", +"America/Santo_Domingo", +"America/Sao_Paulo", +"America/Scoresbysund", +"America/Sitka", +"America/St_Barthelemy", +"America/St_Johns", +"America/St_Kitts", +"America/St_Lucia", +"America/St_Thomas", +"America/St_Vincent", +"America/Swift_Current", +"America/Tegucigalpa", +"America/Thule", +"America/Thunder_Bay", +"America/Tijuana", +"America/Toronto", +"America/Tortola", +"America/Vancouver", +"America/Whitehorse", +"America/Winnipeg", +"America/Yakutat", +"America/Yellowknife", +"Antarctica/Casey", +"Antarctica/Davis", +"Antarctica/DumontDUrville", +"Antarctica/Macquarie", +"Antarctica/Mawson", +"Antarctica/McMurdo", +"Antarctica/Palmer", +"Antarctica/Rothera", +"Antarctica/Syowa", +"Antarctica/Troll", +"Antarctica/Vostok", +"Arctic/Longyearbyen", +"Asia/Aden", +"Asia/Almaty", +"Asia/Amman", +"Asia/Anadyr", +"Asia/Aqtau", +"Asia/Aqtobe", +"Asia/Ashgabat", +"Asia/Baghdad", +"Asia/Bahrain", +"Asia/Baku", +"Asia/Bangkok", +"Asia/Beirut", +"Asia/Bishkek", +"Asia/Brunei", +"Asia/Choibalsan", +"Asia/Chongqing", +"Asia/Colombo", +"Asia/Damascus", +"Asia/Dhaka", +"Asia/Dili", +"Asia/Dubai", +"Asia/Dushanbe", +"Asia/Gaza", +"Asia/Harbin", +"Asia/Hebron", +"Asia/Ho_Chi_Minh", +"Asia/Hong_Kong", +"Asia/Hovd", +"Asia/Irkutsk", +"Asia/Jakarta", +"Asia/Jayapura", +"Asia/Jerusalem", +"Asia/Kabul", +"Asia/Kamchatka", +"Asia/Karachi", +"Asia/Kashgar", +"Asia/Kathmandu", +"Asia/Khandyga", +"Asia/Kolkata", +"Asia/Krasnoyarsk", +"Asia/Kuala_Lumpur", +"Asia/Kuching", +"Asia/Kuwait", +"Asia/Macau", +"Asia/Magadan", +"Asia/Makassar", +"Asia/Manila", +"Asia/Muscat", +"Asia/Nicosia", +"Asia/Novokuznetsk", +"Asia/Novosibirsk", +"Asia/Omsk", +"Asia/Oral", +"Asia/Phnom_Penh", +"Asia/Pontianak", +"Asia/Pyongyang", +"Asia/Qatar", +"Asia/Qyzylorda", +"Asia/Rangoon", +"Asia/Riyadh", +"Asia/Sakhalin", +"Asia/Samarkand", +"Asia/Seoul", +"Asia/Shanghai", +"Asia/Singapore", +"Asia/Taipei", +"Asia/Tashkent", +"Asia/Tbilisi", +"Asia/Tehran", +"Asia/Thimphu", +"Asia/Tokyo", +"Asia/Ulaanbaatar", +"Asia/Urumqi", +"Asia/Ust-Nera", +"Asia/Vientiane", +"Asia/Vladivostok", +"Asia/Yakutsk", +"Asia/Yekaterinburg", +"Asia/Yerevan", +"Atlantic/Azores", +"Atlantic/Bermuda", +"Atlantic/Canary", +"Atlantic/Cape_Verde", +"Atlantic/Faroe", +"Atlantic/Madeira", +"Atlantic/Reykjavik", +"Atlantic/South_Georgia", +"Atlantic/St_Helena", +"Atlantic/Stanley", +"Australia/Adelaide", +"Australia/Brisbane", +"Australia/Broken_Hill", +"Australia/Currie", +"Australia/Darwin", +"Australia/Eucla", +"Australia/Hobart", +"Australia/Lindeman", +"Australia/Lord_Howe", +"Australia/Melbourne", +"Australia/Perth", +"Australia/Sydney", +"Europe/Amsterdam", +"Europe/Andorra", +"Europe/Athens", +"Europe/Belgrade", +"Europe/Berlin", +"Europe/Bratislava", +"Europe/Brussels", +"Europe/Bucharest", +"Europe/Budapest", +"Europe/Busingen", +"Europe/Chisinau", +"Europe/Copenhagen", +"Europe/Dublin", +"Europe/Gibraltar", +"Europe/Guernsey", +"Europe/Helsinki", +"Europe/Isle_of_Man", +"Europe/Istanbul", +"Europe/Jersey", +"Europe/Kaliningrad", +"Europe/Kiev", +"Europe/Lisbon", +"Europe/Ljubljana", +"Europe/London", +"Europe/Luxembourg", +"Europe/Madrid", +"Europe/Malta", +"Europe/Mariehamn", +"Europe/Minsk", +"Europe/Monaco", +"Europe/Moscow", +"Europe/Oslo", +"Europe/Paris", +"Europe/Podgorica", +"Europe/Prague", +"Europe/Riga", +"Europe/Rome", +"Europe/Samara", +"Europe/San_Marino", +"Europe/Sarajevo", +"Europe/Simferopol", +"Europe/Skopje", +"Europe/Sofia", +"Europe/Stockholm", +"Europe/Tallinn", +"Europe/Tirane", +"Europe/Uzhgorod", +"Europe/Vaduz", +"Europe/Vatican", +"Europe/Vienna", +"Europe/Vilnius", +"Europe/Volgograd", +"Europe/Warsaw", +"Europe/Zagreb", +"Europe/Zaporozhye", +"Europe/Zurich", +"Indian/Antananarivo", +"Indian/Chagos", +"Indian/Christmas", +"Indian/Cocos", +"Indian/Comoro", +"Indian/Kerguelen", +"Indian/Mahe", +"Indian/Maldives", +"Indian/Mauritius", +"Indian/Mayotte", +"Indian/Reunion", +"Pacific/Apia", +"Pacific/Auckland", +"Pacific/Chatham", +"Pacific/Chuuk", +"Pacific/Easter", +"Pacific/Efate", +"Pacific/Enderbury", +"Pacific/Fakaofo", +"Pacific/Fiji", +"Pacific/Funafuti", +"Pacific/Galapagos", +"Pacific/Gambier", +"Pacific/Guadalcanal", +"Pacific/Guam", +"Pacific/Johnston", +"Pacific/Kiritimati", +"Pacific/Kosrae", +"Pacific/Kwajalein", +"Pacific/Majuro", +"Pacific/Marquesas", +"Pacific/Midway", +"Pacific/Nauru", +"Pacific/Niue", +"Pacific/Norfolk", +"Pacific/Noumea", +"Pacific/Pago_Pago", +"Pacific/Palau", +"Pacific/Pitcairn", +"Pacific/Pohnpei", +"Pacific/Port_Moresby", +"Pacific/Rarotonga", +"Pacific/Saipan", +"Pacific/Tahiti", +"Pacific/Tarawa", +"Pacific/Tongatapu", +"Pacific/Wake", +"Pacific/Wallis" +] +} diff --git a/app/githop.go b/app/githop.go index ab8c612..96778eb 100644 --- a/app/githop.go +++ b/app/githop.go @@ -12,6 +12,7 @@ import ( "path/filepath" "strconv" "strings" + "time" "appengine" "appengine/mail" @@ -25,12 +26,14 @@ import ( var router *mux.Router var githubOauthConfig oauth.Config +var timezones Timezones var sessionStore *sessions.CookieStore var sessionConfig SessionConfig var templates map[string]*template.Template func init() { initTemplates() + timezones = initTimezones() sessionStore, sessionConfig = initSession() initGithubOAuthConfig() @@ -45,6 +48,8 @@ func init() { router.HandleFunc("/digest/send", sendDigestHandler).Name("send-digest").Methods("POST") router.HandleFunc("/digest/cron", digestCronHandler) + router.HandleFunc("/account/set-timezone", setTimezoneHandler).Name("set-timezone").Methods("POST") + router.HandleFunc("/admin/digest", digestAdminHandler) http.Handle("/", router) } @@ -139,8 +144,11 @@ func indexHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusInternalServerError) return } - - if err := templates["index"].Execute(w, nil); err != nil { + var data = map[string]interface{}{ + "Account": account, + "Timezones": timezones, + } + if err := templates["index"].Execute(w, data); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } @@ -300,6 +308,34 @@ func githubOAuthCallbackHandler(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, indexUrl.String(), http.StatusFound) } +func setTimezoneHandler(w http.ResponseWriter, r *http.Request) { + session, _ := sessionStore.Get(r, sessionConfig.CookieName) + userId := session.Values[sessionConfig.UserIdKey].(int) + c := appengine.NewContext(r) + account, err := getAccount(c, userId) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + timezoneName := r.FormValue("timezone_name") + _, err = time.LoadLocation(timezoneName) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + account.TimezoneName = timezoneName + err = account.put(c) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + indexUrl, _ := router.Get("index").URL() + http.Redirect(w, r, indexUrl.String(), http.StatusFound) +} + func digestAdminHandler(w http.ResponseWriter, r *http.Request) { userId, err := strconv.Atoi(r.FormValue("user_id")) if err != nil { diff --git a/app/templates/index.html b/app/templates/index.html index 9b3e57d..c98a87c 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -14,4 +14,21 @@ +
+
+ + {{end}} diff --git a/app/timezones.go b/app/timezones.go new file mode 100644 index 0000000..2abe67d --- /dev/null +++ b/app/timezones.go @@ -0,0 +1,55 @@ +package githop + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "time" +) + +type TimezoneConfig struct { + LocationNames []string +} + +type Timezone struct { + LocationName string + DisplayUTCOffset string +} + +type Timezones []Timezone + +func initTimezones() Timezones { + configBytes, err := ioutil.ReadFile("config/timezones.json") + if err != nil { + log.Panicf("Could not read timezone config: %s", err.Error()) + } + var config TimezoneConfig + err = json.Unmarshal(configBytes, &config) + if err != nil { + log.Panicf("Could not parse timezones config %s: %s", configBytes, err.Error()) + } + timezones = make(Timezones, len(config.LocationNames)) + // Use a January date so that UTC offsets are not affected by DST in the northern hemisphere + now := time.Unix(380937600, 0) + for i, locationName := range config.LocationNames { + if locationName == "" { + timezones[i] = Timezone{ + LocationName: locationName, + } + continue + } + location, err := time.LoadLocation(locationName) + if err != nil { + log.Panicf("Location %s could not be loaded: %s", locationName, err.Error()) + } + utcDelta := now.Sub(time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), now.Nanosecond(), location)) + utcDeltaHours := int(utcDelta.Hours()) + utcDeltaMinutes := int(utcDelta.Minutes()) - 60*utcDeltaHours + timezones[i] = Timezone{ + LocationName: locationName, + DisplayUTCOffset: fmt.Sprintf("%d:%02d", utcDeltaHours, utcDeltaMinutes), + } + } + return timezones +}