~emersion/hut-dev

pages publish: Add subdirectory support v1 APPLIED

Thanks for developing and maintaining hut. This is my first contribution
to it. I read the existing code before working on this patch.
Suggestions are welcome!

This patch adds support for subdirectory flag to pages publish. I split
the patch into two parts. One is to update the schema, and the other is
to implement the flag. While changing the schema I see that the code
generation is adding the comments in a different way than it used to.

I am uncertain about the use of String! vs String for subdirectory flag.
In this patch, I went with String!. Following the schema, it should be
String, but since we pass "/" as the default value of subdirectory (if
not provided), String! should be fine too.

The only example in favor of String (over String!) I found in the code
is in note flag of build job submission. Rest are String!.

Dhruvin Gandhi (2):
  pages: Update the schema
  pages publish: Add subdirectory flag

 pages.go                          |   5 +-
 srht/pagessrht/gql.go             |  54 ++++++++++-----
 srht/pagessrht/operations.graphql |   4 +-
 srht/pagessrht/schema.graphqls    | 107 ++++++++++++++++++------------
 4 files changed, 106 insertions(+), 64 deletions(-)

-- 
2.35.1
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/~emersion/hut-dev/patches/29739/mbox | git am -3
Learn more about email & git

[PATCH 1/2] pages: Update the schema Export this patch

---
 srht/pagessrht/gql.go          |  49 ++++++++++-----
 srht/pagessrht/schema.graphqls | 107 +++++++++++++++++++--------------
 2 files changed, 98 insertions(+), 58 deletions(-)

