├── GoBaby ├── cmd └── web │ ├── domain │ ├── error │ │ └── error.domain.go │ ├── log │ │ └── log.domain.go │ ├── main │ │ ├── clock.domain.go │ │ └── main.domain.go │ ├── options │ │ └── options.domain.go │ └── repository │ │ ├── adapters │ │ ├── monitor.adapter.go │ │ └── user.adapter.go │ │ ├── config │ │ └── db.config.go │ │ └── repository.domain.go │ ├── main.go │ └── routes │ ├── error.route.go │ ├── log.route.go │ ├── mainRoute │ ├── clock.route.go │ └── main.route.go │ ├── options.route.go │ └── starter.route.go ├── documents └── entities.mkd ├── go.mod ├── go.sum ├── internal ├── models │ ├── error.model.go │ ├── log.model.go │ ├── monitor.model.go │ ├── routes.model.go │ └── user.model.go └── utils │ ├── api.go │ ├── clockUtils.go │ └── template.go ├── readme.md ├── ui ├── embed.go ├── html │ ├── base.html │ └── pages │ │ ├── error │ │ ├── clear-error.tmpl.html │ │ └── error.tmpl.html │ │ ├── logs │ │ ├── logTable.tmpl.html │ │ └── logs.tmpl.html │ │ ├── main │ │ ├── clock.tmpl.html │ │ └── main.tmpl.html │ │ └── options │ │ └── options.tmpl.html └── static │ └── css │ └── main.css └── web /GoBaby: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gentleman-Programming/GoBaby/256f89d0d926dac32e74286a0ab70f884b97c1b5/GoBaby -------------------------------------------------------------------------------- /cmd/web/domain/error/error.domain.go: -------------------------------------------------------------------------------- 1 | package errorDomain 2 | 3 | import ( 4 | repository_domain "GoBaby/cmd/web/domain/repository" 5 | "GoBaby/internal/models" 6 | "GoBaby/internal/utils" 7 | "GoBaby/ui" 8 | "fmt" 9 | "net/http" 10 | ) 11 | 12 | func setErrorHeader(w http.ResponseWriter) { 13 | w.Header().Set("HX-Retarget", "#error") 14 | w.Header().Add("HX-Reswap", "outerHTML") 15 | 16 | w.WriteHeader(http.StatusOK) 17 | } 18 | 19 | func ErrorTemplate(w http.ResponseWriter, r *http.Request, errMsg *models.AppError) { 20 | setErrorHeader(w) 21 | 22 | monitorLog := models.MonitorLog{ 23 | Err: errMsg.Err, 24 | Code: errMsg.Code, 25 | Message: errMsg.Message, 26 | Path: r.URL.Path, 27 | } 28 | 29 | repository_domain.AddMonitorLog(monitorLog) 30 | 31 | file := "html/pages/error/error.tmpl.html" 32 | 33 | context := struct{ ErrorMessage string }{ErrorMessage: fmt.Sprint(errMsg)} 34 | utils.ParseTemplateFiles(w, "error", context, utils.EmptyFuncMap, ui.Content, file) 35 | } 36 | 37 | func ClearErrorTemplate(w http.ResponseWriter, r *http.Request) { 38 | setErrorHeader(w) 39 | 40 | file := "html/pages/error/clear-error.tmpl.html" 41 | 42 | utils.ParseTemplateFiles(w, "error", utils.EmptyStruct, utils.EmptyFuncMap, ui.Content, file) 43 | } 44 | -------------------------------------------------------------------------------- /cmd/web/domain/log/log.domain.go: -------------------------------------------------------------------------------- 1 | package logDomain 2 | 3 | import ( 4 | errorDomain "GoBaby/cmd/web/domain/error" 5 | repository_domain "GoBaby/cmd/web/domain/repository" 6 | "GoBaby/internal/models" 7 | "GoBaby/internal/utils" 8 | "GoBaby/ui" 9 | "net/http" 10 | "time" 11 | 12 | "go.mongodb.org/mongo-driver/bson/primitive" 13 | ) 14 | 15 | type LogViewModel struct { 16 | Logs []models.Log 17 | } 18 | 19 | func LogTable(w http.ResponseWriter, r *http.Request) { 20 | utils.CheckIfPath(w, r, models.RoutesInstance.LOG_TABLE) 21 | 22 | user, err := repository_domain.GetUserByUUID(0) 23 | if err != nil { 24 | errorDomain.ErrorTemplate(w, r, err) 25 | } else { 26 | utils.ParseTemplateFiles(w, "logTable", user.Logs, utils.EmptyFuncMap, ui.Content, "html/pages/logs/logTable.tmpl.html") 27 | } 28 | } 29 | 30 | func LogView(w http.ResponseWriter, r *http.Request) { 31 | utils.CheckIfPath(w, r, models.RoutesInstance.LOGS) 32 | 33 | // use this utility if you are under a version lower than 1.7 34 | if utils.IsValidHTTPMethod(r.Method, utils.GET.String(), w) { 35 | files := []string{ 36 | "html/base.html", 37 | "html/pages/logs/logs.tmpl.html", 38 | } 39 | 40 | utils.ParseTemplateFiles(w, "base", utils.EmptyStruct, utils.EmptyFuncMap, ui.Content, files...) 41 | } 42 | } 43 | 44 | func SaveLog(countdown int) *models.AppError { 45 | uuid := 0 46 | currentTime := time.Now() 47 | primitiveDateTime := primitive.NewDateTimeFromTime(currentTime) 48 | err := repository_domain.AddLogByUUID(uuid, models.Log{Date: primitiveDateTime, Duration: countdown}) 49 | return err 50 | } 51 | -------------------------------------------------------------------------------- /cmd/web/domain/main/clock.domain.go: -------------------------------------------------------------------------------- 1 | package mainDomain 2 | 3 | import ( 4 | errorDomain "GoBaby/cmd/web/domain/error" 5 | logDomain "GoBaby/cmd/web/domain/log" 6 | "GoBaby/internal/models" 7 | "GoBaby/internal/utils" 8 | "GoBaby/ui" 9 | "net/http" 10 | ) 11 | 12 | var duration = 14400 13 | 14 | var clockInstance = utils.NewClock() 15 | 16 | func GetClock() *utils.Clock { 17 | return clockInstance 18 | } 19 | 20 | func GetDuration() int { 21 | return duration 22 | } 23 | 24 | func ClockFragment(w http.ResponseWriter, r *http.Request) { 25 | utils.CheckIfPath(w, r, models.RoutesInstance.CLOCK) 26 | 27 | utils.ParseTemplateFiles(w, "clock", clockInstance, utils.EmptyFuncMap, ui.Content, "html/pages/main/clock.tmpl.html") 28 | } 29 | 30 | func RestartCycle(w http.ResponseWriter, r *http.Request) { 31 | utils.CheckIfPath(w, r, models.RoutesInstance.RESTART_CYCLE) 32 | 33 | select { 34 | case <-clockInstance.Stop: // If the channel is already closed, do nothing 35 | default: 36 | err := logDomain.SaveLog(utils.FormatCountdownToTimestamp(clockInstance.CountDown)) 37 | if err != nil { 38 | errorDomain.ErrorTemplate(w, r, err) 39 | return 40 | } 41 | 42 | utils.StopCountdown(clockInstance) 43 | clockInstance.CountDown = "04:00:00" 44 | utils.SetDuration(duration) 45 | go utils.StartCountdown(clockInstance, duration) 46 | 47 | w.Write([]byte("Cycle restarted")) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /cmd/web/domain/main/main.domain.go: -------------------------------------------------------------------------------- 1 | package mainDomain 2 | 3 | import ( 4 | "GoBaby/internal/models" 5 | "GoBaby/internal/utils" 6 | "GoBaby/ui" 7 | "net/http" 8 | ) 9 | 10 | func MainView(w http.ResponseWriter, r *http.Request) { 11 | utils.CheckIfPath(w, r, models.RoutesInstance.MAIN) 12 | 13 | files := []string{ 14 | "html/base.html", 15 | "html/pages/main/main.tmpl.html", 16 | } 17 | 18 | utils.ParseTemplateFiles(w, "base", utils.EmptyStruct, utils.EmptyFuncMap, ui.Content, files...) 19 | } 20 | -------------------------------------------------------------------------------- /cmd/web/domain/options/options.domain.go: -------------------------------------------------------------------------------- 1 | package optionsDomain 2 | 3 | import ( 4 | "GoBaby/internal/models" 5 | "GoBaby/internal/utils" 6 | "net/http" 7 | ) 8 | 9 | func OptionsView(w http.ResponseWriter, r *http.Request) { 10 | utils.CheckIfPath(w, r, models.RoutesInstance.OPTIONS) 11 | 12 | w.Write([]byte("Hello, Options!")) 13 | } 14 | -------------------------------------------------------------------------------- /cmd/web/domain/repository/adapters/monitor.adapter.go: -------------------------------------------------------------------------------- 1 | package repository_adapters 2 | 3 | import ( 4 | db_config "GoBaby/cmd/web/domain/repository/config" 5 | "GoBaby/internal/models" 6 | "context" 7 | "fmt" 8 | ) 9 | 10 | func AddMonitorLog(monitorLog models.MonitorLog) *models.AppError { 11 | _, err := db_config.MonitorCollection.InsertOne(context.TODO(), monitorLog) 12 | if err != nil { 13 | return &models.AppError{ 14 | Message: "Error updating Monitor", 15 | Err: err, 16 | Code: 500, 17 | } 18 | } 19 | 20 | fmt.Println("Monitor Log added", monitorLog) 21 | return nil 22 | } 23 | -------------------------------------------------------------------------------- /cmd/web/domain/repository/adapters/user.adapter.go: -------------------------------------------------------------------------------- 1 | package repository_adapters 2 | 3 | import ( 4 | db_config "GoBaby/cmd/web/domain/repository/config" 5 | "GoBaby/internal/models" 6 | "context" 7 | 8 | "go.mongodb.org/mongo-driver/bson" 9 | ) 10 | 11 | func GetUserByUUID(uuid int) (models.User, *models.AppError) { 12 | filter := bson.D{ 13 | { 14 | Key: "$and", 15 | Value: bson.A{ 16 | bson.D{ 17 | {Key: "_id", Value: uuid}, 18 | }, 19 | }, 20 | }, 21 | } 22 | 23 | var result models.User 24 | var error *models.AppError 25 | 26 | err := db_config.UserCollection.FindOne(context.TODO(), filter).Decode(&result) 27 | if err != nil { 28 | error = &models.AppError{ 29 | Message: "failed to get user by uuid", 30 | Code: models.ErrNotFound, 31 | Err: err, 32 | } 33 | } 34 | 35 | return result, error 36 | } 37 | 38 | func SetUser(user *models.User) *models.AppError { 39 | _, err := db_config.UserCollection.InsertOne(context.TODO(), user) 40 | if err != nil { 41 | return &models.AppError{ 42 | Message: "failed to set user by uuid", 43 | Code: models.ErrInternalServer, 44 | Err: err, 45 | } 46 | } 47 | 48 | return nil 49 | } 50 | 51 | func AddLogByUUID(uuid int, log models.Log) *models.AppError { 52 | // create a filter to find the user by uuid 53 | filter := bson.M{"_id": uuid} 54 | 55 | // create an update to push the log to the logs array 56 | update := bson.M{ 57 | "$push": bson.M{ 58 | "logs": log, 59 | }, 60 | } 61 | 62 | // perform the update 63 | _, err := db_config.UserCollection.UpdateOne(context.TODO(), filter, update) 64 | if err != nil { 65 | return &models.AppError{ 66 | Message: "failed to add log by uuid", 67 | Code: models.ErrInternalServer, 68 | Err: err, 69 | } 70 | } 71 | 72 | return nil // return nothing if update is successful 73 | } 74 | -------------------------------------------------------------------------------- /cmd/web/domain/repository/config/db.config.go: -------------------------------------------------------------------------------- 1 | package db_config 2 | 3 | import ( 4 | "GoBaby/internal/models" 5 | "context" 6 | "log/slog" 7 | 8 | "go.mongodb.org/mongo-driver/bson" 9 | "go.mongodb.org/mongo-driver/mongo" 10 | "go.mongodb.org/mongo-driver/mongo/options" 11 | ) 12 | 13 | var database = "GoBaby" 14 | 15 | var collections = map[string]string{ 16 | "users": "users", 17 | "monitor": "monitor", 18 | } 19 | 20 | var ( 21 | UserCollection *mongo.Collection 22 | MonitorCollection *mongo.Collection 23 | ) 24 | 25 | func InitializeUsersCollection(client *mongo.Client) *mongo.Collection { 26 | return client.Database(database).Collection(collections["users"]) 27 | } 28 | 29 | func InitializeMonitorCollection(client *mongo.Client) *mongo.Collection { 30 | return client.Database(database).Collection(collections["monitor"]) 31 | } 32 | 33 | func InitializeLogsCollection(client *mongo.Client) *mongo.Collection { 34 | return client.Database(database).Collection(collections["logs"]) 35 | } 36 | 37 | func InitializeDb() (*mongo.Client, *models.AppError) { 38 | client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI("mongodb://localhost:27017")) 39 | if err != nil { 40 | return nil, &models.AppError{ 41 | Message: "failed to connect to MongoDB", 42 | Code: 500, 43 | Err: err, 44 | } 45 | } 46 | 47 | UserCollection = InitializeUsersCollection(client) 48 | MonitorCollection = InitializeMonitorCollection(client) 49 | 50 | // check if the user with _id equal to 0 exists 51 | result := UserCollection.FindOne(context.TODO(), bson.M{"_id": 0}) 52 | 53 | if err := result.Err(); err != nil { 54 | if err == mongo.ErrNoDocuments { 55 | // if there is an error that means that the user has not been found so we can create a new one 56 | user := models.User{UserName: "test", Logs: make([]models.Log, 0), Id: 0} 57 | 58 | _, err := UserCollection.InsertOne(context.TODO(), user) 59 | if err != nil { 60 | return nil, &models.AppError{ 61 | Message: "failed to create user with _id 0", 62 | Code: 500, 63 | Err: err, 64 | } 65 | } 66 | } else { 67 | // unexpected error while querying MongoDB 68 | return nil, &models.AppError{ 69 | Message: "error querying MongoDB for user with _id 0", 70 | Code: 500, 71 | Err: err, 72 | } 73 | } 74 | } else { 75 | slog.Info("User with _id 0 exists") 76 | } 77 | 78 | slog.Info("DB STARTED") 79 | return client, nil 80 | } 81 | -------------------------------------------------------------------------------- /cmd/web/domain/repository/repository.domain.go: -------------------------------------------------------------------------------- 1 | package repository_domain 2 | 3 | import ( 4 | repository_adapters "GoBaby/cmd/web/domain/repository/adapters" 5 | db_config "GoBaby/cmd/web/domain/repository/config" 6 | "GoBaby/internal/models" 7 | "log/slog" 8 | ) 9 | 10 | func InitializeBD() { 11 | _, err := db_config.InitializeDb() 12 | if err != nil { 13 | slog.Error(err.Message) 14 | 15 | // we want it to panic if the database is not initialized as it is crucial for the application 16 | panic(err.Err) 17 | } 18 | } 19 | 20 | func GetUserByUUID(uuid int) (models.User, *models.AppError) { 21 | return repository_adapters.GetUserByUUID(uuid) 22 | } 23 | 24 | func SetUser(user *models.User) *models.AppError { 25 | return repository_adapters.SetUser(user) 26 | } 27 | 28 | func AddLogByUUID(uuid int, log models.Log) *models.AppError { 29 | return repository_adapters.AddLogByUUID(uuid, log) 30 | } 31 | 32 | func AddMonitorLog(monitorLog models.MonitorLog) { 33 | err := repository_adapters.AddMonitorLog(monitorLog) 34 | if err != nil { 35 | slog.Error(err.Message) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cmd/web/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | repository_domain "GoBaby/cmd/web/domain/repository" 5 | "GoBaby/cmd/web/routes" 6 | "GoBaby/cmd/web/routes/mainRoute" 7 | "context" 8 | "log" 9 | "log/slog" 10 | "net/http" 11 | ) 12 | 13 | func InitRoutes() { 14 | routes.OptionsRender() 15 | routes.LogRender() 16 | routes.ErrorRender() 17 | mainRoute.MainRender() 18 | mainRoute.ClockRender() 19 | } 20 | 21 | func main() { 22 | // init routes and database 23 | InitRoutes() 24 | repository_domain.InitializeBD() 25 | 26 | // serve static files 27 | mux := routes.GetMuxInstance() 28 | fileServer := routes.GetFileServerInstance() 29 | mux.Handle("GET /static/", http.StripPrefix("/static", fileServer)) 30 | 31 | // start server 32 | slog.Log(context.TODO(), slog.LevelInfo, "Starring server on :4000") 33 | err := http.ListenAndServe(":4000", routes.GetMuxInstance()) 34 | if err != nil { 35 | log.Fatal(err) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cmd/web/routes/error.route.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | errorDomain "GoBaby/cmd/web/domain/error" 5 | "GoBaby/internal/models" 6 | ) 7 | 8 | func ErrorRender() { 9 | mux.HandleFunc("GET "+models.RoutesInstance.CLEAR_ERROR, errorDomain.ClearErrorTemplate) 10 | } 11 | -------------------------------------------------------------------------------- /cmd/web/routes/log.route.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | logDomain "GoBaby/cmd/web/domain/log" 5 | "GoBaby/internal/models" 6 | ) 7 | 8 | func LogRender() { 9 | mux.HandleFunc("GET "+models.RoutesInstance.LOG_TABLE, logDomain.LogTable) 10 | mux.HandleFunc("GET "+models.RoutesInstance.LOGS, logDomain.LogView) 11 | } 12 | -------------------------------------------------------------------------------- /cmd/web/routes/mainRoute/clock.route.go: -------------------------------------------------------------------------------- 1 | package mainRoute 2 | 3 | import ( 4 | mainDomain "GoBaby/cmd/web/domain/main" 5 | "GoBaby/cmd/web/routes" 6 | "GoBaby/internal/models" 7 | "GoBaby/internal/utils" 8 | ) 9 | 10 | func ClockRender() { 11 | duration := mainDomain.GetDuration() 12 | clock := mainDomain.GetClock() 13 | clockFragment := mainDomain.ClockFragment 14 | restartCycle := mainDomain.RestartCycle 15 | 16 | utils.SetDuration(duration) 17 | go utils.StartCountdown(clock, duration) 18 | 19 | // Routes 20 | routes.GetMuxInstance().HandleFunc("GET "+models.RoutesInstance.CLOCK, clockFragment) 21 | routes.GetMuxInstance().HandleFunc("POST "+models.RoutesInstance.RESTART_CYCLE, restartCycle) 22 | } 23 | -------------------------------------------------------------------------------- /cmd/web/routes/mainRoute/main.route.go: -------------------------------------------------------------------------------- 1 | package mainRoute 2 | 3 | import ( 4 | mainDomain "GoBaby/cmd/web/domain/main" 5 | "GoBaby/cmd/web/routes" 6 | "GoBaby/internal/models" 7 | ) 8 | 9 | func MainRender() { 10 | routes.GetMuxInstance().HandleFunc("GET "+models.RoutesInstance.MAIN, mainDomain.MainView) 11 | } 12 | -------------------------------------------------------------------------------- /cmd/web/routes/options.route.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | optionsDomain "GoBaby/cmd/web/domain/options" 5 | "GoBaby/internal/models" 6 | ) 7 | 8 | func OptionsRender() { 9 | mux.HandleFunc("GET "+models.RoutesInstance.OPTIONS, optionsDomain.OptionsView) 10 | } 11 | -------------------------------------------------------------------------------- /cmd/web/routes/starter.route.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "io/fs" 5 | "net/http" 6 | 7 | "GoBaby/ui" 8 | ) 9 | 10 | var ( 11 | mux = http.NewServeMux() 12 | fileServer http.Handler 13 | ) 14 | 15 | func GetMuxInstance() *http.ServeMux { 16 | return mux 17 | } 18 | 19 | func GetFileServerInstance() http.Handler { 20 | if fileServer == nil { 21 | staticFS, _ := fs.Sub(ui.Content, "static") 22 | fileServer = http.FileServerFS(staticFS) 23 | } 24 | 25 | return fileServer 26 | } 27 | -------------------------------------------------------------------------------- /documents/entities.mkd: -------------------------------------------------------------------------------- 1 | --- 2 | title: User 3 | description: This is the User entity 4 | --- 5 | 6 | ```mermaid 7 | classDiagram 8 | USER <|-- LOG 9 | USER : +int Id 10 | USER : +String UserName 11 | USER: +LOG Logs 12 | 13 | class LOG{ 14 | +String IdLog 15 | +Date Date 16 | +int Duration 17 | } 18 | ``` 19 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module GoBaby 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/golang/snappy v0.0.1 // indirect 7 | github.com/klauspost/compress v1.13.6 // indirect 8 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect 9 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect 10 | github.com/xdg-go/scram v1.1.2 // indirect 11 | github.com/xdg-go/stringprep v1.0.4 // indirect 12 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect 13 | go.mongodb.org/mongo-driver v1.15.0 // indirect 14 | golang.org/x/crypto v0.17.0 // indirect 15 | golang.org/x/sync v0.1.0 // indirect 16 | golang.org/x/text v0.14.0 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= 2 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 3 | github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= 4 | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 5 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= 6 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= 7 | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= 8 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= 9 | github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= 10 | github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= 11 | github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= 12 | github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= 13 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= 14 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= 15 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 16 | go.mongodb.org/mongo-driver v1.15.0 h1:rJCKC8eEliewXjZGf0ddURtl7tTVy1TK3bfl0gkUSLc= 17 | go.mongodb.org/mongo-driver v1.15.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= 18 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 19 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 20 | golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= 21 | golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= 22 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 23 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 24 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 25 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 26 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 27 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 28 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= 29 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 30 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 31 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 32 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 33 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 34 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 35 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 36 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 37 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 38 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 39 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 40 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 41 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 42 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 43 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 44 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 45 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 46 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 47 | -------------------------------------------------------------------------------- /internal/models/error.model.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | type AppError struct { 9 | Err error 10 | Message string 11 | Code int 12 | } 13 | 14 | func (e *AppError) String() string { 15 | return fmt.Sprintf("error: %s (code: %d)", e.Message, e.Code) 16 | } 17 | 18 | // Custom error codes 19 | 20 | const ( 21 | ErrBadRequest = http.StatusBadRequest 22 | ErrUnauthorized = http.StatusUnauthorized 23 | ErrForbidden = http.StatusForbidden 24 | ErrNotFound = http.StatusNotFound 25 | ErrConflict = http.StatusConflict 26 | ErrInternalServer = http.StatusInternalServerError 27 | ErrServiceUnavailable = http.StatusServiceUnavailable 28 | ErrGatewayTimeout = http.StatusGatewayTimeout 29 | ) 30 | -------------------------------------------------------------------------------- /internal/models/log.model.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "GoBaby/internal/utils" 5 | 6 | "go.mongodb.org/mongo-driver/bson/primitive" 7 | ) 8 | 9 | type Log struct { 10 | Date primitive.DateTime `bson:"date"` 11 | Duration int `bson:"duration"` // Duration of the intake 12 | } 13 | 14 | func (c Log) FormatDuration(duration int) string { 15 | return utils.FormatDuration(duration) 16 | } 17 | 18 | func (c Log) FormatPrimitiveDateTime(date primitive.DateTime) string { 19 | return utils.FormatPrimitiveDate(date) 20 | } 21 | -------------------------------------------------------------------------------- /internal/models/monitor.model.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type MonitorLog struct { 4 | Err error 5 | Id string `bson:"_id omitempty"` 6 | Message string 7 | Path string 8 | Code int 9 | } 10 | -------------------------------------------------------------------------------- /internal/models/routes.model.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type Routes struct { 4 | MAIN string 5 | LOGS string 6 | OPTIONS string 7 | CLOCK string 8 | RESTART_CYCLE string 9 | LOG_TABLE string 10 | ERROR string 11 | CLEAR_ERROR string 12 | } 13 | 14 | var RoutesInstance = Routes{ 15 | MAIN: "/", 16 | LOGS: "/logs", 17 | OPTIONS: "/options", 18 | CLOCK: "/clock", 19 | RESTART_CYCLE: "/clock/restart-cycle", 20 | LOG_TABLE: "/log-table", 21 | ERROR: "/error", 22 | CLEAR_ERROR: "/clear-error", 23 | } 24 | -------------------------------------------------------------------------------- /internal/models/user.model.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type User struct { 4 | UserName string `bson:"username"` 5 | Logs []Log `bson:"logs"` 6 | Id int `bson:"_id, omitempty"` 7 | } 8 | -------------------------------------------------------------------------------- /internal/utils/api.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | // HTTPMethod represents an enumeration of HTTP methods 9 | type HTTPMethod int 10 | 11 | const ( 12 | // GET HTTP method 13 | GET HTTPMethod = iota 14 | // POST HTTP method 15 | POST 16 | // PUT HTTP method 17 | PUT 18 | // Other methods can be added similarly 19 | ) 20 | 21 | // String returns the string representation of the HTTPMethod 22 | func (m HTTPMethod) String() string { 23 | switch m { 24 | case GET: 25 | return http.MethodGet 26 | case POST: 27 | return http.MethodPost 28 | case PUT: 29 | return http.MethodPut 30 | default: 31 | return "" 32 | } 33 | } 34 | 35 | func IsValidHTTPMethod(method string, acceptedMethod string, w http.ResponseWriter) bool { 36 | if method == acceptedMethod { 37 | w.Header().Set("Allow", acceptedMethod) 38 | return true 39 | } 40 | 41 | http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) 42 | 43 | return false 44 | } 45 | 46 | var acceptedMethods = []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete} 47 | 48 | func CheckIfPath(w http.ResponseWriter, r *http.Request, path string) { 49 | if r.URL.Path != path { 50 | fmt.Println("error: "+r.URL.Path, path) 51 | http.NotFound(w, r) 52 | return 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /internal/utils/clockUtils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | "sync" 8 | "time" 9 | 10 | "go.mongodb.org/mongo-driver/bson/primitive" 11 | ) 12 | 13 | type Clock struct { 14 | Stop chan struct{} 15 | CountDown string 16 | mu sync.Mutex 17 | running bool 18 | } 19 | 20 | var currentDuration = 0 21 | 22 | func SetDuration(duration int) { 23 | currentDuration = duration 24 | } 25 | 26 | func FormatCountdownToTimestamp(countdown string) int { 27 | parts := strings.Split(countdown, ":") 28 | if len(parts) != 3 { 29 | return 0 30 | } 31 | 32 | hours, err := strconv.Atoi(parts[0]) 33 | if err != nil { 34 | return 0 35 | } 36 | minutes, err := strconv.Atoi(parts[1]) 37 | if err != nil { 38 | return 0 39 | } 40 | seconds, err := strconv.Atoi(parts[2]) 41 | if err != nil { 42 | return 0 43 | } 44 | 45 | totalSeconds := hours*3600 + minutes*60 + seconds 46 | 47 | return totalSeconds 48 | } 49 | 50 | func FormatDuration(seconds int) string { 51 | hours := seconds / 3600 52 | minutes := (seconds % 3600) / 60 53 | seconds = seconds % 60 54 | return fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) 55 | } 56 | 57 | func FormatPrimitiveDate(dateTime primitive.DateTime) string { 58 | date := time.Unix(0, int64(dateTime)*int64(time.Millisecond)) 59 | return date.Format("2006-01-02") 60 | } 61 | 62 | func NewClock() *Clock { 63 | return &Clock{ 64 | Stop: make(chan struct{}, 1), // Channel for stop signal (buffered to prevent blocking) 65 | CountDown: "04:00:00", 66 | mu: sync.Mutex{}, 67 | } 68 | } 69 | 70 | func StartCountdown(clock *Clock, duration int) { 71 | clock.mu.Lock() 72 | defer clock.mu.Unlock() 73 | 74 | if clock.running { 75 | fmt.Println("Clock already running") 76 | return 77 | } 78 | 79 | clock.running = true 80 | 81 | ticker := time.NewTicker(1 * time.Second) 82 | defer ticker.Stop() 83 | 84 | clock.Stop = make(chan struct{}) 85 | 86 | for range ticker.C { 87 | clock.CountDown = FormatDuration(currentDuration) 88 | 89 | currentDuration-- 90 | 91 | if currentDuration < 0 { 92 | currentDuration = duration // Restart countdown 93 | } 94 | 95 | select { 96 | case <-clock.Stop: 97 | clock.running = false 98 | return 99 | default: 100 | } 101 | } 102 | } 103 | 104 | func StopCountdown(clock *Clock) { 105 | clock.Stop <- struct{}{} 106 | } 107 | -------------------------------------------------------------------------------- /internal/utils/template.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "embed" 5 | "fmt" 6 | "html/template" 7 | "net/http" 8 | ) 9 | 10 | func ParseTemplateFiles(w http.ResponseWriter, templateName string, context any, funcToTemplate template.FuncMap, embedFS embed.FS, files ...string) { 11 | ts, er := template.ParseFS(embedFS, files...) 12 | if er != nil { 13 | http.Error(w, "Internal server error", http.StatusInternalServerError) 14 | fmt.Println("Error parsing template files: ", er) 15 | return 16 | } 17 | 18 | if funcToTemplate != nil { 19 | ts = ts.Funcs(funcToTemplate) 20 | } 21 | 22 | err := ts.ExecuteTemplate(w, templateName, context) 23 | if err != nil { 24 | http.Error(w, "Internal server error", http.StatusInternalServerError) 25 | fmt.Println("Error executing template files: ", err) 26 | } 27 | } 28 | 29 | func TransformToTemplateFuncMap(key string, f interface{}) template.FuncMap { 30 | return template.FuncMap{ 31 | key: f, 32 | } 33 | } 34 | 35 | var ( 36 | EmptyFuncMap = template.FuncMap{} 37 | EmptyStruct = struct{}{} 38 | ) 39 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # GoBaby App 2 | 3 | GoBaby is a Go application designed to help parents track feeding times for newborn babies. The application utilizes Clean Architecture principles to ensure separation of concerns, maintainability, and testability. 4 | 5 | ## Main Features 6 | 7 | - _Countdown Timer_: The app starts a countdown timer set to 4 hours, simulating the interval between feedings for a newborn baby. 8 | - _Reset Button_: Users can reset the countdown timer with a button click. When the timer is reset, the app logs the reset time along with the date. 9 | - _Logging_: The application logs each feeding time, including the time at which the feeding occurred and the date. 10 | - _Log Viewer_: Users can view a log of all feeding times in a table format. 11 | 12 | ## Folder Structure 13 | 14 | The folder structure follows Clean Architecture principles, separating concerns into layers: 15 | 16 | - _cmd_: Contains the main application entry point (main.go) and related documents. 17 | - _internal_: Houses the core business logic of the application. 18 | - _models_: Defines data models such as logs and routes. 19 | - _utils_: Provides utility functions like API helpers and clock utilities. 20 | - _web_: Implements the web interface and handles HTTP requests. 21 | - _domain_: Contains domain-specific logic. 22 | - _error_: Handles error handling functionality, including rendering error templates. 23 | - _log_: Handles logging functionality. 24 | - _main_: Manages the main application logic, including the countdown timer. 25 | - _options_: Handles application options and configurations. 26 | - _repository_: Implements data access logic using adapters and configurations. 27 | - _routes_: Defines HTTP routes for different functionalities. 28 | - _ui_: Contains HTML templates and static assets for the user interface. 29 | - _html_: Houses HTML pages for different parts of the application. 30 | - _pages_: Includes pages for logs, main functionality, options, and error handling. 31 | - _static_: Stores CSS stylesheets and other static assets. 32 | 33 | ```mermaid 34 | graph LR; 35 | A[~/work/GoBaby] 36 | 37 | A --> documents 38 | documents 39 | documents --> entities.mkd 40 | documents --> structure.mkd 41 | 42 | A --> GoBaby 43 | A --> go.mod 44 | A --> go.sum 45 | A --> cmd 46 | 47 | cmd --> web 48 | web --> domain 49 | domain --> error_domain 50 | error_domain --> error.domain.go 51 | domain --> log_domain 52 | log_domain --> log.domain.go 53 | domain --> main_domain 54 | main_domain --> clock.domain.go 55 | main_domain --> main.domain.go 56 | domain --> options_domain 57 | options_domain --> options.domain.go 58 | domain --> repository 59 | repository --> adapters 60 | adapters --> user.adapter.go 61 | adapters --> monitor.adapter.go 62 | repository --> config 63 | config --> db.config.go 64 | repository --> repository.domain.go 65 | web --> routes 66 | routes --> mainRoute 67 | mainRoute --> clock.route.go 68 | mainRoute --> main.route.go 69 | routes --> log.route.go 70 | routes --> options.route.go 71 | routes --> starter.route.go 72 | routes --> error.route.go 73 | web --> main.go 74 | 75 | cmd --> internal 76 | internal --> models 77 | models --> log.model.go 78 | models --> routes.model.go 79 | models --> user.model.go 80 | models --> error.model.go 81 | models --> monitor.model.go 82 | 83 | internal --> utils 84 | utils --> api.go 85 | utils --> clockUtils.go 86 | utils --> template.go 87 | 88 | cmd --> ui 89 | ui --> html 90 | html --> pages 91 | pages --> logs 92 | logs --> logTable.tpml.html 93 | logs --> logs.tmpl.html 94 | pages --> main 95 | main --> clock.tmpl.html 96 | main --> main.tmpl.html 97 | pages --> options 98 | options --> options.tmpl.html 99 | pages --> error 100 | error --> error.tmpl.html 101 | error --> clear-error.tmpl.html 102 | html --> base.html 103 | ui --> static 104 | static --> css 105 | css --> main.css 106 | ``` 107 | 108 | ## Clean Architecture 109 | 110 | GoBaby follows Clean Architecture principles to achieve a modular and maintainable codebase. The application is structured in layers, with dependencies flowing inward toward the core business logic. This architecture allows for easy testing, scalability, and flexibility in adapting to future requirements. 111 | 112 | ## Installation and Usage 113 | 114 | To run the application, ensure you have Go installed on your machine. Clone the repository and navigate to the cmd/web directory. Run the main.go file to start the application. Access the application through a web browser at the specified port. 115 | 116 | ## Contributing 117 | 118 | Contributions to GoBaby are welcome! Feel free to submit bug reports, feature requests, or pull requests through GitHub. 119 | 120 | ## Organizing Files Based on Logic 121 | 122 | To maintain a structured and organized codebase, it's essential to follow a consistent approach when adding new files. This section outlines guidelines for organizing files based on the type of logic they represent within the application. 123 | 124 | ### Domain Logic 125 | 126 | Domain logic represents the core business rules and operations of the application. When adding files related to domain logic, follow these guidelines: 127 | 128 | - _Folder_: Place domain logic files under the web/domain directory. 129 | - _Naming_: Use descriptive names for files and directories that reflect the specific domain aspect they address (e.g., log, main, options). 130 | - _Responsibilities_: Each domain module should encapsulate related functionality, such as handling logs, managing main application logic, or dealing with application options. 131 | 132 | ### Data Access Logic 133 | 134 | Data access logic handles interactions with databases, external APIs, or other data sources. Follow these guidelines when adding files related to data access: 135 | 136 | - _Folder_: Place data access logic files under the web/domain/repository directory. 137 | - _Adapters_: Use the adapters directory to store data access adapters, which encapsulate interactions with external data sources. 138 | - _Configurations_: Store database configurations and other data access configurations under the config directory. 139 | - _Responsibilities_: Separate data access logic based on the type of data source or functionality it addresses. 140 | 141 | ### User Interface Logic 142 | 143 | User interface logic manages the presentation layer of the application, including HTML templates, CSS stylesheets, and static assets. Here's how to organize UI-related files: 144 | 145 | - _Folder_: Place UI-related files under 146 | 147 | the ui directory. 148 | 149 | - _HTML Templates_: Store HTML templates under the html/pages directory, organized by different application views (e.g., logs, main, options). 150 | - _Static Assets_: Store CSS stylesheets and other static assets under the static/css directory. 151 | 152 | ### Internal Logic and Utilities 153 | 154 | Internal logic and utility functions support the overall operation of the application but are not directly tied to domain-specific or user interface logic. Follow these guidelines for organizing internal logic and utilities: 155 | 156 | - _Folder_: Place internal logic and utility files under the internal directory. 157 | - _Models_: Define data models and structures under the internal/models directory. 158 | - _Utilities_: Store utility functions, such as API helpers and clock utilities, under the internal/utils directory. 159 | 160 | By following these guidelines, developers can ensure a consistent and structured approach to organizing files within the GoBaby application, promoting clarity, maintainability, and collaboration across the development team. 161 | 162 | ## Managing Errors 163 | 164 | Proper error handling is crucial for maintaining a robust and reliable application. GoBaby adopts a centralized error management approach to ensure consistent error handling throughout the application. Here's how error management is implemented: 165 | 166 | ### Error Domain 167 | 168 | The errorDomain package contains functionality related to error handling, including rendering error templates. Error-related logic is encapsulated within this domain to keep the code organized and maintainable. 169 | 170 | ### Error Rendering 171 | 172 | The ErrorTemplate function in the errorDomain package is responsible for rendering error templates. When an error occurs, this function is called to display the appropriate error message to the user. 173 | 174 | ### Error Models 175 | 176 | The AppError struct in the models package defines the structure of application errors. Each error includes a message, code, and underlying error, providing comprehensive information for debugging and troubleshooting. 177 | 178 | ### Error Codes 179 | 180 | GoBaby defines custom error codes to categorize different types of errors that may occur within the application. These codes help developers identify the nature of the error and take appropriate action to resolve it. 181 | 182 | ### Error Handling in Application Logic 183 | 184 | Throughout the application logic, errors are handled using the AppError struct and associated error codes. When an error occurs, it is wrapped in an AppError instance and returned to the caller, ensuring consistent error propagation. 185 | 186 | By following these error management practices, GoBaby maintains a reliable and user-friendly error handling system, enhancing the overall robustness and usability of the application. 187 | -------------------------------------------------------------------------------- /ui/embed.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import "embed" 4 | 5 | //go:embed html/* static/* 6 | var Content embed.FS 7 | -------------------------------------------------------------------------------- /ui/html/base.html: -------------------------------------------------------------------------------- 1 | {{define "base"}} 2 | 3 | 4 | 5 | 6 | 7 | Go Baby 8 | 9 | 14 | 15 | 16 |
17 |
18 |
{{template "main" .}}
19 |
20 |
21 |
22 | 23 | 24 | {{end}} 25 | -------------------------------------------------------------------------------- /ui/html/pages/error/clear-error.tmpl.html: -------------------------------------------------------------------------------- 1 | {{define "error"}} 2 |
3 |
4 | {{end}} 5 | -------------------------------------------------------------------------------- /ui/html/pages/error/error.tmpl.html: -------------------------------------------------------------------------------- 1 | {{define "error"}} 2 |
3 |

Oh no ! an error has ocurred

4 | 5 |
6 |

{{.ErrorMessage}}

7 |
8 |
9 | {{end}} 10 | -------------------------------------------------------------------------------- /ui/html/pages/logs/logTable.tmpl.html: -------------------------------------------------------------------------------- 1 | {{define "logTable"}} 2 | 3 | 4 | Date 5 | Duration 6 | 7 | 8 | 9 | {{range .}} 10 | 11 | {{.FormatPrimitiveDateTime .Date}} 12 | {{.FormatDuration .Duration }} 13 | 14 | 15 | {{end}} {{end}} 16 | -------------------------------------------------------------------------------- /ui/html/pages/logs/logs.tmpl.html: -------------------------------------------------------------------------------- 1 | {{define "main"}} 2 |

Logs

3 | 4 |
10 | 11 | 12 | 13 | 14 | {{end}} 15 | -------------------------------------------------------------------------------- /ui/html/pages/main/clock.tmpl.html: -------------------------------------------------------------------------------- 1 | {{define "clock"}} 2 | {{.CountDown}} 3 | {{end}} 4 | -------------------------------------------------------------------------------- /ui/html/pages/main/main.tmpl.html: -------------------------------------------------------------------------------- 1 | {{define "main"}} 2 |

Go Baby!

3 |
4 | 5 | 6 | 7 | 10 | {{end}} 11 | -------------------------------------------------------------------------------- /ui/html/pages/options/options.tmpl.html: -------------------------------------------------------------------------------- 1 | {{define "main"}} 2 |

This is the options template !

3 | {{end}} 4 | -------------------------------------------------------------------------------- /ui/static/css/main.css: -------------------------------------------------------------------------------- 1 | /* Structure */ 2 | 3 | body { 4 | margin: 0; 5 | padding: 0; 6 | font-family: "Hack", monospace; 7 | font-weight: bold; 8 | letter-spacing: 0.125rem; 9 | background-color: #000; 10 | color: #fff; 11 | display: flex; 12 | justify-content: center; 13 | align-items: center; 14 | min-height: 100vh; 15 | font-size: 16px; 16 | } 17 | 18 | .main-container { 19 | display: flex; 20 | gap: 1rem; 21 | flex-flow: column; 22 | } 23 | 24 | .container { 25 | background-color: #222; 26 | border: 2px solid #fff; 27 | border-radius: 0.5rem; 28 | padding: 1rem 2rem; 29 | overflow: auto; 30 | width: fit-content; 31 | margin: 0 auto; 32 | } 33 | 34 | .content { 35 | margin: 0; 36 | padding: 0; 37 | white-space: pre-wrap; 38 | user-select: none; 39 | display: flex; 40 | gap: 1rem; 41 | flex-direction: column; 42 | } 43 | 44 | #error { 45 | position: fixed; 46 | bottom: 20px; 47 | left: 50%; 48 | transform: translateX(-50%); 49 | background-color: #ff3333; 50 | color: #fff; 51 | padding: 10px 20px; 52 | border-radius: 5px; 53 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); 54 | visibility: hidden; 55 | opacity: 0; 56 | transition: visibility 0s, opacity 0.3s linear; 57 | } 58 | 59 | #error.visible { 60 | visibility: visible; 61 | opacity: 1; 62 | } 63 | 64 | /* Main View */ 65 | 66 | .title { 67 | font-size: 2.5em; 68 | margin-bottom: 0.625rem; 69 | overflow: hidden; 70 | } 71 | 72 | .link { 73 | text-decoration: none; 74 | color: inherit; 75 | cursor: pointer; 76 | } 77 | 78 | .counter { 79 | display: flex; 80 | font-size: 2.5em; 81 | font-weight: bold; 82 | letter-spacing: 0.125rem; 83 | } 84 | 85 | .counter>span { 86 | text-align: center; 87 | padding: 0 0.3125rem; 88 | } 89 | 90 | .action-button { 91 | width: 100%; 92 | display: inline-block; 93 | padding: 0.5rem 1rem; 94 | border: 2px solid #fff; 95 | border-radius: 0.5rem; 96 | cursor: pointer; 97 | transition: all 0.3s ease-in-out; 98 | background-color: #000; 99 | color: #fff; 100 | font-size: 1rem; 101 | white-space: nowrap; 102 | } 103 | 104 | .log:hover, 105 | .restart-cycle:hover { 106 | background-color: #333; 107 | } 108 | 109 | /* Log View */ 110 | table { 111 | width: 100%; 112 | border-collapse: collapse; 113 | background-color: #222; 114 | color: #fff; 115 | font-family: monospace; 116 | } 117 | 118 | th { 119 | padding: 10px; 120 | background-color: #444; 121 | border: 1px solid #111; 122 | text-align: left; 123 | } 124 | 125 | td { 126 | padding: 10px; 127 | border: 1px solid #111; 128 | } 129 | 130 | tr:nth-child(odd) { 131 | background-color: #333; 132 | } 133 | 134 | tr:nth-child(even) { 135 | background-color: #222; 136 | } 137 | 138 | /* Animations */ 139 | 140 | .pulsing { 141 | animation: pulse 1s ease-in-out infinite alternate; 142 | } 143 | 144 | @keyframes pulse { 145 | from { 146 | transform: scale(1); 147 | } 148 | 149 | to { 150 | transform: scale(1.1); 151 | } 152 | } 153 | 154 | /* Media queries for different screen sizes (optional): */ 155 | 156 | @media (max-width: 768px) { 157 | .terminal { 158 | font-size: 0.8em; 159 | } 160 | 161 | .counter { 162 | font-size: 1.5em; 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /web: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gentleman-Programming/GoBaby/256f89d0d926dac32e74286a0ab70f884b97c1b5/web --------------------------------------------------------------------------------