From 4eea7769ffd5f61150f798bbca5ece077668aca8 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Sat, 1 Oct 2022 19:37:12 +0200 Subject: [PATCH] Initial commit for the web interface --- .dockerignore | 2 + .gitignore | 4 + Dockerfile | 27 ++++++ api/routes.go | 23 +++++ app.go | 68 +++++++++++++++ config/cli.go | 45 ++++++++++ config/config.go | 29 +++++++ config/custom.go | 44 ++++++++++ config/env.go | 21 +++++ go.mod | 45 ++++++++++ go.sum | 144 +++++++++++++++++++++++++++++++ main.go | 31 +++++++ renovate.json | 10 +++ ui/.eslintrc.cjs | 20 +++++ ui/.gitignore | 8 ++ ui/.npmrc | 1 + ui/.prettierrc | 6 ++ ui/README.md | 38 ++++++++ ui/assets-dev.go | 32 +++++++ ui/assets.go | 28 ++++++ ui/package.json | 41 +++++++++ ui/routes.go | 77 +++++++++++++++++ ui/src/app.html | 19 ++++ ui/src/components/Header.svelte | 81 +++++++++++++++++ ui/src/components/Toaster.svelte | 22 +++++ ui/src/global.d.ts | 1 + ui/src/reveil.scss | 34 ++++++++ ui/src/routes/+layout.js | 2 + ui/src/routes/+layout.svelte | 34 ++++++++ ui/src/routes/+page.svelte | 48 +++++++++++ ui/src/service-worker.ts | 80 +++++++++++++++++ ui/src/stores/toasts.js | 41 +++++++++ ui/static/manifest.json | 20 +++++ ui/svelte.config.js | 20 +++++ ui/tsconfig.json | 32 +++++++ ui/vite.config.js | 8 ++ 36 files changed, 1186 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 api/routes.go create mode 100644 app.go create mode 100644 config/cli.go create mode 100644 config/config.go create mode 100644 config/custom.go create mode 100644 config/env.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 renovate.json create mode 100644 ui/.eslintrc.cjs create mode 100644 ui/.gitignore create mode 100644 ui/.npmrc create mode 100644 ui/.prettierrc create mode 100644 ui/README.md create mode 100644 ui/assets-dev.go create mode 100644 ui/assets.go create mode 100644 ui/package.json create mode 100644 ui/routes.go create mode 100644 ui/src/app.html create mode 100644 ui/src/components/Header.svelte create mode 100644 ui/src/components/Toaster.svelte create mode 100644 ui/src/global.d.ts create mode 100644 ui/src/reveil.scss create mode 100644 ui/src/routes/+layout.js create mode 100644 ui/src/routes/+layout.svelte create mode 100644 ui/src/routes/+page.svelte create mode 100644 ui/src/service-worker.ts create mode 100644 ui/src/stores/toasts.js create mode 100644 ui/static/manifest.json create mode 100644 ui/svelte.config.js create mode 100644 ui/tsconfig.json create mode 100644 ui/vite.config.js diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..eca3b89 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +ui/node_modules +ui/build \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..11ac438 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +reveil +vendor +ui/build +ui/node_modules \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4f7f430 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +FROM node:18-alpine as nodebuild + +WORKDIR /ui + +COPY ui/ . + +RUN npm install --network-timeout=100000 && \ + sed -i 's!@popperjs/core/dist/esm/popper!@popperjs/core!' node_modules/sveltestrap/src/*.js node_modules/sveltestrap/src/*.svelte && \ + npm run build + + +FROM golang:1-alpine AS build + +RUN apk --no-cache add git go-bindata + +COPY . /go/src/git.nemunai.re/nemunaire/reveil +COPY --from=nodebuild /ui/build /go/src/git.nemunai.re/nemunaire/reveil/ui/build +WORKDIR /go/src/git.nemunai.re/nemunaire/reveil +RUN go get -v && go generate -v && go build -v -ldflags="-s -w" + + +FROM alpine:3.16 + +EXPOSE 8080 +CMD ["/srv/reveil"] + +COPY --from=build /go/src/git.nemunai.re/nemunaire/reveil/reveil /srv/reveil diff --git a/api/routes.go b/api/routes.go new file mode 100644 index 0000000..86b1b91 --- /dev/null +++ b/api/routes.go @@ -0,0 +1,23 @@ +package api + +import ( + "net/http" + + "github.com/gin-gonic/gin" + + "git.nemunai.re/nemunaire/reveil/config" + //"git.nemunai.re/nemunaire/reveil/model" +) + +func DeclareRoutes(router *gin.Engine, cfg *config.Config) { + apiRoutes := router.Group("/api") + + apiRoutes.GET("/test", func(c *gin.Context) { + c.JSON(http.StatusOK, "test") + }) + + //declareFilesRoutes(cfg, apiAuthRoutes) + //declareIngredientsRoutes(cfg, apiAuthRoutes) + //declareRecipesRoutes(cfg, apiAuthRoutes) + //declareUsersRoutes(cfg, apiAuthRoutes) +} diff --git a/app.go b/app.go new file mode 100644 index 0000000..e5b8640 --- /dev/null +++ b/app.go @@ -0,0 +1,68 @@ +package main + +import ( + "context" + "log" + "net/http" + "time" + + "github.com/gin-gonic/gin" + + "git.nemunai.re/nemunaire/reveil/api" + "git.nemunai.re/nemunaire/reveil/config" + "git.nemunai.re/nemunaire/reveil/ui" +) + +type App struct { + cfg *config.Config + router *gin.Engine + srv *http.Server +} + +func NewApp(cfg *config.Config) App { + if cfg.DevProxy == "" { + gin.SetMode(gin.ReleaseMode) + } + gin.ForceConsoleColor() + router := gin.Default() + + router.Use(func(c *gin.Context) { + c.Next() + }) + + // Register routes + ui.DeclareRoutes(router, cfg) + api.DeclareRoutes(router, cfg) + + router.GET("/api/version", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"version": Version}) + }) + + // We are ready! + app := App{ + cfg: cfg, + router: router, + } + + return app +} + +func (app *App) Start() { + app.srv = &http.Server{ + Addr: app.cfg.Bind, + Handler: app.router, + } + + log.Printf("Ready, listening on %s\n", app.cfg.Bind) + if err := app.srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatalf("listen: %s\n", err) + } +} + +func (app *App) Stop() { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := app.srv.Shutdown(ctx); err != nil { + log.Fatal("Server Shutdown:", err) + } +} diff --git a/config/cli.go b/config/cli.go new file mode 100644 index 0000000..172c537 --- /dev/null +++ b/config/cli.go @@ -0,0 +1,45 @@ +package config + +import ( + "flag" +) + +// declareFlags registers flags for the structure Options. +func (c *Config) declareFlags() { + flag.Var(&c.ExternalURL, "external-url", "Public URL of the service") + flag.StringVar(&c.BaseURL, "baseurl", c.BaseURL, "URL prepended to each URL") + flag.StringVar(&c.Bind, "bind", c.Bind, "Bind port/socket") + flag.StringVar(&c.DevProxy, "dev", c.DevProxy, "Use ui directory instead of embedded assets") + + // Others flags are declared in some other files when they need specials configurations +} + +func Consolidated() (cfg *Config, err error) { + // Define defaults options + cfg = &Config{ + Bind: "127.0.0.1:8080", + } + + cfg.declareFlags() + + // Then, overwrite that by what is present in the environment + err = cfg.FromEnv() + if err != nil { + return + } + + // Finaly, command line takes precedence + err = cfg.parseCLI() + if err != nil { + return + } + + return +} + +// parseCLI parse the flags and treats extra args as configuration filename. +func (c *Config) parseCLI() error { + flag.Parse() + + return nil +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..45119f5 --- /dev/null +++ b/config/config.go @@ -0,0 +1,29 @@ +package config + +import ( + "flag" + "strings" +) + +type Config struct { + DevProxy string + Bind string + ExternalURL URL + BaseURL string +} + +// parseLine treats a config line and place the read value in the variable +// declared to the corresponding flag. +func (c *Config) parseLine(line string) (err error) { + fields := strings.SplitN(line, "=", 2) + orig_key := strings.TrimSpace(fields[0]) + value := strings.TrimSpace(fields[1]) + + key := strings.TrimPrefix(orig_key, "REVEIL_") + key = strings.Replace(key, "_", "-", -1) + key = strings.ToLower(key) + + err = flag.Set(key, value) + + return +} diff --git a/config/custom.go b/config/custom.go new file mode 100644 index 0000000..716a2f7 --- /dev/null +++ b/config/custom.go @@ -0,0 +1,44 @@ +package config + +import ( + "encoding/base64" + "net/url" +) + +type JWTSecretKey []byte + +func (i *JWTSecretKey) String() string { + return base64.StdEncoding.EncodeToString(*i) +} + +func (i *JWTSecretKey) Set(value string) error { + z, err := base64.StdEncoding.DecodeString(value) + if err != nil { + return err + } + + *i = z + return nil +} + +type URL struct { + URL *url.URL +} + +func (i *URL) String() string { + if i.URL != nil { + return i.URL.String() + } else { + return "" + } +} + +func (i *URL) Set(value string) error { + u, err := url.Parse(value) + if err != nil { + return err + } + + i.URL = u + return nil +} diff --git a/config/env.go b/config/env.go new file mode 100644 index 0000000..adac3ea --- /dev/null +++ b/config/env.go @@ -0,0 +1,21 @@ +package config + +import ( + "fmt" + "os" + "strings" +) + +// FromEnv analyzes all the environment variables to find each one +// starting by REVEIL_ +func (c *Config) FromEnv() error { + for _, line := range os.Environ() { + if strings.HasPrefix(line, "REVEIL_") { + err := c.parseLine(line) + if err != nil { + return fmt.Errorf("error in environment (%q): %w", line, err) + } + } + } + return nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4a768b4 --- /dev/null +++ b/go.mod @@ -0,0 +1,45 @@ +module git.nemunai.re/nemunaire/reveil + +go 1.18 + +require ( + github.com/gin-contrib/sessions v0.0.5 + github.com/gin-gonic/gin v1.8.1 +) + +require ( + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.0 // indirect + github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/go-playground/validator/v10 v10.10.0 // indirect + github.com/goccy/go-json v0.9.7 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/snappy v0.0.1 // indirect + github.com/gorilla/context v1.1.1 // indirect + github.com/gorilla/securecookie v1.1.1 // indirect + github.com/gorilla/sessions v1.2.1 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.13.6 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect + github.com/pelletier/go-toml/v2 v2.0.1 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pquerna/cachecontrol v0.1.0 // indirect + github.com/ugorji/go/codec v1.2.7 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.1 // indirect + github.com/xdg-go/stringprep v1.0.3 // indirect + github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect + golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect + golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect + golang.org/x/text v0.3.7 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.28.0 // indirect + gopkg.in/square/go-jose.v2 v2.6.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..431e372 --- /dev/null +++ b/go.sum @@ -0,0 +1,144 @@ +github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk= +github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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= +github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfmF+0jE= +github.com/gin-contrib/sessions v0.0.5/go.mod h1:vYAuaUPqie3WUSsft6HUlCjlwwoJQs97miaG2+7neKY= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= +github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= +github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM= +github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= +github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= +github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +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/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc= +github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E= +github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs= +github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +go.mongodb.org/mongo-driver v1.10.2 h1:4Wk3cnqOrQCn0P92L3/mmurMxzdvWWs5J9jinAVKD+k= +go.mongodb.org/mongo-driver v1.10.2/go.mod h1:z4XpeoU6w+9Vht+jAFyLgVrD+jGSQQe0+CBWFHNiHt8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 h1:lxqLZaMad/dJHMFZH0NiNpiEZI/nhgWhe4wgzpE+MuA= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= +gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..a999bbf --- /dev/null +++ b/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "log" + "os" + "os/signal" + "syscall" + + "git.nemunai.re/nemunaire/reveil/config" +) + +var ( + Version = "custom-build" +) + +func main() { + cfg, err := config.Consolidated() + if err != nil { + log.Fatal("Unable to read configuration:", err) + } + + a := NewApp(cfg) + go a.Start() + + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt, syscall.SIGTERM) + <-quit + log.Println("Stopping the service...") + a.Stop() + log.Println("Stopped") +} diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..7155a72 --- /dev/null +++ b/renovate.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "packageRules": [ + { + "matchPackageNames": ["alpine", "github.com/gin-gonic/gin"], + "automerge": true, + "automergeType": "branch" + } + ] +} diff --git a/ui/.eslintrc.cjs b/ui/.eslintrc.cjs new file mode 100644 index 0000000..3ccf435 --- /dev/null +++ b/ui/.eslintrc.cjs @@ -0,0 +1,20 @@ +module.exports = { + root: true, + parser: '@typescript-eslint/parser', + extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], + plugins: ['svelte3', '@typescript-eslint'], + ignorePatterns: ['*.cjs'], + overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], + settings: { + 'svelte3/typescript': () => require('typescript') + }, + parserOptions: { + sourceType: 'module', + ecmaVersion: 2020 + }, + env: { + browser: true, + es2017: true, + node: true + } +}; diff --git a/ui/.gitignore b/ui/.gitignore new file mode 100644 index 0000000..f4401a3 --- /dev/null +++ b/ui/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +node_modules +/build +/.svelte-kit +/package +.env +.env.* +!.env.example diff --git a/ui/.npmrc b/ui/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/ui/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/ui/.prettierrc b/ui/.prettierrc new file mode 100644 index 0000000..ff2677e --- /dev/null +++ b/ui/.prettierrc @@ -0,0 +1,6 @@ +{ + "useTabs": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100 +} diff --git a/ui/README.md b/ui/README.md new file mode 100644 index 0000000..82510ca --- /dev/null +++ b/ui/README.md @@ -0,0 +1,38 @@ +# create-svelte + +Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte); + +## Creating a project + +If you're seeing this, you've probably already done this step. Congrats! + +```bash +# create a new project in the current directory +npm init svelte@next + +# create a new project in my-app +npm init svelte@next my-app +``` + +> Note: the `@next` is temporary + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: + +```bash +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +Before creating a production version of your app, install an [adapter](https://kit.svelte.dev/docs#adapters) for your target environment. Then: + +```bash +npm run build +``` + +> You can preview the built app with `npm run preview`, regardless of whether you installed an adapter. This should _not_ be used to serve your app in production. diff --git a/ui/assets-dev.go b/ui/assets-dev.go new file mode 100644 index 0000000..fac4f77 --- /dev/null +++ b/ui/assets-dev.go @@ -0,0 +1,32 @@ +//go:build dev +// +build dev + +package ui + +import ( + "flag" + "net/http" + "os" + "path/filepath" +) + +var ( + Assets http.FileSystem + StaticDir string = "ui/" +) + +func init() { + flag.StringVar(&StaticDir, "static", StaticDir, "Directory containing static files") +} + +func sanitizeStaticOptions() error { + StaticDir, _ = filepath.Abs(StaticDir) + if _, err := os.Stat(StaticDir); os.IsNotExist(err) { + StaticDir, _ = filepath.Abs(filepath.Join(filepath.Dir(os.Args[0]), "ui")) + if _, err := os.Stat(StaticDir); os.IsNotExist(err) { + return err + } + } + Assets = http.Dir(StaticDir) + return nil +} diff --git a/ui/assets.go b/ui/assets.go new file mode 100644 index 0000000..6ec6008 --- /dev/null +++ b/ui/assets.go @@ -0,0 +1,28 @@ +//go:build !dev +// +build !dev + +package ui + +import ( + "embed" + "io/fs" + "log" + "net/http" +) + +//go:embed all:build +var _assets embed.FS + +var Assets http.FileSystem + +func init() { + sub, err := fs.Sub(_assets, "build") + if err != nil { + log.Fatal("Unable to cd to ui/build directory:", err) + } + Assets = http.FS(sub) +} + +func sanitizeStaticOptions() error { + return nil +} diff --git a/ui/package.json b/ui/package.json new file mode 100644 index 0000000..528c5c8 --- /dev/null +++ b/ui/package.json @@ -0,0 +1,41 @@ +{ + "name": "reveil", + "version": "0.0.1", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "package": "vite package", + "preview": "vite preview", + "check": "svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch", + "lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .", + "format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. ." + }, + "devDependencies": { + "@sveltejs/adapter-auto": "^1.0.0-next.18", + "@sveltejs/adapter-static": "^1.0.0-next.26", + "@sveltejs/kit": "^1.0.0-next.260", + "@typescript-eslint/eslint-plugin": "^5.0.0", + "@typescript-eslint/parser": "^5.0.0", + "bootstrap": "^5.1.3", + "bootstrap-icons": "^1.8.0", + "bootswatch": "^5.1.3", + "eslint": "^8.0.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-svelte3": "^4.0.0", + "prettier": "^2.4.1", + "prettier-plugin-svelte": "^2.6.0", + "svelte": "^3.46.4", + "svelte-check": "^2.4.2", + "svelte-preprocess": "^4.10.2", + "tslib": "^2.3.1", + "typescript": "^4.5.5" + }, + "type": "module", + "dependencies": { + "sass": "^1.49.7", + "sass-loader": "^13.0.0", + "sveltestrap": "^5.8.3", + "vite": "^3.0.0" + } +} diff --git a/ui/routes.go b/ui/routes.go new file mode 100644 index 0000000..a6cd979 --- /dev/null +++ b/ui/routes.go @@ -0,0 +1,77 @@ +package ui + +import ( + "io" + "net/http" + "net/url" + "path" + + "github.com/gin-gonic/gin" + + "git.nemunai.re/nemunaire/reveil/config" +) + +func serveOrReverse(forced_url string, cfg *config.Config) gin.HandlerFunc { + if cfg.DevProxy != "" { + // Forward to the Vue dev proxy + return func(c *gin.Context) { + if u, err := url.Parse(cfg.DevProxy); err != nil { + http.Error(c.Writer, err.Error(), http.StatusInternalServerError) + } else { + if forced_url != "" { + u.Path = path.Join(u.Path, forced_url) + } else { + u.Path = path.Join(u.Path, c.Request.URL.Path) + } + + if r, err := http.NewRequest(c.Request.Method, u.String(), c.Request.Body); err != nil { + http.Error(c.Writer, err.Error(), http.StatusInternalServerError) + } else if resp, err := http.DefaultClient.Do(r); err != nil { + http.Error(c.Writer, err.Error(), http.StatusBadGateway) + } else { + defer resp.Body.Close() + + for key := range resp.Header { + c.Writer.Header().Add(key, resp.Header.Get(key)) + } + c.Writer.WriteHeader(resp.StatusCode) + + io.Copy(c.Writer, resp.Body) + } + } + } + } else if forced_url != "" { + // Serve forced_url + return func(c *gin.Context) { + c.FileFromFS(forced_url, Assets) + } + } else { + // Serve requested file + return func(c *gin.Context) { + c.FileFromFS(c.Request.URL.Path, Assets) + } + } +} + +func DeclareRoutes(router *gin.Engine, cfg *config.Config) { + if cfg.DevProxy != "" { + router.GET("/.svelte-kit/*_", serveOrReverse("", cfg)) + router.GET("/node_modules/*_", serveOrReverse("", cfg)) + router.GET("/@vite/*_", serveOrReverse("", cfg)) + router.GET("/@fs/*_", serveOrReverse("", cfg)) + router.GET("/src/*_", serveOrReverse("", cfg)) + } + + router.GET("/", serveOrReverse("", cfg)) + router.GET("/alarms", serveOrReverse("/", cfg)) + router.GET("/settings", serveOrReverse("/", cfg)) + router.GET("/routines", serveOrReverse("/", cfg)) + router.GET("/musiks", serveOrReverse("/", cfg)) + router.GET("/history", serveOrReverse("/", cfg)) + + router.GET("/_app/*_", serveOrReverse("", cfg)) + router.GET("/img/*_", serveOrReverse("", cfg)) + router.GET("/favicon.ico", serveOrReverse("", cfg)) + router.GET("/manifest.json", serveOrReverse("", cfg)) + router.GET("/service-worker.js", serveOrReverse("", cfg)) +} diff --git a/ui/src/app.html b/ui/src/app.html new file mode 100644 index 0000000..cb871a8 --- /dev/null +++ b/ui/src/app.html @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/ui/src/components/Header.svelte b/ui/src/components/Header.svelte new file mode 100644 index 0000000..5950b8e --- /dev/null +++ b/ui/src/components/Header.svelte @@ -0,0 +1,81 @@ + + + + + Réveil + + + + diff --git a/ui/src/components/Toaster.svelte b/ui/src/components/Toaster.svelte new file mode 100644 index 0000000..af00200 --- /dev/null +++ b/ui/src/components/Toaster.svelte @@ -0,0 +1,22 @@ + + +
+ {#each $ToastsStore.toasts as toast} + + + {#if toast.title}{toast.title}{:else}Gustus{/if} + + + {toast.msg} + + + {/each} +
diff --git a/ui/src/global.d.ts b/ui/src/global.d.ts new file mode 100644 index 0000000..63908c6 --- /dev/null +++ b/ui/src/global.d.ts @@ -0,0 +1 @@ +/// diff --git a/ui/src/reveil.scss b/ui/src/reveil.scss new file mode 100644 index 0000000..2c2c9f1 --- /dev/null +++ b/ui/src/reveil.scss @@ -0,0 +1,34 @@ +// Your variable overrides can go here, e.g.: +// $h1-font-size: 3rem; +//$primary: #ff485a; +//$secondary: #ff7b88; + +$blue: #2a9fd6; +$indigo: #6610f2; +$purple: #6f42c1; +$pink: #e83e8c; +$red: #c00; +$orange: #fd7e14; +$yellow: #f80; +$green: #77b300; +$teal: #20c997; +$cyan: #93c; + +$primary: $pink; +$success: $green; +$info: $cyan; +$warning: $yellow; +$danger: $red; + +$min-contrast-ratio: 2.25; + +$enable-shadows: true; +$enable-gradients: true; +$enable-responsive-font-sizes: true; + +$link-color: $primary; + +$navbar-padding-y: 0; +$nav-link-padding-y: 0.2rem; + +@import "bootstrap/scss/bootstrap"; diff --git a/ui/src/routes/+layout.js b/ui/src/routes/+layout.js new file mode 100644 index 0000000..89da957 --- /dev/null +++ b/ui/src/routes/+layout.js @@ -0,0 +1,2 @@ +export const ssr = false; +export const prerender = true; diff --git a/ui/src/routes/+layout.svelte b/ui/src/routes/+layout.svelte new file mode 100644 index 0000000..ccbd4f7 --- /dev/null +++ b/ui/src/routes/+layout.svelte @@ -0,0 +1,34 @@ + + + + Réveil + + + + +
+
+ +
+ +
+ + diff --git a/ui/src/routes/+page.svelte b/ui/src/routes/+page.svelte new file mode 100644 index 0000000..6427d1e --- /dev/null +++ b/ui/src/routes/+page.svelte @@ -0,0 +1,48 @@ + + + +
+
+

A well-known quote, contained in a blockquote element.

+
+ +
+
+ Prochain réveil : demain matin à 7h10 (dans 5 cycles) +
+
+ + + +
+
+ + + +
+
diff --git a/ui/src/service-worker.ts b/ui/src/service-worker.ts new file mode 100644 index 0000000..96b80f5 --- /dev/null +++ b/ui/src/service-worker.ts @@ -0,0 +1,80 @@ +/// + +import { build, files, timestamp } from '$service-worker'; + +const worker = (self as unknown) as ServiceWorkerGlobalScope; +const FILES = `cache${timestamp}`; + +// `build` is an array of all the files generated by the bundler, +// `files` is an array of everything in the `static` directory +const to_cache = build.concat(files); +const staticAssets = new Set(to_cache); + +worker.addEventListener('install', (event) => { + event.waitUntil( + caches + .open(FILES) + .then((cache) => cache.addAll(to_cache)) + .then(() => { + worker.skipWaiting(); + }) + ); +}); + +worker.addEventListener('activate', (event) => { + event.waitUntil( + caches.keys().then(async (keys) => { + // delete old caches + for (const key of keys) { + if (key !== FILES) await caches.delete(key); + } + + worker.clients.claim(); + }) + ); +}); + +/** + * Fetch the asset from the network and store it in the cache. + * Fall back to the cache if the user is offline. + */ +async function fetchAndCache(request: Request) { + const cache = await caches.open(`offline${timestamp}`); + + try { + const response = await fetch(request); + cache.put(request, response.clone()); + return response; + } catch (err) { + const response = await cache.match(request); + if (response) return response; + + throw err; + } +} + +worker.addEventListener('fetch', (event) => { + if (event.request.method !== 'GET' || event.request.headers.has('range')) return; + + const url = new URL(event.request.url); + + // don't try to handle e.g. data: URIs + const isHttp = url.protocol.startsWith('http'); + const isDevServerRequest = + url.hostname === self.location.hostname && url.port !== self.location.port; + const isStaticAsset = url.host === self.location.host && staticAssets.has(url.pathname); + const skipBecauseUncached = event.request.cache === 'only-if-cached' && !isStaticAsset; + + if (isHttp && !isDevServerRequest && !skipBecauseUncached) { + event.respondWith( + (async () => { + // always serve static files and bundler-generated assets from cache. + // if your application has other URLs with data that will never change, + // set this variable to true for them and they will only be fetched once. + const cachedAsset = isStaticAsset && (await caches.match(event.request)); + + return cachedAsset || fetchAndCache(event.request); + })() + ); + } +}); diff --git a/ui/src/stores/toasts.js b/ui/src/stores/toasts.js new file mode 100644 index 0000000..f6e4d14 --- /dev/null +++ b/ui/src/stores/toasts.js @@ -0,0 +1,41 @@ +import { writable } from 'svelte/store'; + +function createToastsStore() { + const { subscribe, update } = writable({toasts: []}); + + const addToast = (o) => { + o.timestamp = new Date(); + + o.close = () => { + update((i) => { + i.toasts = i.toasts.filter((j) => { + return !(j.title === o.title && j.msg === o.msg && j.timestamp === o.timestamp) + }); + return i; + }); + } + + update((i) => { + i.toasts.unshift(o); + return i; + }); + + o.cancel = setTimeout(o.close, o.dismiss?o.dismiss:5000); + }; + + const addErrorToast = (o) => { + if (!o.title) o.title = 'Une erreur est survenue !'; + if (!o.color) o.color = 'danger'; + + return addToast(o); + }; + + return { + subscribe, + addToast, + addErrorToast, + }; + +} + +export const ToastsStore = createToastsStore(); diff --git a/ui/static/manifest.json b/ui/static/manifest.json new file mode 100644 index 0000000..6582523 --- /dev/null +++ b/ui/static/manifest.json @@ -0,0 +1,20 @@ +{ + "manifest_version": 2, + "short_name": "Gustus", + "name": "Gustus", + "version": "0.1", + "author": "nemucorp", + "start_url": "/", + "icons": [ + { + "src": "img/android-chrome-512x512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "background_color": "#d62a49", + "display": "standalone", + "scope": "/", + "theme_color": "#ffffff", + "description": "Retrouvez facilement toutes vos recettes préférées" +} diff --git a/ui/svelte.config.js b/ui/svelte.config.js new file mode 100644 index 0000000..cb269a1 --- /dev/null +++ b/ui/svelte.config.js @@ -0,0 +1,20 @@ +import adapter from '@sveltejs/adapter-static'; +import preprocess from 'svelte-preprocess'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + // Consult https://github.com/sveltejs/svelte-preprocess + // for more information about preprocessors + preprocess: preprocess(), + + kit: { + adapter: adapter({ + fallback: 'index.html' + }), + paths: { + // base: '{{.urlbase}}', + } + } +}; + +export default config; diff --git a/ui/tsconfig.json b/ui/tsconfig.json new file mode 100644 index 0000000..b6bfc59 --- /dev/null +++ b/ui/tsconfig.json @@ -0,0 +1,32 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "moduleResolution": "node", + "module": "es2020", + "lib": ["es2020", "DOM"], + "target": "es2020", + /** + svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript + to enforce using \`import type\` instead of \`import\` for Types. + */ + "importsNotUsedAsValues": "error", + "isolatedModules": true, + "resolveJsonModule": true, + /** + To have warnings/errors of the Svelte compiler at the correct position, + enable source maps by default. + */ + "sourceMap": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": ".", + "allowJs": true, + "checkJs": true, + "paths": { + "$lib": ["src/lib"], + "$lib/*": ["src/lib/*"] + } + }, + "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts", "src/**/*.svelte"] +} diff --git a/ui/vite.config.js b/ui/vite.config.js new file mode 100644 index 0000000..8747050 --- /dev/null +++ b/ui/vite.config.js @@ -0,0 +1,8 @@ +import { sveltekit } from '@sveltejs/kit/vite'; + +/** @type {import('vite').UserConfig} */ +const config = { + plugins: [sveltekit()] +}; + +export default config;