An experimental IndieWeb site built in Go.

init commit

Minimum working example for IndieAuth and Micropub

Graham Barber 64e3edd3

+5
.gitignore
··· 1 + .env 2 + 3 + node_modules 4 + *_templ.go 5 + data/**/*.db
+75
db/storage.go
··· 1 + package db 2 + 3 + import ( 4 + "log" 5 + "time" 6 + 7 + "github.com/jellydator/ttlcache/v3" 8 + "github.com/ostafen/clover/v2" 9 + "go.hacdias.com/indielib/indieauth" 10 + ) 11 + 12 + type Storage struct { 13 + Docs *clover.DB 14 + Authorization *ttlcache.Cache[string, *indieauth.AuthenticationRequest] 15 + } 16 + 17 + type CollectionName string 18 + 19 + var ( 20 + PostCollection CollectionName = "posts" 21 + ) 22 + 23 + func NewStorage() *Storage { 24 + c, err := clover.Open("data/docs") 25 + if err != nil { 26 + log.Fatal(err) 27 + } 28 + 29 + cache := ttlcache.New[string, *indieauth.AuthenticationRequest]( 30 + ttlcache.WithTTL[string, *indieauth.AuthenticationRequest](10 * time.Minute), 31 + ) 32 + 33 + go cache.Start() 34 + 35 + store := &Storage{ 36 + Docs: c, 37 + Authorization: cache, 38 + } 39 + 40 + store.SetupClover() 41 + 42 + return store 43 + } 44 + 45 + func (db *Storage) SetupClover() { 46 + if ok, err := db.Docs.HasCollection(string(PostCollection)); err != nil { 47 + panic(err) 48 + } else if !ok { 49 + err := db.Docs.CreateCollection(string(PostCollection)) 50 + if err != nil { 51 + panic(err) 52 + } 53 + } 54 + 55 + if ok, err := db.Docs.HasIndex(string(PostCollection), "createdAt"); err != nil { 56 + panic(err) 57 + } else if !ok { 58 + err := db.Docs.CreateIndex(string(PostCollection), "createdAt") 59 + if err != nil { 60 + panic(err) 61 + } 62 + } 63 + } 64 + 65 + func (db *Storage) Cleanup() { 66 + if err := db.Docs.Close(); err != nil { 67 + panic(err) 68 + } 69 + 70 + db.Authorization.Stop() 71 + } 72 + 73 + func (db *Storage) SetupTokenCache() { 74 + 75 + }
+4
gen.go
··· 1 + package main 2 + 3 + //go:generate templ generate 4 + //go:generate pnpm tailwindcss -o static/styles.css --minify
+49
go.mod
··· 1 + module github.com/puregarlic/space 2 + 3 + go 1.22.5 4 + 5 + require ( 6 + github.com/aidarkhanov/nanoid v1.0.8 7 + github.com/jellydator/ttlcache/v3 v3.2.0 8 + github.com/joho/godotenv v1.5.1 9 + github.com/ostafen/clover/v2 v2.0.0-alpha.3 10 + go.hacdias.com/indielib v0.3.1 11 + ) 12 + 13 + require ( 14 + github.com/cespare/xxhash v1.1.0 // indirect 15 + github.com/cespare/xxhash/v2 v2.3.0 // indirect 16 + github.com/dgraph-io/badger/v3 v3.2103.2 // indirect 17 + github.com/dgraph-io/ristretto v0.1.0 // indirect 18 + github.com/dustin/go-humanize v1.0.0 // indirect 19 + github.com/gogo/protobuf v1.3.2 // indirect 20 + github.com/golang/glog v1.0.0 // indirect 21 + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 22 + github.com/golang/protobuf v1.5.2 // indirect 23 + github.com/golang/snappy v0.0.4 // indirect 24 + github.com/google/flatbuffers v2.0.6+incompatible // indirect 25 + github.com/google/orderedcode v0.0.1 // indirect 26 + github.com/klauspost/compress v1.15.9 // indirect 27 + github.com/pkg/errors v0.9.1 // indirect 28 + github.com/satori/go.uuid v1.2.0 // indirect 29 + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect 30 + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 31 + go.etcd.io/bbolt v1.3.6 // indirect 32 + go.opencensus.io v0.23.0 // indirect 33 + golang.org/x/sync v0.7.0 // indirect 34 + golang.org/x/sys v0.22.0 // indirect 35 + golang.org/x/text v0.16.0 // indirect 36 + google.golang.org/protobuf v1.28.1 // indirect 37 + ) 38 + 39 + require ( 40 + github.com/a-h/templ v0.2.747 41 + github.com/go-chi/chi/v5 v5.1.0 42 + github.com/go-chi/httprate v0.12.0 43 + github.com/golang-jwt/jwt/v5 v5.2.1 44 + github.com/samber/lo v1.46.0 45 + golang.org/x/net v0.27.0 // indirect 46 + golang.org/x/oauth2 v0.21.0 // indirect 47 + willnorris.com/go/microformats v1.2.1-0.20240301064101-b5d1b9d2120e // indirect 48 + willnorris.com/go/webmention v0.0.0-20220108183051-4a23794272f0 // indirect 49 + )
+267
go.sum
··· 1 + cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 + github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 + github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= 4 + github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 5 + github.com/a-h/templ v0.2.747 h1:D0dQ2lxC3W7Dxl6fxQ/1zZHBQslSkTSvl5FxP/CfdKg= 6 + github.com/a-h/templ v0.2.747/go.mod h1:69ObQIbrcuwPCU32ohNaWce3Cb7qM5GMiqN1K+2yop4= 7 + github.com/aidarkhanov/nanoid v1.0.8 h1:yxyJkgsEDFXP7+97vc6JevMcjyb03Zw+/9fqhlVXBXA= 8 + github.com/aidarkhanov/nanoid v1.0.8/go.mod h1:vadfZHT+m4uDhttg0yY4wW3GKtl2T6i4d2Age+45pYk= 9 + github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= 10 + github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 11 + github.com/brianvoe/gofakeit/v6 v6.17.0 h1:obbQTJeHfktJtiZzq0Q1bEpsNUs+yHrYlPVWt7BtmJ4= 12 + github.com/brianvoe/gofakeit/v6 v6.17.0/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8= 13 + github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 14 + github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= 15 + github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 16 + github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 17 + github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 18 + github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 19 + github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 20 + github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 21 + github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 22 + github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 23 + github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= 24 + github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 25 + github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 26 + github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 27 + github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 28 + github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 29 + github.com/dgraph-io/badger/v3 v3.2103.2 h1:dpyM5eCJAtQCBcMCZcT4UBZchuTJgCywerHHgmxfxM8= 30 + github.com/dgraph-io/badger/v3 v3.2103.2/go.mod h1:RHo4/GmYcKKh5Lxu63wLEMHJ70Pac2JqZRYGhlyAo2M= 31 + github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= 32 + github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= 33 + github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= 34 + github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= 35 + github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= 36 + github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 37 + github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 38 + github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 39 + github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 40 + github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 41 + github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 42 + github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= 43 + github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= 44 + github.com/go-chi/httprate v0.12.0 h1:08D/te3pOTJe5+VAZTQrHxwdsH2NyliiUoRD1naKaMg= 45 + github.com/go-chi/httprate v0.12.0/go.mod h1:TUepLXaz/pCjmCtf/obgOQJ2Sz6rC8fSf5cAt5cnTt0= 46 + github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 47 + github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 48 + github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= 49 + github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 50 + github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 51 + github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= 52 + github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= 53 + github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 54 + github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 55 + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 56 + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 57 + github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 58 + github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 59 + github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 60 + github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 61 + github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 62 + github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 63 + github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 64 + github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 65 + github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 66 + github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 67 + github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 68 + github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 69 + github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 70 + github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 71 + github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 72 + github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 73 + github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 74 + github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= 75 + github.com/google/flatbuffers v2.0.6+incompatible h1:XHFReMv7nFFusa+CEokzWbzaYocKXI6C7hdU5Kgh9Lw= 76 + github.com/google/flatbuffers v2.0.6+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= 77 + github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 78 + github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 79 + github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 80 + github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 81 + github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 82 + github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 83 + github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 84 + github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 85 + github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 86 + github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 87 + github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 88 + github.com/google/orderedcode v0.0.1 h1:UzfcAexk9Vhv8+9pNOgRu41f16lHq725vPwnSeiG/Us= 89 + github.com/google/orderedcode v0.0.1/go.mod h1:iVyU4/qPKHY5h/wSd6rZZCDcLJNxiWO6dvsYES2Sb20= 90 + github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 91 + github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 92 + github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 93 + github.com/jellydator/ttlcache/v3 v3.2.0 h1:6lqVJ8X3ZaUwvzENqPAobDsXNExfUJd61u++uW8a3LE= 94 + github.com/jellydator/ttlcache/v3 v3.2.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4= 95 + github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 96 + github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 97 + github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 98 + github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 99 + github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= 100 + github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= 101 + github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= 102 + github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 103 + github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= 104 + github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 105 + github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 106 + github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 107 + github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 108 + github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 109 + github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 110 + github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 111 + github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 112 + github.com/ostafen/clover/v2 v2.0.0-alpha.3 h1:fXC7tVHQkUPFlxlj/kD98h0ngrTpIeJymaxVIqDzw3Q= 113 + github.com/ostafen/clover/v2 v2.0.0-alpha.3/go.mod h1:5YCDt+wJDUNN1uSXE5csxSQBuJrNjidkOkJTXWuNhDY= 114 + github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 115 + github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 116 + github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 117 + github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 118 + github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 119 + github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 120 + github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 121 + github.com/samber/lo v1.46.0 h1:w8G+oaCPgz1PoCJztqymCFaKwXt+5cCXn51uPxExFfQ= 122 + github.com/samber/lo v1.46.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= 123 + github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= 124 + github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 125 + github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 126 + github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= 127 + github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 128 + github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 129 + github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 130 + github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= 131 + github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 132 + github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 133 + github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 134 + github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 135 + github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= 136 + github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 137 + github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 138 + github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 139 + github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 140 + github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 141 + github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 142 + github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 143 + github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 144 + github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= 145 + github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= 146 + github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= 147 + github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= 148 + github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM= 149 + github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 150 + github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 151 + github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 152 + go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= 153 + go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= 154 + go.hacdias.com/indielib v0.3.1 h1:t6IUp2lfQBa3baXcBN9S/A4fBq4vzMWVbfCQgPcDpy8= 155 + go.hacdias.com/indielib v0.3.1/go.mod h1:ushJ07W6LxAbZWhyqXzQQxXkalPkZo6cGz5Uj2wOdb4= 156 + go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= 157 + go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= 158 + go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= 159 + go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= 160 + go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= 161 + golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 162 + golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 163 + golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 164 + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 165 + golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 166 + golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 167 + golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 168 + golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 169 + golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 170 + golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 171 + golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 172 + golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 173 + golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 174 + golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 175 + golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 176 + golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 177 + golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 178 + golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 179 + golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 180 + golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 181 + golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 182 + golang.org/x/net v0.0.0-20220728211354-c7608f3a8462/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 183 + golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= 184 + golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= 185 + golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 186 + golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= 187 + golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 188 + golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 189 + golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 190 + golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 191 + golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 192 + golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 193 + golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 194 + golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= 195 + golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 196 + golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 197 + golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 198 + golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 199 + golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 200 + golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 201 + golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 202 + golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 203 + golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 204 + golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 205 + golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 206 + golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 207 + golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 208 + golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= 209 + golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 210 + golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 211 + golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 212 + golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 213 + golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 214 + golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 215 + golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 216 + golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= 217 + golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= 218 + golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 219 + golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 220 + golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 221 + golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 222 + golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 223 + golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 224 + golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 225 + golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 226 + golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 227 + golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 228 + golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 229 + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 230 + google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 231 + google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 232 + google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 233 + google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 234 + google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 235 + google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 236 + google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 237 + google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 238 + google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 239 + google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 240 + google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 241 + google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 242 + google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 243 + google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 244 + google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 245 + google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 246 + google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 247 + google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 248 + google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 249 + google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 250 + google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 251 + google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 252 + google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 253 + google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= 254 + google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 255 + gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 256 + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 257 + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 258 + gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 259 + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 260 + gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 261 + gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 262 + honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 263 + honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 264 + willnorris.com/go/microformats v1.2.1-0.20240301064101-b5d1b9d2120e h1:TRIOwo0NxN4KVSgYlYmiQktd9I96YgZ3942/JVzhwTM= 265 + willnorris.com/go/microformats v1.2.1-0.20240301064101-b5d1b9d2120e/go.mod h1:zzo0hFA/E/nl1ZAjXiXA7KCKwCTdgBU+7HXltGgHeGA= 266 + willnorris.com/go/webmention v0.0.0-20220108183051-4a23794272f0 h1:V5+O+YZHchEwu6ZmPcqT1dQ+mHgE356Q+w9SVOQ+QZg= 267 + willnorris.com/go/webmention v0.0.0-20220108183051-4a23794272f0/go.mod h1:DgeruqKIsZtcDXVXNbBHa0YYEm88oAnK7PahkDtuCvw=
+241
indieauth.go
··· 1 + package main 2 + 3 + import ( 4 + "context" 5 + "crypto/sha256" 6 + "crypto/subtle" 7 + "errors" 8 + "net/http" 9 + "net/url" 10 + "os" 11 + "strings" 12 + "time" 13 + 14 + "github.com/puregarlic/space/pages" 15 + 16 + "github.com/a-h/templ" 17 + "github.com/aidarkhanov/nanoid" 18 + "github.com/golang-jwt/jwt/v5" 19 + "go.hacdias.com/indielib/indieauth" 20 + ) 21 + 22 + // storeAuthorization stores the authorization request and returns a code for it. 23 + // Something such as JWT tokens could be used in a production environment. 24 + func (s *server) storeAuthorization(req *indieauth.AuthenticationRequest) string { 25 + code := nanoid.New() 26 + 27 + s.db.Authorization.Set(code, req, 0) 28 + 29 + return code 30 + } 31 + 32 + type CustomTokenClaims struct { 33 + Scopes []string `json:"scopes"` 34 + jwt.RegisteredClaims 35 + } 36 + 37 + type contextKey string 38 + 39 + const ( 40 + scopesContextKey contextKey = "scopes" 41 + ) 42 + 43 + // authorizationGetHandler handles the GET method for the authorization endpoint. 44 + func (s *server) authorizationGetHandler(w http.ResponseWriter, r *http.Request) { 45 + // In a production server, this page would usually be protected. In order for 46 + // the user to authorize this request, they must be authenticated. This could 47 + // be done in different ways: username/password, passkeys, etc. 48 + 49 + // Parse the authorization request. 50 + req, err := s.ias.ParseAuthorization(r) 51 + if err != nil { 52 + serveErrorJSON(w, http.StatusBadRequest, "invalid_request", err.Error()) 53 + return 54 + } 55 + 56 + // Do a best effort attempt at fetching more information about the application 57 + // that we can show to the user. Not all applications provide this sort of 58 + // information. 59 + app, _ := s.ias.DiscoverApplicationMetadata(r.Context(), req.ClientID) 60 + 61 + // Here, we just display a small HTML document where the user has to press 62 + // to authorize this request. Please note that this template contains a form 63 + // where we dump all the request information. This makes it possible to reuse 64 + // [indieauth.Server.ParseAuthorization] when the user authorizes the request. 65 + templ.Handler(pages.Auth(req, app)).ServeHTTP(w, r) 66 + } 67 + 68 + // authorizationPostHandler handles the POST method for the authorization endpoint. 69 + func (s *server) authorizationPostHandler(w http.ResponseWriter, r *http.Request) { 70 + s.authorizationCodeExchange(w, r, false) 71 + } 72 + 73 + // tokenHandler handles the token endpoint. In our case, we only accept the default 74 + // type which is exchanging an authorization code for a token. 75 + func (s *server) tokenHandler(w http.ResponseWriter, r *http.Request) { 76 + if r.Method != http.MethodPost { 77 + httpError(w, http.StatusMethodNotAllowed) 78 + return 79 + } 80 + 81 + if r.Form.Get("grant_type") == "refresh_token" { 82 + // NOTE: this server does not implement refresh tokens. 83 + // https://indieauth.spec.indieweb.org/#refresh-tokens 84 + w.WriteHeader(http.StatusNotImplemented) 85 + return 86 + } 87 + 88 + s.authorizationCodeExchange(w, r, true) 89 + } 90 + 91 + type tokenResponse struct { 92 + Me string `json:"me"` 93 + AccessToken string `json:"access_token,omitempty"` 94 + TokenType string `json:"token_type,omitempty"` 95 + Scope string `json:"scope,omitempty"` 96 + ExpiresIn int64 `json:"expires_in,omitempty"` 97 + } 98 + 99 + // authorizationCodeExchange handles the authorization code exchange. It is used by 100 + // both the authorization handler to exchange the code for the user's profile URL, 101 + // and by the token endpoint, to exchange the code by a token. 102 + func (s *server) authorizationCodeExchange(w http.ResponseWriter, r *http.Request, withToken bool) { 103 + if err := r.ParseForm(); err != nil { 104 + serveErrorJSON(w, http.StatusBadRequest, "invalid_request", err.Error()) 105 + return 106 + } 107 + 108 + // t := s.getAuthorization(r.Form.Get("code")) 109 + req, present := s.db.Authorization.GetAndDelete(r.Form.Get("code")) 110 + if !present { 111 + serveErrorJSON(w, http.StatusBadRequest, "invalid_request", "invalid authorization") 112 + return 113 + } 114 + authRequest := req.Value() 115 + 116 + err := s.ias.ValidateTokenExchange(authRequest, r) 117 + if err != nil { 118 + serveErrorJSON(w, http.StatusBadRequest, "invalid_request", err.Error()) 119 + return 120 + } 121 + 122 + response := &tokenResponse{ 123 + Me: s.profileURL, 124 + } 125 + 126 + scopes := authRequest.Scopes 127 + 128 + if withToken { 129 + now := time.Now() 130 + expiresAt := now.Add(15 * time.Minute) 131 + claims := CustomTokenClaims{ 132 + scopes, 133 + jwt.RegisteredClaims{ 134 + ExpiresAt: jwt.NewNumericDate(expiresAt), 135 + IssuedAt: jwt.NewNumericDate(now), 136 + NotBefore: jwt.NewNumericDate(now), 137 + }, 138 + } 139 + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 140 + 141 + secret := os.Getenv("JWT_SECRET") 142 + jwt, err := token.SignedString([]byte(secret)) 143 + if err != nil { 144 + panic(err) 145 + } 146 + 147 + response.AccessToken = jwt 148 + response.TokenType = "Bearer" 149 + response.ExpiresIn = int64(time.Until(expiresAt).Seconds()) 150 + response.Scope = strings.Join(scopes, " ") 151 + } 152 + 153 + // An actual server may want to include the "profile" in the response if the 154 + // scope "profile" is included. 155 + serveJSON(w, http.StatusOK, response) 156 + } 157 + 158 + func (s *server) authorizationAcceptHandler(w http.ResponseWriter, r *http.Request) { 159 + // Parse authorization information. This only works because our authorization page 160 + // includes all the required information. This can be done in other ways: database, 161 + // whether temporary or not, cookies, etc. 162 + req, err := s.ias.ParseAuthorization(r) 163 + if err != nil { 164 + serveErrorJSON(w, http.StatusBadRequest, "invalid_request", err.Error()) 165 + return 166 + } 167 + 168 + // Generate a random code and persist the information associated to that code. 169 + // You could do this in other ways: database, or JWT tokens, or both, for example. 170 + code := s.storeAuthorization(req) 171 + 172 + // Redirect to client callback. 173 + query := url.Values{} 174 + query.Set("code", code) 175 + query.Set("state", req.State) 176 + http.Redirect(w, r, req.RedirectURI+"?"+query.Encode(), http.StatusFound) 177 + } 178 + 179 + // mustAuth is a middleware to ensure that the request is authorized. The way this 180 + // works depends on the implementation. It then stores the scopes in the context. 181 + func (s *server) mustAuth(next http.Handler) http.Handler { 182 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 183 + tokenStr := r.Header.Get("Authorization") 184 + tokenStr = strings.TrimPrefix(tokenStr, "Bearer") 185 + tokenStr = strings.TrimSpace(tokenStr) 186 + 187 + if len(tokenStr) <= 0 { 188 + serveErrorJSON(w, http.StatusUnauthorized, "invalid_request", "no credentials") 189 + return 190 + } 191 + 192 + token, err := jwt.ParseWithClaims(tokenStr, &CustomTokenClaims{}, func(t *jwt.Token) (interface{}, error) { 193 + return []byte(os.Getenv("JWT_SECRET")), nil 194 + }) 195 + 196 + if err != nil { 197 + serveErrorJSON(w, http.StatusUnauthorized, "invalid_request", "invalid token") 198 + return 199 + } else if claims, ok := token.Claims.(*CustomTokenClaims); ok { 200 + ctx := context.WithValue(r.Context(), scopesContextKey, claims.Scopes) 201 + next.ServeHTTP(w, r.WithContext(ctx)) 202 + return 203 + } else { 204 + serveErrorJSON(w, http.StatusUnauthorized, "invalid_request", "malformed claims") 205 + return 206 + } 207 + }) 208 + } 209 + 210 + func (s *server) mustBasicAuth(next http.Handler) http.Handler { 211 + user, ok := os.LookupEnv("ADMIN_USERNAME") 212 + if !ok { 213 + panic(errors.New("ADMIN_USERNAME is not set, cannot start")) 214 + } 215 + 216 + pass, ok := os.LookupEnv("ADMIN_PASSWORD") 217 + if !ok { 218 + panic(errors.New("ADMIN_PASSWORD is not set, cannot start")) 219 + } 220 + 221 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 222 + username, password, ok := r.BasicAuth() 223 + if ok { 224 + usernameHash := sha256.Sum256([]byte(username)) 225 + passwordHash := sha256.Sum256([]byte(password)) 226 + expectedUsernameHash := sha256.Sum256([]byte(user)) 227 + expectedPasswordHash := sha256.Sum256([]byte(pass)) 228 + 229 + usernameMatch := (subtle.ConstantTimeCompare(usernameHash[:], expectedUsernameHash[:]) == 1) 230 + passwordMatch := (subtle.ConstantTimeCompare(passwordHash[:], expectedPasswordHash[:]) == 1) 231 + 232 + if usernameMatch && passwordMatch { 233 + next.ServeHTTP(w, r) 234 + return 235 + } 236 + } 237 + 238 + w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`) 239 + http.Error(w, "Unauthorized", http.StatusUnauthorized) 240 + }) 241 + }
+186
main.go
··· 1 + package main 2 + 3 + import ( 4 + "encoding/json" 5 + "flag" 6 + "time" 7 + 8 + "log" 9 + "net/http" 10 + "strconv" 11 + 12 + "github.com/ostafen/clover/v2/query" 13 + 14 + "github.com/puregarlic/space/db" 15 + "github.com/puregarlic/space/pages" 16 + "github.com/puregarlic/space/types" 17 + 18 + "github.com/a-h/templ" 19 + "github.com/go-chi/chi/v5" 20 + "github.com/go-chi/chi/v5/middleware" 21 + "github.com/go-chi/httprate" 22 + 23 + "go.hacdias.com/indielib/indieauth" 24 + "go.hacdias.com/indielib/microformats" 25 + "go.hacdias.com/indielib/micropub" 26 + 27 + _ "github.com/joho/godotenv/autoload" 28 + ) 29 + 30 + func main() { 31 + // Setup flags. 32 + portPtr := flag.Int("port", 80, "port to listen on") 33 + addressPtr := flag.String("profile", "http://localhost/", "client URL and front facing address to listen on") 34 + flag.Parse() 35 + 36 + profileURL := *addressPtr 37 + 38 + // Validate the given Client ID before starting the HTTP server. 39 + err := indieauth.IsValidProfileURL(profileURL) 40 + if err != nil { 41 + log.Fatal(err) 42 + } 43 + 44 + // Setup storage handlers 45 + store := db.NewStorage() 46 + defer store.Cleanup() 47 + 48 + // Create a new client. 49 + s := &server{ 50 + profileURL: profileURL, 51 + ias: indieauth.NewServer(true, nil), 52 + db: store, 53 + } 54 + 55 + r := chi.NewRouter() 56 + 57 + r.Use(middleware.RequestID) 58 + r.Use(middleware.RealIP) 59 + r.Use(middleware.Logger) 60 + r.Use(middleware.Recoverer) 61 + 62 + r.Use(httprate.LimitByIP(100, 1*time.Minute)) 63 + 64 + // Static resources 65 + r.Get("/static/*", http.StripPrefix("/static", http.FileServer(http.Dir("static"))).ServeHTTP) 66 + 67 + // Pages 68 + r.Get("/", s.serveHomeTemplate) 69 + r.Get("/posts/{slug}", s.servePostTemplate) 70 + 71 + // IndieAuth handlers 72 + r.Group(func(r chi.Router) { 73 + r.Post("/token", s.tokenHandler) 74 + r.Post("/authorization", s.authorizationPostHandler) 75 + r.Post("/authorization/accept", s.authorizationAcceptHandler) 76 + 77 + // User authentication portal 78 + r.With(s.mustBasicAuth).Get("/authorization", s.authorizationGetHandler) 79 + }) 80 + 81 + // Micropub handler 82 + r.Route("/micropub", func(r chi.Router) { 83 + r.Use(s.mustAuth) 84 + r.Get("/", s.serveMicropub) 85 + r.Post("/", s.serveMicropub) 86 + }) 87 + 88 + // Start it! 89 + log.Printf("Listening on http://localhost:%d", *portPtr) 90 + log.Printf("Listening on %s", profileURL) 91 + if err := http.ListenAndServe(":"+strconv.Itoa(*portPtr), r); err != nil { 92 + log.Fatal(err) 93 + } 94 + } 95 + 96 + type server struct { 97 + profileURL string 98 + ias *indieauth.Server 99 + db *db.Storage 100 + } 101 + 102 + func (s *server) serveHomeTemplate(w http.ResponseWriter, r *http.Request) { 103 + q := query.NewQuery( 104 + string(db.PostCollection), 105 + ).Sort(query.SortOption{ 106 + Field: "createdAt", 107 + Direction: -1, 108 + }).Limit(10) 109 + 110 + docs, err := s.db.Docs.FindAll(q) 111 + if err != nil { 112 + httpError(w, http.StatusInternalServerError) 113 + panic(err) 114 + } 115 + 116 + posts := make([]*types.Post, len(docs)) 117 + for i, doc := range docs { 118 + id := doc.ObjectId() 119 + post := &types.Post{ 120 + ID: id, 121 + } 122 + 123 + if err := doc.Unmarshal(post); err != nil { 124 + httpError(w, http.StatusInternalServerError) 125 + panic(err) 126 + } 127 + 128 + post.ID = id 129 + 130 + posts[i] = post 131 + } 132 + 133 + templ.Handler(pages.Home(s.profileURL, posts)).ServeHTTP(w, r) 134 + } 135 + 136 + func (s *server) servePostTemplate(w http.ResponseWriter, r *http.Request) { 137 + id := chi.URLParam(r, "slug") 138 + post := &types.Post{} 139 + 140 + doc, err := s.db.Docs.FindById(string(db.PostCollection), id) 141 + if err != nil { 142 + httpError(w, http.StatusInternalServerError) 143 + return 144 + } else if doc == nil { 145 + httpError(w, http.StatusNotFound) 146 + return 147 + } 148 + 149 + if err := doc.Unmarshal(post); err != nil { 150 + httpError(w, http.StatusInternalServerError) 151 + return 152 + } 153 + 154 + templ.Handler(pages.Post(post)).ServeHTTP(w, r) 155 + } 156 + 157 + func (s *server) serveMicropub(w http.ResponseWriter, r *http.Request) { 158 + micropub.NewHandler( 159 + &micropubImplementation{s}, 160 + micropub.WithGetPostTypes(func() []micropub.PostType { 161 + return []micropub.PostType{ 162 + { 163 + Name: "Post", 164 + Type: string(microformats.TypeNote), 165 + }, 166 + } 167 + }), 168 + ).ServeHTTP(w, r) 169 + } 170 + 171 + func httpError(w http.ResponseWriter, status int) { 172 + http.Error(w, http.StatusText(status), status) 173 + } 174 + 175 + func serveJSON(w http.ResponseWriter, code int, data interface{}) { 176 + w.Header().Set("Content-Type", "application/json; charset=utf-8") 177 + w.WriteHeader(code) 178 + _ = json.NewEncoder(w).Encode(data) 179 + } 180 + 181 + func serveErrorJSON(w http.ResponseWriter, code int, err, errDescription string) { 182 + serveJSON(w, code, map[string]string{ 183 + "error": err, 184 + "error_description": errDescription, 185 + }) 186 + }
+212
micropub.go
··· 1 + package main 2 + 3 + import ( 4 + "errors" 5 + "fmt" 6 + "net/http" 7 + urlpkg "net/url" 8 + "reflect" 9 + "strings" 10 + "time" 11 + 12 + "github.com/ostafen/clover/v2/document" 13 + "github.com/puregarlic/space/db" 14 + "github.com/puregarlic/space/types" 15 + "github.com/samber/lo" 16 + 17 + "go.hacdias.com/indielib/micropub" 18 + ) 19 + 20 + type micropubImplementation struct { 21 + *server 22 + } 23 + 24 + func postIdFromUrlPath(path string) string { 25 + return strings.TrimPrefix(path, "/posts/") 26 + } 27 + 28 + func (s *micropubImplementation) HasScope(r *http.Request, scope string) bool { 29 + v := r.Context().Value(scopesContextKey) 30 + if scopes, ok := v.([]string); ok { 31 + for _, sc := range scopes { 32 + if sc == scope { 33 + return true 34 + } 35 + } 36 + } 37 + 38 + return false 39 + } 40 + 41 + func (s *micropubImplementation) Source(urlStr string) (map[string]any, error) { 42 + url, err := urlpkg.Parse(urlStr) 43 + if err != nil { 44 + return nil, fmt.Errorf("%w: %w", micropub.ErrBadRequest, err) 45 + } 46 + 47 + id := postIdFromUrlPath(url.Path) 48 + post := &types.Post{} 49 + doc, err := s.server.db.Docs.FindById(string(db.PostCollection), id) 50 + if err != nil { 51 + panic(err) 52 + } else if doc == nil { 53 + return nil, micropub.ErrNotFound 54 + } 55 + 56 + if err := doc.Unmarshal(post); err != nil { 57 + panic(err) 58 + } 59 + 60 + return map[string]any{ 61 + "type": []string{post.Type}, 62 + "properties": post.Properties, 63 + }, nil 64 + } 65 + 66 + func (s *micropubImplementation) SourceMany(limit, offset int) ([]map[string]any, error) { 67 + return nil, micropub.ErrNotImplemented 68 + } 69 + 70 + func (s *micropubImplementation) Create(req *micropub.Request) (string, error) { 71 + post := types.Post{ 72 + Type: req.Type, 73 + Properties: req.Properties, 74 + CreatedAt: time.Now().Unix(), 75 + } 76 + doc := document.NewDocumentOf(post) 77 + if doc == nil { 78 + return "", errors.New("Could not marshal post to Clover document") 79 + } 80 + 81 + id, err := s.server.db.Docs.InsertOne(string(db.PostCollection), doc) 82 + if err != nil { 83 + return "", err 84 + } 85 + 86 + return s.profileURL + "posts/" + id, nil 87 + } 88 + 89 + func (s *micropubImplementation) Update(req *micropub.Request) (string, error) { 90 + url, err := urlpkg.Parse(req.URL) 91 + if err != nil { 92 + return "", fmt.Errorf("%w: %w", micropub.ErrBadRequest, err) 93 + } 94 + 95 + id := postIdFromUrlPath(url.Path) 96 + 97 + if err := s.server.db.Docs.UpdateById( 98 + string(db.PostCollection), 99 + id, 100 + func(doc *document.Document) *document.Document { 101 + post := &types.Post{} 102 + if err := doc.Unmarshal(post); err != nil { 103 + panic(err) 104 + } 105 + 106 + props, err := updateProperties(post.Properties, req) 107 + if err != nil { 108 + panic(err) 109 + } 110 + 111 + doc.Set("properties", props) 112 + 113 + return doc 114 + }, 115 + ); err != nil { 116 + return "", fmt.Errorf("%w: %w", micropub.ErrBadRequest, err) 117 + } 118 + 119 + return s.profileURL + url.Path, nil 120 + } 121 + 122 + func (s *micropubImplementation) Delete(urlStr string) error { 123 + url, err := urlpkg.Parse(urlStr) 124 + if err != nil { 125 + return fmt.Errorf("%w: %w", micropub.ErrBadRequest, err) 126 + } 127 + 128 + id := postIdFromUrlPath(url.Path) 129 + 130 + if err := s.server.db.Docs.DeleteById(string(db.PostCollection), id); err != nil { 131 + return fmt.Errorf("%w: %w", micropub.ErrBadRequest, err) 132 + } 133 + 134 + return nil 135 + } 136 + 137 + func (s *micropubImplementation) Undelete(url string) error { 138 + return micropub.ErrNotImplemented 139 + } 140 + 141 + // updateProperties applies the updates (additions, deletions, replacements) 142 + // in the given [micropub.Request] to a set of existing microformats properties. 143 + func updateProperties(properties map[string][]any, req *micropub.Request) (map[string][]any, error) { 144 + if req.Updates.Replace != nil { 145 + for key, value := range req.Updates.Replace { 146 + properties[key] = value 147 + } 148 + } 149 + 150 + if req.Updates.Add != nil { 151 + for key, value := range req.Updates.Add { 152 + switch key { 153 + case "name": 154 + return nil, errors.New("cannot add a new name") 155 + case "content": 156 + return nil, errors.New("cannot add content") 157 + default: 158 + if key == "published" { 159 + if _, ok := properties["published"]; ok { 160 + return nil, errors.New("cannot replace published through add method") 161 + } 162 + } 163 + 164 + if _, ok := properties[key]; !ok { 165 + properties[key] = []any{} 166 + } 167 + 168 + properties[key] = append(properties[key], value...) 169 + } 170 + } 171 + } 172 + 173 + if req.Updates.Delete != nil { 174 + if reflect.TypeOf(req.Updates.Delete).Kind() == reflect.Slice { 175 + toDelete, ok := req.Updates.Delete.([]any) 176 + if !ok { 177 + return nil, errors.New("invalid delete array") 178 + } 179 + 180 + for _, key := range toDelete { 181 + delete(properties, fmt.Sprint(key)) 182 + } 183 + } else { 184 + toDelete, ok := req.Updates.Delete.(map[string]any) 185 + if !ok { 186 + return nil, fmt.Errorf("invalid delete object: expected map[string]any, got: %s", reflect.TypeOf(req.Updates.Delete)) 187 + } 188 + 189 + for key, v := range toDelete { 190 + value, ok := v.([]any) 191 + if !ok { 192 + return nil, fmt.Errorf("invalid value: expected []any, got: %s", reflect.TypeOf(value)) 193 + } 194 + 195 + if _, ok := properties[key]; !ok { 196 + properties[key] = []any{} 197 + } 198 + 199 + properties[key] = lo.Filter(properties[key], func(ss any, _ int) bool { 200 + for _, s := range value { 201 + if s == ss { 202 + return false 203 + } 204 + } 205 + return true 206 + }) 207 + } 208 + } 209 + } 210 + 211 + return properties, nil 212 + }
+15
package.json
··· 1 + { 2 + "name": "puregarlicspace", 3 + "version": "1.0.0", 4 + "description": "", 5 + "main": "index.js", 6 + "scripts": { 7 + "test": "echo \"Error: no test specified\" && exit 1" 8 + }, 9 + "keywords": [], 10 + "author": "", 11 + "license": "ISC", 12 + "devDependencies": { 13 + "tailwindcss": "^3.4.7" 14 + } 15 + }
+56
pages/auth.templ
··· 1 + package pages 2 + 3 + import "strings" 4 + 5 + import "go.hacdias.com/indielib/indieauth" 6 + 7 + templ Auth( req *indieauth.AuthenticationRequest, app *indieauth.ApplicationMetadata ) { 8 + <!DOCTYPE html> 9 + <html> 10 + <head> 11 + <title>Authorization | Micropub and IndieAuth Server Demo</title> 12 + </head> 13 + <body> 14 + <h1>IndieAuth Server Demo: Authorization</h1> 15 + 16 + <p> 17 + You received an authorization request from 18 + 19 + if app != nil { 20 + if len(app.Logo) > 0 { 21 + <img style="width: 1em; vertical-align: middle" src={ app.Logo } /> 22 + } 23 + 24 + <strong>{ app.Name }</strong> by { app.Author }: 25 + } else { 26 + the following client: 27 + } 28 + </p> 29 + 30 + <ul> 31 + <li><strong>Redirect:</strong> <code>{ req.ClientID }</code></li> 32 + <li><strong>Client:</strong> <code>{ req.RedirectURI }</code></li> 33 + </ul> 34 + 35 + <p>For the following scopes: 36 + for _, scope := range req.Scopes { 37 + <code>{ scope }</code> 38 + } 39 + .</p> 40 + 41 + <form method='post' action='/authorization/accept'> 42 + <input type="hidden" name="response_type" value="code"> 43 + <input type="hidden" name="scope" value={ strings.Join(req.Scopes, " ") }> 44 + <input type="hidden" name="redirect_uri" value={ req.RedirectURI }> 45 + <input type="hidden" name="client_id" value={ req.ClientID }> 46 + <input type="hidden" name="state" value={ req.State }> 47 + <input type="hidden" name="code_challenge" value={ req.CodeChallenge }> 48 + <input type="hidden" name="code_challenge_method" value={ req.CodeChallengeMethod }> 49 + 50 + <p>In a production server, this page could be behind some sort of authentication mechanism, such as username and password, PassKey, etc.</p> 51 + 52 + <button id="submit">Authorize</button> 53 + </form> 54 + </body> 55 + </html> 56 + }
+32
pages/home.templ
··· 1 + package pages 2 + 3 + import "github.com/puregarlic/space/types" 4 + import "fmt" 5 + 6 + templ Home(profileUrl string, posts []*types.Post) { 7 + <!DOCTYPE html> 8 + <html> 9 + <head> 10 + <title>Micropub and IndieAuth Server Demo</title> 11 + <link rel="authorization_endpoint" href="/authorization"> 12 + <link rel="token_endpoint" href="/token"> 13 + <link rel="micropub" href="/micropub"> 14 + 15 + <link rel="stylesheet" href="/static/styles.css" /> 16 + </head> 17 + <body> 18 + <h1>Micropub and IndieAuth Server Demo</h1> 19 + 20 + <p>Sign in on a website that supports IndieAuth. Use <code>{ profileUrl }</code> as your domain.</p> 21 + 22 + <h2>Posts</h2> 23 + 24 + <p>You can create posts using a Micropub client.</p> 25 + <ul> 26 + for _, post := range posts { 27 + <li><a href={ templ.URL("/posts/" + post.ID) }>{ post.ID }</a> - { fmt.Sprint(post.Properties["content"]) }</li> 28 + } 29 + </ul> 30 + </body> 31 + </html> 32 + }
+56
pages/post.templ
··· 1 + package pages 2 + 3 + import "github.com/puregarlic/space/types" 4 + import "fmt" 5 + import "encoding/json" 6 + import "reflect" 7 + 8 + func printProperty(post *types.Post, name string) string { 9 + if val, ok := post.Properties[name]; ok { 10 + tp := reflect.TypeOf(val) 11 + switch tp.Kind() { 12 + default: 13 + return fmt.Sprint(val) 14 + case reflect.Slice: 15 + str := "" 16 + for _, v := range val { 17 + str = str + fmt.Sprint(v) 18 + } 19 + 20 + return str 21 + } 22 + 23 + } 24 + 25 + return "<no name provided>" 26 + } 27 + 28 + func printPost(post *types.Post) string { 29 + out, err := json.Marshal(post) 30 + 31 + if (err != nil) { 32 + panic (err) 33 + } 34 + 35 + return fmt.Sprint(string(out)) 36 + } 37 + 38 + templ Post(post *types.Post) { 39 + <!DOCTYPE html> 40 + <html> 41 + <head> 42 + <title>Post | Micropub and IndieAuth Server Demo</title> 43 + </head> 44 + <body> 45 + <div class={ post.Type }> 46 + <h1 class="p-name">{ printProperty(post, "name") }</h1> 47 + <p class="p-content">{ printProperty(post, "content") }</p> 48 + 49 + <h3>Stored Microformats</h3> 50 + <code> 51 + <pre>{ printPost(post) }</pre> 52 + </code> 53 + </div> 54 + </body> 55 + </html> 56 + }
+842
pnpm-lock.yaml
··· 1 + lockfileVersion: '9.0' 2 + 3 + settings: 4 + autoInstallPeers: true 5 + excludeLinksFromLockfile: false 6 + 7 + importers: 8 + 9 + .: 10 + devDependencies: 11 + tailwindcss: 12 + specifier: ^3.4.7 13 + version: 3.4.7 14 + 15 + packages: 16 + 17 + '@alloc/quick-lru@5.2.0': 18 + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} 19 + engines: {node: '>=10'} 20 + 21 + '@isaacs/cliui@8.0.2': 22 + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} 23 + engines: {node: '>=12'} 24 + 25 + '@jridgewell/gen-mapping@0.3.5': 26 + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} 27 + engines: {node: '>=6.0.0'} 28 + 29 + '@jridgewell/resolve-uri@3.1.2': 30 + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 31 + engines: {node: '>=6.0.0'} 32 + 33 + '@jridgewell/set-array@1.2.1': 34 + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} 35 + engines: {node: '>=6.0.0'} 36 + 37 + '@jridgewell/sourcemap-codec@1.5.0': 38 + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} 39 + 40 + '@jridgewell/trace-mapping@0.3.25': 41 + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} 42 + 43 + '@nodelib/fs.scandir@2.1.5': 44 + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 45 + engines: {node: '>= 8'} 46 + 47 + '@nodelib/fs.stat@2.0.5': 48 + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} 49 + engines: {node: '>= 8'} 50 + 51 + '@nodelib/fs.walk@1.2.8': 52 + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 53 + engines: {node: '>= 8'} 54 + 55 + '@pkgjs/parseargs@0.11.0': 56 + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} 57 + engines: {node: '>=14'} 58 + 59 + ansi-regex@5.0.1: 60 + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 61 + engines: {node: '>=8'} 62 + 63 + ansi-regex@6.0.1: 64 + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} 65 + engines: {node: '>=12'} 66 + 67 + ansi-styles@4.3.0: 68 + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 69 + engines: {node: '>=8'} 70 + 71 + ansi-styles@6.2.1: 72 + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} 73 + engines: {node: '>=12'} 74 + 75 + any-promise@1.3.0: 76 + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} 77 + 78 + anymatch@3.1.3: 79 + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} 80 + engines: {node: '>= 8'} 81 + 82 + arg@5.0.2: 83 + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} 84 + 85 + balanced-match@1.0.2: 86 + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 87 + 88 + binary-extensions@2.3.0: 89 + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} 90 + engines: {node: '>=8'} 91 + 92 + brace-expansion@2.0.1: 93 + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} 94 + 95 + braces@3.0.3: 96 + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} 97 + engines: {node: '>=8'} 98 + 99 + camelcase-css@2.0.1: 100 + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} 101 + engines: {node: '>= 6'} 102 + 103 + chokidar@3.6.0: 104 + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} 105 + engines: {node: '>= 8.10.0'} 106 + 107 + color-convert@2.0.1: 108 + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 109 + engines: {node: '>=7.0.0'} 110 + 111 + color-name@1.1.4: 112 + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 113 + 114 + commander@4.1.1: 115 + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} 116 + engines: {node: '>= 6'} 117 + 118 + cross-spawn@7.0.3: 119 + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} 120 + engines: {node: '>= 8'} 121 + 122 + cssesc@3.0.0: 123 + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} 124 + engines: {node: '>=4'} 125 + hasBin: true 126 + 127 + didyoumean@1.2.2: 128 + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} 129 + 130 + dlv@1.1.3: 131 + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} 132 + 133 + eastasianwidth@0.2.0: 134 + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} 135 + 136 + emoji-regex@8.0.0: 137 + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 138 + 139 + emoji-regex@9.2.2: 140 + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} 141 + 142 + fast-glob@3.3.2: 143 + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} 144 + engines: {node: '>=8.6.0'} 145 + 146 + fastq@1.17.1: 147 + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} 148 + 149 + fill-range@7.1.1: 150 + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} 151 + engines: {node: '>=8'} 152 + 153 + foreground-child@3.2.1: 154 + resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==} 155 + engines: {node: '>=14'} 156 + 157 + fsevents@2.3.3: 158 + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 159 + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 160 + os: [darwin] 161 + 162 + function-bind@1.1.2: 163 + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 164 + 165 + glob-parent@5.1.2: 166 + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 167 + engines: {node: '>= 6'} 168 + 169 + glob-parent@6.0.2: 170 + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 171 + engines: {node: '>=10.13.0'} 172 + 173 + glob@10.4.5: 174 + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} 175 + hasBin: true 176 + 177 + hasown@2.0.2: 178 + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} 179 + engines: {node: '>= 0.4'} 180 + 181 + is-binary-path@2.1.0: 182 + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} 183 + engines: {node: '>=8'} 184 + 185 + is-core-module@2.15.0: 186 + resolution: {integrity: sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==} 187 + engines: {node: '>= 0.4'} 188 + 189 + is-extglob@2.1.1: 190 + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 191 + engines: {node: '>=0.10.0'} 192 + 193 + is-fullwidth-code-point@3.0.0: 194 + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 195 + engines: {node: '>=8'} 196 + 197 + is-glob@4.0.3: 198 + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 199 + engines: {node: '>=0.10.0'} 200 + 201 + is-number@7.0.0: 202 + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 203 + engines: {node: '>=0.12.0'} 204 + 205 + isexe@2.0.0: 206 + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 207 + 208 + jackspeak@3.4.3: 209 + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} 210 + 211 + jiti@1.21.6: 212 + resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} 213 + hasBin: true 214 + 215 + lilconfig@2.1.0: 216 + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} 217 + engines: {node: '>=10'} 218 + 219 + lilconfig@3.1.2: 220 + resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} 221 + engines: {node: '>=14'} 222 + 223 + lines-and-columns@1.2.4: 224 + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} 225 + 226 + lru-cache@10.4.3: 227 + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} 228 + 229 + merge2@1.4.1: 230 + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 231 + engines: {node: '>= 8'} 232 + 233 + micromatch@4.0.7: 234 + resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} 235 + engines: {node: '>=8.6'} 236 + 237 + minimatch@9.0.5: 238 + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} 239 + engines: {node: '>=16 || 14 >=14.17'} 240 + 241 + minipass@7.1.2: 242 + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} 243 + engines: {node: '>=16 || 14 >=14.17'} 244 + 245 + mz@2.7.0: 246 + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} 247 + 248 + nanoid@3.3.7: 249 + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} 250 + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 251 + hasBin: true 252 + 253 + normalize-path@3.0.0: 254 + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} 255 + engines: {node: '>=0.10.0'} 256 + 257 + object-assign@4.1.1: 258 + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} 259 + engines: {node: '>=0.10.0'} 260 + 261 + object-hash@3.0.0: 262 + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} 263 + engines: {node: '>= 6'} 264 + 265 + package-json-from-dist@1.0.0: 266 + resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} 267 + 268 + path-key@3.1.1: 269 + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 270 + engines: {node: '>=8'} 271 + 272 + path-parse@1.0.7: 273 + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} 274 + 275 + path-scurry@1.11.1: 276 + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} 277 + engines: {node: '>=16 || 14 >=14.18'} 278 + 279 + picocolors@1.0.1: 280 + resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} 281 + 282 + picomatch@2.3.1: 283 + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 284 + engines: {node: '>=8.6'} 285 + 286 + pify@2.3.0: 287 + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} 288 + engines: {node: '>=0.10.0'} 289 + 290 + pirates@4.0.6: 291 + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} 292 + engines: {node: '>= 6'} 293 + 294 + postcss-import@15.1.0: 295 + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} 296 + engines: {node: '>=14.0.0'} 297 + peerDependencies: 298 + postcss: ^8.0.0 299 + 300 + postcss-js@4.0.1: 301 + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} 302 + engines: {node: ^12 || ^14 || >= 16} 303 + peerDependencies: 304 + postcss: ^8.4.21 305 + 306 + postcss-load-config@4.0.2: 307 + resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} 308 + engines: {node: '>= 14'} 309 + peerDependencies: 310 + postcss: '>=8.0.9' 311 + ts-node: '>=9.0.0' 312 + peerDependenciesMeta: 313 + postcss: 314 + optional: true 315 + ts-node: 316 + optional: true 317 + 318 + postcss-nested@6.2.0: 319 + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} 320 + engines: {node: '>=12.0'} 321 + peerDependencies: 322 + postcss: ^8.2.14 323 + 324 + postcss-selector-parser@6.1.1: 325 + resolution: {integrity: sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==} 326 + engines: {node: '>=4'} 327 + 328 + postcss-value-parser@4.2.0: 329 + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} 330 + 331 + postcss@8.4.40: 332 + resolution: {integrity: sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==} 333 + engines: {node: ^10 || ^12 || >=14} 334 + 335 + queue-microtask@1.2.3: 336 + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 337 + 338 + read-cache@1.0.0: 339 + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} 340 + 341 + readdirp@3.6.0: 342 + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} 343 + engines: {node: '>=8.10.0'} 344 + 345 + resolve@1.22.8: 346 + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} 347 + hasBin: true 348 + 349 + reusify@1.0.4: 350 + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} 351 + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 352 + 353 + run-parallel@1.2.0: 354 + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 355 + 356 + shebang-command@2.0.0: 357 + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 358 + engines: {node: '>=8'} 359 + 360 + shebang-regex@3.0.0: 361 + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 362 + engines: {node: '>=8'} 363 + 364 + signal-exit@4.1.0: 365 + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} 366 + engines: {node: '>=14'} 367 + 368 + source-map-js@1.2.0: 369 + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} 370 + engines: {node: '>=0.10.0'} 371 + 372 + string-width@4.2.3: 373 + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 374 + engines: {node: '>=8'} 375 + 376 + string-width@5.1.2: 377 + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} 378 + engines: {node: '>=12'} 379 + 380 + strip-ansi@6.0.1: 381 + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 382 + engines: {node: '>=8'} 383 + 384 + strip-ansi@7.1.0: 385 + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} 386 + engines: {node: '>=12'} 387 + 388 + sucrase@3.35.0: 389 + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} 390 + engines: {node: '>=16 || 14 >=14.17'} 391 + hasBin: true 392 + 393 + supports-preserve-symlinks-flag@1.0.0: 394 + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} 395 + engines: {node: '>= 0.4'} 396 + 397 + tailwindcss@3.4.7: 398 + resolution: {integrity: sha512-rxWZbe87YJb4OcSopb7up2Ba4U82BoiSGUdoDr3Ydrg9ckxFS/YWsvhN323GMcddgU65QRy7JndC7ahhInhvlQ==} 399 + engines: {node: '>=14.0.0'} 400 + hasBin: true 401 + 402 + thenify-all@1.6.0: 403 + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} 404 + engines: {node: '>=0.8'} 405 + 406 + thenify@3.3.1: 407 + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} 408 + 409 + to-regex-range@5.0.1: 410 + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 411 + engines: {node: '>=8.0'} 412 + 413 + ts-interface-checker@0.1.13: 414 + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} 415 + 416 + util-deprecate@1.0.2: 417 + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} 418 + 419 + which@2.0.2: 420 + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 421 + engines: {node: '>= 8'} 422 + hasBin: true 423 + 424 + wrap-ansi@7.0.0: 425 + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} 426 + engines: {node: '>=10'} 427 + 428 + wrap-ansi@8.1.0: 429 + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} 430 + engines: {node: '>=12'} 431 + 432 + yaml@2.5.0: 433 + resolution: {integrity: sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==} 434 + engines: {node: '>= 14'} 435 + hasBin: true 436 + 437 + snapshots: 438 + 439 + '@alloc/quick-lru@5.2.0': {} 440 + 441 + '@isaacs/cliui@8.0.2': 442 + dependencies: 443 + string-width: 5.1.2 444 + string-width-cjs: string-width@4.2.3 445 + strip-ansi: 7.1.0 446 + strip-ansi-cjs: strip-ansi@6.0.1 447 + wrap-ansi: 8.1.0 448 + wrap-ansi-cjs: wrap-ansi@7.0.0 449 + 450 + '@jridgewell/gen-mapping@0.3.5': 451 + dependencies: 452 + '@jridgewell/set-array': 1.2.1 453 + '@jridgewell/sourcemap-codec': 1.5.0 454 + '@jridgewell/trace-mapping': 0.3.25 455 + 456 + '@jridgewell/resolve-uri@3.1.2': {} 457 + 458 + '@jridgewell/set-array@1.2.1': {} 459 + 460 + '@jridgewell/sourcemap-codec@1.5.0': {} 461 + 462 + '@jridgewell/trace-mapping@0.3.25': 463 + dependencies: 464 + '@jridgewell/resolve-uri': 3.1.2 465 + '@jridgewell/sourcemap-codec': 1.5.0 466 + 467 + '@nodelib/fs.scandir@2.1.5': 468 + dependencies: 469 + '@nodelib/fs.stat': 2.0.5 470 + run-parallel: 1.2.0 471 + 472 + '@nodelib/fs.stat@2.0.5': {} 473 + 474 + '@nodelib/fs.walk@1.2.8': 475 + dependencies: 476 + '@nodelib/fs.scandir': 2.1.5 477 + fastq: 1.17.1 478 + 479 + '@pkgjs/parseargs@0.11.0': 480 + optional: true 481 + 482 + ansi-regex@5.0.1: {} 483 + 484 + ansi-regex@6.0.1: {} 485 + 486 + ansi-styles@4.3.0: 487 + dependencies: 488 + color-convert: 2.0.1 489 + 490 + ansi-styles@6.2.1: {} 491 + 492 + any-promise@1.3.0: {} 493 + 494 + anymatch@3.1.3: 495 + dependencies: 496 + normalize-path: 3.0.0 497 + picomatch: 2.3.1 498 + 499 + arg@5.0.2: {} 500 + 501 + balanced-match@1.0.2: {} 502 + 503 + binary-extensions@2.3.0: {} 504 + 505 + brace-expansion@2.0.1: 506 + dependencies: 507 + balanced-match: 1.0.2 508 + 509 + braces@3.0.3: 510 + dependencies: 511 + fill-range: 7.1.1 512 + 513 + camelcase-css@2.0.1: {} 514 + 515 + chokidar@3.6.0: 516 + dependencies: 517 + anymatch: 3.1.3 518 + braces: 3.0.3 519 + glob-parent: 5.1.2 520 + is-binary-path: 2.1.0 521 + is-glob: 4.0.3 522 + normalize-path: 3.0.0 523 + readdirp: 3.6.0 524 + optionalDependencies: 525 + fsevents: 2.3.3 526 + 527 + color-convert@2.0.1: 528 + dependencies: 529 + color-name: 1.1.4 530 + 531 + color-name@1.1.4: {} 532 + 533 + commander@4.1.1: {} 534 + 535 + cross-spawn@7.0.3: 536 + dependencies: 537 + path-key: 3.1.1 538 + shebang-command: 2.0.0 539 + which: 2.0.2 540 + 541 + cssesc@3.0.0: {} 542 + 543 + didyoumean@1.2.2: {} 544 + 545 + dlv@1.1.3: {} 546 + 547 + eastasianwidth@0.2.0: {} 548 + 549 + emoji-regex@8.0.0: {} 550 + 551 + emoji-regex@9.2.2: {} 552 + 553 + fast-glob@3.3.2: 554 + dependencies: 555 + '@nodelib/fs.stat': 2.0.5 556 + '@nodelib/fs.walk': 1.2.8 557 + glob-parent: 5.1.2 558 + merge2: 1.4.1 559 + micromatch: 4.0.7 560 + 561 + fastq@1.17.1: 562 + dependencies: 563 + reusify: 1.0.4 564 + 565 + fill-range@7.1.1: 566 + dependencies: 567 + to-regex-range: 5.0.1 568 + 569 + foreground-child@3.2.1: 570 + dependencies: 571 + cross-spawn: 7.0.3 572 + signal-exit: 4.1.0 573 + 574 + fsevents@2.3.3: 575 + optional: true 576 + 577 + function-bind@1.1.2: {} 578 + 579 + glob-parent@5.1.2: 580 + dependencies: 581 + is-glob: 4.0.3 582 + 583 + glob-parent@6.0.2: 584 + dependencies: 585 + is-glob: 4.0.3 586 + 587 + glob@10.4.5: 588 + dependencies: 589 + foreground-child: 3.2.1 590 + jackspeak: 3.4.3 591 + minimatch: 9.0.5 592 + minipass: 7.1.2 593 + package-json-from-dist: 1.0.0 594 + path-scurry: 1.11.1 595 + 596 + hasown@2.0.2: 597 + dependencies: 598 + function-bind: 1.1.2 599 + 600 + is-binary-path@2.1.0: 601 + dependencies: 602 + binary-extensions: 2.3.0 603 + 604 + is-core-module@2.15.0: 605 + dependencies: 606 + hasown: 2.0.2 607 + 608 + is-extglob@2.1.1: {} 609 + 610 + is-fullwidth-code-point@3.0.0: {} 611 + 612 + is-glob@4.0.3: 613 + dependencies: 614 + is-extglob: 2.1.1 615 + 616 + is-number@7.0.0: {} 617 + 618 + isexe@2.0.0: {} 619 + 620 + jackspeak@3.4.3: 621 + dependencies: 622 + '@isaacs/cliui': 8.0.2 623 + optionalDependencies: 624 + '@pkgjs/parseargs': 0.11.0 625 + 626 + jiti@1.21.6: {} 627 + 628 + lilconfig@2.1.0: {} 629 + 630 + lilconfig@3.1.2: {} 631 + 632 + lines-and-columns@1.2.4: {} 633 + 634 + lru-cache@10.4.3: {} 635 + 636 + merge2@1.4.1: {} 637 + 638 + micromatch@4.0.7: 639 + dependencies: 640 + braces: 3.0.3 641 + picomatch: 2.3.1 642 + 643 + minimatch@9.0.5: 644 + dependencies: 645 + brace-expansion: 2.0.1 646 + 647 + minipass@7.1.2: {} 648 + 649 + mz@2.7.0: 650 + dependencies: 651 + any-promise: 1.3.0 652 + object-assign: 4.1.1 653 + thenify-all: 1.6.0 654 + 655 + nanoid@3.3.7: {} 656 + 657 + normalize-path@3.0.0: {} 658 + 659 + object-assign@4.1.1: {} 660 + 661 + object-hash@3.0.0: {} 662 + 663 + package-json-from-dist@1.0.0: {} 664 + 665 + path-key@3.1.1: {} 666 + 667 + path-parse@1.0.7: {} 668 + 669 + path-scurry@1.11.1: 670 + dependencies: 671 + lru-cache: 10.4.3 672 + minipass: 7.1.2 673 + 674 + picocolors@1.0.1: {} 675 + 676 + picomatch@2.3.1: {} 677 + 678 + pify@2.3.0: {} 679 + 680 + pirates@4.0.6: {} 681 + 682 + postcss-import@15.1.0(postcss@8.4.40): 683 + dependencies: 684 + postcss: 8.4.40 685 + postcss-value-parser: 4.2.0 686 + read-cache: 1.0.0 687 + resolve: 1.22.8 688 + 689 + postcss-js@4.0.1(postcss@8.4.40): 690 + dependencies: 691 + camelcase-css: 2.0.1 692 + postcss: 8.4.40 693 + 694 + postcss-load-config@4.0.2(postcss@8.4.40): 695 + dependencies: 696 + lilconfig: 3.1.2 697 + yaml: 2.5.0 698 + optionalDependencies: 699 + postcss: 8.4.40 700 + 701 + postcss-nested@6.2.0(postcss@8.4.40): 702 + dependencies: 703 + postcss: 8.4.40 704 + postcss-selector-parser: 6.1.1 705 + 706 + postcss-selector-parser@6.1.1: 707 + dependencies: 708 + cssesc: 3.0.0 709 + util-deprecate: 1.0.2 710 + 711 + postcss-value-parser@4.2.0: {} 712 + 713 + postcss@8.4.40: 714 + dependencies: 715 + nanoid: 3.3.7 716 + picocolors: 1.0.1 717 + source-map-js: 1.2.0 718 + 719 + queue-microtask@1.2.3: {} 720 + 721 + read-cache@1.0.0: 722 + dependencies: 723 + pify: 2.3.0 724 + 725 + readdirp@3.6.0: 726 + dependencies: 727 + picomatch: 2.3.1 728 + 729 + resolve@1.22.8: 730 + dependencies: 731 + is-core-module: 2.15.0 732 + path-parse: 1.0.7 733 + supports-preserve-symlinks-flag: 1.0.0 734 + 735 + reusify@1.0.4: {} 736 + 737 + run-parallel@1.2.0: 738 + dependencies: 739 + queue-microtask: 1.2.3 740 + 741 + shebang-command@2.0.0: 742 + dependencies: 743 + shebang-regex: 3.0.0 744 + 745 + shebang-regex@3.0.0: {} 746 + 747 + signal-exit@4.1.0: {} 748 + 749 + source-map-js@1.2.0: {} 750 + 751 + string-width@4.2.3: 752 + dependencies: 753 + emoji-regex: 8.0.0 754 + is-fullwidth-code-point: 3.0.0 755 + strip-ansi: 6.0.1 756 + 757 + string-width@5.1.2: 758 + dependencies: 759 + eastasianwidth: 0.2.0 760 + emoji-regex: 9.2.2 761 + strip-ansi: 7.1.0 762 + 763 + strip-ansi@6.0.1: 764 + dependencies: 765 + ansi-regex: 5.0.1 766 + 767 + strip-ansi@7.1.0: 768 + dependencies: 769 + ansi-regex: 6.0.1 770 + 771 + sucrase@3.35.0: 772 + dependencies: 773 + '@jridgewell/gen-mapping': 0.3.5 774 + commander: 4.1.1 775 + glob: 10.4.5 776 + lines-and-columns: 1.2.4 777 + mz: 2.7.0 778 + pirates: 4.0.6 779 + ts-interface-checker: 0.1.13 780 + 781 + supports-preserve-symlinks-flag@1.0.0: {} 782 + 783 + tailwindcss@3.4.7: 784 + dependencies: 785 + '@alloc/quick-lru': 5.2.0 786 + arg: 5.0.2 787 + chokidar: 3.6.0 788 + didyoumean: 1.2.2 789 + dlv: 1.1.3 790 + fast-glob: 3.3.2 791 + glob-parent: 6.0.2 792 + is-glob: 4.0.3 793 + jiti: 1.21.6 794 + lilconfig: 2.1.0 795 + micromatch: 4.0.7 796 + normalize-path: 3.0.0 797 + object-hash: 3.0.0 798 + picocolors: 1.0.1 799 + postcss: 8.4.40 800 + postcss-import: 15.1.0(postcss@8.4.40) 801 + postcss-js: 4.0.1(postcss@8.4.40) 802 + postcss-load-config: 4.0.2(postcss@8.4.40) 803 + postcss-nested: 6.2.0(postcss@8.4.40) 804 + postcss-selector-parser: 6.1.1 805 + resolve: 1.22.8 806 + sucrase: 3.35.0 807 + transitivePeerDependencies: 808 + - ts-node 809 + 810 + thenify-all@1.6.0: 811 + dependencies: 812 + thenify: 3.3.1 813 + 814 + thenify@3.3.1: 815 + dependencies: 816 + any-promise: 1.3.0 817 + 818 + to-regex-range@5.0.1: 819 + dependencies: 820 + is-number: 7.0.0 821 + 822 + ts-interface-checker@0.1.13: {} 823 + 824 + util-deprecate@1.0.2: {} 825 + 826 + which@2.0.2: 827 + dependencies: 828 + isexe: 2.0.0 829 + 830 + wrap-ansi@7.0.0: 831 + dependencies: 832 + ansi-styles: 4.3.0 833 + string-width: 4.2.3 834 + strip-ansi: 6.0.1 835 + 836 + wrap-ansi@8.1.0: 837 + dependencies: 838 + ansi-styles: 6.2.1 839 + string-width: 5.1.2 840 + strip-ansi: 7.1.0 841 + 842 + yaml@2.5.0: {}
+11
tailwind.config.js
··· 1 + /** @type {import('tailwindcss').Config} */ 2 + module.exports = { 3 + content: [ 4 + "./**/*.{html,templ}" 5 + ], 6 + theme: { 7 + extend: {}, 8 + }, 9 + plugins: [], 10 + } 11 +
+8
types/post.go
··· 1 + package types 2 + 3 + type Post struct { 4 + ID string `clover:""` 5 + CreatedAt int64 `clover:"createdAt"` 6 + Type string `clover:"type"` 7 + Properties map[string][]any `clover:"properties"` 8 + }