Skip to content

Commit

Permalink
Holy shit a kinda working UI
Browse files Browse the repository at this point in the history
  • Loading branch information
szabolcs-horvath committed Dec 30, 2024
1 parent 7e6ed5f commit c8c2fe9
Show file tree
Hide file tree
Showing 21 changed files with 702 additions and 251 deletions.
2 changes: 1 addition & 1 deletion .air.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ tmp_dir = "out"
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata", "generated"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_regex = ["_test.go", "_string.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
Expand Down
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ IT_SQLITE_DB_FILE ?= sqlite/nutrition-tracker-test.db
SQLITE_MIGRATIONS_DIR ?= sqlite/migrations
SQLC_VERSION ?= v1.27.0
GOLANG_MIGRATE_VERSION ?= v4.18.1
STRINGER_VERSION ?= v0.28.0
HTMX_VERSION ?= 2.0.3
BOOTSTRAP_VERSION ?= 5.3.3
GOCOVERDIR ?= coverage
Expand All @@ -11,16 +12,20 @@ CGO_ENABLED=1 # Required for sqlite3 driver
install-go-deps:
go install -v github.com/sqlc-dev/sqlc/cmd/sqlc@$(SQLC_VERSION)
go install -v -tags 'sqlite3' github.com/golang-migrate/migrate/v4/cmd/migrate@$(GOLANG_MIGRATE_VERSION)
go install -v golang.org/x/tools/cmd/stringer@$(STRINGER_VERSION)

init-db: migrate-up
sqlite3 $(SQLITE_DB_FILE) < sqlite/seed.sql

build: sqlc
build: sqlc generate
go build -o out/nutrition-tracker -mod=readonly

sqlc:
sqlc generate

generate:
go generate ./...

