+1
-1
.github/workflows/fly-deploy.yml
+1
-1
.github/workflows/fly-deploy.yml
+3
-10
Dockerfile
config/Dockerfile
+3
-10
Dockerfile
config/Dockerfile
···
1
1
# Build styles
2
-
FROM node:22 AS build-styles
3
-
ENV PNPM_HOME="/pnpm"
4
-
ENV PATH="$PNPM_HOME:$PATH"
2
+
FROM denoland/deno:1.45.5 AS build-styles
5
3
WORKDIR /app
6
4
7
5
COPY . /app
8
6
9
-
RUN corepack enable pnpm
10
-
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
11
-
RUN pnpm run build
7
+
RUN deno run --allow-all npm:tailwindcss -o static/styles.css -c config/tailwind.config.js --minify
12
8
13
9
FROM golang:1.22-alpine AS build-server
14
10
WORKDIR /app
···
34
30
RUN apk add bash
35
31
RUN mkdir -p /data
36
32
37
-
COPY etc/litestream.yml /etc/litestream.yml
38
-
COPY scripts/run.sh /scripts/run.sh
39
-
40
33
EXPOSE 80
41
34
42
-
COPY etc/litestream.yml /etc/litestream.yml
35
+
COPY config/litestream.yml /etc/litestream.yml
43
36
COPY scripts/run.sh /scripts/run.sh
44
37
45
38
CMD [ "/scripts/run.sh" ]
-78
db/storage.go
-78
db/storage.go
···
1
-
package db
2
-
3
-
import (
4
-
"context"
5
-
"log"
6
-
"os"
7
-
"path/filepath"
8
-
"time"
9
-
10
-
"github.com/puregarlic/space/models"
11
-
12
-
"github.com/jellydator/ttlcache/v3"
13
-
"go.hacdias.com/indielib/indieauth"
14
-
15
-
"github.com/glebarez/sqlite"
16
-
"gorm.io/gorm"
17
-
18
-
"github.com/aws/aws-sdk-go-v2/aws"
19
-
"github.com/aws/aws-sdk-go-v2/config"
20
-
"github.com/aws/aws-sdk-go-v2/service/s3"
21
-
)
22
-
23
-
type Storage struct {
24
-
Db *gorm.DB
25
-
Authorization *ttlcache.Cache[string, *indieauth.AuthenticationRequest]
26
-
Media *s3.Client
27
-
}
28
-
29
-
type CollectionName string
30
-
31
-
var (
32
-
PostCollection CollectionName = "posts"
33
-
)
34
-
35
-
func NewStorage() *Storage {
36
-
dataDir := filepath.Join(".", "data")
37
-
if err := os.MkdirAll(dataDir, os.ModePerm); err != nil {
38
-
log.Fatal(err)
39
-
}
40
-
41
-
db, err := gorm.Open(sqlite.Open(filepath.Join(dataDir, "data.db")), &gorm.Config{})
42
-
if err != nil {
43
-
log.Fatal(err)
44
-
}
45
-
46
-
if err := db.AutoMigrate(&models.Post{}); err != nil {
47
-
panic(err)
48
-
}
49
-
50
-
sdkConfig, err := config.LoadDefaultConfig(context.TODO())
51
-
if err != nil {
52
-
log.Printf("Couldn't load default configuration. Here's why: %v\n", err)
53
-
panic(err)
54
-
}
55
-
56
-
svc := s3.NewFromConfig(sdkConfig, func(o *s3.Options) {
57
-
o.BaseEndpoint = aws.String("https://" + os.Getenv("AWS_S3_ENDPOINT"))
58
-
o.Region = os.Getenv("AWS_REGION")
59
-
})
60
-
61
-
cache := ttlcache.New[string, *indieauth.AuthenticationRequest](
62
-
ttlcache.WithTTL[string, *indieauth.AuthenticationRequest](10 * time.Minute),
63
-
)
64
-
65
-
go cache.Start()
66
-
67
-
store := &Storage{
68
-
Db: db,
69
-
Authorization: cache,
70
-
Media: svc,
71
-
}
72
-
73
-
return store
74
-
}
75
-
76
-
func (db *Storage) Cleanup() {
77
-
db.Authorization.Stop()
78
-
}
-1
etc/litestream.yml
config/litestream.yml
-1
etc/litestream.yml
config/litestream.yml
fly.toml
config/fly.toml
fly.toml
config/fly.toml
-4
gen.go
-4
gen.go
+23
handlers/http.go
+23
handlers/http.go
···
1
+
package handlers
2
+
3
+
import (
4
+
"encoding/json"
5
+
"net/http"
6
+
)
7
+
8
+
func SendHttpError(w http.ResponseWriter, status int) {
9
+
http.Error(w, http.StatusText(status), status)
10
+
}
11
+
12
+
func SendJSON(w http.ResponseWriter, code int, data interface{}) {
13
+
w.Header().Set("Content-Type", "application/json; charset=utf-8")
14
+
w.WriteHeader(code)
15
+
_ = json.NewEncoder(w).Encode(data)
16
+
}
17
+
18
+
func SendErrorJSON(w http.ResponseWriter, code int, err, errDescription string) {
19
+
SendJSON(w, code, map[string]string{
20
+
"error": err,
21
+
"error_description": errDescription,
22
+
})
23
+
}
+37
handlers/media.go
+37
handlers/media.go
···
1
+
package handlers
2
+
3
+
import (
4
+
"fmt"
5
+
"io"
6
+
"net/http"
7
+
"os"
8
+
"strings"
9
+
10
+
"github.com/aws/aws-sdk-go-v2/aws"
11
+
"github.com/aws/aws-sdk-go-v2/service/s3"
12
+
"github.com/puregarlic/space/storage"
13
+
)
14
+
15
+
func ServeMedia(w http.ResponseWriter, r *http.Request) {
16
+
key := strings.TrimPrefix(r.URL.Path, "/")
17
+
18
+
res, err := storage.S3().GetObject(r.Context(), &s3.GetObjectInput{
19
+
Bucket: aws.String(os.Getenv("AWS_S3_BUCKET_NAME")),
20
+
Key: &key,
21
+
})
22
+
if err != nil {
23
+
fmt.Println("failed to get object", err)
24
+
SendHttpError(w, http.StatusInternalServerError)
25
+
return
26
+
}
27
+
28
+
defer res.Body.Close()
29
+
30
+
w.Header().Set("Cache-Control", "604800")
31
+
32
+
if _, err := io.Copy(w, res.Body); err != nil {
33
+
fmt.Println("failed to send object", err)
34
+
SendHttpError(w, http.StatusInternalServerError)
35
+
return
36
+
}
37
+
}
+35
handlers/routes.go
+35
handlers/routes.go
···
1
+
package handlers
2
+
3
+
import (
4
+
"net/http"
5
+
6
+
"github.com/a-h/templ"
7
+
"github.com/go-chi/chi/v5"
8
+
"github.com/puregarlic/space/models"
9
+
"github.com/puregarlic/space/pages"
10
+
"github.com/puregarlic/space/storage"
11
+
)
12
+
13
+
func ServeHomePage(w http.ResponseWriter, r *http.Request) {
14
+
posts := make([]*models.Post, 0)
15
+
result := storage.GORM().Limit(10).Find(&posts)
16
+
if result.Error != nil {
17
+
panic(result.Error)
18
+
}
19
+
20
+
templ.Handler(pages.Home(posts)).ServeHTTP(w, r)
21
+
}
22
+
23
+
func ServePostPage(w http.ResponseWriter, r *http.Request) {
24
+
id := chi.URLParam(r, "slug")
25
+
post := &models.Post{}
26
+
27
+
result := storage.GORM().First(post, "id = ?", id)
28
+
29
+
if result.RowsAffected == 0 {
30
+
SendHttpError(w, http.StatusNotFound)
31
+
return
32
+
}
33
+
34
+
templ.Handler(pages.Post(post)).ServeHTTP(w, r)
35
+
}
+40
-33
indieauth.go
services/indieauth.go
+40
-33
indieauth.go
services/indieauth.go
···
1
-
package main
1
+
package services
2
2
3
3
import (
4
4
"context"
···
11
11
"strings"
12
12
"time"
13
13
14
+
"github.com/puregarlic/space/handlers"
14
15
"github.com/puregarlic/space/pages"
16
+
"github.com/puregarlic/space/storage"
15
17
16
18
"github.com/a-h/templ"
17
19
"github.com/aidarkhanov/nanoid"
···
19
21
"go.hacdias.com/indielib/indieauth"
20
22
)
21
23
24
+
type IndieAuth struct {
25
+
ProfileURL string
26
+
Server *indieauth.Server
27
+
}
28
+
22
29
// storeAuthorization stores the authorization request and returns a code for it.
23
30
// Something such as JWT tokens could be used in a production environment.
24
-
func (s *server) storeAuthorization(req *indieauth.AuthenticationRequest) string {
31
+
func (i *IndieAuth) storeAuthorization(req *indieauth.AuthenticationRequest) string {
25
32
code := nanoid.New()
26
33
27
-
s.db.Authorization.Set(code, req, 0)
34
+
storage.AuthCache().Set(code, req, 0)
28
35
29
36
return code
30
37
}
···
40
47
scopesContextKey contextKey = "scopes"
41
48
)
42
49
43
-
// authorizationGetHandler handles the GET method for the authorization endpoint.
44
-
func (s *server) authorizationGetHandler(w http.ResponseWriter, r *http.Request) {
50
+
// HandleAuthGET handles the GET method for the authorization endpoint.
51
+
func (i *IndieAuth) HandleAuthGET(w http.ResponseWriter, r *http.Request) {
45
52
// In a production server, this page would usually be protected. In order for
46
53
// the user to authorize this request, they must be authenticated. This could
47
54
// be done in different ways: username/password, passkeys, etc.
48
55
49
56
// Parse the authorization request.
50
-
req, err := s.ias.ParseAuthorization(r)
57
+
req, err := i.Server.ParseAuthorization(r)
51
58
if err != nil {
52
-
serveErrorJSON(w, http.StatusBadRequest, "invalid_request", err.Error())
59
+
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
53
60
return
54
61
}
55
62
56
63
// Do a best effort attempt at fetching more information about the application
57
64
// that we can show to the user. Not all applications provide this sort of
58
65
// information.
59
-
app, _ := s.ias.DiscoverApplicationMetadata(r.Context(), req.ClientID)
66
+
app, _ := i.Server.DiscoverApplicationMetadata(r.Context(), req.ClientID)
60
67
61
68
// Here, we just display a small HTML document where the user has to press
62
69
// to authorize this request. Please note that this template contains a form
···
65
72
templ.Handler(pages.Auth(req, app)).ServeHTTP(w, r)
66
73
}
67
74
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)
75
+
// HandleAuthPOST handles the POST method for the authorization endpoint.
76
+
func (i *IndieAuth) HandleAuthPOST(w http.ResponseWriter, r *http.Request) {
77
+
i.authorizationCodeExchange(w, r, false)
71
78
}
72
79
73
-
// tokenHandler handles the token endpoint. In our case, we only accept the default
80
+
// HandleToken handles the token endpoint. In our case, we only accept the default
74
81
// type which is exchanging an authorization code for a token.
75
-
func (s *server) tokenHandler(w http.ResponseWriter, r *http.Request) {
82
+
func (i *IndieAuth) HandleToken(w http.ResponseWriter, r *http.Request) {
76
83
if r.Method != http.MethodPost {
77
-
httpError(w, http.StatusMethodNotAllowed)
84
+
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
78
85
return
79
86
}
80
87
···
85
92
return
86
93
}
87
94
88
-
s.authorizationCodeExchange(w, r, true)
95
+
i.authorizationCodeExchange(w, r, true)
89
96
}
90
97
91
98
type tokenResponse struct {
···
99
106
// authorizationCodeExchange handles the authorization code exchange. It is used by
100
107
// both the authorization handler to exchange the code for the user's profile URL,
101
108
// 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) {
109
+
func (i *IndieAuth) authorizationCodeExchange(w http.ResponseWriter, r *http.Request, withToken bool) {
103
110
if err := r.ParseForm(); err != nil {
104
-
serveErrorJSON(w, http.StatusBadRequest, "invalid_request", err.Error())
111
+
handlers.SendErrorJSON(w, http.StatusBadRequest, "invalid_request", err.Error())
105
112
return
106
113
}
107
114
108
115
// t := s.getAuthorization(r.Form.Get("code"))
109
-
req, present := s.db.Authorization.GetAndDelete(r.Form.Get("code"))
116
+
req, present := storage.AuthCache().GetAndDelete(r.Form.Get("code"))
110
117
if !present {
111
-
serveErrorJSON(w, http.StatusBadRequest, "invalid_request", "invalid authorization")
118
+
handlers.SendErrorJSON(w, http.StatusBadRequest, "invalid_request", "invalid authorization")
112
119
return
113
120
}
114
121
authRequest := req.Value()
115
122
116
-
err := s.ias.ValidateTokenExchange(authRequest, r)
123
+
err := i.Server.ValidateTokenExchange(authRequest, r)
117
124
if err != nil {
118
-
serveErrorJSON(w, http.StatusBadRequest, "invalid_request", err.Error())
125
+
handlers.SendErrorJSON(w, http.StatusBadRequest, "invalid_request", err.Error())
119
126
return
120
127
}
121
128
122
129
response := &tokenResponse{
123
-
Me: s.profileURL,
130
+
Me: i.ProfileURL,
124
131
}
125
132
126
133
scopes := authRequest.Scopes
···
152
159
153
160
// An actual server may want to include the "profile" in the response if the
154
161
// scope "profile" is included.
155
-
serveJSON(w, http.StatusOK, response)
162
+
handlers.SendJSON(w, http.StatusOK, response)
156
163
}
157
164
158
-
func (s *server) authorizationAcceptHandler(w http.ResponseWriter, r *http.Request) {
165
+
func (i *IndieAuth) HandleAuthApproval(w http.ResponseWriter, r *http.Request) {
159
166
// Parse authorization information. This only works because our authorization page
160
167
// includes all the required information. This can be done in other ways: database,
161
168
// whether temporary or not, cookies, etc.
162
-
req, err := s.ias.ParseAuthorization(r)
169
+
req, err := i.Server.ParseAuthorization(r)
163
170
if err != nil {
164
-
serveErrorJSON(w, http.StatusBadRequest, "invalid_request", err.Error())
171
+
handlers.SendErrorJSON(w, http.StatusBadRequest, "invalid_request", err.Error())
165
172
return
166
173
}
167
174
168
175
// Generate a random code and persist the information associated to that code.
169
176
// You could do this in other ways: database, or JWT tokens, or both, for example.
170
-
code := s.storeAuthorization(req)
177
+
code := i.storeAuthorization(req)
171
178
172
179
// Redirect to client callback.
173
180
query := url.Values{}
···
176
183
http.Redirect(w, r, req.RedirectURI+"?"+query.Encode(), http.StatusFound)
177
184
}
178
185
179
-
// mustAuth is a middleware to ensure that the request is authorized. The way this
186
+
// MustAuth is a middleware to ensure that the request is authorized. The way this
180
187
// works depends on the implementation. It then stores the scopes in the context.
181
-
func (s *server) mustAuth(next http.Handler) http.Handler {
188
+
func MustAuth(next http.Handler) http.Handler {
182
189
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
183
190
tokenStr := r.Header.Get("Authorization")
184
191
tokenStr = strings.TrimPrefix(tokenStr, "Bearer")
185
192
tokenStr = strings.TrimSpace(tokenStr)
186
193
187
194
if len(tokenStr) <= 0 {
188
-
serveErrorJSON(w, http.StatusUnauthorized, "invalid_request", "no credentials")
195
+
handlers.SendErrorJSON(w, http.StatusUnauthorized, "invalid_request", "no credentials")
189
196
return
190
197
}
191
198
···
194
201
})
195
202
196
203
if err != nil {
197
-
serveErrorJSON(w, http.StatusUnauthorized, "invalid_request", "invalid token")
204
+
handlers.SendErrorJSON(w, http.StatusUnauthorized, "invalid_request", "invalid token")
198
205
return
199
206
} else if claims, ok := token.Claims.(*CustomTokenClaims); ok {
200
207
ctx := context.WithValue(r.Context(), scopesContextKey, claims.Scopes)
201
208
next.ServeHTTP(w, r.WithContext(ctx))
202
209
return
203
210
} else {
204
-
serveErrorJSON(w, http.StatusUnauthorized, "invalid_request", "malformed claims")
211
+
handlers.SendErrorJSON(w, http.StatusUnauthorized, "invalid_request", "malformed claims")
205
212
return
206
213
}
207
214
})
208
215
}
209
216
210
-
func (s *server) mustBasicAuth(next http.Handler) http.Handler {
217
+
func MustBasicAuth(next http.Handler) http.Handler {
211
218
user, ok := os.LookupEnv("ADMIN_USERNAME")
212
219
if !ok {
213
220
panic(errors.New("ADMIN_USERNAME is not set, cannot start"))
+24
-103
main.go
+24
-103
main.go
···
1
1
package main
2
2
3
+
//go:generate templ generate
4
+
//go:generate deno run --allow-all npm:tailwindcss -o static/styles.css -c config/tailwind.config.js --minify
5
+
3
6
import (
4
-
"context"
5
-
"encoding/json"
6
-
"fmt"
7
-
"io"
8
7
"os"
9
-
"strings"
10
8
"time"
11
9
12
10
"log"
13
11
"net/http"
14
12
"strconv"
15
13
16
-
"github.com/aws/aws-sdk-go-v2/aws"
17
-
"github.com/aws/aws-sdk-go-v2/service/s3"
18
-
"github.com/puregarlic/space/db"
19
-
"github.com/puregarlic/space/models"
20
-
"github.com/puregarlic/space/pages"
14
+
"github.com/puregarlic/space/handlers"
15
+
"github.com/puregarlic/space/services"
16
+
"github.com/puregarlic/space/storage"
21
17
22
-
"github.com/a-h/templ"
23
18
"github.com/go-chi/chi/v5"
24
19
"github.com/go-chi/chi/v5/middleware"
25
20
"github.com/go-chi/cors"
···
56
51
log.Fatal(err)
57
52
}
58
53
59
-
// Setup storage handlers
60
-
store := db.NewStorage()
61
-
defer store.Cleanup()
62
-
63
-
// Create a new client.
64
-
s := &server{
65
-
profileURL: profileURL,
66
-
ias: indieauth.NewServer(true, nil),
67
-
db: store,
68
-
}
54
+
defer storage.CleanupAuthCache()
69
55
70
56
r := chi.NewRouter()
71
57
···
73
59
r.Use(middleware.RealIP)
74
60
r.Use(middleware.Logger)
75
61
r.Use(middleware.Recoverer)
76
-
77
62
r.Use(httprate.LimitByIP(100, 1*time.Minute))
78
63
79
64
// Static resources
80
65
r.Get("/static/*", http.StripPrefix("/static", http.FileServer(http.Dir("static"))).ServeHTTP)
81
66
82
67
// Pages
83
-
r.Get("/", s.serveHomeTemplate)
84
-
r.Get("/posts/{slug}", s.servePostTemplate)
85
-
r.Get("/media/*", s.serveMedia)
68
+
r.Get("/", handlers.ServeHomePage)
69
+
r.Get("/posts/{slug}", handlers.ServePostPage)
70
+
r.Get("/media/*", handlers.ServeMedia)
86
71
87
72
// IndieAuth handlers
88
73
r.Group(func(r chi.Router) {
89
-
r.Post("/token", s.tokenHandler)
90
-
r.Post("/authorization", s.authorizationPostHandler)
91
-
r.Post("/authorization/accept", s.authorizationAcceptHandler)
74
+
ias := &services.IndieAuth{
75
+
ProfileURL: profileURL,
76
+
Server: indieauth.NewServer(true, nil),
77
+
}
78
+
79
+
r.Post("/token", ias.HandleToken)
80
+
r.Post("/authorization", ias.HandleAuthPOST)
81
+
r.Post("/authorization/accept", ias.HandleAuthApproval)
92
82
93
83
// User authentication portal
94
-
r.With(s.mustBasicAuth).Get("/authorization", s.authorizationGetHandler)
84
+
r.With(services.MustBasicAuth).Get("/authorization", ias.HandleAuthGET)
95
85
})
96
86
97
87
// Micropub handler
···
102
92
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"},
103
93
},
104
94
))
105
-
r.Use(s.mustAuth)
95
+
r.Use(services.MustAuth)
106
96
107
-
mp := µpubImplementation{s}
97
+
mp := &services.Micropub{
98
+
ProfileURL: profileURL,
99
+
}
108
100
mpHandler := micropub.NewHandler(
109
101
mp,
110
102
micropub.WithGetPostTypes(func() []micropub.PostType {
···
119
111
},
120
112
}
121
113
}),
122
-
micropub.WithMediaEndpoint(s.profileURL+"micropub/media"),
114
+
micropub.WithMediaEndpoint(profileURL+"micropub/media"),
123
115
)
124
116
125
117
r.Get("/", mpHandler.ServeHTTP)
···
143
135
log.Fatal(err)
144
136
}
145
137
}
146
-
147
-
type server struct {
148
-
profileURL string
149
-
ias *indieauth.Server
150
-
db *db.Storage
151
-
}
152
-
153
-
func (s *server) serveHomeTemplate(w http.ResponseWriter, r *http.Request) {
154
-
posts := make([]*models.Post, 0)
155
-
result := s.db.Db.Limit(10).Find(&posts)
156
-
if result.Error != nil {
157
-
panic(result.Error)
158
-
}
159
-
160
-
templ.Handler(pages.Home(s.profileURL, posts)).ServeHTTP(w, r)
161
-
}
162
-
163
-
func (s *server) servePostTemplate(w http.ResponseWriter, r *http.Request) {
164
-
id := chi.URLParam(r, "slug")
165
-
post := &models.Post{}
166
-
167
-
result := s.db.Db.First(post, "id = ?", id)
168
-
169
-
if result.RowsAffected == 0 {
170
-
httpError(w, http.StatusNotFound)
171
-
return
172
-
}
173
-
174
-
templ.Handler(pages.Post(post)).ServeHTTP(w, r)
175
-
}
176
-
177
-
func (s *server) serveMedia(w http.ResponseWriter, r *http.Request) {
178
-
key := strings.TrimPrefix(r.URL.Path, "/")
179
-
180
-
res, err := s.db.Media.GetObject(context.TODO(), &s3.GetObjectInput{
181
-
Bucket: aws.String(os.Getenv("AWS_S3_BUCKET_NAME")),
182
-
Key: &key,
183
-
})
184
-
if err != nil {
185
-
fmt.Println("failed to get object", err)
186
-
httpError(w, http.StatusInternalServerError)
187
-
return
188
-
}
189
-
190
-
defer res.Body.Close()
191
-
192
-
w.Header().Set("Cache-Control", "604800")
193
-
194
-
if _, err := io.Copy(w, res.Body); err != nil {
195
-
fmt.Println("failed to send object", err)
196
-
httpError(w, http.StatusInternalServerError)
197
-
return
198
-
}
199
-
}
200
-
201
-
func httpError(w http.ResponseWriter, status int) {
202
-
http.Error(w, http.StatusText(status), status)
203
-
}
204
-
205
-
func serveJSON(w http.ResponseWriter, code int, data interface{}) {
206
-
w.Header().Set("Content-Type", "application/json; charset=utf-8")
207
-
w.WriteHeader(code)
208
-
_ = json.NewEncoder(w).Encode(data)
209
-
}
210
-
211
-
func serveErrorJSON(w http.ResponseWriter, code int, err, errDescription string) {
212
-
serveJSON(w, code, map[string]string{
213
-
"error": err,
214
-
"error_description": errDescription,
215
-
})
216
-
}
+22
-21
micropub.go
services/micropub.go
+22
-21
micropub.go
services/micropub.go
···
1
-
package main
1
+
package services
2
2
3
3
import (
4
4
"context"
···
13
13
"strings"
14
14
15
15
"github.com/puregarlic/space/models"
16
+
"github.com/puregarlic/space/storage"
16
17
17
18
"github.com/aidarkhanov/nanoid"
18
19
"github.com/aws/aws-sdk-go-v2/aws"
···
23
24
"go.hacdias.com/indielib/micropub"
24
25
)
25
26
26
-
type micropubImplementation struct {
27
-
*server
27
+
type Micropub struct {
28
+
ProfileURL string
28
29
}
29
30
30
31
func postIdFromUrlPath(path string) string {
31
32
return strings.TrimPrefix(path, "/posts/")
32
33
}
33
34
34
-
func (s *micropubImplementation) HasScope(r *http.Request, scope string) bool {
35
+
func (m *Micropub) HasScope(r *http.Request, scope string) bool {
35
36
v := r.Context().Value(scopesContextKey)
36
37
if scopes, ok := v.([]string); ok {
37
38
for _, sc := range scopes {
···
44
45
return false
45
46
}
46
47
47
-
func (s *micropubImplementation) Source(urlStr string) (map[string]any, error) {
48
+
func (m *Micropub) Source(urlStr string) (map[string]any, error) {
48
49
url, err := urlpkg.Parse(urlStr)
49
50
if err != nil {
50
51
return nil, fmt.Errorf("%w: %w", micropub.ErrBadRequest, err)
···
53
54
id := postIdFromUrlPath(url.Path)
54
55
post := &models.Post{}
55
56
56
-
res := s.server.db.Db.Find(post, "id = ?", id)
57
+
res := storage.GORM().Find(post, "id = ?", id)
57
58
if res.Error != nil {
58
59
panic(res.Error)
59
60
} else if res.RowsAffected == 0 {
···
66
67
}, nil
67
68
}
68
69
69
-
func (s *micropubImplementation) SourceMany(limit, offset int) ([]map[string]any, error) {
70
+
func (m *Micropub) SourceMany(limit, offset int) ([]map[string]any, error) {
70
71
return nil, micropub.ErrNotImplemented
71
72
}
72
73
73
-
func (s *micropubImplementation) HandleMediaUpload(file multipart.File, header *multipart.FileHeader) (string, error) {
74
+
func (m *Micropub) HandleMediaUpload(file multipart.File, header *multipart.FileHeader) (string, error) {
74
75
defer file.Close()
75
76
76
77
kind, err := filetype.MatchReader(file)
···
83
84
}
84
85
85
86
key := fmt.Sprintf("media/%s.%s", nanoid.New(), kind.Extension)
86
-
_, err = s.db.Media.PutObject(context.TODO(), &s3.PutObjectInput{
87
+
_, err = storage.S3().PutObject(context.TODO(), &s3.PutObjectInput{
87
88
Bucket: aws.String(os.Getenv("AWS_S3_BUCKET_NAME")),
88
89
Key: &key,
89
90
Body: file,
···
92
93
return "", fmt.Errorf("%w: %w", errors.New("failed to upload"), err)
93
94
}
94
95
95
-
return s.profileURL + key, nil
96
+
return m.ProfileURL + key, nil
96
97
}
97
98
98
-
func (s *micropubImplementation) Create(req *micropub.Request) (string, error) {
99
+
func (m *Micropub) Create(req *micropub.Request) (string, error) {
99
100
props, err := json.Marshal(req.Properties)
100
101
if err != nil {
101
102
return "", err
···
107
108
Properties: props,
108
109
}
109
110
110
-
res := s.server.db.Db.Create(post)
111
+
res := storage.GORM().Create(post)
111
112
if res.Error != nil {
112
113
return "", res.Error
113
114
}
114
115
115
-
return s.profileURL + "posts/" + post.ID.String(), nil
116
+
return m.ProfileURL + "posts/" + post.ID.String(), nil
116
117
}
117
118
118
-
func (s *micropubImplementation) Update(req *micropub.Request) (string, error) {
119
+
func (m *Micropub) Update(req *micropub.Request) (string, error) {
119
120
url, err := urlpkg.Parse(req.URL)
120
121
if err != nil {
121
122
return "", fmt.Errorf("%w: %w", micropub.ErrBadRequest, err)
···
124
125
id := postIdFromUrlPath(url.Path)
125
126
post := &models.Post{}
126
127
127
-
res := s.server.db.Db.Find(post, "id = ?", id)
128
+
res := storage.GORM().Find(post, "id = ?", id)
128
129
if res.Error != nil {
129
130
panic(res.Error)
130
131
} else if res.RowsAffected != 1 {
···
138
139
139
140
post.Properties = newProps
140
141
141
-
s.server.db.Db.Save(post)
142
+
storage.GORM().Save(post)
142
143
143
-
return s.profileURL + url.Path, nil
144
+
return url.String(), nil
144
145
}
145
146
146
-
func (s *micropubImplementation) Delete(urlStr string) error {
147
+
func (m *Micropub) Delete(urlStr string) error {
147
148
url, err := urlpkg.Parse(urlStr)
148
149
if err != nil {
149
150
return fmt.Errorf("%w: %w", micropub.ErrBadRequest, err)
···
151
152
152
153
id := postIdFromUrlPath(url.Path)
153
154
154
-
res := s.server.db.Db.Delete(&models.Post{}, "id = ?", id)
155
+
res := storage.GORM().Delete(&models.Post{}, "id = ?", id)
155
156
if res.Error != nil {
156
157
panic(res.Error)
157
158
} else if res.RowsAffected == 0 {
···
161
162
return nil
162
163
}
163
164
164
-
func (s *micropubImplementation) Undelete(urlStr string) error {
165
+
func (m *Micropub) Undelete(urlStr string) error {
165
166
url, err := urlpkg.Parse(urlStr)
166
167
if err != nil {
167
168
return fmt.Errorf("%w: %w", micropub.ErrBadRequest, err)
168
169
}
169
170
170
171
id := postIdFromUrlPath(url.Path)
171
-
res := s.server.db.Db.Unscoped().Model(&models.Post{}).Where("id = ?", id).Update("deleted_at", nil)
172
+
res := storage.GORM().Unscoped().Model(&models.Post{}).Where("id = ?", id).Update("deleted_at", nil)
172
173
if res.Error != nil {
173
174
return res.Error
174
175
} else if res.RowsAffected != 1 {
-15
package.json
-15
package.json
···
1
-
{
2
-
"name": "puregarlicspace",
3
-
"version": "1.0.0",
4
-
"description": "",
5
-
"main": "index.js",
6
-
"scripts": {
7
-
"build": "tailwindcss -o static/styles.css --minify"
8
-
},
9
-
"keywords": [],
10
-
"author": "",
11
-
"license": "ISC",
12
-
"devDependencies": {
13
-
"tailwindcss": "^3.4.7"
14
-
}
15
-
}
+1
-3
pages/home.templ
+1
-3
pages/home.templ
···
13
13
return props[name]
14
14
}
15
15
16
-
templ Home(profileUrl string, posts []*models.Post) {
16
+
templ Home(posts []*models.Post) {
17
17
<!DOCTYPE html>
18
18
<html>
19
19
<head>
···
26
26
</head>
27
27
<body>
28
28
<h1>Micropub and IndieAuth Server Demo</h1>
29
-
30
-
<p>Sign in on a website that supports IndieAuth. Use <code>{ profileUrl }</code> as your domain.</p>
31
29
32
30
<h2>Posts</h2>
33
31
+12
-25
pages/home_templ.go
+12
-25
pages/home_templ.go
···
21
21
return props[name]
22
22
}
23
23
24
-
func Home(profileUrl string, posts []*models.Post) templ.Component {
24
+
func Home(posts []*models.Post) templ.Component {
25
25
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
26
26
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
27
27
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
···
39
39
templ_7745c5c3_Var1 = templ.NopComponent
40
40
}
41
41
ctx = templ.ClearChildren(ctx)
42
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html><head><title>Micropub and IndieAuth Server Demo</title><link rel=\"authorization_endpoint\" href=\"/authorization\"><link rel=\"token_endpoint\" href=\"/token\"><link rel=\"micropub\" href=\"/micropub\"></head><body><h1>Micropub and IndieAuth Server Demo</h1><p>Sign in on a website that supports IndieAuth. Use <code>")
43
-
if templ_7745c5c3_Err != nil {
44
-
return templ_7745c5c3_Err
45
-
}
46
-
var templ_7745c5c3_Var2 string
47
-
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(profileUrl)
48
-
if templ_7745c5c3_Err != nil {
49
-
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/home.templ`, Line: 30, Col: 77}
50
-
}
51
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
52
-
if templ_7745c5c3_Err != nil {
53
-
return templ_7745c5c3_Err
54
-
}
55
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</code> as your domain.</p><h2>Posts</h2><p>You can create posts using a Micropub client.</p><ul>")
42
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html><head><title>Micropub and IndieAuth Server Demo</title><link rel=\"authorization_endpoint\" href=\"/authorization\"><link rel=\"token_endpoint\" href=\"/token\"><link rel=\"micropub\" href=\"/micropub\"></head><body><h1>Micropub and IndieAuth Server Demo</h1><h2>Posts</h2><p>You can create posts using a Micropub client.</p><ul>")
56
43
if templ_7745c5c3_Err != nil {
57
44
return templ_7745c5c3_Err
58
45
}
···
61
48
if templ_7745c5c3_Err != nil {
62
49
return templ_7745c5c3_Err
63
50
}
64
-
var templ_7745c5c3_Var3 templ.SafeURL = templ.URL("/posts/" + post.ID.String())
65
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var3)))
51
+
var templ_7745c5c3_Var2 templ.SafeURL = templ.URL("/posts/" + post.ID.String())
52
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var2)))
66
53
if templ_7745c5c3_Err != nil {
67
54
return templ_7745c5c3_Err
68
55
}
···
70
57
if templ_7745c5c3_Err != nil {
71
58
return templ_7745c5c3_Err
72
59
}
73
-
var templ_7745c5c3_Var4 string
74
-
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(post.ID.String())
60
+
var templ_7745c5c3_Var3 string
61
+
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(post.ID.String())
75
62
if templ_7745c5c3_Err != nil {
76
-
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/home.templ`, Line: 37, Col: 82}
63
+
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/home.templ`, Line: 35, Col: 82}
77
64
}
78
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
65
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
79
66
if templ_7745c5c3_Err != nil {
80
67
return templ_7745c5c3_Err
81
68
}
···
83
70
if templ_7745c5c3_Err != nil {
84
71
return templ_7745c5c3_Err
85
72
}
86
-
var templ_7745c5c3_Var5 string
87
-
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(getPostPropertyValue(post, "content")))
73
+
var templ_7745c5c3_Var4 string
74
+
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(getPostPropertyValue(post, "content")))
88
75
if templ_7745c5c3_Err != nil {
89
-
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/home.templ`, Line: 37, Col: 142}
76
+
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/home.templ`, Line: 35, Col: 142}
90
77
}
91
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
78
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
92
79
if templ_7745c5c3_Err != nil {
93
80
return templ_7745c5c3_Err
94
81
}
-842
pnpm-lock.yaml
-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: {}
+30
storage/cache.go
+30
storage/cache.go
···
1
+
package storage
2
+
3
+
import (
4
+
"time"
5
+
6
+
"github.com/jellydator/ttlcache/v3"
7
+
"go.hacdias.com/indielib/indieauth"
8
+
)
9
+
10
+
var authCache *ttlcache.Cache[string, *indieauth.AuthenticationRequest]
11
+
12
+
func CleanupAuthCache() {
13
+
AuthCache().Stop()
14
+
}
15
+
16
+
func AuthCache() *ttlcache.Cache[string, *indieauth.AuthenticationRequest] {
17
+
if authCache != nil {
18
+
return authCache
19
+
}
20
+
21
+
cache := ttlcache.New(
22
+
ttlcache.WithTTL[string, *indieauth.AuthenticationRequest](10 * time.Minute),
23
+
)
24
+
25
+
go cache.Start()
26
+
27
+
authCache = cache
28
+
29
+
return cache
30
+
}
+37
storage/gorm.go
+37
storage/gorm.go
···
1
+
package storage
2
+
3
+
import (
4
+
"log"
5
+
"os"
6
+
"path/filepath"
7
+
8
+
"github.com/glebarez/sqlite"
9
+
"gorm.io/gorm"
10
+
)
11
+
12
+
var orm *gorm.DB
13
+
14
+
func GORM() *gorm.DB {
15
+
if orm != nil {
16
+
return orm
17
+
}
18
+
19
+
dataDir := filepath.Join(".", "data")
20
+
if err := os.MkdirAll(dataDir, os.ModePerm); err != nil {
21
+
log.Fatal(err)
22
+
}
23
+
24
+
db, err := gorm.Open(sqlite.Open(filepath.Join(dataDir, "data.db")), &gorm.Config{})
25
+
if err != nil {
26
+
log.Fatal(err)
27
+
}
28
+
29
+
orm = db
30
+
31
+
// TODO: Move migration to `models` package
32
+
// if err := db.AutoMigrate(&models.Post{}); err != nil {
33
+
// log.Fatal(err)
34
+
// }
35
+
36
+
return db
37
+
}
+34
storage/s3.go
+34
storage/s3.go
···
1
+
package storage
2
+
3
+
import (
4
+
"context"
5
+
"log"
6
+
"os"
7
+
8
+
"github.com/aws/aws-sdk-go-v2/aws"
9
+
"github.com/aws/aws-sdk-go-v2/config"
10
+
"github.com/aws/aws-sdk-go-v2/service/s3"
11
+
)
12
+
13
+
var s3Client *s3.Client
14
+
15
+
func S3() *s3.Client {
16
+
if s3Client != nil {
17
+
return s3Client
18
+
}
19
+
20
+
sdkConfig, err := config.LoadDefaultConfig(context.Background())
21
+
if err != nil {
22
+
log.Printf("Couldn't load default configuration. Here's why: %v\n", err)
23
+
panic(err)
24
+
}
25
+
26
+
svc := s3.NewFromConfig(sdkConfig, func(o *s3.Options) {
27
+
o.BaseEndpoint = aws.String("https://" + os.Getenv("AWS_S3_ENDPOINT"))
28
+
o.Region = os.Getenv("AWS_REGION")
29
+
})
30
+
31
+
s3Client = svc
32
+
33
+
return svc
34
+
}
tailwind.config.js
config/tailwind.config.js
tailwind.config.js
config/tailwind.config.js