+18
-1
db/storage.go
+18
-1
db/storage.go
···
1
1
package db
2
2
3
3
import (
4
+
"context"
4
5
"log"
5
6
"os"
6
7
"path/filepath"
···
13
14
14
15
"github.com/glebarez/sqlite"
15
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"
16
21
)
17
22
18
23
type Storage struct {
19
24
Db *gorm.DB
20
25
Authorization *ttlcache.Cache[string, *indieauth.AuthenticationRequest]
26
+
Media *s3.Client
21
27
}
22
28
23
29
type CollectionName string
···
41
47
panic(err)
42
48
}
43
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
+
44
61
cache := ttlcache.New[string, *indieauth.AuthenticationRequest](
45
62
ttlcache.WithTTL[string, *indieauth.AuthenticationRequest](10 * time.Minute),
46
63
)
···
48
65
go cache.Start()
49
66
50
67
store := &Storage{
51
-
// Docs: c,
52
68
Db: db,
53
69
Authorization: cache,
70
+
Media: svc,
54
71
}
55
72
56
73
return store
+20
-1
go.mod
+20
-1
go.mod
···
5
5
require (
6
6
codeberg.org/gruf/go-ulid v1.1.0
7
7
github.com/aidarkhanov/nanoid v1.0.8
8
+
github.com/aws/aws-sdk-go-v2 v1.30.3
8
9
github.com/glebarez/sqlite v1.11.0
10
+
github.com/go-chi/cors v1.2.1
11
+
github.com/h2non/filetype v1.1.3
9
12
github.com/jellydator/ttlcache/v3 v3.2.0
10
13
github.com/joho/godotenv v1.5.1
11
14
go.hacdias.com/indielib v0.3.1
···
15
18
16
19
require (
17
20
filippo.io/edwards25519 v1.1.0 // indirect
21
+
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect
22
+
github.com/aws/aws-sdk-go-v2/credentials v1.17.27 // indirect
23
+
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect
24
+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect
25
+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect
26
+
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
27
+
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 // indirect
28
+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect
29
+
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 // indirect
30
+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect
31
+
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 // indirect
32
+
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect
33
+
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect
34
+
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect
35
+
github.com/aws/smithy-go v1.20.3 // indirect
18
36
github.com/cespare/xxhash/v2 v2.3.0 // indirect
19
37
github.com/dustin/go-humanize v1.0.1 // indirect
20
38
github.com/glebarez/go-sqlite v1.21.2 // indirect
21
-
github.com/go-chi/cors v1.2.1 // indirect
22
39
github.com/go-sql-driver/mysql v1.8.1 // indirect
23
40
github.com/google/uuid v1.3.0 // indirect
24
41
github.com/jinzhu/inflection v1.0.0 // indirect
···
37
54
38
55
require (
39
56
github.com/a-h/templ v0.2.747
57
+
github.com/aws/aws-sdk-go-v2/config v1.27.27
58
+
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3
40
59
github.com/go-chi/chi/v5 v5.1.0
41
60
github.com/go-chi/httprate v0.12.0
42
61
github.com/golang-jwt/jwt/v5 v5.2.1
+38
go.sum
+38
go.sum
···
7
7
github.com/aidarkhanov/nanoid v1.0.8 h1:yxyJkgsEDFXP7+97vc6JevMcjyb03Zw+/9fqhlVXBXA=
8
8
github.com/aidarkhanov/nanoid v1.0.8/go.mod h1:vadfZHT+m4uDhttg0yY4wW3GKtl2T6i4d2Age+45pYk=
9
9
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
10
+
github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY=
11
+
github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc=
12
+
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 h1:tW1/Rkad38LA15X4UQtjXZXNKsCgkshC3EbmcUmghTg=
13
+
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3/go.mod h1:UbnqO+zjqk3uIt9yCACHJ9IVNhyhOCnYk8yA19SAWrM=
14
+
github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90=
15
+
github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg=
16
+
github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI=
17
+
github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4=
18
+
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw=
19
+
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU=
20
+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU=
21
+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8=
22
+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI=
23
+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM=
24
+
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
25
+
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
26
+
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 h1:Z5r7SycxmSllHYmaAZPpmN8GviDrSGhMS6bldqtXZPw=
27
+
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15/go.mod h1:CetW7bDE00QoGEmPUoZuRog07SGVAUVW6LFpNP0YfIg=
28
+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8=
29
+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI=
30
+
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 h1:YPYe6ZmvUfDDDELqEKtAd6bo8zxhkm+XEFEzQisqUIE=
31
+
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17/go.mod h1:oBtcnYua/CgzCWYN7NZ5j7PotFDaFSUjCYVTtfyn7vw=
32
+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE=
33
+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII=
34
+
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 h1:246A4lSTXWJw/rmlQI+TT2OcqeDMKBdyjEQrafMaQdA=
35
+
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15/go.mod h1:haVfg3761/WF7YPuJOER2MP0k4UAXyHaLclKXB6usDg=
36
+
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3 h1:hT8ZAZRIfqBqHbzKTII+CIiY8G2oC9OpLedkZ51DWl8=
37
+
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3/go.mod h1:Lcxzg5rojyVPU/0eFwLtcyTaek/6Mtic5B1gJo7e/zE=
38
+
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM=
39
+
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU=
40
+
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE=
41
+
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw=
42
+
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE=
43
+
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ=
44
+
github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE=
45
+
github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
10
46
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
11
47
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
12
48
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
···
39
75
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
40
76
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
41
77
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
78
+
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
79
+
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
42
80
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
43
81
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
44
82
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
+53
-14
main.go
+53
-14
main.go
···
2
2
3
3
import (
4
4
"encoding/json"
5
+
"fmt"
6
+
"io"
5
7
"os"
8
+
"strings"
6
9
"time"
7
10
8
11
"log"
9
12
"net/http"
10
13
"strconv"
11
14
15
+
"github.com/aws/aws-sdk-go-v2/aws"
16
+
"github.com/aws/aws-sdk-go-v2/service/s3"
12
17
"github.com/puregarlic/space/db"
13
18
"github.com/puregarlic/space/models"
14
19
"github.com/puregarlic/space/pages"
···
76
81
// Pages
77
82
r.Get("/", s.serveHomeTemplate)
78
83
r.Get("/posts/{slug}", s.servePostTemplate)
84
+
r.Get("/media/*", s.serveMedia)
79
85
80
86
// IndieAuth handlers
81
87
r.Group(func(r chi.Router) {
···
96
102
},
97
103
))
98
104
r.Use(s.mustAuth)
99
-
r.Get("/", s.serveMicropub)
100
-
r.Post("/", s.serveMicropub)
105
+
106
+
mp := µpubImplementation{s}
107
+
mpHandler := micropub.NewHandler(
108
+
mp,
109
+
micropub.WithGetPostTypes(func() []micropub.PostType {
110
+
return []micropub.PostType{
111
+
{
112
+
Name: "Post",
113
+
Type: string(microformats.TypeNote),
114
+
},
115
+
{
116
+
Name: "Photo",
117
+
Type: string(microformats.TypePhoto),
118
+
},
119
+
}
120
+
}),
121
+
micropub.WithMediaEndpoint(s.profileURL+"/micropub/media"),
122
+
)
123
+
124
+
r.Get("/", mpHandler.ServeHTTP)
125
+
r.Post("/", mpHandler.ServeHTTP)
126
+
r.Post("/media", micropub.NewMediaHandler(
127
+
mp.HandleMediaUpload,
128
+
mp.HasScope,
129
+
).ServeHTTP)
101
130
})
102
131
103
132
// Start it!
···
138
167
templ.Handler(pages.Post(post)).ServeHTTP(w, r)
139
168
}
140
169
141
-
func (s *server) serveMicropub(w http.ResponseWriter, r *http.Request) {
142
-
micropub.NewHandler(
143
-
µpubImplementation{s},
144
-
micropub.WithGetPostTypes(func() []micropub.PostType {
145
-
return []micropub.PostType{
146
-
{
147
-
Name: "Post",
148
-
Type: string(microformats.TypeNote),
149
-
},
150
-
}
151
-
}),
152
-
).ServeHTTP(w, r)
170
+
func (s *server) serveMedia(w http.ResponseWriter, r *http.Request) {
171
+
key := strings.TrimPrefix(r.URL.Path, "/")
172
+
173
+
res, err := s.db.Media.GetObject(r.Context(), &s3.GetObjectInput{
174
+
Bucket: aws.String(os.Getenv("AWS_S3_BUCKET_NAME")),
175
+
Key: &key,
176
+
})
177
+
if err != nil {
178
+
fmt.Println("failed to get object", err)
179
+
httpError(w, http.StatusInternalServerError)
180
+
return
181
+
}
182
+
183
+
defer res.Body.Close()
184
+
185
+
w.Header().Set("Cache-Control", "604800")
186
+
187
+
if _, err := io.Copy(w, res.Body); err != nil {
188
+
fmt.Println("failed to send object", err)
189
+
httpError(w, http.StatusInternalServerError)
190
+
return
191
+
}
153
192
}
154
193
155
194
func httpError(w http.ResponseWriter, status int) {
+29
micropub.go
+29
micropub.go
···
1
1
package main
2
2
3
3
import (
4
+
"context"
4
5
"encoding/json"
5
6
"errors"
6
7
"fmt"
8
+
"mime/multipart"
7
9
"net/http"
8
10
urlpkg "net/url"
11
+
"os"
9
12
"reflect"
10
13
"strings"
11
14
12
15
"github.com/puregarlic/space/models"
16
+
17
+
"github.com/aidarkhanov/nanoid"
18
+
"github.com/aws/aws-sdk-go-v2/aws"
19
+
"github.com/aws/aws-sdk-go-v2/service/s3"
20
+
"github.com/h2non/filetype"
13
21
"github.com/samber/lo"
14
22
15
23
"go.hacdias.com/indielib/micropub"
···
60
68
61
69
func (s *micropubImplementation) SourceMany(limit, offset int) ([]map[string]any, error) {
62
70
return nil, micropub.ErrNotImplemented
71
+
}
72
+
73
+
func (s *micropubImplementation) HandleMediaUpload(file multipart.File, header *multipart.FileHeader) (string, error) {
74
+
defer file.Close()
75
+
76
+
kind, err := filetype.MatchReader(file)
77
+
if err != nil {
78
+
return "", fmt.Errorf("%w: %w", errors.New("failed to upload"), err)
79
+
}
80
+
81
+
key := fmt.Sprintf("media/%s.%s", nanoid.New(), kind.Extension)
82
+
_, err = s.db.Media.PutObject(context.TODO(), &s3.PutObjectInput{
83
+
Bucket: aws.String(os.Getenv("AWS_S3_BUCKET_NAME")),
84
+
Key: &key,
85
+
Body: file,
86
+
})
87
+
if err != nil {
88
+
return "", fmt.Errorf("%w: %w", errors.New("failed to upload"), err)
89
+
}
90
+
91
+
return s.profileURL + key, nil
63
92
}
64
93
65
94
func (s *micropubImplementation) Create(req *micropub.Request) (string, error) {