From 67e4882bb372bc45d178e3eacc409cdbf60f1344 Mon Sep 17 00:00:00 2001 From: Jake Gold <52801504+Jacob2161@users.noreply.github.com> Date: Mon, 20 Mar 2023 14:41:15 -0700 Subject: [PATCH] bskyweb additions (#296) Add some minor bskyweb improvements, Mailmodo endpoint, Dockerfile for bskyweb, container image push --- .../workflows/build-and-push-bskyweb-aws.yaml | 54 +++++++++ .../build-and-push-bskyweb-ghcr.yaml | 56 +++++++++ .prettierignore | 3 + Dockerfile | 75 ++++++++++++ bskyweb/.gitignore | 5 +- bskyweb/cmd/bskyweb/mailmodo.go | 68 +++++++++++ bskyweb/cmd/bskyweb/main.go | 68 +++++++---- bskyweb/cmd/bskyweb/renderer.go | 82 +++++++++++++ bskyweb/cmd/bskyweb/server.go | 108 +++++++++--------- bskyweb/go.mod | 2 +- bskyweb/go.sum | 6 +- bskyweb/static.go | 6 + bskyweb/static/js/.gitkeep | 1 + bskyweb/templates.go | 6 + bskyweb/templates/scripts.html | 2 - 15 files changed, 458 insertions(+), 84 deletions(-) create mode 100644 .github/workflows/build-and-push-bskyweb-aws.yaml create mode 100644 .github/workflows/build-and-push-bskyweb-ghcr.yaml create mode 100644 Dockerfile create mode 100644 bskyweb/cmd/bskyweb/mailmodo.go create mode 100644 bskyweb/cmd/bskyweb/renderer.go create mode 100644 bskyweb/static.go create mode 100644 bskyweb/static/js/.gitkeep create mode 100644 bskyweb/templates.go delete mode 100644 bskyweb/templates/scripts.html diff --git a/.github/workflows/build-and-push-bskyweb-aws.yaml b/.github/workflows/build-and-push-bskyweb-aws.yaml new file mode 100644 index 00000000..9385fc75 --- /dev/null +++ b/.github/workflows/build-and-push-bskyweb-aws.yaml @@ -0,0 +1,54 @@ +name: build-and-push-bskyweb-aws +on: + push: + branches: + - main + - jake/bskyweb-additions +env: + REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }} + USERNAME: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_USERNAME }} + PASSWORD: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_PASSWORD }} + IMAGE_NAME: bskyweb + +jobs: + bskyweb-container-aws: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v1 + + - name: Log into registry ${{ env.REGISTRY }} + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ env.USERNAME}} + password: ${{ env.PASSWORD }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: | + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=sha,enable=true,priority=100,prefix=,suffix=,format=long + + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v4 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + file: ./Dockerfile + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/build-and-push-bskyweb-ghcr.yaml b/.github/workflows/build-and-push-bskyweb-ghcr.yaml new file mode 100644 index 00000000..5958874b --- /dev/null +++ b/.github/workflows/build-and-push-bskyweb-ghcr.yaml @@ -0,0 +1,56 @@ +name: build-and-push-bskyweb-ghcr +on: + push: + branches: + - main + - jake/bskyweb-additions +env: + REGISTRY: ghcr.io + USERNAME: ${{ github.actor }} + PASSWORD: ${{ secrets.GITHUB_TOKEN }} + + # github.repository as / + IMAGE_NAME: ${{ github.repository }} + +jobs: + bskyweb-container-ghcr: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v1 + + - name: Log into registry ${{ env.REGISTRY }} + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ env.USERNAME }} + password: ${{ env.PASSWORD }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: | + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=sha,enable=true,priority=100,prefix=bskyweb:,suffix=,format=long + + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v4 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + file: ./Dockerfile + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.prettierignore b/.prettierignore index bd66c8d8..06cb77f4 100644 --- a/.prettierignore +++ b/.prettierignore @@ -6,3 +6,6 @@ public /bskyweb/templates /dist/ /.watchmanconfig + +web/index.html +web-build/* diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..95f0ec02 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,75 @@ +FROM golang:1.20-bullseye AS build-env + +WORKDIR /usr/src/social-app + +ENV DEBIAN_FRONTEND=noninteractive + +# Node +ENV NODE_VERSION=18 +ENV NVM_DIR=/usr/share/nvm + +# Go +ENV GODEBUG="netdns=go" +ENV GOOS="linux" +ENV GOARCH="amd64" +ENV CGO_ENABLED=1 + +COPY . . + +# +# Generate the Javascript webpack. +# +RUN mkdir --parents $NVM_DIR && \ + wget \ + --output-document=/tmp/nvm-install.sh \ + https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh && \ + bash /tmp/nvm-install.sh + +RUN \. "$NVM_DIR/nvm.sh" && \ + nvm install $NODE_VERSION && \ + nvm use $NODE_VERSION && \ + npm install --global yarn && \ + yarn && \ + yarn build-web + +# DEBUG +RUN find ./bskyweb/static && find ./web-build/static + +# Copy the bundle js files. +RUN cp --verbose ./web-build/static/js/*.* ./bskyweb/static/js/ + +# +# Generate the bksyweb Go binary. +# +RUN cd bskyweb/ && \ + go mod download && \ + go mod verify + +RUN cd bskyweb/ && \ + go build \ + -v \ + -trimpath \ + -tags timetzdata \ + -o /bskyweb \ + ./cmd/bskyweb + +FROM debian:bullseye-slim + +ENV GODEBUG=netdns=go +ENV TZ=Etc/UTC +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get install --yes \ + dumb-init \ + ca-certificates + +ENTRYPOINT ["dumb-init", "--"] + +WORKDIR /bskyweb +COPY --from=build-env /bskyweb /usr/bin/bskyweb + +CMD ["/usr/bin/bskyweb"] + +LABEL org.opencontainers.image.source=https://github.com/bluesky-social/social-app +LABEL org.opencontainers.image.description="bsky.app Web App" +LABEL org.opencontainers.image.licenses=MIT diff --git a/bskyweb/.gitignore b/bskyweb/.gitignore index 7994736d..4dc17cf0 100644 --- a/bskyweb/.gitignore +++ b/bskyweb/.gitignore @@ -1,3 +1,4 @@ -/static/bundle.web.js -/bskyweb .env + +# Don't check in the binary. +/bskyweb diff --git a/bskyweb/cmd/bskyweb/mailmodo.go b/bskyweb/cmd/bskyweb/mailmodo.go new file mode 100644 index 00000000..67ee6a2d --- /dev/null +++ b/bskyweb/cmd/bskyweb/mailmodo.go @@ -0,0 +1,68 @@ +package main + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/json" + "fmt" + "net/http" + "time" +) + +type Mailmodo struct { + httpClient *http.Client + APIKey string + BaseURL string +} + +func NewMailmodo(apiKey string) *Mailmodo { + return &Mailmodo{ + APIKey: apiKey, + BaseURL: "https://api.mailmodo.com/api/v1", + httpClient: &http.Client{}, + } +} + +func (m *Mailmodo) request(ctx context.Context, httpMethod string, apiMethod string, data any) error { + endpoint := fmt.Sprintf("%s/%s", m.BaseURL, apiMethod) + js, err := json.Marshal(data) + if err != nil { + return fmt.Errorf("Mailmodo JSON encoding failed: %w", err) + } + req, err := http.NewRequestWithContext(ctx, httpMethod, endpoint, bytes.NewBuffer(js)) + if err != nil { + return fmt.Errorf("Mailmodo HTTP creating request %s %s failed: %w", httpMethod, apiMethod, err) + } + req.Header.Set("mmApiKey", m.APIKey) + req.Header.Set("Content-Type", "application/json") + + res, err := m.httpClient.Do(req) + if err != nil { + return fmt.Errorf("Mailmodo HTTP making request %s %s failed: %w", httpMethod, apiMethod, err) + } + defer res.Body.Close() + + status := struct { + Success bool `json:"success"` + Message string `json:"message"` + }{} + if err := json.NewDecoder(res.Body).Decode(&status); err != nil { + return fmt.Errorf("Mailmodo HTTP parsing response %s %s failed: %w", httpMethod, apiMethod, err) + } + if !status.Success { + return fmt.Errorf("Mailmodo API response %s %s failed: %s", httpMethod, apiMethod, status.Message) + } + return nil +} + +func (m *Mailmodo) AddToList(ctx context.Context, listName, email string) error { + return m.request(ctx, "POST", "addToList", map[string]any{ + "listName": listName, + "email": email, + "data": map[string]any{ + "email_hashed": fmt.Sprintf("%x", sha256.Sum256([]byte(email))), + }, + "created_at": time.Now().UTC().Format(time.RFC3339), + }) +} diff --git a/bskyweb/cmd/bskyweb/main.go b/bskyweb/cmd/bskyweb/main.go index 4b691b68..fd142990 100644 --- a/bskyweb/cmd/bskyweb/main.go +++ b/bskyweb/cmd/bskyweb/main.go @@ -35,33 +35,57 @@ func run(args []string) { Usage: "web server for bsky.app web app (SPA)", } - app.Flags = []cli.Flag{ - &cli.StringFlag{ - Name: "pds-host", - Usage: "method, hostname, and port of PDS instance", - Value: "http://localhost:4849", - EnvVars: []string{"ATP_PDS_HOST"}, - }, - &cli.StringFlag{ - Name: "handle", - Usage: "for PDS login", - Required: true, - EnvVars: []string{"ATP_AUTH_HANDLE"}, - }, - &cli.StringFlag{ - Name: "password", - Usage: "for PDS login", - Required: true, - EnvVars: []string{"ATP_AUTH_PASSWORD"}, - }, - // TODO: local IP/port to bind on - } - app.Commands = []*cli.Command{ &cli.Command{ Name: "serve", Usage: "run the server", Action: serve, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "pds-host", + Usage: "method, hostname, and port of PDS instance", + Value: "http://localhost:4849", + EnvVars: []string{"ATP_PDS_HOST"}, + }, + &cli.StringFlag{ + Name: "handle", + Usage: "for PDS login", + Required: true, + EnvVars: []string{"ATP_AUTH_HANDLE"}, + }, + &cli.StringFlag{ + Name: "password", + Usage: "for PDS login", + Required: true, + EnvVars: []string{"ATP_AUTH_PASSWORD"}, + }, + &cli.StringFlag{ + Name: "mailmodo-api-key", + Usage: "Mailmodo API key", + Required: false, + EnvVars: []string{"MAILMODO_API_KEY"}, + }, + &cli.StringFlag{ + Name: "mailmodo-list-name", + Usage: "Mailmodo contact list to add email addresses to", + Required: false, + EnvVars: []string{"MAILMODO_LIST_NAME"}, + }, + &cli.StringFlag{ + Name: "http-address", + Usage: "Specify the local IP/port to bind to", + Required: false, + Value: ":8100", + EnvVars: []string{"HTTP_ADDRESS"}, + }, + &cli.BoolFlag{ + Name: "debug", + Usage: "Enable debug mode", + Value: false, + Required: false, + EnvVars: []string{"DEBUG"}, + }, + }, }, } app.RunAndExitOnError() diff --git a/bskyweb/cmd/bskyweb/renderer.go b/bskyweb/cmd/bskyweb/renderer.go new file mode 100644 index 00000000..4bf8b80c --- /dev/null +++ b/bskyweb/cmd/bskyweb/renderer.go @@ -0,0 +1,82 @@ +package main + +import ( + "bytes" + "embed" + "errors" + "fmt" + "io" + "path/filepath" + + "github.com/flosch/pongo2/v6" + "github.com/labstack/echo/v4" +) + +type RendererLoader struct { + prefix string + fs *embed.FS +} + +func NewRendererLoader(prefix string, fs *embed.FS) pongo2.TemplateLoader { + return &RendererLoader{ + prefix: prefix, + fs: fs, + } +} +func (l *RendererLoader) Abs(_, name string) string { + // TODO: remove this workaround + // Figure out why this method is being called + // twice on template names resulting in a failure to resolve + // the template name. + if filepath.HasPrefix(name, l.prefix) { + return name + } + return filepath.Join(l.prefix, name) +} + +func (l *RendererLoader) Get(path string) (io.Reader, error) { + b, err := l.fs.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("reading template %q failed: %w", path, err) + } + return bytes.NewReader(b), nil +} + +type Renderer struct { + TemplateSet *pongo2.TemplateSet + Debug bool +} + +func NewRenderer(prefix string, fs *embed.FS, debug bool) *Renderer { + return &Renderer{ + TemplateSet: pongo2.NewSet(prefix, NewRendererLoader(prefix, fs)), + Debug: debug, + } +} + +func (r Renderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error { + var ctx pongo2.Context + + if data != nil { + var ok bool + ctx, ok = data.(pongo2.Context) + if !ok { + return errors.New("no pongo2.Context data was passed") + } + } + + var t *pongo2.Template + var err error + + if r.Debug { + t, err = pongo2.FromFile(name) + } else { + t, err = r.TemplateSet.FromFile(name) + } + + if err != nil { + return err + } + + return t.ExecuteWriter(ctx, w) +} diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go index 7ae0fb54..efa5f205 100644 --- a/bskyweb/cmd/bskyweb/server.go +++ b/bskyweb/cmd/bskyweb/server.go @@ -2,15 +2,17 @@ package main import ( "context" - "errors" "fmt" - "io" + "io/fs" "net/http" + "os" + "strings" comatproto "github.com/bluesky-social/indigo/api/atproto" appbsky "github.com/bluesky-social/indigo/api/bsky" cliutil "github.com/bluesky-social/indigo/cmd/gosky/util" "github.com/bluesky-social/indigo/xrpc" + "github.com/bluesky-social/social-app/bskyweb" "github.com/flosch/pongo2/v6" "github.com/labstack/echo/v4" @@ -18,60 +20,35 @@ import ( "github.com/urfave/cli/v2" ) -// TODO: embed templates in executable - -type Renderer struct { - Debug bool -} - -func (r Renderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error { - - var ctx pongo2.Context - - if data != nil { - var ok bool - ctx, ok = data.(pongo2.Context) - - if !ok { - return errors.New("no pongo2.Context data was passed...") - } - } - - var t *pongo2.Template - var err error - - if r.Debug { - t, err = pongo2.FromFile(name) - } else { - t, err = pongo2.FromCache(name) - } - - if err != nil { - return err - } - - return t.ExecuteWriter(ctx, w) -} - type Server struct { xrpcc *xrpc.Client } func serve(cctx *cli.Context) error { + debug := cctx.Bool("debug") + httpAddress := cctx.String("http-address") + pdsHost := cctx.String("pds-host") + atpHandle := cctx.String("handle") + atpPassword := cctx.String("password") + mailmodoAPIKey := cctx.String("mailmodo-api-key") + mailmodoListName := cctx.String("mailmodo-list-name") + + // Mailmodo client. + mailmodo := NewMailmodo(mailmodoAPIKey) // create a new session // TODO: does this work with no auth at all? xrpcc := &xrpc.Client{ Client: cliutil.NewHttpClient(), - Host: cctx.String("pds-host"), + Host: pdsHost, Auth: &xrpc.AuthInfo{ - Handle: cctx.String("handle"), + Handle: atpHandle, }, } auth, err := comatproto.SessionCreate(context.TODO(), xrpcc, &comatproto.SessionCreate_Input{ Identifier: &xrpcc.Auth.Handle, - Password: cctx.String("password"), + Password: atpPassword, }) if err != nil { return err @@ -83,19 +60,32 @@ func serve(cctx *cli.Context) error { server := Server{xrpcc} + staticHandler := http.FileServer(func() http.FileSystem { + if debug { + return http.FS(os.DirFS("static")) + } + fsys, err := fs.Sub(bskyweb.StaticFS, "static") + if err != nil { + log.Fatal(err) + } + return http.FS(fsys) + }()) + e := echo.New() e.HideBanner = true e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ + // Don't log requests for static content. + Skipper: func(c echo.Context) bool { + return strings.HasPrefix(c.Request().URL.Path, "/static") + }, Format: "method=${method} path=${uri} status=${status} latency=${latency_human}\n", })) - e.Renderer = Renderer{Debug: true} + e.Renderer = NewRenderer("templates/", &bskyweb.TemplateFS, debug) e.HTTPErrorHandler = customHTTPErrorHandler // configure routes - e.File("/robots.txt", "static/robots.txt") - e.Static("/static", "static") - e.Static("/static/js", "../web-build/static/js") - + e.GET("/robots.txt", echo.WrapHandler(staticHandler)) + e.GET("/static/*", echo.WrapHandler(http.StripPrefix("/static/", staticHandler))) e.GET("/", server.WebHome) // generic routes @@ -118,9 +108,17 @@ func serve(cctx *cli.Context) error { e.GET("/profile/:handle/post/:rkey/downvoted-by", server.WebGeneric) e.GET("/profile/:handle/post/:rkey/reposted-by", server.WebGeneric) - bind := "localhost:8100" - log.Infof("starting server bind=%s", bind) - return e.Start(bind) + // Mailmodo + e.POST("/waitlist", func(c echo.Context) error { + email := strings.TrimSpace(c.FormValue("email")) + if err := mailmodo.AddToList(c.Request().Context(), mailmodoListName, email); err != nil { + return err + } + return c.JSON(http.StatusOK, map[string]bool{"success": true}) + }) + + log.Infof("starting server address=%s", httpAddress) + return e.Start(httpAddress) } func customHTTPErrorHandler(err error, c echo.Context) { @@ -132,18 +130,18 @@ func customHTTPErrorHandler(err error, c echo.Context) { data := pongo2.Context{ "statusCode": code, } - c.Render(code, "templates/error.html", data) + c.Render(code, "error.html", data) } // handler for endpoint that have no specific server-side handling func (srv *Server) WebGeneric(c echo.Context) error { data := pongo2.Context{} - return c.Render(http.StatusOK, "templates/base.html", data) + return c.Render(http.StatusOK, "base.html", data) } func (srv *Server) WebHome(c echo.Context) error { data := pongo2.Context{} - return c.Render(http.StatusOK, "templates/home.html", data) + return c.Render(http.StatusOK, "home.html", data) } func (srv *Server) WebPost(c echo.Context) error { @@ -152,7 +150,7 @@ func (srv *Server) WebPost(c echo.Context) error { rkey := c.Param("rkey") // sanity check argument if len(handle) > 4 && len(handle) < 128 && len(rkey) > 0 { - ctx := context.TODO() + ctx := c.Request().Context() // requires two fetches: first fetch profile (!) pv, err := appbsky.ActorGetProfile(ctx, srv.xrpcc, handle) if err != nil { @@ -172,7 +170,7 @@ func (srv *Server) WebPost(c echo.Context) error { } } - return c.Render(http.StatusOK, "templates/post.html", data) + return c.Render(http.StatusOK, "post.html", data) } func (srv *Server) WebProfile(c echo.Context) error { @@ -180,7 +178,7 @@ func (srv *Server) WebProfile(c echo.Context) error { handle := c.Param("handle") // sanity check argument if len(handle) > 4 && len(handle) < 128 { - ctx := context.TODO() + ctx := c.Request().Context() pv, err := appbsky.ActorGetProfile(ctx, srv.xrpcc, handle) if err != nil { log.Warnf("failed to fetch handle: %s\t%v", handle, err) @@ -189,5 +187,5 @@ func (srv *Server) WebProfile(c echo.Context) error { } } - return c.Render(http.StatusOK, "templates/profile.html", data) + return c.Render(http.StatusOK, "profile.html", data) } diff --git a/bskyweb/go.mod b/bskyweb/go.mod index dc7af572..f6e03f4f 100644 --- a/bskyweb/go.mod +++ b/bskyweb/go.mod @@ -4,6 +4,7 @@ go 1.19 require ( github.com/bluesky-social/indigo v0.0.0-20230307000525-294e33e70185 + github.com/flosch/pongo2/v6 v6.0.0 github.com/ipfs/go-log v1.0.5 github.com/joho/godotenv v1.5.1 github.com/labstack/echo/v4 v4.10.2 @@ -13,7 +14,6 @@ require ( require ( github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect - github.com/flosch/pongo2/v6 v6.0.0 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/goccy/go-json v0.10.0 // indirect diff --git a/bskyweb/go.sum b/bskyweb/go.sum index df9d87dc..1f4bfa90 100644 --- a/bskyweb/go.sum +++ b/bskyweb/go.sum @@ -1,8 +1,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/bluesky-social/indigo v0.0.0-20230306194356-5958f14d5152 h1:7fHM+tQHJN5lsMU8FvV4bNuWpD0Dd+pAUSuoLYdcYIQ= -github.com/bluesky-social/indigo v0.0.0-20230306194356-5958f14d5152/go.mod h1:xy2hI4NMC6fgUefSJcCst6E0yo9Xbfd97aF27lgHyHE= github.com/bluesky-social/indigo v0.0.0-20230307000525-294e33e70185 h1:WnaOpRFWE8Tmw0IeXEEthsqBZtNG6/niokmWANv/aEU= github.com/bluesky-social/indigo v0.0.0-20230307000525-294e33e70185/go.mod h1:xy2hI4NMC6fgUefSJcCst6E0yo9Xbfd97aF27lgHyHE= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= @@ -64,9 +62,11 @@ github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y7 github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 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/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M= github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k= @@ -129,6 +129,7 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 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/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -266,6 +267,7 @@ golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3j golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= 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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/bskyweb/static.go b/bskyweb/static.go new file mode 100644 index 00000000..a67d189f --- /dev/null +++ b/bskyweb/static.go @@ -0,0 +1,6 @@ +package bskyweb + +import "embed" + +//go:embed static/* +var StaticFS embed.FS diff --git a/bskyweb/static/js/.gitkeep b/bskyweb/static/js/.gitkeep new file mode 100644 index 00000000..57404ad7 --- /dev/null +++ b/bskyweb/static/js/.gitkeep @@ -0,0 +1 @@ +NOOP diff --git a/bskyweb/templates.go b/bskyweb/templates.go new file mode 100644 index 00000000..ce3fa29a --- /dev/null +++ b/bskyweb/templates.go @@ -0,0 +1,6 @@ +package bskyweb + +import "embed" + +//go:embed templates/* +var TemplateFS embed.FS diff --git a/bskyweb/templates/scripts.html b/bskyweb/templates/scripts.html deleted file mode 100644 index 30826bfb..00000000 --- a/bskyweb/templates/scripts.html +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file