bskyweb additions (#296)
Add some minor bskyweb improvements, Mailmodo endpoint, Dockerfile for bskyweb, container image pushzio/stable
parent
d8f4475696
commit
67e4882bb3
|
@ -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
|
|
@ -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 <account>/<repo>
|
||||||
|
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
|
|
@ -6,3 +6,6 @@ public
|
||||||
/bskyweb/templates
|
/bskyweb/templates
|
||||||
/dist/
|
/dist/
|
||||||
/.watchmanconfig
|
/.watchmanconfig
|
||||||
|
|
||||||
|
web/index.html
|
||||||
|
web-build/*
|
||||||
|
|
|
@ -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
|
|
@ -1,3 +1,4 @@
|
||||||
/static/bundle.web.js
|
|
||||||
/bskyweb
|
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
# Don't check in the binary.
|
||||||
|
/bskyweb
|
||||||
|
|
|
@ -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),
|
||||||
|
})
|
||||||
|
}
|
|
@ -35,33 +35,57 @@ func run(args []string) {
|
||||||
Usage: "web server for bsky.app web app (SPA)",
|
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{
|
app.Commands = []*cli.Command{
|
||||||
&cli.Command{
|
&cli.Command{
|
||||||
Name: "serve",
|
Name: "serve",
|
||||||
Usage: "run the server",
|
Usage: "run the server",
|
||||||
Action: serve,
|
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()
|
app.RunAndExitOnError()
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -2,15 +2,17 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
comatproto "github.com/bluesky-social/indigo/api/atproto"
|
comatproto "github.com/bluesky-social/indigo/api/atproto"
|
||||||
appbsky "github.com/bluesky-social/indigo/api/bsky"
|
appbsky "github.com/bluesky-social/indigo/api/bsky"
|
||||||
cliutil "github.com/bluesky-social/indigo/cmd/gosky/util"
|
cliutil "github.com/bluesky-social/indigo/cmd/gosky/util"
|
||||||
"github.com/bluesky-social/indigo/xrpc"
|
"github.com/bluesky-social/indigo/xrpc"
|
||||||
|
"github.com/bluesky-social/social-app/bskyweb"
|
||||||
|
|
||||||
"github.com/flosch/pongo2/v6"
|
"github.com/flosch/pongo2/v6"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
@ -18,60 +20,35 @@ import (
|
||||||
"github.com/urfave/cli/v2"
|
"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 {
|
type Server struct {
|
||||||
xrpcc *xrpc.Client
|
xrpcc *xrpc.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func serve(cctx *cli.Context) error {
|
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
|
// create a new session
|
||||||
// TODO: does this work with no auth at all?
|
// TODO: does this work with no auth at all?
|
||||||
xrpcc := &xrpc.Client{
|
xrpcc := &xrpc.Client{
|
||||||
Client: cliutil.NewHttpClient(),
|
Client: cliutil.NewHttpClient(),
|
||||||
Host: cctx.String("pds-host"),
|
Host: pdsHost,
|
||||||
Auth: &xrpc.AuthInfo{
|
Auth: &xrpc.AuthInfo{
|
||||||
Handle: cctx.String("handle"),
|
Handle: atpHandle,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
auth, err := comatproto.SessionCreate(context.TODO(), xrpcc, &comatproto.SessionCreate_Input{
|
auth, err := comatproto.SessionCreate(context.TODO(), xrpcc, &comatproto.SessionCreate_Input{
|
||||||
Identifier: &xrpcc.Auth.Handle,
|
Identifier: &xrpcc.Auth.Handle,
|
||||||
Password: cctx.String("password"),
|
Password: atpPassword,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -83,19 +60,32 @@ func serve(cctx *cli.Context) error {
|
||||||
|
|
||||||
server := Server{xrpcc}
|
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 := echo.New()
|
||||||
e.HideBanner = true
|
e.HideBanner = true
|
||||||
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
|
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",
|
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
|
e.HTTPErrorHandler = customHTTPErrorHandler
|
||||||
|
|
||||||
// configure routes
|
// configure routes
|
||||||
e.File("/robots.txt", "static/robots.txt")
|
e.GET("/robots.txt", echo.WrapHandler(staticHandler))
|
||||||
e.Static("/static", "static")
|
e.GET("/static/*", echo.WrapHandler(http.StripPrefix("/static/", staticHandler)))
|
||||||
e.Static("/static/js", "../web-build/static/js")
|
|
||||||
|
|
||||||
e.GET("/", server.WebHome)
|
e.GET("/", server.WebHome)
|
||||||
|
|
||||||
// generic routes
|
// 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/downvoted-by", server.WebGeneric)
|
||||||
e.GET("/profile/:handle/post/:rkey/reposted-by", server.WebGeneric)
|
e.GET("/profile/:handle/post/:rkey/reposted-by", server.WebGeneric)
|
||||||
|
|
||||||
bind := "localhost:8100"
|
// Mailmodo
|
||||||
log.Infof("starting server bind=%s", bind)
|
e.POST("/waitlist", func(c echo.Context) error {
|
||||||
return e.Start(bind)
|
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) {
|
func customHTTPErrorHandler(err error, c echo.Context) {
|
||||||
|
@ -132,18 +130,18 @@ func customHTTPErrorHandler(err error, c echo.Context) {
|
||||||
data := pongo2.Context{
|
data := pongo2.Context{
|
||||||
"statusCode": code,
|
"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
|
// handler for endpoint that have no specific server-side handling
|
||||||
func (srv *Server) WebGeneric(c echo.Context) error {
|
func (srv *Server) WebGeneric(c echo.Context) error {
|
||||||
data := pongo2.Context{}
|
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 {
|
func (srv *Server) WebHome(c echo.Context) error {
|
||||||
data := pongo2.Context{}
|
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 {
|
func (srv *Server) WebPost(c echo.Context) error {
|
||||||
|
@ -152,7 +150,7 @@ func (srv *Server) WebPost(c echo.Context) error {
|
||||||
rkey := c.Param("rkey")
|
rkey := c.Param("rkey")
|
||||||
// sanity check argument
|
// sanity check argument
|
||||||
if len(handle) > 4 && len(handle) < 128 && len(rkey) > 0 {
|
if len(handle) > 4 && len(handle) < 128 && len(rkey) > 0 {
|
||||||
ctx := context.TODO()
|
ctx := c.Request().Context()
|
||||||
// requires two fetches: first fetch profile (!)
|
// requires two fetches: first fetch profile (!)
|
||||||
pv, err := appbsky.ActorGetProfile(ctx, srv.xrpcc, handle)
|
pv, err := appbsky.ActorGetProfile(ctx, srv.xrpcc, handle)
|
||||||
if err != nil {
|
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 {
|
func (srv *Server) WebProfile(c echo.Context) error {
|
||||||
|
@ -180,7 +178,7 @@ func (srv *Server) WebProfile(c echo.Context) error {
|
||||||
handle := c.Param("handle")
|
handle := c.Param("handle")
|
||||||
// sanity check argument
|
// sanity check argument
|
||||||
if len(handle) > 4 && len(handle) < 128 {
|
if len(handle) > 4 && len(handle) < 128 {
|
||||||
ctx := context.TODO()
|
ctx := c.Request().Context()
|
||||||
pv, err := appbsky.ActorGetProfile(ctx, srv.xrpcc, handle)
|
pv, err := appbsky.ActorGetProfile(ctx, srv.xrpcc, handle)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("failed to fetch handle: %s\t%v", handle, err)
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bluesky-social/indigo v0.0.0-20230307000525-294e33e70185
|
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/ipfs/go-log v1.0.5
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/labstack/echo/v4 v4.10.2
|
github.com/labstack/echo/v4 v4.10.2
|
||||||
|
@ -13,7 +14,6 @@ require (
|
||||||
require (
|
require (
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // 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/logr v1.2.3 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/goccy/go-json v0.10.0 // indirect
|
github.com/goccy/go-json v0.10.0 // indirect
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
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 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
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 h1:WnaOpRFWE8Tmw0IeXEEthsqBZtNG6/niokmWANv/aEU=
|
||||||
github.com/bluesky-social/indigo v0.0.0-20230307000525-294e33e70185/go.mod h1:xy2hI4NMC6fgUefSJcCst6E0yo9Xbfd97aF27lgHyHE=
|
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=
|
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/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.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
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/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
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.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/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 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M=
|
||||||
github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=
|
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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.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/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 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
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=
|
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 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-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/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
package bskyweb
|
||||||
|
|
||||||
|
import "embed"
|
||||||
|
|
||||||
|
//go:embed static/*
|
||||||
|
var StaticFS embed.FS
|
|
@ -0,0 +1 @@
|
||||||
|
NOOP
|
|
@ -0,0 +1,6 @@
|
||||||
|
package bskyweb
|
||||||
|
|
||||||
|
import "embed"
|
||||||
|
|
||||||
|
//go:embed templates/*
|
||||||
|
var TemplateFS embed.FS
|
|
@ -1,2 +0,0 @@
|
||||||
<script defer="defer" src="/static/js/412.e47ad7b9.js"></script>
|
|
||||||
<script defer="defer" src="/static/js/main.f526ceaa.js"></script>
|
|
Loading…
Reference in New Issue