clean:
rm -rf generated out

Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ This project's purpose is to help Kinga track and plan her diet.
- :white_check_mark: [`go@1.23.1`](https://go.dev/dl/)
- :white_check_mark: [`sqlite3`](https://command-not-found.com/sqlite3)
- :white_check_mark: go dependencies
- :white_check_mark: [`sqlc`](https://docs.sqlc.dev/en/latest/overview/install.html)
- :white_check_mark: [`golang-migrate`](https://github.com/golang-migrate/migrate/tree/master)
- :white_check_mark: [`sqlc`](https://docs.sqlc.dev/en/latest/)
- :white_check_mark: [`golang-migrate`](https://github.com/golang-migrate/migrate/)
- :white_check_mark: [`stringer`](https://pkg.go.dev/golang.org/x/tools/cmd/stringer)
- Install these with:
```shell
make go-deps
Expand Down
32 changes: 24 additions & 8 deletions custom_types/date.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,27 @@ type Date struct {
_time time.Time
}

func NewDate(t time.Time) (*Date, error) {
onlyDate, err := time.Parse(time.DateOnly, t.Format(time.DateOnly))
if err != nil {
return nil, err
}
return &Date{_time: onlyDate}, nil
}

func (d Date) UnderlyingTime() time.Time {
return d._time
}

func ParseDate(str string) (*Date, error) {
t, err := time.Parse(time.DateOnly, str)
if err != nil {
return nil, err
}
return &Date{_time: t}, nil
func (d Date) Sub(arg Date) time.Duration {
return d._time.Sub(arg._time)
}

func (d Date) Equal(arg Date) bool {
return d._time.Format(time.DateOnly) == arg._time.Format(time.DateOnly)
}

func (d Date) Scan(src any) error {
func (d *Date) Scan(src any) error {
if src == nil {
return nil
}
Expand All @@ -45,6 +49,18 @@ func (d Date) Scan(src any) error {
return nil
}

func (d Date) Value() (driver.Value, error) {
func (d *Date) Value() (driver.Value, error) {
return d._time.Format(time.DateOnly), nil
}

func ParseDate(str string) (*Date, error) {
t, err := time.Parse(time.DateOnly, str)
if err != nil {
return nil, err
}
return &Date{_time: t}, nil
}

func DateDiffAbs(a, b Date) time.Duration {
return a.Sub(b).Abs()
}
33 changes: 33 additions & 0 deletions custom_types/quota.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package custom_types

import "github.com/szabolcs-horvath/nutrition-tracker/util"

//go:generate stringer -type=Quota

type Quota int64

const (
Calories Quota = iota
Fats
FatsSaturated
Carbs
CarbsSugar
CarbsSlowRelease
CarbsFastRelease
Proteins
Salt
)

var AllQuotas = []Quota{
Calories,
Fats,
FatsSaturated,
Carbs,
CarbsSugar,
CarbsSlowRelease,
CarbsFastRelease,
Proteins,
Salt,
}

var AllQuotaStrings = util.Map(AllQuotas, func(quota Quota) string { return quota.String() })
31 changes: 31 additions & 0 deletions custom_types/quota_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 25 additions & 9 deletions custom_types/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,27 @@ type Time struct {
_time time.Time
}

func (t Time) UnderlyingTime() time.Time {
return t._time
}

func ParseTime(str string) (*Time, error) {
t, err := time.Parse(time.TimeOnly, str)
func NewTime(t time.Time) (*Time, error) {
onlyTime, err := time.Parse(time.TimeOnly, t.Format(time.TimeOnly))
if err != nil {
return nil, err
}
return &Time{_time: t}, nil
return &Time{_time: onlyTime}, nil
}

func (t Time) UnderlyingTime() time.Time {
return t._time
}

func (t Time) Equal(arg Time) bool {
return t._time.Format(time.TimeOnly) == arg._time.Format(time.TimeOnly)
}

func (t Time) Scan(src any) error {
func (t Time) Sub(arg Time) time.Duration {
return t._time.Sub(arg._time)
}

func (t *Time) Scan(src any) error {
if src == nil {
return nil
}
Expand All @@ -45,6 +49,18 @@ func (t Time) Scan(src any) error {
return nil
}

func (t Time) Value() (driver.Value, error) {
func (t *Time) Value() (driver.Value, error) {
return t._time.Format(time.TimeOnly), nil
}

func ParseTime(str string) (*Time, error) {
t, err := time.Parse(time.TimeOnly, str)
if err != nil {
return nil, err
}
return &Time{_time: t}, nil
}

func TimeDiffAbs(a, b Time) time.Duration {
return a.Sub(b).Abs()
}
13 changes: 12 additions & 1 deletion http_server/routes/helpers.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package routes

import "net/http"
import (
"net/http"
"strings"
)

func SubRouteHandlerFuncs(routes map[string]http.HandlerFunc) *http.ServeMux {
mux := http.NewServeMux()
Expand All @@ -23,6 +26,14 @@ func ServeRoute(router *http.ServeMux, prefix string, routes map[string]*http.Se
router.Handle(prefix+"/", http.StripPrefix(prefix, mux))
}

func ServeRouteHandlers(router *http.ServeMux, prefix string, routes map[string]http.HandlerFunc) {
for pattern, handlerFunc := range routes {
split := strings.SplitN(pattern, " ", 2)
path := split[0] + " " + prefix + split[1]
router.HandleFunc(path, handlerFunc)
}
}

func ServeFS(router *http.ServeMux, prefix, dir string) {
router.Handle(prefix+"/", http.StripPrefix(prefix, http.FileServer(http.Dir(dir))))
}
127 changes: 127 additions & 0 deletions http_server/routes/htmx/routes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package htmx

import (
"github.com/szabolcs-horvath/nutrition-tracker/repository"
"github.com/szabolcs-horvath/nutrition-tracker/util"
"net/http"
)

const Prefix = "/htmx"

func Routes() map[string]http.HandlerFunc {
return map[string]http.HandlerFunc{
"GET /": rootHandler,
"GET /meals": mealsHandler,
"GET /notifications": notificationsHandler,
}
}
func rootHandler(w http.ResponseWriter, r *http.Request) {
dailyQuota, err := repository.FindDailyQuotaByOwnerAndCurrentDay(r.Context(), 1)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
meals, err := repository.FindMealsForUser(r.Context(), 1, false)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
mealLogs, err := repository.FindMealLogsForUserAndCurrentDay(r.Context(), 1)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
items, err := repository.ListItems(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

mealLogsByMeal := util.GroupByKeys(mealLogs, meals, func(ml *repository.MealLog) *repository.Meal {
meal, _ := util.FindFirst(meals, func(m *repository.Meal) bool { return ml.Meal.ID == m.ID })
return meal
})

data := map[string]any{
"Title": "Meals",
"TabName": "meals_tab",
"Data": map[string]any{
"DailyQuota": dailyQuota,
"Meals": meals,
"MealLogs": mealLogs,
"MealLogsByMeal": mealLogsByMeal,
"Items": items,
},
}

err = repository.Render(w, "base", data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}

func mealsHandler(w http.ResponseWriter, r *http.Request) {
dailyQuota, err := repository.FindDailyQuotaByOwnerAndCurrentDay(r.Context(), 1)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
meals, err := repository.FindMealsForUser(r.Context(), 1, false)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
mealLogs, err := repository.FindMealLogsForUserAndCurrentDay(r.Context(), 1)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
items, err := repository.ListItems(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

mealLogsByMeal := util.GroupByKeys(mealLogs, meals, func(ml *repository.MealLog) *repository.Meal {
meal, _ := util.FindFirst(meals, func(m *repository.Meal) bool { return ml.Meal.ID == m.ID })
return meal
})

data := map[string]any{
"Title": "Meals",
"TabName": "meals_tab",
"Data": map[string]any{
"DailyQuota": dailyQuota,
"Meals": meals,
"MealLogs": mealLogs,
"MealLogsByMeal": mealLogsByMeal,
"Items": items,
},
}

err = repository.Render(w, "meals_tab", data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}

func notificationsHandler(w http.ResponseWriter, r *http.Request) {
notifications, err := repository.ListNotificationsByUserId(r.Context(), 1)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

data := map[string]any{
"Title": "Notifications",
"TabName": "notifications_tab",
"Data": map[string]any{
"Notifications": notifications,
},
}

err = repository.Render(w, "notifications_tab", data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
Loading

0 comments on commit c8c2fe9

Please sign in to comment.