From c3e03c14082fd19a762b7b7644b90c29038f1a04 Mon Sep 17 00:00:00 2001 From: John Olheiser Date: Mon, 22 Feb 2021 04:23:06 +0800 Subject: [PATCH] Shiny things (#8) Resolves #4 Resolves #5 Resolves #6 Change list: * Adds topics format * Groups projects by their topics/tags, any without topics are tossed in `other` * Adds minimal page for `go-get` or `git-import` URL query parameters * Changes the version information to explicitly mention Vanity and Go. * Adds manual update mode as an alternative to the automatic intervals. * Moved templates to `.tmpl` files and using `//go:embed` to add to binary. Co-authored-by: jolheiser Reviewed-on: https://gitea.com/jolheiser/vanity/pulls/8 Co-authored-by: John Olheiser Co-committed-by: John Olheiser --- README.md | 19 +++++++ api/package.go | 13 +++-- flags/config.go | 8 ++- flags/flags.go | 19 +++++++ go.mod | 8 ++- go.sum | 24 ++++---- main.go | 6 +- router/api.go | 71 +++++++++++++++++++++++ router/cache.go | 106 ++++++++++++++++++++++++++++++----- router/cron.go | 73 +++++------------------- router/router.go | 67 ++++++++++++++-------- router/templates.go | 44 +++++++++++++++ router/templates/foot.go | 7 --- router/templates/foot.tmpl | 25 +++++++++ router/templates/head.go | 23 -------- router/templates/head.tmpl | 21 +++++++ router/templates/import.tmpl | 20 +++++++ router/templates/index.go | 12 ---- router/templates/index.tmpl | 29 ++++++++++ router/templates/vanity.go | 10 ---- router/templates/vanity.tmpl | 20 +++++++ service/gitea.go | 27 +++++++-- service/github.go | 1 + service/gitlab.go | 1 + service/service.go | 8 ++- 25 files changed, 482 insertions(+), 180 deletions(-) create mode 100644 router/api.go create mode 100644 router/templates.go delete mode 100644 router/templates/foot.go create mode 100644 router/templates/foot.tmpl delete mode 100644 router/templates/head.go create mode 100644 router/templates/head.tmpl create mode 100644 router/templates/import.tmpl delete mode 100644 router/templates/index.go create mode 100644 router/templates/index.tmpl delete mode 100644 router/templates/vanity.go create mode 100644 router/templates/vanity.tmpl diff --git a/README.md b/README.md index 8841a43..16e5b9e 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,25 @@ Overrides are available via config or by setting an environment variable `VANITY To run Vanity in config-only mode for packages, set `--service` to `off`. +## Manual Mode + +To run Vanity without automatic updating, use `--manual`. + +When running with manual-mode, the provided button or `/_/update` endpoint can be used once every `--interval`. + +## Topic Lists + +By setting `--topics`, anyone visiting the index page will see packages grouped by their topics. + +Regardless of the setting, you can switch beteween list-view and topic-view with the provided button +or changing the URL between `?format=list` and `?format=topics`. + +## API + +In order to preserve namespaces for packages, Vanity's API uses the URL `/_/{endpoint}` + +Vanity currently supports `/_/status` and `/_/update`, to get some status information and update the package cache respectively. + ## License [MIT](LICENSE) \ No newline at end of file diff --git a/api/package.go b/api/package.go index afa0ac7..af5a74e 100644 --- a/api/package.go +++ b/api/package.go @@ -6,12 +6,13 @@ import ( ) type Package struct { - Name string `toml:"name"` - Description string `toml:"description"` - Branch string `toml:"branch"` - WebURL string `toml:"web_url"` - CloneHTTP string `toml:"clone_http"` - CloneSSH string `toml:"clone_ssh"` + Name string `toml:"name"` + Description string `toml:"description"` + Branch string `toml:"branch"` + WebURL string `toml:"web_url"` + CloneHTTP string `toml:"clone_http"` + CloneSSH string `toml:"clone_ssh"` + Topics []string `toml:"topics"` Private bool `toml:"-"` Fork bool `toml:"-"` diff --git a/flags/config.go b/flags/config.go index 9becb12..cb1a9d0 100644 --- a/flags/config.go +++ b/flags/config.go @@ -7,7 +7,7 @@ import ( "go.jolheiser.com/vanity/api" - "github.com/BurntSushi/toml" + "github.com/pelletier/go-toml" "github.com/urfave/cli/v2" "go.jolheiser.com/beaver" ) @@ -44,11 +44,15 @@ func setConfig(ctx *cli.Context) { var cfg tomlConfig if configPath != "" { beaver.Infof("Loading configuration from %s", configPath) - _, err := toml.DecodeFile(configPath, &cfg) + tree, err := toml.LoadFile(configPath) if err != nil { beaver.Errorf("Could not load configuration from %s: %v", configPath, err) return } + if err = tree.Unmarshal(&cfg); err != nil { + beaver.Errorf("Could not unmarshal configuration from %s: %v", configPath, err) + return + } } if !ctx.IsSet("port") && cfg.Port > 0 { diff --git a/flags/flags.go b/flags/flags.go index 767691d..c3be0d3 100644 --- a/flags/flags.go +++ b/flags/flags.go @@ -35,6 +35,8 @@ var ( Archive bool Override = make(map[string]string) Interval time.Duration + Manual bool + Topics bool Debug bool ConfigPackages []*api.Package @@ -135,6 +137,18 @@ var Flags = []cli.Flag{ EnvVars: []string{"VANITY_INTERVAL"}, Destination: &Interval, }, + &cli.BoolFlag{ + Name: "manual", + Usage: "Disable cron and only update with endpoint", + EnvVars: []string{"VANITY_MANUAL"}, + Destination: &Manual, + }, + &cli.BoolFlag{ + Name: "topics", + Usage: "Group projects by topic by default", + EnvVars: []string{"VANITY_TOPICS"}, + Destination: &Topics, + }, &cli.BoolFlag{ Name: "debug", Usage: "Debug logging", @@ -196,6 +210,10 @@ func Before(ctx *cli.Context) error { Exclude[idx] = regexp.MustCompile(e) } + if Manual { + beaver.Info("Running in manual mode") + } + if Debug { beaver.Console.Level = beaver.DEBUG } @@ -213,5 +231,6 @@ func Before(ctx *cli.Context) error { beaver.Debugf("Archive: %t", Archive) beaver.Debugf("Override: %s", override.Value()) beaver.Debugf("Interval: %s", Interval) + beaver.Debugf("Manual: %t", Manual) return nil } diff --git a/go.mod b/go.mod index 5637c84..e04bf33 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,18 @@ module go.jolheiser.com/vanity -go 1.12 +go 1.16 require ( - code.gitea.io/sdk/gitea v0.12.2 - github.com/BurntSushi/toml v0.3.1 + code.gitea.io/sdk/gitea v0.13.2 github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/go-chi/chi v4.1.2+incompatible + github.com/go-chi/cors v1.1.1 // indirect github.com/google/go-github/v32 v32.1.0 + github.com/pelletier/go-toml v1.8.1 github.com/urfave/cli/v2 v2.2.0 github.com/xanzy/go-gitlab v0.37.0 go.jolheiser.com/beaver v1.0.2 + go.jolheiser.com/overlay v0.0.2 // indirect golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 // indirect diff --git a/go.sum b/go.sum index 8a0ffdb..f381b7e 100644 --- a/go.sum +++ b/go.sum @@ -30,10 +30,9 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -code.gitea.io/sdk/gitea v0.12.2 h1:NQI8b/CT9AEQjsxbVIZ6gsPUXv38moT5y1ocN7n1YcQ= -code.gitea.io/sdk/gitea v0.12.2/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY= +code.gitea.io/sdk/gitea v0.13.2 h1:wAnT/J7Z62q3fJXbgnecoaOBh8CM1Qq0/DakWxiv4yA= +code.gitea.io/sdk/gitea v0.13.2/go.mod h1:lee2y8LeV3kQb2iK+hHlMqoadL4bp27QOkOV/hawLKg= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -42,7 +41,6 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -55,6 +53,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw= +github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -115,19 +115,19 @@ github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxC github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-retryablehttp v0.6.4 h1:BbgctKO892xEyOXnGiaAwIoSq1QZ/SS4AhjoAh9DnfY= github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= -github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= +github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= +github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -149,12 +149,13 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.jolheiser.com/beaver v1.0.2 h1:KA2D6iO8MQhZi1nZYi/Chak/f1Cxfrs6b1XO623+Khk= go.jolheiser.com/beaver v1.0.2/go.mod h1:7X4F5+XOGSC3LejTShoBdqtRCnPWcnRgmYGmG3EKW8g= +go.jolheiser.com/overlay v0.0.2 h1:cwEHLbWqdH7lEOG87WUwgUGVqfOWBsWe03FiHHmuTWE= +go.jolheiser.com/overlay v0.0.2/go.mod h1:xNbssakJ3HjK4RnjuP38q9yQNS4wxXKsyprYIWWr2bg= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -198,7 +199,6 @@ golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -238,7 +238,6 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -247,7 +246,6 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -268,7 +266,6 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 h1:W0lCpv29Hv0UaM1LXb9QlBHLNP8UFfcKjblhVCWftOM= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -400,7 +397,6 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= diff --git a/main.go b/main.go index 85cce38..9562d94 100644 --- a/main.go +++ b/main.go @@ -36,7 +36,11 @@ func main() { } func doAction(ctx *cli.Context) error { - if err := http.ListenAndServe(fmt.Sprintf(":%s", ctx.String("port")), router.Init()); err != nil { + mux, err := router.Init() + if err != nil { + return err + } + if err := http.ListenAndServe(fmt.Sprintf(":%s", ctx.String("port")), mux); err != nil { return err } return nil diff --git a/router/api.go b/router/api.go new file mode 100644 index 0000000..9687b53 --- /dev/null +++ b/router/api.go @@ -0,0 +1,71 @@ +package router + +import ( + "encoding/json" + "github.com/go-chi/chi" + "github.com/go-chi/cors" + "go.jolheiser.com/beaver" + "go.jolheiser.com/vanity/api" + "go.jolheiser.com/vanity/flags" + "net/http" + "runtime" + "time" +) + +func apiRoutes() *chi.Mux { + r := chi.NewRouter() + r.Use(cors.AllowAll().Handler) + + r.Get("/status", doAPIStatus) + r.Get("/update", doAPIUpdate) + + return r +} + +func doAPIStatus(res http.ResponseWriter, _ *http.Request) { + res.Header().Set("Content-Type", "application/json") + + var nextUpdate *string + if !lastUpdate.IsZero() { + nu := lastUpdate.Add(flags.Interval).Format(time.RFC3339) + nextUpdate = &nu + } + + resp := map[string]interface{}{ + "vanity_version": api.Version, + "go_version": runtime.Version(), + "num_packages": len(cache.Packages), + "next_update": nextUpdate, + } + + data, err := json.Marshal(&resp) + if err != nil { + beaver.Errorf("could not marshal API status: %v", err) + data = []byte("{}") + } + + if _, err = res.Write(data); err != nil { + beaver.Errorf("could not write response: %v", err) + } +} + +func doAPIUpdate(res http.ResponseWriter, _ *http.Request) { + res.Header().Set("Content-Type", "application/json") + + resp := map[string]bool{ + "updated": false, + } + if canUpdate { + updateCache() + resp["updated"] = true + } + + payload, err := json.Marshal(resp) + if err != nil { + beaver.Errorf("could not marshal payload: %v", err) + } + + if _, err = res.Write(payload); err != nil { + beaver.Errorf("could not write response: %v", err) + } +} diff --git a/router/cache.go b/router/cache.go index c5a4c68..88e37d9 100644 --- a/router/cache.go +++ b/router/cache.go @@ -1,32 +1,110 @@ package router import ( + "go.jolheiser.com/beaver" + "go.jolheiser.com/vanity/flags" + "go.jolheiser.com/vanity/service" + "strings" "sync" "go.jolheiser.com/vanity/api" ) var cache = &packageCache{ - packages: make(map[string]*api.Package), + Packages: make(map[string]*api.Package), } -type packageCache struct { - packages map[string]*api.Package - sync.Mutex -} +type packageList map[string]*api.Package -func (c *packageCache) Update(packages map[string]*api.Package) { - c.Lock() - c.packages = packages - c.Unlock() -} - -func (c *packageCache) Names() []string { - names := make([]string, len(c.packages)) +func (p packageList) Names() []string { + names := make([]string, len(p)) idx := 0 - for name := range c.packages { + for name := range p { names[idx] = name idx++ } return names } + +func (p packageList) Topics() map[string][]*api.Package { + topics := make(map[string][]*api.Package, 0) + for _, pkg := range p { + if len(pkg.Topics) == 0 { + if tt, ok := topics["other"]; ok { + topics["other"] = append(tt, pkg) + } else { + topics["other"] = []*api.Package{pkg} + } + } + for _, t := range pkg.Topics { + if tt, ok := topics[t]; ok { + topics[t] = append(tt, pkg) + } else { + topics[t] = []*api.Package{pkg} + } + } + } + return topics +} + +type packageCache struct { + Packages packageList + sync.Mutex +} + +func (c *packageCache) Update(packages map[string]*api.Package) { + c.Lock() + c.Packages = packages + c.Unlock() +} + +func updateCache() { + packages, err := svc.Packages() + if err != nil { + beaver.Errorf("could not update packages: %v", err) + return + } + + // Filter + for name, pkg := range packages { + if err := service.Check(pkg); err != nil { + beaver.Debug(err) + delete(packages, name) + continue + } + goMod, err := svc.GoMod(pkg) + if err != nil { + beaver.Debugf("No go.mod could be found in the root directory of %s", pkg.Name) + delete(packages, name) + continue + } + lines := strings.Split(goMod, "\n") + line := strings.Fields(lines[0]) + if !strings.HasPrefix(line[1], flags.Domain) { + beaver.Debugf("%s is a Go project, however its module does not include this domain", pkg.Name) + delete(packages, name) + continue + } + beaver.Debugf("Including %s", pkg.Name) + } + + // Overrides + for name, pkg := range packages { + for key, override := range flags.Override { + if strings.EqualFold(name, key) { + beaver.Debugf("Overriding %s -> %s", name, override) + delete(packages, key) + pkg.Name = override + packages[override] = pkg + } + } + } + + // Add packages manually added to config + for _, pkg := range flags.ConfigPackages { + packages[pkg.Name] = pkg + } + + cache.Update(packages) + canUpdate = false +} diff --git a/router/cron.go b/router/cron.go index af6b21d..bc609c6 100644 --- a/router/cron.go +++ b/router/cron.go @@ -1,73 +1,30 @@ package router import ( - "strings" + "go.jolheiser.com/beaver" + "go.jolheiser.com/vanity/service" "time" "go.jolheiser.com/vanity/flags" - "go.jolheiser.com/vanity/service" - - "go.jolheiser.com/beaver" ) -var svc service.Service +var ( + svc service.Service + lastUpdate time.Time + canUpdate bool +) func cronStart() { + canUpdate = true ticker := time.NewTicker(flags.Interval) for { <-ticker.C - beaver.Debug("Running package update...") - cronUpdate() - beaver.Debugf("Finished package update: %s", cache.Names()) + if !flags.Manual && canUpdate { + beaver.Debug("Running package update...") + updateCache() + beaver.Debugf("Finished package update: %s", cache.Packages.Names()) + lastUpdate = time.Now() + } + canUpdate = true } } - -func cronUpdate() { - packages, err := svc.Packages() - if err != nil { - beaver.Errorf("could not update packages: %v", err) - return - } - - // Filter - for name, pkg := range packages { - if err := service.Check(pkg); err != nil { - beaver.Debug(err) - delete(packages, name) - continue - } - goMod, err := svc.GoMod(pkg) - if err != nil { - beaver.Debugf("No go.mod could be found in the root directory of %s", pkg.Name) - delete(packages, name) - continue - } - lines := strings.Split(goMod, "\n") - line := strings.Fields(lines[0]) - if !strings.HasPrefix(line[1], flags.Domain) { - beaver.Debugf("%s is a Go project, however its module does not include this domain", pkg.Name) - delete(packages, name) - continue - } - beaver.Debugf("Including %s", pkg.Name) - } - - // Overrides - for name, pkg := range packages { - for key, override := range flags.Override { - if strings.EqualFold(name, key) { - beaver.Debugf("Overriding %s -> %s", name, override) - delete(packages, key) - pkg.Name = override - packages[override] = pkg - } - } - } - - // Add packages manually added to config - for _, pkg := range flags.ConfigPackages { - packages[pkg.Name] = pkg - } - - cache.Update(packages) -} diff --git a/router/router.go b/router/router.go index 2a77243..1b742b8 100644 --- a/router/router.go +++ b/router/router.go @@ -4,13 +4,10 @@ import ( "fmt" "html/template" "net/http" - "runtime" "strings" "time" - "go.jolheiser.com/vanity/api" "go.jolheiser.com/vanity/flags" - "go.jolheiser.com/vanity/router/templates" "go.jolheiser.com/vanity/service" "github.com/go-chi/chi" @@ -18,56 +15,78 @@ import ( "go.jolheiser.com/beaver" ) -var ( - index = template.Must(template.New("index").Parse(templates.Head + templates.Index + templates.Foot)) - vanity = template.Must(template.New("vanity").Parse(templates.Head + templates.Vanity + templates.Foot)) -) +var tmpl *template.Template + +func Init() (*chi.Mux, error) { + var err error + tmpl, err = Templates() + if err != nil { + return nil, err + } -func Init() *chi.Mux { r := chi.NewRouter() r.Use(middleware.RedirectSlashes) r.Use(middleware.Recoverer) - r.Use(middleware.Timeout(30 * time.Second)) + r.Use(middleware.Timeout(60 * time.Second)) r.Get("/", doIndex) r.Get("/*", doVanity) + r.Mount("/_", apiRoutes()) svc = service.New() beaver.Info("Warming up cache...") - cronUpdate() - beaver.Infof("Finished warming up cache: %s", cache.Names()) + updateCache() + beaver.Infof("Finished warming up cache: %s", cache.Packages.Names()) go cronStart() beaver.Infof("Running vanity server at http://localhost:%d", flags.Port) - return r + return r, nil } -func doIndex(res http.ResponseWriter, _ *http.Request) { - if err := index.Execute(res, map[string]interface{}{ - "Packages": cache.packages, - "AppVer": api.Version, - "GoVer": runtime.Version(), +func doIndex(res http.ResponseWriter, req *http.Request) { + format := "list" + if flags.Topics { + format = "topics" + } + q := req.URL.Query().Get("format") + if q != "" { + format = q + } + + if err := tmpl.Lookup("index.tmpl").Execute(res, map[string]interface{}{ + "Packages": cache.Packages, + "Index": true, + "Format": format, }); err != nil { - beaver.Error(err) + beaver.Errorf("could not write response: %v", err) } } func doVanity(res http.ResponseWriter, req *http.Request) { key := chi.URLParam(req, "*") - pkg, ok := cache.packages[strings.Split(key, "/")[0]] + pkg, ok := cache.Packages[strings.Split(key, "/")[0]] if !ok { http.NotFound(res, req) return } - if err := vanity.Execute(res, map[string]interface{}{ + ctx := map[string]interface{}{ "Package": pkg, "Module": pkg.Module(flags.Domain), - "AppVer": api.Version, - "GoVer": runtime.Version(), "GoSource": fmt.Sprintf("%s %s %s %s", pkg.Module(flags.Domain), pkg.CloneHTTP, svc.GoDir(pkg), svc.GoFile(pkg)), - }); err != nil { - beaver.Error(err) + "Index": false, + } + + q := req.URL.Query() + if q.Get("go-get") != "" || q.Get("git-import") != "" { + if err := tmpl.Lookup("import.tmpl").Execute(res, ctx); err != nil { + beaver.Errorf("could not write response: %v", err) + } + return + } + + if err := tmpl.Lookup("vanity.tmpl").Execute(res, ctx); err != nil { + beaver.Errorf("could not write response: %v", err) } } diff --git a/router/templates.go b/router/templates.go new file mode 100644 index 0000000..5bd899d --- /dev/null +++ b/router/templates.go @@ -0,0 +1,44 @@ +package router + +import ( + "embed" + "go.jolheiser.com/overlay" + "go.jolheiser.com/vanity/api" + "html/template" + "os" + "path/filepath" + "runtime" +) + +//go:embed templates +var templates embed.FS + +func Templates() (*template.Template, error) { + bin, err := os.Executable() + if err != nil { + return nil, err + } + customPath := os.Getenv("VANITY_CUSTOM") + if customPath == "" { + customPath = filepath.Join(bin, "custom") + } + + ofs, err := overlay.New(customPath, templates) + if err != nil { + return nil, err + } + + return template.New("vanity").Funcs(funcMap).ParseFS(ofs, "templates/*") +} + +var funcMap = template.FuncMap{ + "AppVer": func() string { + return api.Version + }, + "GoVer": func() string { + return runtime.Version() + }, + "CanUpdate": func() bool { + return canUpdate + }, +} diff --git a/router/templates/foot.go b/router/templates/foot.go deleted file mode 100644 index 82e0492..0000000 --- a/router/templates/foot.go +++ /dev/null @@ -1,7 +0,0 @@ -package templates - -var Foot = ` -Version: {{.AppVer}} | {{.GoVer}} - - -` diff --git a/router/templates/foot.tmpl b/router/templates/foot.tmpl new file mode 100644 index 0000000..4cf7348 --- /dev/null +++ b/router/templates/foot.tmpl @@ -0,0 +1,25 @@ +
+ +

+Vanity Version: +{{AppVer}} +

+Go Version: +{{GoVer}} + + + + diff --git a/router/templates/head.go b/router/templates/head.go deleted file mode 100644 index 9fb8c57..0000000 --- a/router/templates/head.go +++ /dev/null @@ -1,23 +0,0 @@ -package templates - -var Head = ` - - - - - {{if .Package}} - - - - - - - - - - - {{end}} - Vanity - {{if .Package}}{{.Package.Name}}{{else}}Index{{end}} - - -` diff --git a/router/templates/head.tmpl b/router/templates/head.tmpl new file mode 100644 index 0000000..00435a2 --- /dev/null +++ b/router/templates/head.tmpl @@ -0,0 +1,21 @@ + + + + + {{if .Package}} + + + + + + + + + + + {{end}} + Vanity - {{if .Package}}{{.Package.Name}}{{else}}Index{{end}} + + +

Index

+
\ No newline at end of file diff --git a/router/templates/import.tmpl b/router/templates/import.tmpl new file mode 100644 index 0000000..c883b67 --- /dev/null +++ b/router/templates/import.tmpl @@ -0,0 +1,20 @@ + + + + + + + + + + + + + {{.Module}} + + +go get {{.Module}} +
+git-import {{.Module}} + + \ No newline at end of file diff --git a/router/templates/index.go b/router/templates/index.go deleted file mode 100644 index 7e6845e..0000000 --- a/router/templates/index.go +++ /dev/null @@ -1,12 +0,0 @@ -package templates - -var Index = ` -

Index

-
-

Imports:

- -` diff --git a/router/templates/index.tmpl b/router/templates/index.tmpl new file mode 100644 index 0000000..604c6e5 --- /dev/null +++ b/router/templates/index.tmpl @@ -0,0 +1,29 @@ +{{template "head.tmpl" .}} +

{{if eq .Format "list"}}Imports{{else}}Topics{{end}}:

+ {{if eq .Format "list"}} + + {{else}} + {{range $topic, $packages := .Packages.Topics}} +
+ {{$topic}} + +
+ {{end}} + {{end}} +
+
+ {{if eq .Format "list"}} + + {{else}} + + {{end}} +
+{{template "foot.tmpl" .}} \ No newline at end of file diff --git a/router/templates/vanity.go b/router/templates/vanity.go deleted file mode 100644 index 8a76d19..0000000 --- a/router/templates/vanity.go +++ /dev/null @@ -1,10 +0,0 @@ -package templates - -var Vanity = ` -

Index

-
-

Name: {{.Package.Name}}

-

Source: {{.Package.WebURL}}

-{{if .Package.Description}}

Description: {{.Package.Description}}

{{end}} -

Documentation: https://pkg.go.dev/{{.Module}}

-` diff --git a/router/templates/vanity.tmpl b/router/templates/vanity.tmpl new file mode 100644 index 0000000..e36b63c --- /dev/null +++ b/router/templates/vanity.tmpl @@ -0,0 +1,20 @@ +{{template "head.tmpl" .}} +

+ Name: + {{.Package.Name}} +

+

+ Source: + {{.Package.WebURL}} +

+ {{if .Package.Description}} +

+ Description: + {{.Package.Description}} +

+ {{end}} +

+ Documentation: + https://pkg.go.dev/{{.Module}} +

+{{template "foot.tmpl" .}} \ No newline at end of file diff --git a/service/gitea.go b/service/gitea.go index 49017cb..b18a54e 100644 --- a/service/gitea.go +++ b/service/gitea.go @@ -7,12 +7,16 @@ import ( "go.jolheiser.com/vanity/flags" "code.gitea.io/sdk/gitea" + "go.jolheiser.com/beaver" ) var _ Service = &Gitea{} func NewGitea() *Gitea { - client := gitea.NewClient(flags.BaseURL.String(), flags.Token) + client, err := gitea.NewClient(flags.BaseURL.String(), gitea.SetToken(flags.Token)) + if err != nil { + beaver.Errorf("could not create Gitea client: %v", err) + } return &Gitea{ client: client, } @@ -24,6 +28,12 @@ type Gitea struct { func (g Gitea) Packages() (map[string]*api.Package, error) { packages := make(map[string]*api.Package) + topicOpts := gitea.ListRepoTopicsOptions{ + ListOptions: gitea.ListOptions{ + Page: 1, + PageSize: 50, + }, + } page := 0 for { opts := gitea.ListReposOptions{ @@ -33,13 +43,13 @@ func (g Gitea) Packages() (map[string]*api.Package, error) { }, } - repos, err := g.client.ListUserRepos(flags.Namespace, opts) + repos, _, err := g.client.ListUserRepos(flags.Namespace, opts) if err != nil { return nil, err } for _, repo := range repos { - packages[repo.Name] = &api.Package{ + pkg := &api.Package{ Name: repo.Name, Description: repo.Description, Branch: repo.DefaultBranch, @@ -51,6 +61,15 @@ func (g Gitea) Packages() (map[string]*api.Package, error) { Mirror: repo.Mirror, Archive: repo.Archived, } + + // Get tags + topics, _, err := g.client.ListRepoTopics(flags.Namespace, repo.Name, topicOpts) + if err != nil { + beaver.Warnf("could not get topics for %s: %v", repo.Name, err) + } + pkg.Topics = topics + + packages[repo.Name] = pkg } page++ @@ -71,6 +90,6 @@ func (g Gitea) GoFile(pkg *api.Package) string { } func (g Gitea) GoMod(pkg *api.Package) (string, error) { - content, err := g.client.GetFile(flags.Namespace, pkg.Name, pkg.Branch, "go.mod") + content, _, err := g.client.GetFile(flags.Namespace, pkg.Name, pkg.Branch, "go.mod") return string(content), err } diff --git a/service/github.go b/service/github.go index a124ac9..3f11861 100644 --- a/service/github.go +++ b/service/github.go @@ -57,6 +57,7 @@ func (g GitHub) Packages() (map[string]*api.Package, error) { Fork: repo.GetFork(), Mirror: false, Archive: repo.GetArchived(), + Topics: repo.Topics, } } diff --git a/service/gitlab.go b/service/gitlab.go index 6adb5d7..b101775 100644 --- a/service/gitlab.go +++ b/service/gitlab.go @@ -55,6 +55,7 @@ func (g GitLab) Packages() (map[string]*api.Package, error) { Fork: repo.ForkedFromProject != nil, Mirror: repo.Mirror, Archive: repo.Archived, + Topics: repo.TagList, } } diff --git a/service/service.go b/service/service.go index 2934fe8..327401e 100644 --- a/service/service.go +++ b/service/service.go @@ -59,12 +59,16 @@ func Check(pkg *api.Package) error { // Inclusions if len(flags.Include) > 0 { + var included bool for _, include := range flags.Include { if include.MatchString(pkg.Name) { - return fmt.Errorf("%s was included by the rule %s", pkg.Name, include.String()) + included = true + break } } - return fmt.Errorf("%s wasn't included by any existing inclusion rule", pkg.Name) + if !included { + return fmt.Errorf("%s wasn't included by any existing inclusion rule", pkg.Name) + } } return nil