commit d08def228f6b05aeec76130dcd285f6b9f24312c
parent 6306164c020de6df16af94ec2a60a964eae67b5b
Author: Andreas Gruhler <andreas.gruhler@adfinis.com>
Date: Sun, 17 Sep 2023 11:39:22 +0200
fix: dayOfISOWeek loop and html tmpl
Diffstat:
5 files changed, 217 insertions(+), 62 deletions(-)
diff --git a/css/index.css b/css/index.css
@@ -0,0 +1,33 @@
+* {
+ padding: 0;
+ margin: 0;
+ font-family: monospace;
+ font-size: 18px;
+}
+
+a {
+ color: black;
+}
+
+h1, h2, p {
+ padding: 10px 20px;
+}
+
+button, input {
+ width: 95%;
+ padding: 5px 10px;
+ border-radius: 3px;
+ box-shadow: 0 1px #b1b0b6;
+ background: white;
+ border: 1px solid #f3f2f7;
+ color: black;
+}
+
+button {
+ cursor: pointer;
+ text-align: center;
+}
+
+button:disabled {
+ display: none;
+}
diff --git a/css/style.css b/css/style.css
@@ -0,0 +1,27 @@
+table {
+ border-collapse: collapse;
+ width: 100%;
+}
+
+th, td {
+ padding: 5px 20px;
+ text-align: left;
+ border-right: 1px solid #b0b0b6;
+}
+
+th {
+ font-weight: normal;
+ font-size: 0.8em;
+ text-transform: uppercase;
+ color: #b0b0b6;
+}
+
+tbody tr:nth-child(even):not(.input) {
+ background-color: #f9f9fc;
+}
+
+footer {
+ margin-top: 5px;
+ padding: 5px;
+ flex-wrap: wrap;
+}
diff --git a/main.go b/main.go
@@ -68,6 +68,7 @@ func main() {
router := httprouter.New()
router.GET("/", server.Index)
router.GET("/week/:week", server.Week)
+ router.ServeFiles("/css/*filepath", http.Dir("./css"))
log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%d", conf.ListenAddress, conf.ListenPort), router))
}
diff --git a/server.go b/server.go
@@ -10,6 +10,7 @@ import (
"hivedav/caldav"
"fmt"
"github.com/pquerna/termchalk/prettytable"
+ "html/template"
_ "github.com/mattn/go-sqlite3"
"database/sql"
"io"
@@ -24,6 +25,14 @@ type Server struct {
db *sql.DB
}
+type TableData struct {
+ TableHead []string
+ Rows [][]string
+ Week int
+ Year int
+ Version string
+}
+
var drop = `DROP TABLE IF EXISTS availability;
DROP TABLE IF EXISTS availability_1;`
var create_availability = `CREATE TABLE IF NOT EXISTS availability (
@@ -68,32 +77,44 @@ func (s *Server) NewServer(c *config.Config) (*Server, error) {
return s, nil
}
+// Returns Time with requested weekday and week. If out of bounds, the returned
+// week is set to the maximum for the specific year.
// https://xferion.com/golang-reverse-isoweek-get-the-date-of-the-first-day-of-iso-week
-func dayOfISOWeek(weekday int, week int, year int) time.Time {
- timeNow := time.Now()
- yearNow, weekNow:= timeNow.ISOWeek()
+func dayOfISOWeek(weekday int, week int, year int) (timeIt time.Time, weekNow int) {
+ // check out of bounds week (last week of the year)
+ limit := time.Date(year, 12, 31, 0, 0, 0, 0, time.Local)
+ _, limitWeek := limit.ISOWeek()
+
+ if week > limitWeek {
+ // set week to maximum
+ week = limitWeek
+ }
+
+ // start time iterator in week 0
+ timeIt = time.Date(year, 0, 0, 0, 0, 0, 0, time.Local)
+ yearNow, weekNow := timeIt.ISOWeek()
// iterate back to the weekday
- for timeNow.Weekday() != time.Weekday(weekday) {
+ for timeIt.Weekday() != time.Weekday(weekday) {
// years, months, days
- timeNow = timeNow.AddDate(0, 0, -1)
- yearNow, weekNow = timeNow.ISOWeek()
+ timeIt = timeIt.AddDate(0, 0, -1)
+ yearNow, weekNow = timeIt.ISOWeek()
}
// if we iterated back into the old year, iterate forward in weekly
// steps (7) until we reach the weekday of the first week in yearNow
for yearNow < year {
- timeNow = timeNow.AddDate(0, 0, 7)
- yearNow, weekNow = timeNow.ISOWeek()
+ timeIt = timeIt.AddDate(0, 0, 7)
+ yearNow, weekNow = timeIt.ISOWeek()
}
// iterate forward to the first day of the given ISO week
for weekNow < week {
- timeNow = timeNow.AddDate(0, 0, 7)
- yearNow, weekNow = timeNow.ISOWeek()
+ timeIt = timeIt.AddDate(0, 0, 7)
+ yearNow, weekNow = timeIt.ISOWeek()
}
- return timeNow
+ return timeIt, weekNow
}
func (s *Server) available(start string, end string) (bool, error) {
@@ -133,76 +154,117 @@ func (s *Server) Week(w http.ResponseWriter, req *http.Request, ps httprouter.Pa
// overwrite with requested week
week, err = strconv.Atoi(ps.ByName("week"))
if err != nil {
- log.Printf("Cannot serve requested week '%s'")
+ log.Printf("Cannot serve requested week '%d'\n", week)
w.WriteHeader(http.StatusBadRequest)
+ // TODO: Print human readable error to w
return
}
+ monday, week := dayOfISOWeek(1, week, year)
log.Printf("Current weekday is '%d'\n", weekday)
log.Printf("Serving week '%v' of year '%v'\n", week, year)
-
- monday := dayOfISOWeek(1, week, year)
log.Printf("Monday is '%v'\n", monday)
+
sqlDateFmt := "2006-01-02 15:04"
+ rows := make([][]string, 9)
+ for i := range rows {
+ rows[i] = make([]string, 6)
+ }
+
+ // Define the table header
+ // TODO: Make heading date format configurable, start of week, etc..
+ tableHead := []string{
+ "Time ",
+ fmt.Sprintf("Mon %s", monday.Format("02.01.")),
+ fmt.Sprintf("Tue %s", monday.Add(time.Hour).Format("02.01.")),
+ fmt.Sprintf("Wed %s", monday.Add(time.Hour*24*2).Format("02.01.")),
+ fmt.Sprintf("Thu %s", monday.Add(time.Hour*24*3).Format("02.01.")),
+ fmt.Sprintf("Fri %s", monday.Add(time.Hour*24*4).Format("02.01.")),
+ }
+
+ tableData := TableData {
+ TableHead: tableHead,
+ Rows: rows,
+ Week: week,
+ Year: year,
+ Version: hivedavVersion,
+ }
+
+ // TODO: use timeIt to go through the loops below, remove the static arrays
+ timeIt := time.Date(monday.Year(), monday.Month(), monday.Day(), 0, 0, 0, 0, localLocation)
+ //dayEnd := time.Date(monday.Year(), monday.Month(), monday.Day(), 17, 0, 0, 0, localLocation)
+ //weekEnd := dayEnd.Add(time.Hour * 24*5)
+
+ // Working hours - Eight to Five
+ //for h := 8; h < 17; h++ {
+ for _, h := range []int{8,9,10,11,12,13,14,15,16} {
+ var availability [5]string
+ // Working days - Monday through Friday
+ //for d := 0; d < 5; d++ {
+ for _, d := range []int{1,2,3,4,5} {
+ // add/remove 1 minute to make the sql time query in available() below behave correctly
+ // convert to read/compare in UTC from db
+ start := timeIt.Add(time.Hour * time.Duration((d-1)*24 + h)).Add(1 * time.Minute).UTC().Format(sqlDateFmt)
+ end := timeIt.Add(time.Hour * time.Duration((d-1)*24 + h+1)).Add(-1 * time.Minute).UTC().Format(sqlDateFmt)
+ avi, err := s.available(start, end)
+ if err != nil {
+ log.Printf("Error getting availability on day '%d' hour '%d'\n", d, h)
+ return
+ }
+ if avi {
+ availability[d-1] = ""
+ } else {
+ availability[d-1] = "X"
+ }
+ //timeIt = timeIt.Add(time.Hour * 24)
+ }
+ tableData.Rows[h-8] = []string{
+ fmt.Sprintf("%02d:00 - %02d:00", h, h+1),
+ availability[0],
+ availability[1],
+ availability[2],
+ availability[3],
+ availability[4],
+ }
+ //timeIt = timeIt.Add(time.Hour)
+ }
+
if strings.Contains(userAgent, "curl") {
w.Header().Set("Content-Type", "text/plain")
io.WriteString(w, fmt.Sprintf("Serving week %d of year %d\n", week, year))
- // TODO: Make heading date format configurable, start of week, etc..
- pt := prettytable.New([]string{
- "Time ",
- fmt.Sprintf("Mon %s", monday.Format("02.01.")),
- fmt.Sprintf("Tue %s", monday.Add(time.Hour).Format("02.01.")),
- fmt.Sprintf("Wed %s", monday.Add(time.Hour*24*2).Format("02.01.")),
- fmt.Sprintf("Thu %s", monday.Add(time.Hour*24*3).Format("02.01.")),
- fmt.Sprintf("Fri %s", monday.Add(time.Hour*24*4).Format("02.01.")),
- })
-
- monTime:= dayOfISOWeek(1, week, year)
- // TODO: use timeIt to go through the loops below, remove the static arrays
- timeIt := time.Date(monTime.Year(), monTime.Month(), monTime.Day(), 0, 0, 0, 0, localLocation)
- //dayEnd := time.Date(monTime.Year(), monTime.Month(), monTime.Day(), 17, 0, 0, 0, localLocation)
- //weekEnd := dayEnd.Add(time.Hour * 24*5)
-
- // Working hours - Eight to Five
- //for h := 8; h < 17; h++ {
- for _, h := range []int{8,9,10,11,12,13,14,15,16} {
- var availability [5]string
- // Working days - Monday through Friday
- //for d := 0; d < 5; d++ {
- for _, d := range []int{1,2,3,4,5} {
- // add/remove 1 minute to make the sql time query in available() below behave correctly
- // convert to read/compare in UTC from db
- start := timeIt.Add(time.Hour * time.Duration((d-1)*24 + h)).Add(1 * time.Minute).UTC().Format(sqlDateFmt)
- end := timeIt.Add(time.Hour * time.Duration((d-1)*24 + h+1)).Add(-1 * time.Minute).UTC().Format(sqlDateFmt)
- avi, err := s.available(start, end)
- if err != nil {
- log.Printf("Error getting availability on day '%d' hour '%d'\n", d, h)
- return
- }
- if avi {
- availability[d-1] = ""
- } else {
- availability[d-1] = "X"
- }
- //timeIt = timeIt.Add(time.Hour * 24)
+ pt := prettytable.New(tableData.TableHead)
+ for _, r := range tableData.Rows {
+ // convert to slice of interface
+ // https://go.dev/doc/faq#convert_slice_of_interface
+ s := make([]interface{}, len(r))
+ for i, v := range r {
+ s[i] = v
}
- pt.AddRow(fmt.Sprintf("%02d:00 - %02d:00", h, h+1),
- availability[0],
- availability[1],
- availability[2],
- availability[3],
- availability[4])
- //timeIt = timeIt.Add(time.Hour)
+ // unpack the string array into row arguments
+ pt.AddRow(s...)
}
- io.WriteString(w, fmt.Sprintf("HiveDAV %s\n", hivedavVersion))
io.WriteString(w, fmt.Sprint(pt))
+ io.WriteString(w, fmt.Sprintf("HiveDAV %s 🍯\n", hivedavVersion))
} else {
w.Header().Set("Content-Type", "text/html")
- io.WriteString(w, "<h1>Welcome to the hive!</h1>")
- io.WriteString(w, "<p>HTML Calendar here</p>")
+
+ tmpl, err := template.ParseFiles("./templates/index.html")
+ if err != nil {
+ log.Printf("Error parsing html template: %v\n", err)
+ w.WriteHeader(http.StatusInternalServerError)
+ // TODO: Print human readable error to w
+ return
+ }
+ err = tmpl.Execute(w, tableData)
+ if err != nil {
+ log.Printf("Error executing html template: %v\n", err)
+ w.WriteHeader(http.StatusInternalServerError)
+ // TODO: Print human readable error to w
+ return
+ }
}
}
diff --git a/templates/index.html b/templates/index.html
@@ -0,0 +1,32 @@
+<html>
+ <head>
+ <title>HiveDAV - Week {{ .Week }}, Year {{ .Year }}</title>
+ <link rel="stylesheet" href="/css/index.css">
+ <link rel="stylesheet" href="/css/style.css">
+ </head>
+ <body>
+ <p>
+ Serving week <i>{{ .Week }}</i> of year <i>{{ .Year }}</i>
+ </p>
+ <table>
+ <tr>
+ {{ range $i, $h := .TableHead }}
+ <th>{{ $h }}</th>
+ {{ end }}
+ </tr>
+ {{ range $i, $r := .Rows }}
+ <tr>
+ <td>{{ index $r 0 }}</td>
+ <td>{{ index $r 1 }}</td>
+ <td>{{ index $r 2 }}</td>
+ <td>{{ index $r 3 }}</td>
+ <td>{{ index $r 4 }}</td>
+ <td>{{ index $r 5 }}</td>
+ </tr>
+ {{ end }}
+ </table>
+ <footer>
+ HiveDAV <a href="https://code.in0rdr.ch/hivedav/refs.html">{{ .Version }}</a> 🍯
+ </footer>
+ </body>
+</html>