~sircmpwn/sr.ht-dev

pages.sr.ht: Add support for custom 404.html page v6 APPLIED

Dhruvin Gandhi: 1
 Add support for custom 404.html page

 8 files changed, 128 insertions(+), 16 deletions(-)
#684816 alpine.yml success
#684817 archlinux.yml success
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~sircmpwn/sr.ht-dev/patches/28884/mbox | git am -3
Learn more about email & git

[PATCH pages.sr.ht v6] Add support for custom 404.html page Export this patch

---
 gqlgen.yml                       |  3 ++
 graph/api/generated.go           | 75 +++++++++++++++++++++++++++++---
 graph/model/site.go              |  2 +
 graph/schema.graphqls            |  9 +++-
 graph/schema.resolvers.go        | 34 ++++++++++++---
 migrations/003-add-not-found.sql |  7 +++
 schema.sql                       |  3 +-
 server.go                        | 11 +++--
 8 files changed, 128 insertions(+), 16 deletions(-)
 create mode 100644 migrations/003-add-not-found.sql

diff --git a/gqlgen.yml b/gqlgen.yml
index 33d349a..28cd866 100644
--- a/gqlgen.yml
+++ b/gqlgen.yml
@@ -60,3 +60,6 @@ models:
  Filter:
    model:
      - git.sr.ht/~sircmpwn/core-go/model.Filter
  SiteConfig:
    model:
      - "map[string]interface{}"
