diff --git a/adapters/secondary/TemplateAdapter.go b/adapters/secondary/TemplateAdapter.go new file mode 100644 index 0000000..3ee6cd6 --- /dev/null +++ b/adapters/secondary/TemplateAdapter.go @@ -0,0 +1,48 @@ +package secondary + +import ( + "bytes" + "html/template" + "os" +) + +func ProcessTemplate(html []byte, data interface{}) ([]byte, error) { + tmpl, err := template.New("template").Parse(string(html)) + if err != nil { + return nil, err + } + + var processedHTML bytes.Buffer + err = tmpl.Execute(&processedHTML, data) + if err != nil { + return nil, err + } + + return processedHTML.Bytes(), nil +} + +func GetTemplate(name string) []byte { + file, err := os.Open("./web/templates/" + name + ".html") + if err != nil { + panic(err) + } + defer func(file *os.File) { + err := file.Close() + if err != nil { + panic(err) + } + }(file) + + stat, err := file.Stat() + if err != nil { + panic(err) + } + + bs := make([]byte, stat.Size()) + _, err = file.Read(bs) + if err != nil { + panic(err) + } + + return bs +} diff --git a/adapters/secondary/gateways/userRepository.go b/adapters/secondary/gateways/userRepository.go index e89e3f8..b0380ef 100644 --- a/adapters/secondary/gateways/userRepository.go +++ b/adapters/secondary/gateways/userRepository.go @@ -4,7 +4,6 @@ import ( entity "GohCMS2/adapters/secondary/gateways/models" . "GohCMS2/domain/gateways" domain "GohCMS2/domain/user" - "fmt" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" ) @@ -33,7 +32,6 @@ func (u *UserRepository) Get(id uint32) (domain.User, error) { func (u *UserRepository) Create(user domain.User) (domain.User, error) { hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(user.Password), 14) - fmt.Println("hashedPassword at register: ", string(hashedPassword)) creationResult := u.db.Create(&entity.User{ Username: user.Username, diff --git a/api/auth.go b/api/auth.go index f9e21d2..656bfd5 100644 --- a/api/auth.go +++ b/api/auth.go @@ -37,7 +37,7 @@ func SetJwtCookie(w *http.ResponseWriter, userId uint32) error { Expires: time.Now().Add(24 * time.Hour), Secure: false, // TODO false in dev, true in prod HttpOnly: true, - Path: "/v1/", + Path: "/", }) return nil } @@ -47,7 +47,7 @@ func isAllowedToCreateUser() bool { return len(users) == 0 } -func isLoggedIn(r *http.Request) bool { +func IsLoggedIn(r *http.Request) bool { token, err := jwtauth.VerifyRequest( TokenAuth, r, @@ -91,7 +91,7 @@ func login(w http.ResponseWriter, r *http.Request) { } func register(w http.ResponseWriter, r *http.Request) { - if !isLoggedIn(r) && !isAllowedToCreateUser() { + if !IsLoggedIn(r) && !isAllowedToCreateUser() { http.Error(w, "You are not allowed to create a user. Log in or reset database.", http.StatusForbidden) return } diff --git a/api/page.go b/api/page.go new file mode 100644 index 0000000..fb202cc --- /dev/null +++ b/api/page.go @@ -0,0 +1,93 @@ +package api + +import ( + "GohCMS2/adapters/secondary" + "bytes" + "encoding/json" + "github.com/go-chi/chi/v5" + "net/http" + "strings" +) + +type LoginPage struct { + IsError bool `json:"isError"` + Error string `json:"error"` +} + +func getPage(page string, data interface{}) ([]byte, error) { + template := secondary.GetTemplate(page) + processed, err := secondary.ProcessTemplate(template, data) + if err != nil { + return nil, err + } + return processed, nil +} + +func getLoginPageHandler(errMsg string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + bs, err := getPage("login", &LoginPage{ + IsError: strings.Compare(errMsg, "") != 0, + Error: errMsg, + }) + if err != nil { + _, _ = w.Write([]byte(err.Error())) + } + _, _ = w.Write(bs) + } +} + +func postLoginPage(w http.ResponseWriter, r *http.Request) { + _ = r.ParseForm() + + credentials, err := json.Marshal(&UserLogin{ + Username: r.FormValue("username"), + Password: r.FormValue("password"), + }) + if err != nil { + getLoginPageHandler("Missing username or password.")(w, r) + return + } + + // TODO replace url with som env variable + response, err := http.Post("http://localhost:8080/v1/auth/login", "application/json", bytes.NewBuffer(credentials)) + // TODO handle possible errors in separate file (api package) + if err != nil || response.StatusCode != http.StatusOK { + getLoginPageHandler("Invalid username or password.")(w, r) + return + } + + w.Header().Set("Set-Cookie", response.Header.Get("Set-Cookie")) + + http.Redirect(w, r, "/home", http.StatusFound) +} + +func getHomePage(w http.ResponseWriter, _ *http.Request) { + bs, err := getPage("home", nil) + if err != nil { + _, _ = w.Write([]byte(err.Error())) + } + _, _ = w.Write(bs) +} + +func IsLoggedInMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !IsLoggedIn(r) { + http.Redirect(w, r, "/login", http.StatusFound) + return + } + next.ServeHTTP(w, r) + }) +} + +func NewPageRouter() http.Handler { + r := chi.NewRouter() + r.Get("/login", getLoginPageHandler("")) + r.Post("/login", postLoginPage) + + r.Group(func(r chi.Router) { + r.Use(IsLoggedInMiddleware) + r.Get("/home", getHomePage) + }) + + return r +} diff --git a/cmd/main.go b/cmd/main.go index 2e54dd7..43adcde 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -4,8 +4,9 @@ import ( "GohCMS2/api" "encoding/json" "fmt" + "github.com/MadAppGang/httplog" "github.com/go-chi/chi/v5" - "github.com/go-chi/httplog" + "github.com/go-chi/cors" "github.com/go-chi/jwtauth/v5" "net/http" ) @@ -17,6 +18,13 @@ func jsonContentTypeMiddleware(next http.Handler) http.Handler { }) } +func htmlContentTypeMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html") + next.ServeHTTP(w, r) + }) +} + func initJwt() { api.TokenAuth = jwtauth.New("HS256", []byte("secret"), nil) } @@ -26,29 +34,61 @@ func getHelloWorld(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write(msg) } -func main() { - api.InitContainer() - api.InitValidator() - initJwt() - +func initBackendRoutes() *chi.Mux { r := chi.NewRouter() - r.Get("/", getHelloWorld) + r.Use(httplog.LoggerWithName("backend")) + r.Use(jsonContentTypeMiddleware) + r.Get("/", getHelloWorld) r.Group(func(r chi.Router) { r.Use(jwtauth.Verifier(api.TokenAuth)) r.Use(jwtauth.Authenticator) r.Mount("/article", api.NewArticleRouter()) }) - r.Mount("/auth", api.NewAuthRouter()) + return r +} + +func initFrontendRoutes() *chi.Mux { + r := chi.NewRouter() + + r.Use(httplog.LoggerWithName("frontend")) + r.Use(htmlContentTypeMiddleware) + r.Mount("/", api.NewPageRouter()) + + return r +} + +func initRoutes() *chi.Mux { + backend := initBackendRoutes() + frontend := initFrontendRoutes() + apiRouter := chi.NewRouter() - apiRouter.Use(httplog.RequestLogger(httplog.NewLogger("GohCMS2"))) - apiRouter.Use(jsonContentTypeMiddleware) - apiRouter.Mount("/v1", r) + // TODO use env variable for allowed origins + apiRouter.Use(cors.Handler(cors.Options{ + AllowedOrigins: []string{"https://*", "http://*"}, + AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, + AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"}, + ExposedHeaders: []string{"Link"}, + AllowCredentials: false, + MaxAge: 300, + })) + apiRouter.Mount("/v1", backend) + apiRouter.Mount("/", frontend) + + return apiRouter +} + +func main() { + api.InitContainer() + api.InitValidator() + initJwt() + + router := initRoutes() fmt.Println("Server starting on port 8080") - err := http.ListenAndServe(":8080", apiRouter) + err := http.ListenAndServe(":8080", router) if err != nil { panic(err) } diff --git a/go.mod b/go.mod index c87ae79..a50a12f 100644 --- a/go.mod +++ b/go.mod @@ -3,23 +3,25 @@ module GohCMS2 go 1.20 require ( + github.com/MadAppGang/httplog v1.3.0 github.com/glebarez/sqlite v1.9.0 - github.com/go-chi/httplog v0.3.1 github.com/go-chi/jwtauth/v5 v5.1.1 + github.com/go-playground/validator/v10 v10.15.0 go.uber.org/dig v1.17.0 golang.org/x/crypto v0.10.0 gorm.io/gorm v1.25.2 ) require ( - github.com/ajg/form v1.5.1 // indirect + github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fatih/color v1.13.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/glebarez/go-sqlite v1.21.2 // indirect + github.com/go-chi/cors v1.2.1 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.15.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/google/uuid v1.3.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect @@ -45,6 +47,5 @@ require ( github.com/go-chi/chi/v5 v5.0.10 github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect - github.com/rs/zerolog v1.29.1 // indirect golang.org/x/sys v0.10.0 // indirect ) diff --git a/go.sum b/go.sum index 961e06c..9a9f1e1 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,7 @@ -github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= -github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/MadAppGang/httplog v1.3.0 h1:1XU54TO8kiqTeO+7oZLKAM3RP/cJ7SadzslRcKspVHo= +github.com/MadAppGang/httplog v1.3.0/go.mod h1:gpYEdkjh/Cda6YxtDy4AB7KY+fR7mb3SqBZw74A5hJ4= +github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 h1:ZBbLwSJqkHBuFDA6DUhhse0IGJ7T5bemHyNILUjvOq4= +github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2/go.mod h1:VSw57q4QFiWDbRnjdX8Cb3Ow0SFncRw+bA/ofY6Q83w= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -9,21 +10,21 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etly github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= github.com/glebarez/sqlite v1.9.0 h1:Aj6bPA12ZEx5GbSF6XADmCkYXlljPNUY+Zf1EQxynXs= github.com/glebarez/sqlite v1.9.0/go.mod h1:YBYCoyupOao60lzp1MVBLEjZfgkq0tdB1voAQ09K9zw= -github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-chi/httplog v0.3.1 h1:uC3IUWCZagtbCinb3ypFh36SEcgd6StWw2Bu0XSXRtg= -github.com/go-chi/httplog v0.3.1/go.mod h1:UoiQQ/MTZH5V6JbNB2FzF0DynTh5okpXxlhsyxoP5m8= +github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= +github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-chi/jwtauth/v5 v5.1.1 h1:Pjixqu5YkjE9sCLpzE01L0Q4sQzJIPdo7uz9r8ftp/c= github.com/go-chi/jwtauth/v5 v5.1.1/go.mod h1:CYP1WSbzD4MPuKCr537EM3kfFhSQgpUEtMJFuYJjqWU= -github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= -github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= @@ -32,10 +33,10 @@ github.com/go-playground/validator/v10 v10.15.0 h1:nDU5XeOKtB3GEa+uB7GNYwhVKsgjA github.com/go-playground/validator/v10 v10.15.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= @@ -55,22 +56,19 @@ github.com/lestrrat-go/jwx/v2 v2.0.11/go.mod h1:ZtPtMFlrfDrH2Y0iwfa3dRFn8VzwBrB+ github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc= -github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -102,10 +100,11 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/web/templates/home.html b/web/templates/home.html new file mode 100644 index 0000000..debcf06 --- /dev/null +++ b/web/templates/home.html @@ -0,0 +1,17 @@ + + +
+ + +