Skip to content

Commit 1fdaa46

Browse files
committed
Move a lot of stuff around
1 parent 9f7eca6 commit 1fdaa46

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+644
-379
lines changed

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
/dist
2-
/server/static/app.css
3-
/server/static/app.css.map
2+
/server/assets/static/app.css
3+
/server/assets/static/app.css.map

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ server/static/app.css: $(wildcard scss/*.scss)
3939
sass scss/app.scss $@
4040

4141
.PHONY: assets
42-
assets: server/static/app.css
42+
assets: server/assets/static/app.css
4343

4444
.PHONY: watch-assets
4545
watch-assets:
46-
sass --watch scss/app.scss:server/static/app.css
46+
sass --watch scss/app.scss:server/assets/static/app.css
4747

4848
.PHONY: test
4949
test:

cmd/server/main.go

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,34 +10,27 @@ import (
1010
"net/http"
1111
"os"
1212
"os/signal"
13+
"strconv"
1314
"time"
1415

1516
"github.com/chriskuehl/fluffy/server"
17+
"github.com/chriskuehl/fluffy/server/config"
1618
"github.com/chriskuehl/fluffy/server/logging"
1719
)
1820

1921
var Version = "(dev)"
2022

21-
type config struct {
22-
*server.Config
23-
24-
host string
25-
port string
26-
}
27-
28-
func newConfigFromArgs(args []string) (*config, error) {
29-
c := config{
30-
Config: server.NewConfig(),
31-
}
23+
func newConfigFromArgs(args []string) (*config.Config, error) {
24+
c := server.NewConfig()
3225
fs := flag.NewFlagSet("fluffy", flag.ExitOnError)
33-
fs.StringVar(&c.host, "host", "localhost", "host to listen on")
34-
fs.StringVar(&c.port, "port", "8080", "port to listen on")
26+
fs.StringVar(&c.Host, "host", "localhost", "host to listen on")
27+
fs.UintVar(&c.Port, "port", 8080, "port to listen on")
3528
fs.BoolVar(&c.DevMode, "dev", false, "enable dev mode")
3629
if err := fs.Parse(args); err != nil {
3730
return nil, err
3831
}
3932
c.Version = Version
40-
return &c, nil
33+
return c, nil
4134
}
4235

4336
func run(ctx context.Context, w io.Writer, args []string) error {
@@ -51,13 +44,13 @@ func run(ctx context.Context, w io.Writer, args []string) error {
5144

5245
logger := logging.NewSlogLogger(slog.New(slog.NewTextHandler(w, nil)))
5346

54-
handler, err := server.NewServer(logger, config.Config)
47+
handler, err := server.NewServer(logger, config)
5548
if err != nil {
5649
return fmt.Errorf("creating server: %w", err)
5750
}
5851

5952
httpServer := &http.Server{
60-
Addr: net.JoinHostPort(config.host, config.port),
53+
Addr: net.JoinHostPort(config.Host, strconv.FormatUint(uint64(config.Port), 10)),
6154
Handler: handler,
6255
}
6356
go func() {

server/assets.go

Lines changed: 0 additions & 123 deletions
This file was deleted.

server/assets/assets.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package assets
2+
3+
import (
4+
"crypto/sha256"
5+
"embed"
6+
"encoding/hex"
7+
"fmt"
8+
"io"
9+
"io/fs"
10+
"path/filepath"
11+
"strings"
12+
13+
"github.com/chriskuehl/fluffy/server/config"
14+
)
15+
16+
//go:embed static/*
17+
var assetsFS embed.FS
18+
var assetHash = make(map[string]string)
19+
20+
var mimeExtensions = []string{}
21+
22+
func init() {
23+
if err := fs.WalkDir(assetsFS, "static", func(path string, d fs.DirEntry, err error) error {
24+
if d.IsDir() {
25+
return nil
26+
}
27+
28+
f, err := assetsFS.Open(path)
29+
if err != nil {
30+
return fmt.Errorf("opening %q: %w", path, err)
31+
}
32+
defer f.Close()
33+
34+
h := sha256.New()
35+
if _, err := io.Copy(h, f); err != nil {
36+
return fmt.Errorf("hashing %q: %w", path, err)
37+
}
38+
assetHash[path] = hex.EncodeToString(h.Sum(nil))
39+
return nil
40+
}); err != nil {
41+
panic("loading asset hashes: " + err.Error())
42+
}
43+
44+
if err := fs.WalkDir(assetsFS, "static/img/mime/small", func(path string, d fs.DirEntry, err error) error {
45+
if d.IsDir() {
46+
return nil
47+
}
48+
if !strings.HasSuffix(path, ".png") {
49+
return nil
50+
}
51+
52+
name := filepath.Base(path)
53+
mimeExtensions = append(mimeExtensions, name[:len(name)-len(".png")])
54+
return nil
55+
}); err != nil {
56+
panic("loading mime extensions: " + err.Error())
57+
}
58+
}
59+
60+
// AssetObjectPath returns the path to the asset in the object store.
61+
//
62+
// Keep in mind the object may not exist yet depending on when this function is called.
63+
func assetObjectPath(path, hash string) string {
64+
return filepath.Join("static", hash, path)
65+
}
66+
67+
// AssetURL returns the URL to the asset.
68+
//
69+
// In development mode, this will return a URL served by the fluffy server itself. In production,
70+
// this will return a URL to the object store.
71+
func AssetURL(c *config.Config, path string) (string, error) {
72+
if c.DevMode {
73+
url := c.HomeURL
74+
url.Path = "/dev/static/" + path
75+
return url.String(), nil
76+
}
77+
78+
hash, ok := assetHash[filepath.Join("static", path)]
79+
if !ok {
80+
return "", fmt.Errorf("asset not found: %s", path)
81+
}
82+
url := c.ObjectURLPattern
83+
url.Path = fmt.Sprintf(url.Path, assetObjectPath(path, hash))
84+
return url.String(), nil
85+
}
86+
87+
// AssetAsString returns the contents of the asset as a string.
88+
func AssetAsString(path string) (string, error) {
89+
data, err := fs.ReadFile(assetsFS, filepath.Join("static", path))
90+
if err != nil {
91+
return "", fmt.Errorf("reading asset: %w", err)
92+
}
93+
return string(data), nil
94+
}
95+
96+
// MimeExtensions returns a list of all the mime extensions, without dot, e.g. "png", "jpg".
97+
func MimeExtensions() []string {
98+
return mimeExtensions
99+
}

server/assets/dev.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package assets
2+
3+
import (
4+
"net/http"
5+
"strings"
6+
7+
"github.com/chriskuehl/fluffy/server/config"
8+
"github.com/chriskuehl/fluffy/server/logging"
9+
)
10+
11+
func HandleDevStatic(config *config.Config, logger logging.Logger) http.HandlerFunc {
12+
if !config.DevMode {
13+
return func(w http.ResponseWriter, r *http.Request) {
14+
logger.Warn(r.Context(), "assets cannot be served from the server in production")
15+
w.WriteHeader(http.StatusNotFound)
16+
w.Write([]byte("Assets cannot be served from the server in production.\n"))
17+
}
18+
}
19+
20+
return func(w http.ResponseWriter, r *http.Request) {
21+
strippedReq := r.Clone(r.Context())
22+
strippedReq.URL.Path = strings.TrimPrefix(strippedReq.URL.Path, "/dev")
23+
http.FileServer(http.FS(assetsFS)).ServeHTTP(w, strippedReq)
24+
}
25+
}
File renamed without changes.
File renamed without changes.
File renamed without changes.

server/config/config.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package config
2+
3+
import (
4+
"context"
5+
"html/template"
6+
"net/url"
7+
"strings"
8+
9+
"github.com/chriskuehl/fluffy/server/storage/storagedata"
10+
)
11+
12+
type StorageBackend interface {
13+
StoreObject(ctx context.Context, obj storagedata.Object) error
14+
StoreHTML(ctx context.Context, obj storagedata.Object) error
15+
}
16+
17+
type Config struct {
18+
19+
// Site configuration.
20+
StorageBackend StorageBackend
21+
Branding string
22+
CustomFooterHTML template.HTML
23+
AbuseContactEmail string
24+
MaxUploadBytes int64
25+
MaxMultipartMemoryBytes int64
26+
HomeURL url.URL
27+
ObjectURLPattern url.URL
28+
HTMLURLPattern url.URL
29+
ForbiddenFileExtensions map[string]struct{}
30+
31+
// Runtime options.
32+
Host string
33+
Port uint
34+
DevMode bool
35+
Version string
36+
}
37+
38+
func (c *Config) Validate() []string {
39+
var errs []string
40+
if c.Branding == "" {
41+
errs = append(errs, "Branding must not be empty")
42+
}
43+
if c.AbuseContactEmail == "" {
44+
errs = append(errs, "AbuseContactEmail must not be empty")
45+
}
46+
if c.MaxUploadBytes <= 0 {
47+
errs = append(errs, "MaxUploadBytes must be greater than 0")
48+
}
49+
if c.MaxMultipartMemoryBytes <= 0 {
50+
errs = append(errs, "MaxMultipartMemoryBytes must be greater than 0")
51+
}
52+
if strings.HasSuffix(c.HomeURL.Path, "/") {
53+
errs = append(errs, "HomeURL must not end with a slash")
54+
}
55+
if !strings.Contains(c.ObjectURLPattern.Path, "%s") {
56+
errs = append(errs, "ObjectURLPattern must contain a '%s' placeholder")
57+
}
58+
if !strings.Contains(c.HTMLURLPattern.Path, "%s") {
59+
errs = append(errs, "HTMLURLPattern must contain a '%s' placeholder")
60+
}
61+
for ext := range c.ForbiddenFileExtensions {
62+
if strings.HasPrefix(ext, ".") {
63+
errs = append(errs, "ForbiddenFileExtensions should not start with a dot: "+ext)
64+
}
65+
}
66+
if c.Version == "" {
67+
errs = append(errs, "Version must not be empty")
68+
}
69+
return errs
70+
}

0 commit comments

Comments
 (0)