This commit is contained in:
commit
12fbb06666
20
.gitea/workflows/golangci-lint.yml
Normal file
20
.gitea/workflows/golangci-lint.yml
Normal file
@ -0,0 +1,20 @@
|
||||
name: golangci-lint
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
golangci:
|
||||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: 'latest'
|
30
.gitignore
vendored
Normal file
30
.gitignore
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/go
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=go
|
||||
|
||||
### Go ###
|
||||
# If you prefer the allow list template instead of the deny list, see community template:
|
||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||
#
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/go
|
||||
|
||||
config.toml
|
||||
server
|
9
assets/embed.go
Normal file
9
assets/embed.go
Normal file
@ -0,0 +1,9 @@
|
||||
package assets
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed public
|
||||
var Templates embed.FS
|
||||
|
||||
//go:embed static
|
||||
var Static embed.FS
|
13
assets/public/html/footer.tmpl
Normal file
13
assets/public/html/footer.tmpl
Normal file
@ -0,0 +1,13 @@
|
||||
{{ define "footer" }}
|
||||
<div class="footbar">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<footer class="footer full-width">
|
||||
<p class="mtop">
|
||||
<a href="#" target="_blank"></a>
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
26
assets/public/html/index.html
Normal file
26
assets/public/html/index.html
Normal file
@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script src="./static/js/htmx.org@2.0.2.js"></script>
|
||||
<!-- <link href="css/style.css" rel="stylesheet"> -->
|
||||
<title>Hello, World!</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
{{ template "navbar" }}
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<p>Content</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ template "footer" }}
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
14
assets/public/html/navbar.tmpl
Normal file
14
assets/public/html/navbar.tmpl
Normal file
@ -0,0 +1,14 @@
|
||||
{{ define "navbar" }}
|
||||
<div class="navbar">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="navigation">
|
||||
<a href="/">Logo</a>
|
||||
<ul class="navigation-items">
|
||||
<li class="navigation-item"><a class="navigation-link" href="/">Home</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
28
assets/public/html/notFound.html
Normal file
28
assets/public/html/notFound.html
Normal file
@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script src="./static/js/htmx.org@2.0.2.js"></script>
|
||||
<!-- <link href="css/style.css" rel="stylesheet"> -->
|
||||
<title>Hello, World!</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
{{ template "navbar" }}
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<h2>Not Found</h2>
|
||||
<p>Page not Found</p>
|
||||
<p>Request-ID: {{ .RequestID }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ template "footer" }}
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
1
assets/static/js/htmx.org@2.0.2.js
Normal file
1
assets/static/js/htmx.org@2.0.2.js
Normal file
File diff suppressed because one or more lines are too long
79
common/config.go
Normal file
79
common/config.go
Normal file
@ -0,0 +1,79 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"github.com/rs/zerolog"
|
||||
l "github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
ConfigPath = "./config.toml"
|
||||
DefaultLogLevel = "info"
|
||||
DefaultPort = 8080
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Log log
|
||||
Http http
|
||||
}
|
||||
|
||||
type http struct {
|
||||
Port uint16
|
||||
}
|
||||
|
||||
type log struct {
|
||||
Level string
|
||||
}
|
||||
|
||||
func defaultConfig() Config {
|
||||
return Config{
|
||||
Log: log{
|
||||
Level: DefaultLogLevel,
|
||||
},
|
||||
Http: http{
|
||||
Port: DefaultPort,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func loadConfig(path string) Config {
|
||||
logger := l.Error().Str("path", path)
|
||||
config := defaultConfig()
|
||||
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
logger.Err(err).Msg("cannot open file, using default config")
|
||||
return config
|
||||
}
|
||||
|
||||
dec := toml.NewDecoder(file)
|
||||
err = dec.Decode(&config)
|
||||
if err != nil {
|
||||
logger.Err(err).Msg("cannot parse config, using default")
|
||||
return config
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func NewConfig() Config {
|
||||
return loadConfig(ConfigPath)
|
||||
}
|
||||
|
||||
func (c *Config) LogLevel() zerolog.Level {
|
||||
switch strings.ToLower(c.Log.Level) {
|
||||
case "error":
|
||||
return zerolog.ErrorLevel
|
||||
case "debug":
|
||||
return zerolog.DebugLevel
|
||||
case "trace":
|
||||
return zerolog.TraceLevel
|
||||
case "warn":
|
||||
return zerolog.WarnLevel
|
||||
default:
|
||||
return zerolog.InfoLevel
|
||||
}
|
||||
}
|
4
example.config.toml
Normal file
4
example.config.toml
Normal file
@ -0,0 +1,4 @@
|
||||
[http]
|
||||
port = 8080
|
||||
[log]
|
||||
level = "info"
|
14
go.mod
Normal file
14
go.mod
Normal file
@ -0,0 +1,14 @@
|
||||
module git.vbrandl.net/vbrandl/go-web-template
|
||||
|
||||
go 1.22.6
|
||||
|
||||
require (
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/justinas/alice v1.2.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/rs/xid v1.5.0 // indirect
|
||||
github.com/rs/zerolog v1.33.0 // indirect
|
||||
golang.org/x/sys v0.12.0 // indirect
|
||||
)
|
22
go.sum
Normal file
22
go.sum
Normal file
@ -0,0 +1,22 @@
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo=
|
||||
github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA=
|
||||
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.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/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
77
handlers/healthcheck.go
Normal file
77
handlers/healthcheck.go
Normal file
@ -0,0 +1,77 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/hlog"
|
||||
)
|
||||
|
||||
const (
|
||||
jsonContentType = "application/json;charset=utf-8"
|
||||
)
|
||||
|
||||
type Timing struct {
|
||||
TimeMillis time.Duration `json:"timeMillis"`
|
||||
Source string `json:"source"`
|
||||
}
|
||||
|
||||
type HealthCheckResult struct {
|
||||
Success bool `json:"success"`
|
||||
Messages []string `json:"messages"`
|
||||
Time time.Time `json:"time"`
|
||||
Timing []Timing `json:"timing"`
|
||||
Response HealthCheckResponse `json:"response"`
|
||||
}
|
||||
|
||||
type HealthCheckResponse struct {
|
||||
IpAddress string `json:"ipAddress"`
|
||||
MemUsage string `json:"memUsage"`
|
||||
}
|
||||
|
||||
func (app *Application) HealthCheck(w http.ResponseWriter, r *http.Request) {
|
||||
l := hlog.FromRequest(r)
|
||||
l.Info().Msg("HealthCheck")
|
||||
start := time.Now()
|
||||
|
||||
t, _ := json.Marshal(HealthCheckResult{
|
||||
Success: true,
|
||||
Messages: []string{},
|
||||
Time: time.Now().UTC(),
|
||||
Timing: []Timing{
|
||||
{
|
||||
Source: "HealthCheck",
|
||||
TimeMillis: time.Since(start),
|
||||
},
|
||||
},
|
||||
Response: HealthCheckResponse{
|
||||
IpAddress: getIP(r),
|
||||
MemUsage: memUsage(),
|
||||
},
|
||||
})
|
||||
|
||||
w.Header().Set("content-type", jsonContentType)
|
||||
_, _ = fmt.Fprint(w, string(t))
|
||||
}
|
||||
|
||||
func getIP(r *http.Request) string {
|
||||
// forwarded := r.Header.Get("X-FORWARDED-FOR")
|
||||
// if forwarded != "" {
|
||||
// return forwarded
|
||||
// }
|
||||
return r.RemoteAddr
|
||||
}
|
||||
|
||||
func memUsage() string {
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
result := fmt.Sprintf("memoryusage::Alloc = %v MB::TotalAlloc = %v MB::Sys = %v MB::tNumGC = %v", bToMb(m.Alloc), bToMb(m.TotalAlloc), bToMb(m.Sys), m.NumGC)
|
||||
return result
|
||||
}
|
||||
|
||||
func bToMb(b uint64) uint64 {
|
||||
return b / 1024 / 1024
|
||||
}
|
22
handlers/index.go
Normal file
22
handlers/index.go
Normal file
@ -0,0 +1,22 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/rs/zerolog/hlog"
|
||||
)
|
||||
|
||||
func (app *Application) Index(w http.ResponseWriter, r *http.Request) {
|
||||
l := hlog.FromRequest(r)
|
||||
l.Info().Msg("Index")
|
||||
|
||||
reqId := RequestID(r)
|
||||
|
||||
err := indexTemplate.Execute(w, templateData{
|
||||
RequestID: reqId,
|
||||
})
|
||||
if err != nil {
|
||||
l.Error().Err(err).Msg("error executing template")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
48
handlers/index_test.go
Normal file
48
handlers/index_test.go
Normal file
@ -0,0 +1,48 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"git.vbrandl.net/vbrandl/go-web-template/assets"
|
||||
"git.vbrandl.net/vbrandl/go-web-template/common"
|
||||
"git.vbrandl.net/vbrandl/go-web-template/handlers"
|
||||
"git.vbrandl.net/vbrandl/go-web-template/service"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func PrepareApplication() handlers.Application {
|
||||
config := common.NewConfig()
|
||||
ser := service.New(config)
|
||||
zerolog.SetGlobalLevel(config.LogLevel())
|
||||
|
||||
app, err := handlers.NewApplication(ser, assets.Static)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed creating application")
|
||||
}
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
func TestIndex(t *testing.T) {
|
||||
app := PrepareApplication()
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
app.Handler().ServeHTTP(w, req)
|
||||
// app.Index(w, req)
|
||||
res := w.Result()
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != 200 {
|
||||
t.Errorf("expected status to be 200, got %d", res.StatusCode)
|
||||
}
|
||||
|
||||
reqId := res.Header.Get("x-request-id")
|
||||
if reqId == "" {
|
||||
t.Errorf("expected `x-request-id` header to be present, got %s", reqId)
|
||||
}
|
||||
}
|
23
handlers/not_found.go
Normal file
23
handlers/not_found.go
Normal file
@ -0,0 +1,23 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/rs/zerolog/hlog"
|
||||
)
|
||||
|
||||
func (app *Application) NotFound(w http.ResponseWriter, r *http.Request) {
|
||||
l := hlog.FromRequest(r)
|
||||
l.Info().Msg("Not found")
|
||||
|
||||
reqId := RequestID(r)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
|
||||
err := notFoundTemplate.Execute(w, templateData{
|
||||
RequestID: reqId,
|
||||
})
|
||||
if err != nil {
|
||||
l.Error().Err(err).Msg("error executing template")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
37
handlers/not_found_test.go
Normal file
37
handlers/not_found_test.go
Normal file
@ -0,0 +1,37 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNotFound(t *testing.T) {
|
||||
app := PrepareApplication()
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/some/invalid/path", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
app.Handler().ServeHTTP(w, req)
|
||||
res := w.Result()
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != 404 {
|
||||
t.Errorf("expected status to be 404, got %d", res.StatusCode)
|
||||
}
|
||||
|
||||
reqId := res.Header.Get("x-request-id")
|
||||
if reqId == "" {
|
||||
t.Errorf("expected `x-request-id` header to be present, got %s", reqId)
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
t.Errorf("expected error to be nil, got %v", err)
|
||||
}
|
||||
if !strings.Contains(string(data), reqId) {
|
||||
t.Errorf("expected body to contain request ID but it does not")
|
||||
}
|
||||
}
|
79
handlers/route.go
Normal file
79
handlers/route.go
Normal file
@ -0,0 +1,79 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.vbrandl.net/vbrandl/go-web-template/service"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/justinas/alice"
|
||||
"github.com/rs/zerolog/hlog"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type Application struct {
|
||||
Service *service.Service
|
||||
StaticFiles fs.FS
|
||||
}
|
||||
|
||||
func NewApplication(service *service.Service, staticFiles fs.FS) (Application, error) {
|
||||
application := Application{
|
||||
Service: service,
|
||||
StaticFiles: staticFiles,
|
||||
}
|
||||
|
||||
err := application.ParseTemplates()
|
||||
|
||||
return application, err
|
||||
}
|
||||
|
||||
func (app *Application) Routes() *mux.Router {
|
||||
router := mux.NewRouter().StrictSlash(true)
|
||||
|
||||
router.HandleFunc("/", app.Index).Methods("GET")
|
||||
router.HandleFunc("/health", app.HealthCheck).Methods("GET")
|
||||
|
||||
// strip off the location such that we route correctly
|
||||
staticContent, err := fs.Sub(app.StaticFiles, "static")
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("error with static content")
|
||||
}
|
||||
f := http.FileServerFS(staticContent)
|
||||
router.PathPrefix("/static/").Handler(http.StripPrefix("/static", f))
|
||||
|
||||
router.NotFoundHandler = http.HandlerFunc(app.NotFound)
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
func (app *Application) Handler() http.Handler {
|
||||
logger := log.Logger
|
||||
middlewares := alice.New().
|
||||
Append(hlog.NewHandler(logger)).
|
||||
Append(hlog.AccessHandler(func(r *http.Request, status int, size int, duration time.Duration) {
|
||||
hlog.FromRequest(r).Info().
|
||||
Str("method", r.Method).
|
||||
Stringer("url", r.URL).
|
||||
Int("status", status).
|
||||
Int("size", size).
|
||||
Str("remote-addr", r.RemoteAddr).
|
||||
Dur("duration", duration).
|
||||
Str("user-agent", r.UserAgent()).
|
||||
Msg("")
|
||||
})).
|
||||
Append(hlog.RequestIDHandler("request-id", "x-request-id"))
|
||||
|
||||
routes := app.Routes()
|
||||
return middlewares.Then(routes)
|
||||
}
|
||||
|
||||
func RequestID(r *http.Request) string {
|
||||
if id, ok := hlog.IDFromRequest(r); ok {
|
||||
return id.String()
|
||||
} else {
|
||||
l := hlog.FromRequest(r)
|
||||
l.Warn().Msg("No associated request ID")
|
||||
return "unknown"
|
||||
}
|
||||
}
|
30
handlers/template.go
Normal file
30
handlers/template.go
Normal file
@ -0,0 +1,30 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
|
||||
"git.vbrandl.net/vbrandl/go-web-template/assets"
|
||||
)
|
||||
|
||||
var indexTemplate *template.Template
|
||||
var notFoundTemplate *template.Template
|
||||
|
||||
type templateData struct {
|
||||
RequestID string
|
||||
}
|
||||
|
||||
func (app *Application) ParseTemplates() error {
|
||||
t, err := template.ParseFS(assets.Templates, "public/html/index.html", "public/html/navbar.tmpl", "public/html/footer.tmpl")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
indexTemplate = t
|
||||
|
||||
t, err = template.ParseFS(assets.Templates, "public/html/notFound.html", "public/html/navbar.tmpl", "public/html/footer.tmpl")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
notFoundTemplate = t
|
||||
|
||||
return nil
|
||||
}
|
36
service/service.go
Normal file
36
service/service.go
Normal file
@ -0,0 +1,36 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.vbrandl.net/vbrandl/go-web-template/common"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
BackgroundJobsStarted bool
|
||||
ServiceMutex sync.Mutex
|
||||
StartTime time.Time
|
||||
Config common.Config
|
||||
}
|
||||
|
||||
func New(config common.Config) *Service {
|
||||
ser := &Service{
|
||||
BackgroundJobsStarted: false,
|
||||
ServiceMutex: sync.Mutex{},
|
||||
StartTime: time.Now(),
|
||||
Config: config,
|
||||
}
|
||||
|
||||
ser.StartBackground()
|
||||
return ser
|
||||
}
|
||||
|
||||
func (ser *Service) StartBackground() {
|
||||
ser.ServiceMutex.Lock()
|
||||
defer ser.ServiceMutex.Unlock()
|
||||
|
||||
ser.BackgroundJobsStarted = true
|
||||
|
||||
// TODO: start jobs
|
||||
}
|
Loading…
Reference in New Issue
Block a user