-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
307 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package vanity | ||
|
||
import "errors" | ||
|
||
var ( | ||
ErrNoRepository = errors.New("a repository url is required") | ||
ErrInvalidRepository = errors.New("expected repository url in the form vcsScheme://vcsHost/user/repo") | ||
ErrInvalidProtocol = errors.New("protocol must be git, github, or gogs") | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,4 +34,8 @@ header div { | |
padding: 24px; | ||
margin: 0; | ||
font-size: 24px; | ||
} | ||
|
||
.redirect { | ||
padding: 32px 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> | ||
<meta name="go-import" content="{{ .GoImportMeta }}" /> | ||
<meta name="go-source" content="{{ .GoSourceMeta }}" /> | ||
<meta http-equiv="refresh" content="0; url={{ .Redirect }}"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<meta name="robots" content="noindex,follow"> | ||
|
||
<title>Rotational Go Packages</title> | ||
|
||
<link rel="icon" type="image/ico" href="/static/img/favicon.ico"> | ||
<link rel="stylesheet" href="/static/css/style.css" /> | ||
</head> | ||
<body> | ||
<header > | ||
<div class="redirect text-center"> | ||
If not automatically redirected please click: <a href="{{ .Redirect }}">{{ .Redirect }}</a>. | ||
</div> | ||
</header> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package server | ||
|
||
import ( | ||
"html/template" | ||
"io/fs" | ||
"net/http" | ||
|
||
"github.com/julienschmidt/httprouter" | ||
"github.com/rotationalio/vanity" | ||
) | ||
|
||
func Vanity(pkg *vanity.GoPackage) httprouter.Handle { | ||
// Compile the template for serving the vanity url. | ||
templates, _ := fs.Sub(content, "templates") | ||
index := template.Must(template.ParseFS(templates, "*.html")) | ||
|
||
return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { | ||
// Construct a request specific response | ||
data := pkg.WithRequest(r) | ||
|
||
// Issue an HTTP redirect if this is definitely a browser. | ||
if r.FormValue("go-get") != "1" { | ||
http.Redirect(w, r, data.Redirect(), http.StatusTemporaryRedirect) | ||
return | ||
} | ||
|
||
// Write go-import and go-source meta tags to response. | ||
w.Header().Set("Cache-Control", "public, max-age=300") | ||
if err := index.ExecuteTemplate(w, "vanity.html", data); err != nil { | ||
http.Error(w, err.Error(), http.StatusInternalServerError) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
package vanity | ||
|
||
import ( | ||
"net/http" | ||
"net/url" | ||
"path/filepath" | ||
"strings" | ||
|
||
"github.com/rotationalio/vanity/config" | ||
) | ||
|
||
const ( | ||
protocolGit = "git" | ||
protocolGitHub = "github" | ||
protocolGOGS = "gogs" | ||
) | ||
|
||
var godoc *url.URL = &url.URL{ | ||
Scheme: "https", | ||
Host: "godoc.org", | ||
} | ||
|
||
var validProtocols = map[string]struct{}{ | ||
protocolGit: {}, | ||
protocolGitHub: {}, | ||
protocolGOGS: {}, | ||
} | ||
|
||
type GoPackage struct { | ||
Domain string `json:"-"` // the vanity URL domain to use | ||
Module string `json:"-"` // the module name where go.mod is located; parsed from the repository | ||
Package string `json:"-"` // the full package path being requested for correct redirects | ||
Protocol string `json:"protocol"` // can be "git", "github", or "gogs" -- defaults to "git" | ||
Repository string `json:"repository"` // a path to the public repository starting with https:// | ||
Branch string `json:"branch"` // the name of the default branch -- defaults to "main" | ||
repo *url.URL `json:"-"` // the parsed repository URL | ||
user string `json:"-"` // the user or organization from the repository | ||
} | ||
|
||
func (p *GoPackage) Resolve(conf *config.Config) (err error) { | ||
// Verify there is a repository | ||
if p.Repository == "" { | ||
return ErrNoRepository | ||
} | ||
|
||
// Parse the repository | ||
if p.repo, err = url.Parse(p.Repository); err != nil { | ||
return ErrInvalidRepository | ||
} | ||
|
||
parts := strings.Split(p.repo.Path, "/") | ||
if len(parts) != 3 { | ||
return ErrInvalidRepository | ||
} | ||
|
||
p.user = parts[1] | ||
p.Module = parts[2] | ||
|
||
// Check protocol | ||
if p.Protocol == "" { | ||
p.Protocol = protocolGit | ||
} | ||
|
||
if _, ok := validProtocols[p.Protocol]; !ok { | ||
return ErrInvalidProtocol | ||
} | ||
|
||
// Manage the configuration | ||
if conf != nil { | ||
p.Domain = conf.Domain | ||
|
||
if p.Branch == "" { | ||
p.Branch = conf.DefaultBranch | ||
} | ||
} | ||
|
||
// Check the ref | ||
if p.Branch == "" { | ||
p.Branch = "main" | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (p *GoPackage) WithRequest(r *http.Request) GoPackage { | ||
pkg := p.Module | ||
if r != nil { | ||
pkg = r.URL.Path | ||
} | ||
|
||
clone := GoPackage{ | ||
Domain: p.Domain, | ||
Module: p.Module, | ||
Package: pkg, | ||
Protocol: p.Protocol, | ||
Repository: p.Repository, | ||
Branch: p.Branch, | ||
repo: p.repo, | ||
user: p.user, | ||
} | ||
|
||
if clone.Domain == "" && r != nil { | ||
clone.Domain = r.Host | ||
} | ||
|
||
return clone | ||
} | ||
|
||
func (p GoPackage) Redirect() string { | ||
return godoc.ResolveReference( | ||
&url.URL{ | ||
Path: filepath.Join("/", p.Domain, p.Package), | ||
}, | ||
).String() | ||
} | ||
|
||
func (p GoPackage) GoImportMeta() string { | ||
parts := []string{ | ||
p.Import(), | ||
p.Protocol, | ||
p.repo.String(), | ||
} | ||
|
||
return strings.Join(parts, " ") | ||
} | ||
|
||
func (p GoPackage) GoSourceMeta() string { | ||
parts := []string{ | ||
p.Import(), | ||
p.repo.String(), | ||
"", | ||
"", | ||
} | ||
parts[2], parts[3] = p.Source() | ||
return strings.Join(parts, " ") | ||
} | ||
|
||
func (p GoPackage) Import() string { | ||
return filepath.Join(p.Domain, p.Module) | ||
} | ||
|
||
func (p GoPackage) Source() (string, string) { | ||
switch p.Protocol { | ||
case protocolGit, protocolGitHub: | ||
return p.githubSource() | ||
case protocolGOGS: | ||
return p.gogsSource() | ||
default: | ||
return "", "" | ||
} | ||
} | ||
|
||
func (p GoPackage) githubSource() (string, string) { | ||
base := filepath.Join(p.user, p.Module) | ||
directoryPath := filepath.Join(base, "tree", p.Branch+"{/dir}") | ||
filePath := filepath.Join(base, "blob", p.Branch+"{/dir}", "{file}#L{line}") | ||
|
||
uri := p.repo.ResolveReference(&url.URL{Path: "/"}).String() | ||
return uri + directoryPath, uri + filePath | ||
} | ||
|
||
func (p GoPackage) gogsSource() (string, string) { | ||
base := filepath.Join(p.user, p.Module) | ||
directoryPath := filepath.Join(base, "src", p.Branch+"{/dir}") | ||
filePath := filepath.Join(base, "src", p.Branch+"{/dir}", "{file}#L{line}") | ||
|
||
uri := p.repo.ResolveReference(&url.URL{Path: "/"}).String() | ||
return uri + directoryPath, uri + filePath | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package vanity_test | ||
|
||
import ( | ||
"net/http" | ||
"net/url" | ||
"testing" | ||
|
||
. "github.com/rotationalio/vanity" | ||
"github.com/rotationalio/vanity/config" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
type expected struct { | ||
redirect string | ||
importMeta string | ||
sourceMeta string | ||
} | ||
|
||
func TestGoPackage(t *testing.T) { | ||
t.Run("Valid", func(t *testing.T) { | ||
testCases := []struct { | ||
in *GoPackage | ||
conf *config.Config | ||
req *http.Request | ||
expected expected | ||
}{ | ||
{ | ||
in: &GoPackage{Repository: "https://github.com/rotationalio/confire"}, | ||
conf: &config.Config{Domain: "go.rotational.io", DefaultBranch: "main"}, | ||
req: &http.Request{URL: &url.URL{Path: "/confire/validate"}}, | ||
expected: expected{ | ||
redirect: "https://godoc.org/go.rotational.io/confire/validate", | ||
importMeta: "go.rotational.io/confire git https://github.com/rotationalio/confire", | ||
sourceMeta: "go.rotational.io/confire https://github.com/rotationalio/confire https://github.com/rotationalio/confire/tree/main{/dir} https://github.com/rotationalio/confire/blob/main{/dir}/{file}#L{line}", | ||
}, | ||
}, | ||
} | ||
|
||
for i, tc := range testCases { | ||
// Resolve the go package data | ||
require.NoError(t, tc.in.Resolve(tc.conf), "test case %d failed: could not resolve", i) | ||
|
||
// Finalize package for the request | ||
pkg := tc.in.WithRequest(tc.req) | ||
|
||
// Perform assertions | ||
require.Equal(t, tc.expected.redirect, pkg.Redirect(), "test case %d failed: bad redirect", i) | ||
require.Equal(t, tc.expected.importMeta, pkg.GoImportMeta(), "test case %d failed: bad import meta", i) | ||
require.Equal(t, tc.expected.sourceMeta, pkg.GoSourceMeta(), "test case %d failed: bad source meta", i) | ||
} | ||
}) | ||
} |