diff --git a/graph/api/generated.go b/graph/api/generated.go
index b3e870a..161fa86 100644
--- a/graph/api/generated.go
+++ b/graph/api/generated.go
@@ -49,7 +49,7 @@ type DirectiveRoot struct {

type ComplexityRoot struct {
	Mutation struct {
		Publish   func(childComplexity int, domain string, content graphql.Upload, protocol *model.Protocol, subdirectory *string) int
		Publish   func(childComplexity int, domain string, content graphql.Upload, protocol *model.Protocol, subdirectory *string, siteConfig map[string]interface{}) int
		Unpublish func(childComplexity int, domain string, protocol *model.Protocol) int
	}

@@ -63,6 +63,7 @@ type ComplexityRoot struct {
		Created  func(childComplexity int) int
		Domain   func(childComplexity int) int
		ID       func(childComplexity int) int
		NotFound func(childComplexity int) int
		Protocol func(childComplexity int) int
		Updated  func(childComplexity int) int
		Version  func(childComplexity int) int
@@ -94,7 +95,7 @@ type ComplexityRoot struct {
}

type MutationResolver interface {
	Publish(ctx context.Context, domain string, content graphql.Upload, protocol *model.Protocol, subdirectory *string) (*model.Site, error)
	Publish(ctx context.Context, domain string, content graphql.Upload, protocol *model.Protocol, subdirectory *string, siteConfig map[string]interface{}) (*model.Site, error)
	Unpublish(ctx context.Context, domain string, protocol *model.Protocol) (*model.Site, error)
}
type QueryResolver interface {
@@ -128,7 +129,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
			return 0, false
		}

		return e.complexity.Mutation.Publish(childComplexity, args["domain"].(string), args["content"].(graphql.Upload), args["protocol"].(*model.Protocol), args["subdirectory"].(*string)), true
		return e.complexity.Mutation.Publish(childComplexity, args["domain"].(string), args["content"].(graphql.Upload), args["protocol"].(*model.Protocol), args["subdirectory"].(*string), args["siteConfig"].(map[string]interface{})), true

	case "Mutation.unpublish":
		if e.complexity.Mutation.Unpublish == nil {
@@ -189,6 +190,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in

		return e.complexity.Site.ID(childComplexity), true

	case "Site.notFound":
		if e.complexity.Site.NotFound == nil {
			break
		}

		return e.complexity.Site.NotFound(childComplexity), true

	case "Site.protocol":
		if e.complexity.Site.Protocol == nil {
			break
@@ -455,6 +463,8 @@ type Site {
  protocol: Protocol!
  "SHA-256 checksum of the source tarball (uncompressed)"
  version: String!
  "Path to the file to serve for 404 Not Found responses"
  notFound: String
}

"""
@@ -469,6 +479,11 @@ type SiteCursor {
  cursor: Cursor
}

input SiteConfig {
  "Path to the file to serve for 404 Not Found responses"
  notFound: String
}

type Query {
  "Returns API version information."
  version: Version!
@@ -503,7 +518,7 @@ type Mutation {
  of the files are unchanged.
  """
  publish(domain: String!, content: Upload!, protocol: Protocol,
    subdirectory: String): Site! @access(scope: PAGES, kind: RW)
    subdirectory: String, siteConfig: SiteConfig): Site! @access(scope: PAGES, kind: RW)

  """
  Deletes a previously published website.
@@ -598,6 +613,15 @@ func (ec *executionContext) field_Mutation_publish_args(ctx context.Context, raw
		}
	}
	args["subdirectory"] = arg3
	var arg4 map[string]interface{}
	if tmp, ok := rawArgs["siteConfig"]; ok {
		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("siteConfig"))
		arg4, err = ec.unmarshalOSiteConfig2map(ctx, tmp)
		if err != nil {
			return nil, err
		}
	}
	args["siteConfig"] = arg4
	return args, nil
}

@@ -719,7 +743,7 @@ func (ec *executionContext) _Mutation_publish(ctx context.Context, field graphql
	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
		directive0 := func(rctx context.Context) (interface{}, error) {
			ctx = rctx // use context from middleware stack in children
			return ec.resolvers.Mutation().Publish(rctx, args["domain"].(string), args["content"].(graphql.Upload), args["protocol"].(*model.Protocol), args["subdirectory"].(*string))
			return ec.resolvers.Mutation().Publish(rctx, args["domain"].(string), args["content"].(graphql.Upload), args["protocol"].(*model.Protocol), args["subdirectory"].(*string), args["siteConfig"].(map[string]interface{}))
		}
		directive1 := func(ctx context.Context) (interface{}, error) {
			scope, err := ec.unmarshalNAccessScope2gitᚗsrᚗhtᚋאsircmpwnᚋpagesᚗsrᚗhtᚋgraphᚋmodelᚐAccessScope(ctx, "PAGES")
@@ -1279,6 +1303,38 @@ func (ec *executionContext) _Site_version(ctx context.Context, field graphql.Col
	return ec.marshalNString2string(ctx, field.Selections, res)
}

func (ec *executionContext) _Site_notFound(ctx context.Context, field graphql.CollectedField, obj *model.Site) (ret graphql.Marshaler) {
	defer func() {
		if r := recover(); r != nil {
			ec.Error(ctx, ec.Recover(ctx, r))
			ret = graphql.Null
		}
	}()
	fc := &graphql.FieldContext{
		Object:     "Site",
		Field:      field,
		Args:       nil,
		IsMethod:   false,
		IsResolver: false,
	}

	ctx = graphql.WithFieldContext(ctx, fc)
	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
		ctx = rctx // use context from middleware stack in children
		return obj.NotFound, nil
	})
	if err != nil {
		ec.Error(ctx, err)
		return graphql.Null
	}
	if resTmp == nil {
		return graphql.Null
	}
	res := resTmp.(*string)
	fc.Result = res
	return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}

func (ec *executionContext) _SiteCursor_results(ctx context.Context, field graphql.CollectedField, obj *model.SiteCursor) (ret graphql.Marshaler) {
	defer func() {
		if r := recover(); r != nil {
@@ -3046,6 +3102,8 @@ func (ec *executionContext) _Site(ctx context.Context, sel ast.SelectionSet, obj
			if out.Values[i] == graphql.Null {
				invalids++
			}
		case "notFound":
			out.Values[i] = ec._Site_notFound(ctx, field, obj)
		default:
			panic("unknown field " + strconv.Quote(field.Name))
		}
@@ -3918,6 +3976,13 @@ func (ec *executionContext) marshalOSite2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋpages
	return ec._Site(ctx, sel, v)
}

func (ec *executionContext) unmarshalOSiteConfig2map(ctx context.Context, v interface{}) (map[string]interface{}, error) {
	if v == nil {
		return nil, nil
	}
	return v.(map[string]interface{}), nil
}

func (ec *executionContext) unmarshalOString2string(ctx context.Context, v interface{}) (string, error) {
	res, err := graphql.UnmarshalString(v)
	return res, graphql.ErrorOnPath(ctx, err)
diff --git a/graph/model/site.go b/graph/model/site.go
index 5370447..3f3fd58 100644
--- a/graph/model/site.go
+++ b/graph/model/site.go
@@ -19,6 +19,7 @@ type Site struct {
	Domain   string    `json:"domain"`
	Protocol Protocol  `json:"protocol"`
	Version  string    `json:"version"`
	NotFound *string   `json:"notFound"`

	alias  string
	fields *database.ModelFields
@@ -49,6 +50,7 @@ func (site *Site) Fields() *database.ModelFields {
			{"domain", "domain", &site.Domain},
			{"protocol", "protocol", &site.Protocol},
			{"version", "version", &site.Version},
			{"not_found", "notFound", &site.NotFound},

			// Always fetch:
			{"id", "", &site.ID},
diff --git a/graph/schema.graphqls b/graph/schema.graphqls
index 393e33f..90935eb 100644
--- a/graph/schema.graphqls
+++ b/graph/schema.graphqls
@@ -74,6 +74,8 @@ type Site {
  protocol: Protocol!
  "SHA-256 checksum of the source tarball (uncompressed)"
  version: String!
  "Path to the file to serve for 404 Not Found responses"
  notFound: String
}

"""
@@ -88,6 +90,11 @@ type SiteCursor {
  cursor: Cursor
}

input SiteConfig {
  "Path to the file to serve for 404 Not Found responses"
  notFound: String
}

type Query {
  "Returns API version information."
  version: Version!
@@ -122,7 +129,7 @@ type Mutation {
  of the files are unchanged.
  """
  publish(domain: String!, content: Upload!, protocol: Protocol,
    subdirectory: String): Site! @access(scope: PAGES, kind: RW)
    subdirectory: String, siteConfig: SiteConfig): Site! @access(scope: PAGES, kind: RW)

  """
  Deletes a previously published website.
diff --git a/graph/schema.resolvers.go b/graph/schema.resolvers.go
index f88e771..7e9162d 100644
--- a/graph/schema.resolvers.go
+++ b/graph/schema.resolvers.go
@@ -30,7 +30,7 @@ import (
	minio "github.com/minio/minio-go/v7"
)

func (r *mutationResolver) Publish(ctx context.Context, domain string, content graphql.Upload, protocol *model.Protocol, subdirectory *string) (*model.Site, error) {
func (r *mutationResolver) Publish(ctx context.Context, domain string, content graphql.Upload, protocol *model.Protocol, subdirectory *string, siteConfig map[string]interface{}) (*model.Site, error) {
	conf := config.ForContext(ctx)
	bucket, _ := conf.Get("pages.sr.ht", "s3-bucket")
	prefix, _ := conf.Get("pages.sr.ht", "s3-prefix")
@@ -62,6 +62,12 @@ func (r *mutationResolver) Publish(ctx context.Context, domain string, content g
		}
	}

	if value, ok := siteConfig["notFound"].(string); ok {
		if value == "" {
			return nil, fmt.Errorf("Invalid path siteConfig.notFound")
		}
	}

	if strings.HasSuffix(domain, "."+userDomain) {
		user := strings.TrimSuffix(domain, "."+userDomain)
		if user != auth.ForContext(ctx).Username {
@@ -130,11 +136,11 @@ func (r *mutationResolver) Publish(ctx context.Context, domain string, content g
			ON CONFLICT ON CONSTRAINT sites_domain_protocol_key
				DO UPDATE SET updated = NOW() at time zone 'utc'
				WHERE sites.user_id = $1
			RETURNING id, created, updated, domain, protocol, version;`,
			RETURNING id, created, updated, domain, protocol, version, not_found;`,
			auth.ForContext(ctx).UserID, domain, proto)
		if err := row.Scan(&site.ID, &site.Created,
			&site.Updated, &site.Domain, &site.Protocol,
			&site.Version); err != nil {
			&site.Version, &site.NotFound); err != nil {
			if err == sql.ErrNoRows {
				return fmt.Errorf("This domain is not available")
			}
@@ -149,6 +155,22 @@ func (r *mutationResolver) Publish(ctx context.Context, domain string, content g
			io.WriteString(sha, site.Version+"\n")
		}

		notFound := site.NotFound
		if value, ok := siteConfig["notFound"]; ok {
			switch value.(type) {
			case nil:
				notFound = nil
			case string:
				value := path.Join("/", value.(string))
				notFound = &value
			default:
				panic("GraphQL schema validation broken")
			}
		}
		if notFound != nil {
			io.WriteString(sha, *notFound+"\n")
		}

		inputReader := io.TeeReader(content.File, sha)
		gzipReader, err := gzip.NewReader(inputReader)
		if err != nil {
@@ -183,9 +205,9 @@ func (r *mutationResolver) Publish(ctx context.Context, domain string, content g
		prevPath := path.Join(prefix, "sites", domain, site.Version)

		row = tx.QueryRowContext(ctx, `
			UPDATE sites SET version = $1 WHERE id = $2 RETURNING version;`,
			shahex, site.ID)
		if err := row.Scan(&site.Version); err != nil {
			UPDATE sites SET version = $1, not_found = $2 WHERE id = $3 RETURNING version, not_found;`,
			shahex, notFound, site.ID)
		if err := row.Scan(&site.Version, &site.NotFound); err != nil {
			return err
		}

diff --git a/migrations/003-add-not-found.sql b/migrations/003-add-not-found.sql
new file mode 100644
index 0000000..dbc47b3
--- /dev/null
+++ b/migrations/003-add-not-found.sql
@@ -0,0 +1,7 @@
BEGIN;

ALTER TABLE sites ADD COLUMN not_found varchar;

UPDATE schema_version SET id = 3;

COMMIT;
diff --git a/schema.sql b/schema.sql
index 2e473ea..1a27f0c 100644
--- a/schema.sql
+++ b/schema.sql
@@ -4,7 +4,7 @@ CREATE TABLE schema_version (
	id integer NOT NULL
);

INSERT INTO schema_version VALUES (2);
INSERT INTO schema_version VALUES (3);

CREATE TABLE "user" (
	id serial PRIMARY KEY,
@@ -29,6 +29,7 @@ CREATE TABLE sites (
	domain varchar NOT NULL,
	protocol protocol NOT NULL,
	version varchar NOT NULL,
	not_found varchar,
	UNIQUE (domain, protocol)
);

diff --git a/server.go b/server.go
index ef51d2f..841232f 100644
--- a/server.go
+++ b/server.go
@@ -141,7 +141,7 @@ func main() {
					File:     f,
					Filename: h.Filename,
					Size:     h.Size,
				}, &protocol, &subdir)
				}, &protocol, &subdir, nil)
			if err != nil {
				http.Error(w, err.Error(), 400)
				return
@@ -176,17 +176,18 @@ func ServeHTTP(conf ini.File, db *sql.DB, mc *minio.Client) *http.Server {
				strings.Join(sandbox, " ")))

		var version string
		var notFound *string
		ctx := database.Context(r.Context(), db)
		if err := database.WithTx(ctx, &sql.TxOptions{
			ReadOnly:  true,
			Isolation: 0,
		}, func(tx *sql.Tx) error {
			row := tx.QueryRowContext(ctx, `
				SELECT version
				SELECT version, not_found
				FROM sites
				WHERE domain = $1 AND protocol = 'https';
			`, r.Host)
			if err := row.Scan(&version); err != nil {
			if err := row.Scan(&version, &notFound); err != nil {
				return err
			}
			return nil
@@ -202,6 +203,10 @@ func ServeHTTP(conf ini.File, db *sql.DB, mc *minio.Client) *http.Server {
			path.Join(r.URL.Path, "index.html"),
		}

		if notFound != nil {
			paths = append(paths, *notFound)
		}

		var object *minio.Object
		for _, cand := range paths {
			s3path := path.Join(prefix, "sites", r.Host, version, cand)
-- 
2.35.1
pages.sr.ht/patches: SUCCESS in 1m28s

[Add support for custom 404.html page][0] v6 from [Dhruvin Gandhi][1]

[0]: https://lists.sr.ht/~sircmpwn/sr.ht-dev/patches/28884
[1]: mailto:contact@dhruvin.dev

✓ #684817 SUCCESS pages.sr.ht/patches/archlinux.yml https://builds.sr.ht/~sircmpwn/job/684817
✓ #684816 SUCCESS pages.sr.ht/patches/alpine.yml    https://builds.sr.ht/~sircmpwn/job/684816
Thanks!

To git@git.sr.ht:~sircmpwn/pages.sr.ht
   e50e698..9cd00b9  master -> master