diff --git a/srht/pagessrht/gql.go b/srht/pagessrht/gql.go
index e3bdb8e..bbabce4 100644
--- a/srht/pagessrht/gql.go
+++ b/srht/pagessrht/gql.go
@@ -26,10 +26,12 @@ const (
type Cursor string

type Entity struct {
	Id            int32     `json:"id"`
	Created       time.Time `json:"created"`
	Updated       time.Time `json:"updated"`
	CanonicalName string    `json:"canonicalName"`
	Id      int32     `json:"id"`
	Created time.Time `json:"created"`
	Updated time.Time `json:"updated"`
	// The canonical name of this entity. For users, this is their username
	// prefixed with '~'. Additional entity types will be supported in the future.
	CanonicalName string `json:"canonicalName"`
}

type Protocol string
@@ -39,17 +41,33 @@ const (
	ProtocolGemini Protocol = "GEMINI"
)

// A published website
type Site struct {
	Id       int32     `json:"id"`
	Created  time.Time `json:"created"`
	Updated  time.Time `json:"updated"`
	Domain   string    `json:"domain"`
	Protocol Protocol  `json:"protocol"`
	Version  string    `json:"version"`
	Id      int32     `json:"id"`
	Created time.Time `json:"created"`
	Updated time.Time `json:"updated"`
	// Domain name the site services
	Domain string `json:"domain"`
	// The site protocol
	Protocol Protocol `json:"protocol"`
	// SHA-256 checksum of the source tarball (uncompressed)
	Version string `json:"version"`
	// Path to the file to serve for 404 Not Found responses
	NotFound *string `json:"notFound,omitempty"`
}

type SiteConfig struct {
	// Path to the file to serve for 404 Not Found responses
	NotFound *string `json:"notFound,omitempty"`
}

// A cursor for enumerating site entries
//
// If there are additional results available, the cursor object may be passed
// back into the same endpoint to retrieve another page. If the cursor is null,
// there are no remaining results to return.
type SiteCursor struct {
	Results []*Site `json:"results"`
	Results []Site  `json:"results"`
	Cursor  *Cursor `json:"cursor,omitempty"`
}

@@ -66,9 +84,12 @@ type User struct {
}

type Version struct {
	Major           int32     `json:"major"`
	Minor           int32     `json:"minor"`
	Patch           int32     `json:"patch"`
	Major int32 `json:"major"`
	Minor int32 `json:"minor"`
	Patch int32 `json:"patch"`
	// If this API version is scheduled for deprecation, this is the date on which
	// it will stop working; or null if this API version is not scheduled for
	// deprecation.
	DeprecationDate time.Time `json:"deprecationDate,omitempty"`
}

diff --git a/srht/pagessrht/schema.graphqls b/srht/pagessrht/schema.graphqls
index d2c03c4..90935eb 100644
--- a/srht/pagessrht/schema.graphqls
+++ b/srht/pagessrht/schema.graphqls
@@ -2,7 +2,7 @@ scalar Cursor
scalar Time
scalar Upload

# Used to provide a human-friendly description of an access scope
"Used to provide a human-friendly description of an access scope"
directive @scopehelp(details: String!) on ENUM_VALUE

enum AccessScope {
@@ -16,8 +16,10 @@ enum AccessKind {
  RW @scopehelp(details: "read and write")
}

# Decorates fields for which access requires a particular OAuth 2.0 scope with
# read or write access.
"""
Decorates fields for which access requires a particular OAuth 0.0 scope with
read or write access.
"""
directive @access(scope: AccessScope!, kind: AccessKind!) on FIELD_DEFINITION

enum Protocol {
@@ -30,9 +32,11 @@ type Version {
  major: Int!
  minor: Int!
  patch: Int!
  # If this API version is scheduled for deprecation, this is the date on which
  # it will stop working; or null if this API version is not scheduled for
  # deprecation.
  """
  If this API version is scheduled for deprecation, this is the date on which
  it will stop working; or null if this API version is not scheduled for
  deprecation.
  """
  deprecationDate: Time
}

@@ -40,8 +44,10 @@ interface Entity {
  id: Int!
  created: Time!
  updated: Time!
  # The canonical name of this entity. For users, this is their username
  # prefixed with '~'. Additional entity types will be supported in the future.
  """
  The canonical name of this entity. For users, this is their username
  prefixed with '~'. Additional entity types will be supported in the future.
  """
  canonicalName: String!
}

@@ -57,65 +63,78 @@ type User implements Entity {
  bio: String
}

# A published website
"A published website"
type Site {
  id: Int!
  created: Time!
  updated: Time!
  # Domain name the site services
  "Domain name the site services"
  domain: String!
  # The site protocol
  "The site protocol"
  protocol: Protocol!
  # SHA-256 checksum of the source tarball (uncompressed)
  "SHA-256 checksum of the source tarball (uncompressed)"
  version: String!
  "Path to the file to serve for 404 Not Found responses"
  notFound: String
}

# A cursor for enumerating site entries
#
# If there are additional results available, the cursor object may be passed
# back into the same endpoint to retrieve another page. If the cursor is null,
# there are no remaining results to return.
"""
A cursor for enumerating site entries

If there are additional results available, the cursor object may be passed
back into the same endpoint to retrieve another page. If the cursor is null,
there are no remaining results to return.
"""
type SiteCursor {
  results: [Site]!
  results: [Site!]!
  cursor: Cursor
}

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

type Query {
  # Returns API version information.
  "Returns API version information."
  version: Version!

  # Returns the authenticated user.
  "Returns the authenticated user."
  me: User! @access(scope: PROFILE, kind: RO)

  # Returns a list of registered sites on your account.
  "Returns a list of registered sites on your account."
  sites(cursor: Cursor): SiteCursor! @access(scope: SITES, kind: RO)
}

type Mutation {
  # Publishes a website. If the domain already exists on your account, it is
  # updated to a new version. If the domain already exists under someone else's
  # account, the request is rejected. If the domain does not exist, a new site
  # is created.
  #
  # Every user is given exclusive use over the 'username.srht.site' domain, and
  # it requires no special configuration to use. Users may also bring their own
  # domain name, in which case they should consult the configuration docs:
  #
  # https://man.sr.ht/pages.sr.ht
  #
  # 'content' must be a .tar.gz file. It must contain only directories and
  # regular files of mode 644. Symlinks are not supported. No error is returned
  # for an invalid tarball; the invalid data is simply discarded.
  #
  # If protocol is unset, HTTPS is presumed.
  #
  # If subdirectory is set, only the specified directory is updated. The rest
  # of the files are unchanged.
  """
  Publishes a website. If the domain already exists on your account, it is
  updated to a new version. If the domain already exists under someone else's
  account, the request is rejected. If the domain does not exist, a new site
  is created.

  Every user is given exclusive use over the 'username.srht.site' domain, and
  it requires no special configuration to use. Users may also bring their own
  domain name, in which case they should consult the configuration docs:

  https://man.sr.ht/pages.sr.ht

  'content' must be a .tar.gz file. It must contain only directories and
  regular files of mode 644. Symlinks are not supported. No error is returned
  for an invalid tarball; the invalid data is simply discarded.

  If protocol is unset, HTTPS is presumed.

  If subdirectory is set, only the specified directory is updated. The rest
  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.

  # Deletes a previously published website.
  #
  # If protocol is unset, HTTPS is presumed.
  If protocol is unset, HTTPS is presumed.
  """
  unpublish(domain: String!, protocol: Protocol): Site @access(scope: SITES, kind: RW)
}
-- 
2.35.1

[PATCH 2/2] pages publish: Add subdirectory flag Export this patch

---
 pages.go                          | 5 +++--
 srht/pagessrht/gql.go             | 5 +++--
 srht/pagessrht/operations.graphql | 4 ++--
 3 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/pages.go b/pages.go
index 36e2e64..59aec1d 100644
--- a/pages.go
+++ b/pages.go
@@ -26,7 +26,7 @@ func newPagesCommand() *cobra.Command {
}

func newPagesPublishCommand() *cobra.Command {
	var domain, protocol string
	var domain, protocol, subdirectory string
	run := func(cmd *cobra.Command, args []string) {
		ctx := cmd.Context()

@@ -51,7 +51,7 @@ func newPagesPublishCommand() *cobra.Command {

		file := gqlclient.Upload{Body: f, Filename: filepath.Base(filename)}

		site, err := pagessrht.Publish(c.Client, ctx, domain, file, pagesProtocol)
		site, err := pagessrht.Publish(c.Client, ctx, domain, file, pagesProtocol, subdirectory)
		if err != nil {
			log.Fatalf("failed to publish site: %v", err)
		}
@@ -70,6 +70,7 @@ func newPagesPublishCommand() *cobra.Command {
	cmd.Flags().StringVarP(&protocol, "protocol", "p", "HTTPS",
		"protocol (HTTPS or GEMINI)")
	cmd.RegisterFlagCompletionFunc("protocol", completeProtocol)
	cmd.Flags().StringVarP(&subdirectory, "subdirectory", "s", "/", "subdirectory")
	return cmd
}

diff --git a/srht/pagessrht/gql.go b/srht/pagessrht/gql.go
index bbabce4..d21f6da 100644
--- a/srht/pagessrht/gql.go
+++ b/srht/pagessrht/gql.go
@@ -93,11 +93,12 @@ type Version struct {
	DeprecationDate time.Time `json:"deprecationDate,omitempty"`
}

func Publish(client *gqlclient.Client, ctx context.Context, domain string, content gqlclient.Upload, protocol Protocol) (publish *Site, err error) {
	op := gqlclient.NewOperation("mutation publish ($domain: String!, $content: Upload!, $protocol: Protocol!) {\n\tpublish(domain: $domain, content: $content, protocol: $protocol) {\n\t\tdomain\n\t}\n}\n")
func Publish(client *gqlclient.Client, ctx context.Context, domain string, content gqlclient.Upload, protocol Protocol, subdirectory string) (publish *Site, err error) {
	op := gqlclient.NewOperation("mutation publish ($domain: String!, $content: Upload!, $protocol: Protocol!, $subdirectory: String!) {\n\tpublish(domain: $domain, content: $content, protocol: $protocol, subdirectory: $subdirectory) {\n\t\tdomain\n\t}\n}\n")
	op.Var("domain", domain)
	op.Var("content", content)
	op.Var("protocol", protocol)
	op.Var("subdirectory", subdirectory)
	var respData struct {
		Publish *Site
	}
diff --git a/srht/pagessrht/operations.graphql b/srht/pagessrht/operations.graphql
index 3b9652d..2adb2d7 100644
--- a/srht/pagessrht/operations.graphql
+++ b/srht/pagessrht/operations.graphql
@@ -1,5 +1,5 @@
mutation publish($domain: String!, $content: Upload!, $protocol: Protocol!) {
    publish(domain: $domain, content: $content, protocol: $protocol) {
mutation publish($domain: String!, $content: Upload!, $protocol: Protocol!, $subdirectory: String!) {
    publish(domain: $domain, content: $content, protocol: $protocol, subdirectory: $subdirectory) {
        domain
    }
}
-- 
2.35.1