+47
README.md
+47
README.md
···
1
+
# Space
2
+
3
+
> A likely-to-be-unfinished experimental IndieWeb site, built with Go.
4
+
5
+
Space is an attempt at building an IndieWeb-capable personal site, before I
6
+
started looking into AT Protocol. It uses [Chi](https://go-chi.io/#/),
7
+
[Templ](https://templ.guide/), and [TailwindCSS](https://tailwindcss.com).
8
+
Data is stored using SQLite, and your DB is continuously backed up to S3 via
9
+
[Litestream](https://litestream.io/).
10
+
11
+
Any unrecognized or unsupported [post types](https://indieweb.org/posts) are
12
+
rendered as raw JSON until they are explicitly supported. Media files are
13
+
stored in the same S3 backend that Litestream uses, so all your data is
14
+
replicated off-machine.
15
+
16
+
Notably, the Tailwind pipeline is not checked into the project, as this was
17
+
built before Go Tool support.
18
+
19
+
If you want to take it for a test-drive, you can check the `.env.example` file
20
+
for all required configuration values.
21
+
22
+
## Supported Specs
23
+
24
+
- [x] [IndieAuth](https://www.w3.org/TR/indieauth/) via Basic Auth
25
+
- [x] [Micropub](https://www.w3.org/TR/micropub/)
26
+
- [ ] [h-card](https://microformats.org/wiki/h-card)
27
+
- [ ] [Webmentions](https://www.w3.org/TR/webmention/)
28
+
- [ ] [Microsub](https://indieweb.org/Microsub-spec)
29
+
30
+
## What happened?
31
+
32
+
At the time, I was quite pleased with how this turned out. I think I nailed the
33
+
aesthetics, and I hit my requirement for data durability and replication. I was
34
+
targeting Fly.io for deployment, and Fly+Tigris was an excellent combo.
35
+
36
+
Day-to-day, though, I couldn't make a habit of using this. IndieWeb is a lonely
37
+
place when you don't have any connections and you're not a social creature by
38
+
default. Implementing support for different post types over time felt like it
39
+
was going to be a drag, and my heart just wasn't in it.
40
+
41
+
I have my hopes up with AT Protocol, however. AppViews remove the need for
42
+
implementing display logic for each "post type" manually, and Lexicons allow
43
+
for myriad post types contributed by different services. It's an easier place
44
+
to live for a lurker such as myself.
45
+
46
+
Maybe at some point, I'll take a stab at implementing my own PDS, but until
47
+
then--here's the thing I already built.
+3
-3
config/Dockerfile
+3
-3
config/Dockerfile
···
1
1
# Build styles
2
-
FROM denoland/deno:1.45.5 AS build-styles
2
+
FROM denoland/deno:2.5.2 AS build-styles
3
3
WORKDIR /app
4
4
5
5
COPY . /app
6
6
7
-
RUN deno run --allow-all npm:tailwindcss -i config/main.css -o static/styles.css -c config/tailwind.config.ts --minify
7
+
RUN deno run --allow-all npm:tailwindcss@3.4.17 -i config/main.css -o static/styles.css -c config/tailwind.config.ts --minify
8
8
9
-
FROM golang:1.22-alpine AS build-server
9
+
FROM golang:1.24-alpine AS build-server
10
10
WORKDIR /app
11
11
12
12
COPY . /app
+2
-2
handlers/routes.go
+2
-2
handlers/routes.go
···
18
18
panic(result.Error)
19
19
}
20
20
21
-
layouts.RenderDefault(pages.Home(posts)).ServeHTTP(w, r)
21
+
layouts.RenderWithSidebar("", pages.Home(posts)).ServeHTTP(w, r)
22
22
}
23
23
24
24
func ServePostPage(w http.ResponseWriter, r *http.Request) {
···
32
32
return
33
33
}
34
34
35
-
layouts.RenderDefault(pages.Post(post)).ServeHTTP(w, r)
35
+
layouts.RenderWithSidebar(string(post.MicroformatType), pages.Post(post)).ServeHTTP(w, r)
36
36
}
+21
html/components/head.templ
+21
html/components/head.templ
···
1
+
package components
2
+
3
+
import "github.com/puregarlic/space/storage"
4
+
5
+
templ Head(title string) {
6
+
<head>
7
+
if len(title) > 0 {
8
+
<title>{ title } | puregarlic dot space</title>
9
+
} else {
10
+
<title>puregarlic dot space</title>
11
+
}
12
+
<meta name="viewport" content="width=device-width, initial-scale=1"/>
13
+
<link rel="preconnect" href="https://fonts.googleapis.com"/>
14
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
15
+
<link href="https://fonts.googleapis.com/css2?family=Recursive:slnt,wght,CASL,CRSV,MONO@-15..0,300..1000,0..1,0..1,0..1&display=swap" rel="stylesheet"/>
16
+
for _, rel := range storage.GetRels() {
17
+
<link rel={ rel.Name } href={ rel.HREF }/>
18
+
}
19
+
<link rel="stylesheet" href="/static/styles.css"/>
20
+
</head>
21
+
}
+101
html/components/head_templ.go
+101
html/components/head_templ.go
···
1
+
// Code generated by templ - DO NOT EDIT.
2
+
3
+
// templ: version: v0.2.747
4
+
package components
5
+
6
+
//lint:file-ignore SA4006 This context is only used if a nested component is present.
7
+
8
+
import "github.com/a-h/templ"
9
+
import templruntime "github.com/a-h/templ/runtime"
10
+
11
+
import "github.com/puregarlic/space/storage"
12
+
13
+
func Head(title string) templ.Component {
14
+
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
15
+
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
16
+
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
17
+
if !templ_7745c5c3_IsBuffer {
18
+
defer func() {
19
+
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
20
+
if templ_7745c5c3_Err == nil {
21
+
templ_7745c5c3_Err = templ_7745c5c3_BufErr
22
+
}
23
+
}()
24
+
}
25
+
ctx = templ.InitializeContext(ctx)
26
+
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
27
+
if templ_7745c5c3_Var1 == nil {
28
+
templ_7745c5c3_Var1 = templ.NopComponent
29
+
}
30
+
ctx = templ.ClearChildren(ctx)
31
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<head>")
32
+
if templ_7745c5c3_Err != nil {
33
+
return templ_7745c5c3_Err
34
+
}
35
+
if len(title) > 0 {
36
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<title>")
37
+
if templ_7745c5c3_Err != nil {
38
+
return templ_7745c5c3_Err
39
+
}
40
+
var templ_7745c5c3_Var2 string
41
+
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title)
42
+
if templ_7745c5c3_Err != nil {
43
+
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/components/head.templ`, Line: 8, Col: 17}
44
+
}
45
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
46
+
if templ_7745c5c3_Err != nil {
47
+
return templ_7745c5c3_Err
48
+
}
49
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" | puregarlic dot space</title>")
50
+
if templ_7745c5c3_Err != nil {
51
+
return templ_7745c5c3_Err
52
+
}
53
+
} else {
54
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<title>puregarlic dot space</title>")
55
+
if templ_7745c5c3_Err != nil {
56
+
return templ_7745c5c3_Err
57
+
}
58
+
}
59
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><link rel=\"preconnect\" href=\"https://fonts.googleapis.com\"><link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin><link href=\"https://fonts.googleapis.com/css2?family=Recursive:slnt,wght,CASL,CRSV,MONO@-15..0,300..1000,0..1,0..1,0..1&display=swap\" rel=\"stylesheet\">")
60
+
if templ_7745c5c3_Err != nil {
61
+
return templ_7745c5c3_Err
62
+
}
63
+
for _, rel := range storage.GetRels() {
64
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<link rel=\"")
65
+
if templ_7745c5c3_Err != nil {
66
+
return templ_7745c5c3_Err
67
+
}
68
+
var templ_7745c5c3_Var3 string
69
+
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(rel.Name)
70
+
if templ_7745c5c3_Err != nil {
71
+
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/components/head.templ`, Line: 17, Col: 23}
72
+
}
73
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
74
+
if templ_7745c5c3_Err != nil {
75
+
return templ_7745c5c3_Err
76
+
}
77
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" href=\"")
78
+
if templ_7745c5c3_Err != nil {
79
+
return templ_7745c5c3_Err
80
+
}
81
+
var templ_7745c5c3_Var4 string
82
+
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(rel.HREF)
83
+
if templ_7745c5c3_Err != nil {
84
+
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/components/head.templ`, Line: 17, Col: 41}
85
+
}
86
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
87
+
if templ_7745c5c3_Err != nil {
88
+
return templ_7745c5c3_Err
89
+
}
90
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
91
+
if templ_7745c5c3_Err != nil {
92
+
return templ_7745c5c3_Err
93
+
}
94
+
}
95
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<link rel=\"stylesheet\" href=\"/static/styles.css\"></head>")
96
+
if templ_7745c5c3_Err != nil {
97
+
return templ_7745c5c3_Err
98
+
}
99
+
return templ_7745c5c3_Err
100
+
})
101
+
}
+4
-6
html/components/posts/note.templ
+4
-6
html/components/posts/note.templ
···
1
1
package posts
2
2
3
-
import (
4
-
"github.com/puregarlic/space/models"
5
-
)
3
+
import "github.com/puregarlic/space/models"
6
4
7
5
templ Note(post *models.Post) {
8
-
<div class="bg-surface p-4">
9
-
{GetPostJSONProperty(post, "content")[0]}
10
-
</div>
6
+
<div class="bg-surface p-4 first:rounded-t last:rounded-b border-2 border-overlay">
7
+
{ GetPostJSONProperty(post, "content")[0] }
8
+
</div>
11
9
}
+3
-5
html/components/posts/note_templ.go
+3
-5
html/components/posts/note_templ.go
···
8
8
import "github.com/a-h/templ"
9
9
import templruntime "github.com/a-h/templ/runtime"
10
10
11
-
import (
12
-
"github.com/puregarlic/space/models"
13
-
)
11
+
import "github.com/puregarlic/space/models"
14
12
15
13
func Note(post *models.Post) templ.Component {
16
14
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
···
30
28
templ_7745c5c3_Var1 = templ.NopComponent
31
29
}
32
30
ctx = templ.ClearChildren(ctx)
33
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"bg-surface p-4\">")
31
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"bg-surface p-4 first:rounded-t last:rounded-b border-2 border-overlay\">")
34
32
if templ_7745c5c3_Err != nil {
35
33
return templ_7745c5c3_Err
36
34
}
37
35
var templ_7745c5c3_Var2 string
38
36
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(GetPostJSONProperty(post, "content")[0])
39
37
if templ_7745c5c3_Err != nil {
40
-
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/components/posts/note.templ`, Line: 9, Col: 43}
38
+
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/components/posts/note.templ`, Line: 7, Col: 43}
41
39
}
42
40
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
43
41
if templ_7745c5c3_Err != nil {
+13
-15
html/components/posts/photo.templ
+13
-15
html/components/posts/photo.templ
···
1
1
package posts
2
2
3
-
import (
4
-
"github.com/puregarlic/space/models"
5
-
)
3
+
import "github.com/puregarlic/space/models"
6
4
7
5
templ Photo(post *models.Post) {
8
-
<div class="bg-base">
9
-
for index, photo := range GetPostJSONProperty(post, "photo") {
10
-
<figure class="relative group">
11
-
<img class="w-full" src={photo} />
12
-
<figcaption
13
-
class="p-4 absolute inset-x-0 bottom-0 opacity-100 group-hover:opacity-0 bg-surface/70 backdrop-blur transition"
14
-
>
15
-
{GetPostJSONProperty(post, "content")[index]}
16
-
</figcaption>
17
-
</figure>
18
-
}
19
-
</div>
6
+
<div class="bg-base">
7
+
for index, photo := range GetPostJSONProperty(post, "photo") {
8
+
<figure class="relative group last:rounded-b">
9
+
<img class="w-full border-x-2 border-overlay group-first:rounded-t group-first:border-t-2" src={ photo }/>
10
+
<figcaption
11
+
class="p-4 bg-surface group-last:rounded-b group-last:border-b-2 group-last:border-x-2 group-last:border-overlay"
12
+
>
13
+
{ GetPostJSONProperty(post, "content")[index] }
14
+
</figcaption>
15
+
</figure>
16
+
}
17
+
</div>
20
18
}
+5
-7
html/components/posts/photo_templ.go
+5
-7
html/components/posts/photo_templ.go
···
8
8
import "github.com/a-h/templ"
9
9
import templruntime "github.com/a-h/templ/runtime"
10
10
11
-
import (
12
-
"github.com/puregarlic/space/models"
13
-
)
11
+
import "github.com/puregarlic/space/models"
14
12
15
13
func Photo(post *models.Post) templ.Component {
16
14
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
···
35
33
return templ_7745c5c3_Err
36
34
}
37
35
for index, photo := range GetPostJSONProperty(post, "photo") {
38
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<figure class=\"relative group\"><img class=\"w-full\" src=\"")
36
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<figure class=\"relative group last:rounded-b\"><img class=\"w-full border-x-2 border-overlay group-first:rounded-t group-first:border-t-2\" src=\"")
39
37
if templ_7745c5c3_Err != nil {
40
38
return templ_7745c5c3_Err
41
39
}
42
40
var templ_7745c5c3_Var2 string
43
41
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(photo)
44
42
if templ_7745c5c3_Err != nil {
45
-
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/components/posts/photo.templ`, Line: 11, Col: 35}
43
+
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/components/posts/photo.templ`, Line: 9, Col: 106}
46
44
}
47
45
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
48
46
if templ_7745c5c3_Err != nil {
49
47
return templ_7745c5c3_Err
50
48
}
51
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><figcaption class=\"p-4 absolute inset-x-0 bottom-0 opacity-100 group-hover:opacity-0 bg-surface/70 backdrop-blur transition\">")
49
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><figcaption class=\"p-4 bg-surface group-last:rounded-b group-last:border-b-2 group-last:border-x-2 group-last:border-overlay\">")
52
50
if templ_7745c5c3_Err != nil {
53
51
return templ_7745c5c3_Err
54
52
}
55
53
var templ_7745c5c3_Var3 string
56
54
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(GetPostJSONProperty(post, "content")[index])
57
55
if templ_7745c5c3_Err != nil {
58
-
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/components/posts/photo.templ`, Line: 15, Col: 52}
56
+
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/components/posts/photo.templ`, Line: 13, Col: 50}
59
57
}
60
58
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
61
59
if templ_7745c5c3_Err != nil {
+66
-50
html/components/posts/post.templ
+66
-50
html/components/posts/post.templ
···
1
1
package posts
2
2
3
3
import (
4
-
"fmt"
5
4
"encoding/json"
6
-
7
-
"github.com/puregarlic/space/models"
5
+
"fmt"
8
6
9
-
"github.com/samber/lo"
7
+
"github.com/puregarlic/space/models"
8
+
"github.com/samber/lo"
10
9
"go.hacdias.com/indielib/microformats"
11
10
)
12
11
13
12
var ImplementedPostTypes = []microformats.Type{
14
-
microformats.TypeNote,
15
-
microformats.TypePhoto,
13
+
microformats.TypeNote,
14
+
microformats.TypePhoto,
16
15
}
17
16
18
17
func GetPostJSONProperty(post *models.Post, name string) []string {
19
-
var tmp map[string]any
20
-
if err := json.Unmarshal(post.Properties, &tmp); err != nil {
21
-
panic(err)
22
-
}
18
+
var tmp map[string]any
19
+
if err := json.Unmarshal(post.Properties, &tmp); err != nil {
20
+
panic(err)
21
+
}
23
22
24
-
prop, ok := tmp[name]
25
-
if !ok {
26
-
return []string{""}
27
-
}
23
+
prop, ok := tmp[name]
24
+
if !ok {
25
+
return []string{""}
26
+
}
28
27
29
-
var out []string
30
-
for _, val := range prop.([]any) {
31
-
out = append(out, val.(string))
32
-
}
28
+
var out []string
29
+
for _, val := range prop.([]any) {
30
+
out = append(out, val.(string))
31
+
}
33
32
34
-
return out
33
+
return out
35
34
}
36
35
37
36
func formatPostTypeName(mfType microformats.Type) string {
38
-
has := lo.ContainsBy(ImplementedPostTypes, func(postType microformats.Type) bool {
39
-
return postType == mfType
40
-
})
37
+
has := lo.ContainsBy(ImplementedPostTypes, func(postType microformats.Type) bool {
38
+
return postType == mfType
39
+
})
41
40
42
-
if has {
43
-
return string(mfType)
44
-
} else {
45
-
return fmt.Sprintf("oops! (%s)", string(mfType))
46
-
}
41
+
if has {
42
+
return string(mfType)
43
+
} else {
44
+
return fmt.Sprintf("%s (oops!)", string(mfType))
45
+
}
47
46
}
48
47
49
48
templ PostFeedHeader(post *models.Post) {
50
-
<div class="px-3 py-2 bg-base text-xs text-subtle/50 group-hover:text-subtle/100 flex justify-between transition">
51
-
<p>{ formatPostTypeName(post.MicroformatType) }</p>
52
-
<a
53
-
class="hover:underline hover:text-iris flex gap-1"
54
-
target="_blank"
55
-
rel="noopener noreferrer"
56
-
href={ templ.URL("/posts/" + post.ID.String()) }
57
-
>
58
-
{ post.CreatedAt.Format("01/02/2006 at 3:04 PM") }
59
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4 -mt-px">
60
-
<path d="M6.22 8.72a.75.75 0 0 0 1.06 1.06l5.22-5.22v1.69a.75.75 0 0 0 1.5 0v-3.5a.75.75 0 0 0-.75-.75h-3.5a.75.75 0 0 0 0 1.5h1.69L6.22 8.72Z" />
61
-
<path d="M3.5 6.75c0-.69.56-1.25 1.25-1.25H7A.75.75 0 0 0 7 4H4.75A2.75 2.75 0 0 0 2 6.75v4.5A2.75 2.75 0 0 0 4.75 14h4.5A2.75 2.75 0 0 0 12 11.25V9a.75.75 0 0 0-1.5 0v2.25c0 .69-.56 1.25-1.25 1.25h-4.5c-.69 0-1.25-.56-1.25-1.25v-4.5Z" />
62
-
</svg>
63
-
</a>
64
-
</div>
49
+
<div class="px-3 py-2 bg-surface text-xs text-muted flex items-center justify-between rounded-t border-2 border-b-0 border-overlay">
50
+
<p>{ post.Timestamp() }</p>
51
+
<p class="flex gap-1.5">
52
+
{ formatPostTypeName(post.MicroformatType) }
53
+
<span class="text-muted/40">•</span>
54
+
<a
55
+
class="hover:underline hover:text-iris flex items-center gap-1 transition"
56
+
target="_blank"
57
+
rel="noopener noreferrer"
58
+
href={ templ.URL("/posts/" + post.ID.String()) }
59
+
>
60
+
open
61
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4 -mt-px">
62
+
<path d="M6.22 8.72a.75.75 0 0 0 1.06 1.06l5.22-5.22v1.69a.75.75 0 0 0 1.5 0v-3.5a.75.75 0 0 0-.75-.75h-3.5a.75.75 0 0 0 0 1.5h1.69L6.22 8.72Z"></path>
63
+
<path d="M3.5 6.75c0-.69.56-1.25 1.25-1.25H7A.75.75 0 0 0 7 4H4.75A2.75 2.75 0 0 0 2 6.75v4.5A2.75 2.75 0 0 0 4.75 14h4.5A2.75 2.75 0 0 0 12 11.25V9a.75.75 0 0 0-1.5 0v2.25c0 .69-.56 1.25-1.25 1.25h-4.5c-.69 0-1.25-.56-1.25-1.25v-4.5Z"></path>
64
+
</svg>
65
+
</a>
66
+
</p>
67
+
</div>
68
+
}
69
+
70
+
templ PostDetails(post *models.Post) {
71
+
<dl class="grid md:grid-cols-2 gap-4">
72
+
<div>
73
+
<dt class="mb-1 text-sm text-muted">Posted At</dt>
74
+
<dd class="text-subtle">{ post.Timestamp() }</dd>
75
+
</div>
76
+
<div>
77
+
<dt class="mb-1 text-sm text-muted">Post Type</dt>
78
+
<dd class="text-subtle">{ string(post.MicroformatType) }</dd>
79
+
</div>
80
+
</dl>
65
81
}
66
82
67
83
templ PostContent(post *models.Post) {
68
-
switch post.MicroformatType {
69
-
case microformats.TypePhoto:
70
-
@Photo(post)
71
-
case microformats.TypeNote:
72
-
@Note(post)
73
-
default:
74
-
@Unsupported(post)
75
-
}
84
+
switch post.MicroformatType {
85
+
case microformats.TypePhoto:
86
+
@Photo(post)
87
+
case microformats.TypeNote:
88
+
@Note(post)
89
+
default:
90
+
@Unsupported(post)
91
+
}
76
92
}
+67
-16
html/components/posts/post_templ.go
+67
-16
html/components/posts/post_templ.go
···
13
13
"fmt"
14
14
15
15
"github.com/puregarlic/space/models"
16
-
17
16
"github.com/samber/lo"
18
17
"go.hacdias.com/indielib/microformats"
19
18
)
···
50
49
if has {
51
50
return string(mfType)
52
51
} else {
53
-
return fmt.Sprintf("oops! (%s)", string(mfType))
52
+
return fmt.Sprintf("%s (oops!)", string(mfType))
54
53
}
55
54
}
56
55
···
72
71
templ_7745c5c3_Var1 = templ.NopComponent
73
72
}
74
73
ctx = templ.ClearChildren(ctx)
75
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"px-3 py-2 bg-base text-xs text-subtle/50 group-hover:text-subtle/100 flex justify-between transition\"><p>")
74
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"px-3 py-2 bg-surface text-xs text-muted flex items-center justify-between rounded-t border-2 border-b-0 border-overlay\"><p>")
76
75
if templ_7745c5c3_Err != nil {
77
76
return templ_7745c5c3_Err
78
77
}
79
78
var templ_7745c5c3_Var2 string
80
-
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(formatPostTypeName(post.MicroformatType))
79
+
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(post.Timestamp())
81
80
if templ_7745c5c3_Err != nil {
82
-
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/components/posts/post.templ`, Line: 51, Col: 49}
81
+
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/components/posts/post.templ`, Line: 50, Col: 23}
83
82
}
84
83
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
85
84
if templ_7745c5c3_Err != nil {
86
85
return templ_7745c5c3_Err
87
86
}
88
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p><a class=\"hover:underline hover:text-iris flex gap-1\" target=\"_blank\" rel=\"noopener noreferrer\" href=\"")
87
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p><p class=\"flex gap-1.5\">")
89
88
if templ_7745c5c3_Err != nil {
90
89
return templ_7745c5c3_Err
91
90
}
92
-
var templ_7745c5c3_Var3 templ.SafeURL = templ.URL("/posts/" + post.ID.String())
93
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var3)))
91
+
var templ_7745c5c3_Var3 string
92
+
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(formatPostTypeName(post.MicroformatType))
94
93
if templ_7745c5c3_Err != nil {
95
-
return templ_7745c5c3_Err
94
+
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/components/posts/post.templ`, Line: 52, Col: 45}
96
95
}
97
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
96
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
98
97
if templ_7745c5c3_Err != nil {
99
98
return templ_7745c5c3_Err
100
99
}
101
-
var templ_7745c5c3_Var4 string
102
-
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(post.CreatedAt.Format("01/02/2006 at 3:04 PM"))
100
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <span class=\"text-muted/40\">•</span> <a class=\"hover:underline hover:text-iris flex items-center gap-1 transition\" target=\"_blank\" rel=\"noopener noreferrer\" href=\"")
103
101
if templ_7745c5c3_Err != nil {
104
-
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/components/posts/post.templ`, Line: 58, Col: 53}
102
+
return templ_7745c5c3_Err
105
103
}
106
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
104
+
var templ_7745c5c3_Var4 templ.SafeURL = templ.URL("/posts/" + post.ID.String())
105
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4)))
107
106
if templ_7745c5c3_Err != nil {
108
107
return templ_7745c5c3_Err
109
108
}
110
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" fill=\"currentColor\" class=\"size-4 -mt-px\"><path d=\"M6.22 8.72a.75.75 0 0 0 1.06 1.06l5.22-5.22v1.69a.75.75 0 0 0 1.5 0v-3.5a.75.75 0 0 0-.75-.75h-3.5a.75.75 0 0 0 0 1.5h1.69L6.22 8.72Z\"></path> <path d=\"M3.5 6.75c0-.69.56-1.25 1.25-1.25H7A.75.75 0 0 0 7 4H4.75A2.75 2.75 0 0 0 2 6.75v4.5A2.75 2.75 0 0 0 4.75 14h4.5A2.75 2.75 0 0 0 12 11.25V9a.75.75 0 0 0-1.5 0v2.25c0 .69-.56 1.25-1.25 1.25h-4.5c-.69 0-1.25-.56-1.25-1.25v-4.5Z\"></path></svg></a></div>")
109
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">open <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" fill=\"currentColor\" class=\"size-4 -mt-px\"><path d=\"M6.22 8.72a.75.75 0 0 0 1.06 1.06l5.22-5.22v1.69a.75.75 0 0 0 1.5 0v-3.5a.75.75 0 0 0-.75-.75h-3.5a.75.75 0 0 0 0 1.5h1.69L6.22 8.72Z\"></path> <path d=\"M3.5 6.75c0-.69.56-1.25 1.25-1.25H7A.75.75 0 0 0 7 4H4.75A2.75 2.75 0 0 0 2 6.75v4.5A2.75 2.75 0 0 0 4.75 14h4.5A2.75 2.75 0 0 0 12 11.25V9a.75.75 0 0 0-1.5 0v2.25c0 .69-.56 1.25-1.25 1.25h-4.5c-.69 0-1.25-.56-1.25-1.25v-4.5Z\"></path></svg></a></p></div>")
111
110
if templ_7745c5c3_Err != nil {
112
111
return templ_7745c5c3_Err
113
112
}
···
115
114
})
116
115
}
117
116
118
-
func PostContent(post *models.Post) templ.Component {
117
+
func PostDetails(post *models.Post) templ.Component {
119
118
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
120
119
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
121
120
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
···
131
130
templ_7745c5c3_Var5 := templ.GetChildren(ctx)
132
131
if templ_7745c5c3_Var5 == nil {
133
132
templ_7745c5c3_Var5 = templ.NopComponent
133
+
}
134
+
ctx = templ.ClearChildren(ctx)
135
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<dl class=\"grid md:grid-cols-2 gap-4\"><div><dt class=\"mb-1 text-sm text-muted\">Posted At</dt><dd class=\"text-subtle\">")
136
+
if templ_7745c5c3_Err != nil {
137
+
return templ_7745c5c3_Err
138
+
}
139
+
var templ_7745c5c3_Var6 string
140
+
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(post.Timestamp())
141
+
if templ_7745c5c3_Err != nil {
142
+
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/components/posts/post.templ`, Line: 74, Col: 45}
143
+
}
144
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
145
+
if templ_7745c5c3_Err != nil {
146
+
return templ_7745c5c3_Err
147
+
}
148
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</dd></div><div><dt class=\"mb-1 text-sm text-muted\">Post Type</dt><dd class=\"text-subtle\">")
149
+
if templ_7745c5c3_Err != nil {
150
+
return templ_7745c5c3_Err
151
+
}
152
+
var templ_7745c5c3_Var7 string
153
+
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(string(post.MicroformatType))
154
+
if templ_7745c5c3_Err != nil {
155
+
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/components/posts/post.templ`, Line: 78, Col: 57}
156
+
}
157
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
158
+
if templ_7745c5c3_Err != nil {
159
+
return templ_7745c5c3_Err
160
+
}
161
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</dd></div></dl>")
162
+
if templ_7745c5c3_Err != nil {
163
+
return templ_7745c5c3_Err
164
+
}
165
+
return templ_7745c5c3_Err
166
+
})
167
+
}
168
+
169
+
func PostContent(post *models.Post) templ.Component {
170
+
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
171
+
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
172
+
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
173
+
if !templ_7745c5c3_IsBuffer {
174
+
defer func() {
175
+
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
176
+
if templ_7745c5c3_Err == nil {
177
+
templ_7745c5c3_Err = templ_7745c5c3_BufErr
178
+
}
179
+
}()
180
+
}
181
+
ctx = templ.InitializeContext(ctx)
182
+
templ_7745c5c3_Var8 := templ.GetChildren(ctx)
183
+
if templ_7745c5c3_Var8 == nil {
184
+
templ_7745c5c3_Var8 = templ.NopComponent
134
185
}
135
186
ctx = templ.ClearChildren(ctx)
136
187
switch post.MicroformatType {
+29
-30
html/components/posts/unsupported.templ
+29
-30
html/components/posts/unsupported.templ
···
1
1
package posts
2
2
3
3
import (
4
-
"bytes"
5
-
"encoding/json"
4
+
"bytes"
5
+
"encoding/json"
6
6
7
-
"github.com/puregarlic/space/models"
8
-
9
-
"github.com/alecthomas/chroma/v2/formatters/html"
10
-
"github.com/alecthomas/chroma/v2/styles"
11
-
"github.com/alecthomas/chroma/v2/lexers"
7
+
"github.com/alecthomas/chroma/v2/formatters/html"
8
+
"github.com/alecthomas/chroma/v2/lexers"
9
+
"github.com/alecthomas/chroma/v2/styles"
10
+
"github.com/puregarlic/space/models"
12
11
)
13
12
14
13
var style = styles.Get("rose-pine")
···
18
17
var dedupeSyntaxStyles = templ.NewOnceHandle()
19
18
20
19
func renderPostAsJSON(post *models.Post) string {
21
-
contents, err := json.MarshalIndent(post.Properties, "", " ")
22
-
if err != nil {
23
-
panic(err)
24
-
}
20
+
contents, err := json.MarshalIndent(post.Properties, "", " ")
21
+
if err != nil {
22
+
panic(err)
23
+
}
25
24
26
-
iterator, err := lexer.Tokenise(nil, string(contents))
25
+
iterator, err := lexer.Tokenise(nil, string(contents))
27
26
28
-
var buf bytes.Buffer
29
-
formatter.Format(&buf, style, iterator)
27
+
var buf bytes.Buffer
28
+
formatter.Format(&buf, style, iterator)
30
29
31
-
return buf.String()
30
+
return buf.String()
32
31
}
33
32
34
33
func generateSyntaxClassNames() string {
35
-
var buf bytes.Buffer
36
-
if err := formatter.WriteCSS(&buf, style); err != nil {
37
-
panic(err)
38
-
}
39
-
40
-
return "<style>"+buf.String()+"</style>"
34
+
var buf bytes.Buffer
35
+
if err := formatter.WriteCSS(&buf, style); err != nil {
36
+
panic(err)
37
+
}
38
+
39
+
return "<style>" + buf.String() + "</style>"
41
40
}
42
41
43
42
templ syntaxStyleTag() {
44
-
@templ.Raw(generateSyntaxClassNames())
43
+
@templ.Raw(generateSyntaxClassNames())
45
44
}
46
45
47
46
templ Unsupported(post *models.Post) {
48
-
<div
49
-
class="block p-4 bg-base overflow-x-scroll min-w-0"
50
-
>
51
-
@dedupeSyntaxStyles.Once() {
52
-
@syntaxStyleTag()
53
-
}
54
-
@templ.Raw(renderPostAsJSON(post))
55
-
</div>
47
+
<div
48
+
class="block p-4 bg-base overflow-x-scroll min-w-0 first:rounded-t last:rounded-b border-2 border-overlay"
49
+
>
50
+
@dedupeSyntaxStyles.Once() {
51
+
@syntaxStyleTag()
52
+
}
53
+
@templ.Raw(renderPostAsJSON(post))
54
+
</div>
56
55
}
+2
-3
html/components/posts/unsupported_templ.go
+2
-3
html/components/posts/unsupported_templ.go
···
12
12
"bytes"
13
13
"encoding/json"
14
14
15
-
"github.com/puregarlic/space/models"
16
-
17
15
"github.com/alecthomas/chroma/v2/formatters/html"
18
16
"github.com/alecthomas/chroma/v2/lexers"
19
17
"github.com/alecthomas/chroma/v2/styles"
18
+
"github.com/puregarlic/space/models"
20
19
)
21
20
22
21
var style = styles.Get("rose-pine")
···
92
91
templ_7745c5c3_Var2 = templ.NopComponent
93
92
}
94
93
ctx = templ.ClearChildren(ctx)
95
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"block p-4 bg-base overflow-x-scroll min-w-0\">")
94
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"block p-4 bg-base overflow-x-scroll min-w-0 first:rounded-t last:rounded-b border-2 border-overlay\">")
96
95
if templ_7745c5c3_Err != nil {
97
96
return templ_7745c5c3_Err
98
97
}
+15
-26
html/layouts/default.templ
+15
-26
html/layouts/default.templ
···
1
1
package layouts
2
2
3
-
import "net/http"
4
-
import "github.com/puregarlic/space/storage"
3
+
import (
4
+
"github.com/puregarlic/space/html/components"
5
+
"net/http"
6
+
)
5
7
6
-
func RenderDefault(page templ.Component) http.Handler {
7
-
rels := storage.GetRels()
8
-
document := Default(page, rels)
8
+
func RenderDefault(title string, page templ.Component) http.Handler {
9
+
document := Default(title, page)
9
10
10
-
return templ.Handler(document)
11
+
return templ.Handler(document)
11
12
}
12
13
13
-
templ Default(body templ.Component, rels []*storage.RelEntry) {
14
-
<!DOCTYPE html>
15
-
<html>
16
-
<head>
17
-
<title>Micropub and IndieAuth Server Demo</title>
18
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
19
-
<link rel="preconnect" href="https://fonts.googleapis.com" />
20
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
21
-
<link href="https://fonts.googleapis.com/css2?family=Recursive:slnt,wght,CASL,CRSV,MONO@-15..0,300..1000,0..1,0..1,0..1&display=swap" rel="stylesheet">
22
-
23
-
for _, rel := range rels {
24
-
<link rel={ rel.Name } href={ rel.HREF } />
25
-
}
26
-
27
-
<link rel="stylesheet" href="/static/styles.css" />
28
-
</head>
29
-
<body class="text-text bg-base">
30
-
@body
31
-
</body>
32
-
</html>
14
+
templ Default(title string, body templ.Component) {
15
+
<!DOCTYPE html>
16
+
<html>
17
+
@components.Head(title)
18
+
<body class="px-4 py-12 md:py-20 text-text bg-base">
19
+
@body
20
+
</body>
21
+
</html>
33
22
}
+12
-39
html/layouts/default_templ.go
+12
-39
html/layouts/default_templ.go
···
8
8
import "github.com/a-h/templ"
9
9
import templruntime "github.com/a-h/templ/runtime"
10
10
11
-
import "net/http"
12
-
import "github.com/puregarlic/space/storage"
11
+
import (
12
+
"github.com/puregarlic/space/html/components"
13
+
"net/http"
14
+
)
13
15
14
-
func RenderDefault(page templ.Component) http.Handler {
15
-
rels := storage.GetRels()
16
-
document := Default(page, rels)
16
+
func RenderDefault(title string, page templ.Component) http.Handler {
17
+
document := Default(title, page)
17
18
18
19
return templ.Handler(document)
19
20
}
20
21
21
-
func Default(body templ.Component, rels []*storage.RelEntry) templ.Component {
22
+
func Default(title string, body templ.Component) templ.Component {
22
23
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
23
24
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
24
25
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
···
36
37
templ_7745c5c3_Var1 = templ.NopComponent
37
38
}
38
39
ctx = templ.ClearChildren(ctx)
39
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html><head><title>Micropub and IndieAuth Server Demo</title><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><link rel=\"preconnect\" href=\"https://fonts.googleapis.com\"><link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin><link href=\"https://fonts.googleapis.com/css2?family=Recursive:slnt,wght,CASL,CRSV,MONO@-15..0,300..1000,0..1,0..1,0..1&display=swap\" rel=\"stylesheet\">")
40
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html>")
40
41
if templ_7745c5c3_Err != nil {
41
42
return templ_7745c5c3_Err
42
43
}
43
-
for _, rel := range rels {
44
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<link rel=\"")
45
-
if templ_7745c5c3_Err != nil {
46
-
return templ_7745c5c3_Err
47
-
}
48
-
var templ_7745c5c3_Var2 string
49
-
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(rel.Name)
50
-
if templ_7745c5c3_Err != nil {
51
-
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/layouts/default.templ`, Line: 24, Col: 28}
52
-
}
53
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
54
-
if templ_7745c5c3_Err != nil {
55
-
return templ_7745c5c3_Err
56
-
}
57
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" href=\"")
58
-
if templ_7745c5c3_Err != nil {
59
-
return templ_7745c5c3_Err
60
-
}
61
-
var templ_7745c5c3_Var3 string
62
-
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(rel.HREF)
63
-
if templ_7745c5c3_Err != nil {
64
-
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/layouts/default.templ`, Line: 24, Col: 46}
65
-
}
66
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
67
-
if templ_7745c5c3_Err != nil {
68
-
return templ_7745c5c3_Err
69
-
}
70
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
71
-
if templ_7745c5c3_Err != nil {
72
-
return templ_7745c5c3_Err
73
-
}
44
+
templ_7745c5c3_Err = components.Head(title).Render(ctx, templ_7745c5c3_Buffer)
45
+
if templ_7745c5c3_Err != nil {
46
+
return templ_7745c5c3_Err
74
47
}
75
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<link rel=\"stylesheet\" href=\"/static/styles.css\"></head><body class=\"text-text bg-base\">")
48
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<body class=\"px-4 py-12 md:py-20 text-text bg-base\">")
76
49
if templ_7745c5c3_Err != nil {
77
50
return templ_7745c5c3_Err
78
51
}
+59
-52
html/pages/auth.templ
+59
-52
html/pages/auth.templ
···
1
1
package pages
2
2
3
-
import "strings"
3
+
import (
4
+
"go.hacdias.com/indielib/indieauth"
5
+
"strings"
6
+
)
4
7
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>
8
+
templ Auth(req *indieauth.AuthenticationRequest, app *indieauth.ApplicationMetadata, nonceId string, nonce string) {
9
+
<main class="mx-auto max-w-screen-sm">
10
+
<p class="text-sm font-thin italic">authorize access to</p>
11
+
<h1 class="mb-8 text-3xl font-extrabold">puregarlic dot space</h1>
12
+
<div class="pt-6 border border-highlightMed rounded bg-surface">
13
+
if app != nil {
14
+
<div class="px-6 flex gap-6 items-center">
15
+
if len(app.Logo) > 0 {
16
+
<img class="max-w-12" src={ app.Logo }/>
17
+
}
18
+
<div>
19
+
<h2 class="font-bold text-lg">{ app.Name }</h2>
20
+
if len(app.Author) > 0 {
21
+
<p class="text-sm font-light">by { app.Author }</p>
22
+
}
23
+
</div>
24
+
</div>
25
+
} else {
26
+
<h2 class="px-6 font-bold text-subtle">unidentified client</h2>
27
+
}
28
+
<div class="mt-6 grid md:grid-cols-[max-content_1fr] *:border-highlightMed">
29
+
<h3 class="px-6 pt-4 pb-2 md:py-4 border-t bg-overlay md:border-r text-subtle font-bold">Client ID</h3>
30
+
<p class="px-6 pb-4 pt-1 md:py-4 md:border-t bg-overlay min-w-0 overflow-x-scroll">{ req.ClientID }</p>
31
+
<h3 class="px-6 pt-4 pb-2 md:py-4 border-t md:border-b md:border-r text-subtle font-bold">Redirect URL</h3>
32
+
<p class="px-6 pb-4 pt-1 md:py-4 md:border-y min-w-0 overflow-x-scroll">{ req.RedirectURI }</p>
33
+
<h3 class="px-6 pt-4 pb-2 md:py-4 border-t bg-overlay md:border-r text-subtle font-bold">Scopes</h3>
34
+
<ul class="px-6 pb-4 pt-1 md:py-4 bg-overlay flex flex-wrap gap-3">
35
+
for _, scope := range req.Scopes {
36
+
<li class="px-2 py-1 text-sm bg-pine rounded">{ scope }</li>
37
+
}
38
+
</ul>
39
+
</div>
40
+
</div>
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
+
// CSRF protections
50
+
<input type="hidden" name="nonce_id" value={ nonceId }/>
51
+
<input type="hidden" name="nonce" value={ nonce }/>
52
+
<button
53
+
class="mt-8 px-3 py-2 flex items-center justify-center gap-2 bg-surface border border-highlightMed text-sm font-bold transition rounded hover:bg-foam hover:text-surface"
54
+
id="submit"
55
+
>
56
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4">
57
+
<path fill-rule="evenodd" d="M8 15A7 7 0 1 0 8 1a7 7 0 0 0 0 14Zm3.844-8.791a.75.75 0 0 0-1.188-.918l-3.7 4.79-1.649-1.833a.75.75 0 1 0-1.114 1.004l2.25 2.5a.75.75 0 0 0 1.15-.043l4.25-5.5Z" clip-rule="evenodd"></path>
58
+
</svg>
59
+
Authorize
60
+
</button>
61
+
</form>
62
+
</main>
56
63
}
+77
-36
html/pages/auth_templ.go
+77
-36
html/pages/auth_templ.go
···
8
8
import "github.com/a-h/templ"
9
9
import templruntime "github.com/a-h/templ/runtime"
10
10
11
-
import "strings"
11
+
import (
12
+
"go.hacdias.com/indielib/indieauth"
13
+
"strings"
14
+
)
12
15
13
-
import "go.hacdias.com/indielib/indieauth"
14
-
15
-
func Auth(req *indieauth.AuthenticationRequest, app *indieauth.ApplicationMetadata) templ.Component {
16
+
func Auth(req *indieauth.AuthenticationRequest, app *indieauth.ApplicationMetadata, nonceId string, nonce string) templ.Component {
16
17
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
17
18
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
18
19
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
···
30
31
templ_7745c5c3_Var1 = templ.NopComponent
31
32
}
32
33
ctx = templ.ClearChildren(ctx)
33
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html><head><title>Authorization | Micropub and IndieAuth Server Demo</title></head><body><h1>IndieAuth Server Demo: Authorization</h1><p>You received an authorization request from ")
34
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<main class=\"mx-auto max-w-screen-sm\"><p class=\"text-sm font-thin italic\">authorize access to</p><h1 class=\"mb-8 text-3xl font-extrabold\">puregarlic dot space</h1><div class=\"pt-6 border border-highlightMed rounded bg-surface\">")
34
35
if templ_7745c5c3_Err != nil {
35
36
return templ_7745c5c3_Err
36
37
}
37
38
if app != nil {
39
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"px-6 flex gap-6 items-center\">")
40
+
if templ_7745c5c3_Err != nil {
41
+
return templ_7745c5c3_Err
42
+
}
38
43
if len(app.Logo) > 0 {
39
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<img style=\"width: 1em; vertical-align: middle\" src=\"")
44
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<img class=\"max-w-12\" src=\"")
40
45
if templ_7745c5c3_Err != nil {
41
46
return templ_7745c5c3_Err
42
47
}
43
48
var templ_7745c5c3_Var2 string
44
49
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(app.Logo)
45
50
if templ_7745c5c3_Err != nil {
46
-
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/auth.templ`, Line: 21, Col: 72}
51
+
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/auth.templ`, Line: 16, Col: 42}
47
52
}
48
53
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
49
54
if templ_7745c5c3_Err != nil {
···
54
59
return templ_7745c5c3_Err
55
60
}
56
61
}
57
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <strong>")
62
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div><h2 class=\"font-bold text-lg\">")
58
63
if templ_7745c5c3_Err != nil {
59
64
return templ_7745c5c3_Err
60
65
}
61
66
var templ_7745c5c3_Var3 string
62
67
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(app.Name)
63
68
if templ_7745c5c3_Err != nil {
64
-
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/auth.templ`, Line: 24, Col: 26}
69
+
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/auth.templ`, Line: 19, Col: 46}
65
70
}
66
71
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
67
72
if templ_7745c5c3_Err != nil {
68
73
return templ_7745c5c3_Err
69
74
}
70
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</strong> by ")
75
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h2>")
71
76
if templ_7745c5c3_Err != nil {
72
77
return templ_7745c5c3_Err
73
78
}
74
-
var templ_7745c5c3_Var4 string
75
-
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(app.Author)
76
-
if templ_7745c5c3_Err != nil {
77
-
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/auth.templ`, Line: 24, Col: 53}
78
-
}
79
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
80
-
if templ_7745c5c3_Err != nil {
81
-
return templ_7745c5c3_Err
79
+
if len(app.Author) > 0 {
80
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<p class=\"text-sm font-light\">by ")
81
+
if templ_7745c5c3_Err != nil {
82
+
return templ_7745c5c3_Err
83
+
}
84
+
var templ_7745c5c3_Var4 string
85
+
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(app.Author)
86
+
if templ_7745c5c3_Err != nil {
87
+
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/auth.templ`, Line: 21, Col: 52}
88
+
}
89
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
90
+
if templ_7745c5c3_Err != nil {
91
+
return templ_7745c5c3_Err
92
+
}
93
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p>")
94
+
if templ_7745c5c3_Err != nil {
95
+
return templ_7745c5c3_Err
96
+
}
82
97
}
83
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(":")
98
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div>")
84
99
if templ_7745c5c3_Err != nil {
85
100
return templ_7745c5c3_Err
86
101
}
87
102
} else {
88
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("the following client:")
103
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<h2 class=\"px-6 font-bold text-subtle\">unidentified client</h2>")
89
104
if templ_7745c5c3_Err != nil {
90
105
return templ_7745c5c3_Err
91
106
}
92
107
}
93
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p><ul><li><strong>Redirect:</strong> <code>")
108
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"mt-6 grid md:grid-cols-[max-content_1fr] *:border-highlightMed\"><h3 class=\"px-6 pt-4 pb-2 md:py-4 border-t bg-overlay md:border-r text-subtle font-bold\">Client ID</h3><p class=\"px-6 pb-4 pt-1 md:py-4 md:border-t bg-overlay min-w-0 overflow-x-scroll\">")
94
109
if templ_7745c5c3_Err != nil {
95
110
return templ_7745c5c3_Err
96
111
}
97
112
var templ_7745c5c3_Var5 string
98
113
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(req.ClientID)
99
114
if templ_7745c5c3_Err != nil {
100
-
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/auth.templ`, Line: 31, Col: 57}
115
+
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/auth.templ`, Line: 30, Col: 101}
101
116
}
102
117
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
103
118
if templ_7745c5c3_Err != nil {
104
119
return templ_7745c5c3_Err
105
120
}
106
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</code></li><li><strong>Client:</strong> <code>")
121
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p><h3 class=\"px-6 pt-4 pb-2 md:py-4 border-t md:border-b md:border-r text-subtle font-bold\">Redirect URL</h3><p class=\"px-6 pb-4 pt-1 md:py-4 md:border-y min-w-0 overflow-x-scroll\">")
107
122
if templ_7745c5c3_Err != nil {
108
123
return templ_7745c5c3_Err
109
124
}
110
125
var templ_7745c5c3_Var6 string
111
126
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(req.RedirectURI)
112
127
if templ_7745c5c3_Err != nil {
113
-
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/auth.templ`, Line: 32, Col: 58}
128
+
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/auth.templ`, Line: 32, Col: 93}
114
129
}
115
130
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
116
131
if templ_7745c5c3_Err != nil {
117
132
return templ_7745c5c3_Err
118
133
}
119
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</code></li></ul><p>For the following scopes: ")
134
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p><h3 class=\"px-6 pt-4 pb-2 md:py-4 border-t bg-overlay md:border-r text-subtle font-bold\">Scopes</h3><ul class=\"px-6 pb-4 pt-1 md:py-4 bg-overlay flex flex-wrap gap-3\">")
120
135
if templ_7745c5c3_Err != nil {
121
136
return templ_7745c5c3_Err
122
137
}
123
138
for _, scope := range req.Scopes {
124
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<code>")
139
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li class=\"px-2 py-1 text-sm bg-pine rounded\">")
125
140
if templ_7745c5c3_Err != nil {
126
141
return templ_7745c5c3_Err
127
142
}
128
143
var templ_7745c5c3_Var7 string
129
144
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(scope)
130
145
if templ_7745c5c3_Err != nil {
131
-
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/auth.templ`, Line: 37, Col: 21}
146
+
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/auth.templ`, Line: 36, Col: 59}
132
147
}
133
148
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
134
149
if templ_7745c5c3_Err != nil {
135
150
return templ_7745c5c3_Err
136
151
}
137
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</code> ")
152
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</li>")
138
153
if templ_7745c5c3_Err != nil {
139
154
return templ_7745c5c3_Err
140
155
}
141
156
}
142
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(".</p><form method=\"post\" action=\"/authorization/accept\"><input type=\"hidden\" name=\"response_type\" value=\"code\"> <input type=\"hidden\" name=\"scope\" value=\"")
157
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</ul></div></div><form method=\"post\" action=\"/authorization/accept\"><input type=\"hidden\" name=\"response_type\" value=\"code\"> <input type=\"hidden\" name=\"scope\" value=\"")
143
158
if templ_7745c5c3_Err != nil {
144
159
return templ_7745c5c3_Err
145
160
}
146
161
var templ_7745c5c3_Var8 string
147
162
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(strings.Join(req.Scopes, " "))
148
163
if templ_7745c5c3_Err != nil {
149
-
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/auth.templ`, Line: 43, Col: 77}
164
+
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/auth.templ`, Line: 43, Col: 74}
150
165
}
151
166
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
152
167
if templ_7745c5c3_Err != nil {
···
159
174
var templ_7745c5c3_Var9 string
160
175
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(req.RedirectURI)
161
176
if templ_7745c5c3_Err != nil {
162
-
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/auth.templ`, Line: 44, Col: 70}
177
+
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/auth.templ`, Line: 44, Col: 67}
163
178
}
164
179
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
165
180
if templ_7745c5c3_Err != nil {
···
172
187
var templ_7745c5c3_Var10 string
173
188
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(req.ClientID)
174
189
if templ_7745c5c3_Err != nil {
175
-
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/auth.templ`, Line: 45, Col: 64}
190
+
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/auth.templ`, Line: 45, Col: 61}
176
191
}
177
192
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
178
193
if templ_7745c5c3_Err != nil {
···
185
200
var templ_7745c5c3_Var11 string
186
201
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(req.State)
187
202
if templ_7745c5c3_Err != nil {
188
-
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/auth.templ`, Line: 46, Col: 57}
203
+
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/auth.templ`, Line: 46, Col: 54}
189
204
}
190
205
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
191
206
if templ_7745c5c3_Err != nil {
···
198
213
var templ_7745c5c3_Var12 string
199
214
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(req.CodeChallenge)
200
215
if templ_7745c5c3_Err != nil {
201
-
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/auth.templ`, Line: 47, Col: 74}
216
+
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/auth.templ`, Line: 47, Col: 71}
202
217
}
203
218
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
204
219
if templ_7745c5c3_Err != nil {
···
211
226
var templ_7745c5c3_Var13 string
212
227
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(req.CodeChallengeMethod)
213
228
if templ_7745c5c3_Err != nil {
214
-
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/auth.templ`, Line: 48, Col: 87}
229
+
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/auth.templ`, Line: 48, Col: 84}
215
230
}
216
231
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
217
232
if templ_7745c5c3_Err != nil {
218
233
return templ_7745c5c3_Err
219
234
}
220
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><p>In a production server, this page could be behind some sort of authentication mechanism, such as username and password, PassKey, etc.</p><button id=\"submit\">Authorize</button></form></body></html>")
235
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><input type=\"hidden\" name=\"nonce_id\" value=\"")
236
+
if templ_7745c5c3_Err != nil {
237
+
return templ_7745c5c3_Err
238
+
}
239
+
var templ_7745c5c3_Var14 string
240
+
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(nonceId)
241
+
if templ_7745c5c3_Err != nil {
242
+
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/auth.templ`, Line: 50, Col: 55}
243
+
}
244
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
245
+
if templ_7745c5c3_Err != nil {
246
+
return templ_7745c5c3_Err
247
+
}
248
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"> <input type=\"hidden\" name=\"nonce\" value=\"")
249
+
if templ_7745c5c3_Err != nil {
250
+
return templ_7745c5c3_Err
251
+
}
252
+
var templ_7745c5c3_Var15 string
253
+
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(nonce)
254
+
if templ_7745c5c3_Err != nil {
255
+
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/auth.templ`, Line: 51, Col: 50}
256
+
}
257
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
258
+
if templ_7745c5c3_Err != nil {
259
+
return templ_7745c5c3_Err
260
+
}
261
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"> <button class=\"mt-8 px-3 py-2 flex items-center justify-center gap-2 bg-surface border border-highlightMed text-sm font-bold transition rounded hover:bg-foam hover:text-surface\" id=\"submit\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" fill=\"currentColor\" class=\"size-4\"><path fill-rule=\"evenodd\" d=\"M8 15A7 7 0 1 0 8 1a7 7 0 0 0 0 14Zm3.844-8.791a.75.75 0 0 0-1.188-.918l-3.7 4.79-1.649-1.833a.75.75 0 1 0-1.114 1.004l2.25 2.5a.75.75 0 0 0 1.15-.043l4.25-5.5Z\" clip-rule=\"evenodd\"></path></svg> Authorize</button></form></main>")
221
262
if templ_7745c5c3_Err != nil {
222
263
return templ_7745c5c3_Err
223
264
}
+16
-30
html/pages/home.templ
+16
-30
html/pages/home.templ
···
1
1
package pages
2
2
3
3
import (
4
-
"github.com/puregarlic/space/models"
5
-
p "github.com/puregarlic/space/html/components/posts"
4
+
p "github.com/puregarlic/space/html/components/posts"
5
+
"github.com/puregarlic/space/models"
6
6
)
7
7
8
8
templ Home(posts []*models.Post) {
9
-
<div class="px-4 py-12 md:py-20 md:mx-auto md:max-w-screen-lg grid md:grid-cols-[1fr_2fr] gap-12 md:gap-16">
10
-
<aside class="min-w-0">
11
-
<div class="w-full md:sticky md:top-8">
12
-
<h1 class="font-extrabold text-xl">puregarlic dot space</h1>
13
-
<p class="font-light mt-3 text-subtle italic md:text-sm">
14
-
this space is mine, it was <a class="underline hover:text-iris" href="https://github.com/puregarlic/space">made by me</a>!
15
-
</p>
16
-
</div>
17
-
</aside>
18
-
<main class="min-w-0">
19
-
<ul class="flex flex-col gap-6 md:gap-12">
20
-
if len(posts) > 0 {
21
-
for _, post := range posts {
22
-
<li class="flex flex-col divide-y-2 divide-highlightLow group border-2 border-highlightLow">
23
-
@p.PostContent(post)
24
-
@p.PostFeedHeader(post)
25
-
</li>
26
-
}
27
-
} else {
28
-
<li class="text-muted bg-surface px-4 py-8 text-center border border-highlightLow">
29
-
intention-rich, content-poor
30
-
</li>
31
-
}
32
-
</ul>
33
-
</main>
34
-
</div>
35
-
9
+
<ul class="flex flex-col gap-6">
10
+
if len(posts) > 0 {
11
+
for _, post := range posts {
12
+
<li class="flex flex-col">
13
+
@p.PostFeedHeader(post)
14
+
@p.PostContent(post)
15
+
</li>
16
+
}
17
+
} else {
18
+
<li class="text-muted bg-surface px-4 py-8 text-center border border-overlay">
19
+
intention-rich, content-poor
20
+
</li>
21
+
}
22
+
</ul>
36
23
}
37
-
+6
-6
html/pages/home_templ.go
+6
-6
html/pages/home_templ.go
···
31
31
templ_7745c5c3_Var1 = templ.NopComponent
32
32
}
33
33
ctx = templ.ClearChildren(ctx)
34
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"px-4 py-12 md:py-20 md:mx-auto md:max-w-screen-lg grid md:grid-cols-[1fr_2fr] gap-12 md:gap-16\"><aside class=\"min-w-0\"><div class=\"w-full md:sticky md:top-8\"><h1 class=\"font-extrabold text-xl\">puregarlic dot space</h1><p class=\"font-light mt-3 text-subtle italic md:text-sm\">this space is mine, it was <a class=\"underline hover:text-iris\" href=\"https://github.com/puregarlic/space\">made by me</a>!</p></div></aside><main class=\"min-w-0\"><ul class=\"flex flex-col gap-6 md:gap-12\">")
34
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<ul class=\"flex flex-col gap-6\">")
35
35
if templ_7745c5c3_Err != nil {
36
36
return templ_7745c5c3_Err
37
37
}
38
38
if len(posts) > 0 {
39
39
for _, post := range posts {
40
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li class=\"flex flex-col divide-y-2 divide-highlightLow group border-2 border-highlightLow\">")
40
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li class=\"flex flex-col\">")
41
41
if templ_7745c5c3_Err != nil {
42
42
return templ_7745c5c3_Err
43
43
}
44
-
templ_7745c5c3_Err = p.PostContent(post).Render(ctx, templ_7745c5c3_Buffer)
44
+
templ_7745c5c3_Err = p.PostFeedHeader(post).Render(ctx, templ_7745c5c3_Buffer)
45
45
if templ_7745c5c3_Err != nil {
46
46
return templ_7745c5c3_Err
47
47
}
48
-
templ_7745c5c3_Err = p.PostFeedHeader(post).Render(ctx, templ_7745c5c3_Buffer)
48
+
templ_7745c5c3_Err = p.PostContent(post).Render(ctx, templ_7745c5c3_Buffer)
49
49
if templ_7745c5c3_Err != nil {
50
50
return templ_7745c5c3_Err
51
51
}
···
55
55
}
56
56
}
57
57
} else {
58
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li class=\"text-muted bg-surface px-4 py-8 text-center border border-highlightLow\">intention-rich, content-poor</li>")
58
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li class=\"text-muted bg-surface px-4 py-8 text-center border border-overlay\">intention-rich, content-poor</li>")
59
59
if templ_7745c5c3_Err != nil {
60
60
return templ_7745c5c3_Err
61
61
}
62
62
}
63
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</ul></main></div>")
63
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</ul>")
64
64
if templ_7745c5c3_Err != nil {
65
65
return templ_7745c5c3_Err
66
66
}
+22
-56
html/pages/post.templ
+22
-56
html/pages/post.templ
···
1
1
package pages
2
2
3
-
import "github.com/puregarlic/space/models"
4
-
import "fmt"
5
-
import "encoding/json"
6
-
import "reflect"
7
-
8
-
func printProperty(post *models.Post, name string) string {
9
-
props := make(map[string]any)
10
-
if err := json.Unmarshal(post.Properties, &props); err != nil {
11
-
panic(err)
12
-
}
13
-
14
-
if val, ok := props[name]; ok {
15
-
tp := reflect.TypeOf(val)
16
-
switch tp.Kind() {
17
-
default:
18
-
return fmt.Sprint(val)
19
-
case reflect.Slice:
20
-
str := ""
21
-
for _, v := range val.([]any) {
22
-
str = str + fmt.Sprint(v)
23
-
}
24
-
25
-
return str
26
-
}
27
-
28
-
}
29
-
30
-
return "<no name provided>"
31
-
}
32
-
33
-
func printPost(post *models.Post) string {
34
-
out, err := json.Marshal(post)
35
-
36
-
if (err != nil) {
37
-
panic (err)
38
-
}
39
-
40
-
return fmt.Sprint(string(out))
41
-
}
3
+
import (
4
+
"github.com/puregarlic/space/html/components/posts"
5
+
"github.com/puregarlic/space/models"
6
+
)
42
7
43
8
templ Post(post *models.Post) {
44
-
<!DOCTYPE html>
45
-
<html>
46
-
<head>
47
-
<title>Post | Micropub and IndieAuth Server Demo</title>
48
-
</head>
49
-
<body>
50
-
<div class={ post.Type }>
51
-
<h1 class="p-name">{ printProperty(post, "name") }</h1>
52
-
<p class="p-content">{ printProperty(post, "content") }</p>
53
-
54
-
<h3>Stored Microformats</h3>
55
-
<code>
56
-
<pre>{ printPost(post) }</pre>
57
-
</code>
58
-
</div>
59
-
</body>
60
-
</html>
9
+
<div class="flex flex-col gap-8">
10
+
<a href="/" class="text-sm text-muted flex items-center gap-1">
11
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4">
12
+
<path fill-rule="evenodd" d="M14 8a.75.75 0 0 1-.75.75H4.56l1.22 1.22a.75.75 0 1 1-1.06 1.06l-2.5-2.5a.75.75 0 0 1 0-1.06l2.5-2.5a.75.75 0 0 1 1.06 1.06L4.56 7.25h8.69A.75.75 0 0 1 14 8Z" clip-rule="evenodd"></path>
13
+
</svg>
14
+
back to home
15
+
</a>
16
+
<div>
17
+
@posts.PostContent(post)
18
+
</div>
19
+
@posts.PostDetails(post)
20
+
<div class="py-12 flex flex-col gap-1 items-center text-muted text-xs font-light">
21
+
interactions not implemented yet
22
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4">
23
+
<path fill-rule="evenodd" d="M15 8A7 7 0 1 1 1 8a7 7 0 0 1 14 0ZM6 8c.552 0 1-.672 1-1.5S6.552 5 6 5s-1 .672-1 1.5S5.448 8 6 8Zm5-1.5c0 .828-.448 1.5-1 1.5s-1-.672-1-1.5S9.448 5 10 5s1 .672 1 1.5Zm-6.005 5.805a.75.75 0 0 0 1.06 0 2.75 2.75 0 0 1 3.89 0 .75.75 0 0 0 1.06-1.06 4.25 4.25 0 0 0-6.01 0 .75.75 0 0 0 0 1.06Z" clip-rule="evenodd"></path>
24
+
</svg>
25
+
</div>
26
+
</div>
61
27
}
+9
-89
html/pages/post_templ.go
+9
-89
html/pages/post_templ.go
···
8
8
import "github.com/a-h/templ"
9
9
import templruntime "github.com/a-h/templ/runtime"
10
10
11
-
import "github.com/puregarlic/space/models"
12
-
import "fmt"
13
-
import "encoding/json"
14
-
import "reflect"
15
-
16
-
func printProperty(post *models.Post, name string) string {
17
-
props := make(map[string]any)
18
-
if err := json.Unmarshal(post.Properties, &props); err != nil {
19
-
panic(err)
20
-
}
21
-
22
-
if val, ok := props[name]; ok {
23
-
tp := reflect.TypeOf(val)
24
-
switch tp.Kind() {
25
-
default:
26
-
return fmt.Sprint(val)
27
-
case reflect.Slice:
28
-
str := ""
29
-
for _, v := range val.([]any) {
30
-
str = str + fmt.Sprint(v)
31
-
}
32
-
33
-
return str
34
-
}
35
-
36
-
}
37
-
38
-
return "<no name provided>"
39
-
}
40
-
41
-
func printPost(post *models.Post) string {
42
-
out, err := json.Marshal(post)
43
-
44
-
if err != nil {
45
-
panic(err)
46
-
}
47
-
48
-
return fmt.Sprint(string(out))
49
-
}
11
+
import (
12
+
"github.com/puregarlic/space/html/components/posts"
13
+
"github.com/puregarlic/space/models"
14
+
)
50
15
51
16
func Post(post *models.Post) templ.Component {
52
17
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
···
66
31
templ_7745c5c3_Var1 = templ.NopComponent
67
32
}
68
33
ctx = templ.ClearChildren(ctx)
69
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html><head><title>Post | Micropub and IndieAuth Server Demo</title></head><body>")
70
-
if templ_7745c5c3_Err != nil {
71
-
return templ_7745c5c3_Err
72
-
}
73
-
var templ_7745c5c3_Var2 = []any{post.Type}
74
-
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
34
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"flex flex-col gap-8\"><a href=\"/\" class=\"text-sm text-muted flex items-center gap-1\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" fill=\"currentColor\" class=\"size-4\"><path fill-rule=\"evenodd\" d=\"M14 8a.75.75 0 0 1-.75.75H4.56l1.22 1.22a.75.75 0 1 1-1.06 1.06l-2.5-2.5a.75.75 0 0 1 0-1.06l2.5-2.5a.75.75 0 0 1 1.06 1.06L4.56 7.25h8.69A.75.75 0 0 1 14 8Z\" clip-rule=\"evenodd\"></path></svg> back to home</a><div>")
75
35
if templ_7745c5c3_Err != nil {
76
36
return templ_7745c5c3_Err
77
37
}
78
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
38
+
templ_7745c5c3_Err = posts.PostContent(post).Render(ctx, templ_7745c5c3_Buffer)
79
39
if templ_7745c5c3_Err != nil {
80
40
return templ_7745c5c3_Err
81
41
}
82
-
var templ_7745c5c3_Var3 string
83
-
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var2).String())
84
-
if templ_7745c5c3_Err != nil {
85
-
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/post.templ`, Line: 1, Col: 0}
86
-
}
87
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
42
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
88
43
if templ_7745c5c3_Err != nil {
89
44
return templ_7745c5c3_Err
90
45
}
91
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><h1 class=\"p-name\">")
46
+
templ_7745c5c3_Err = posts.PostDetails(post).Render(ctx, templ_7745c5c3_Buffer)
92
47
if templ_7745c5c3_Err != nil {
93
48
return templ_7745c5c3_Err
94
49
}
95
-
var templ_7745c5c3_Var4 string
96
-
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(printProperty(post, "name"))
97
-
if templ_7745c5c3_Err != nil {
98
-
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/post.templ`, Line: 51, Col: 56}
99
-
}
100
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
101
-
if templ_7745c5c3_Err != nil {
102
-
return templ_7745c5c3_Err
103
-
}
104
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h1><p class=\"p-content\">")
105
-
if templ_7745c5c3_Err != nil {
106
-
return templ_7745c5c3_Err
107
-
}
108
-
var templ_7745c5c3_Var5 string
109
-
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(printProperty(post, "content"))
110
-
if templ_7745c5c3_Err != nil {
111
-
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/post.templ`, Line: 52, Col: 61}
112
-
}
113
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
114
-
if templ_7745c5c3_Err != nil {
115
-
return templ_7745c5c3_Err
116
-
}
117
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p><h3>Stored Microformats</h3><code><pre>")
118
-
if templ_7745c5c3_Err != nil {
119
-
return templ_7745c5c3_Err
120
-
}
121
-
var templ_7745c5c3_Var6 string
122
-
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(printPost(post))
123
-
if templ_7745c5c3_Err != nil {
124
-
return templ.Error{Err: templ_7745c5c3_Err, FileName: `html/pages/post.templ`, Line: 56, Col: 32}
125
-
}
126
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
127
-
if templ_7745c5c3_Err != nil {
128
-
return templ_7745c5c3_Err
129
-
}
130
-
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</pre></code></div></body></html>")
50
+
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"py-12 flex flex-col gap-1 items-center text-muted text-xs font-light\">interactions not implemented yet <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" fill=\"currentColor\" class=\"size-4\"><path fill-rule=\"evenodd\" d=\"M15 8A7 7 0 1 1 1 8a7 7 0 0 1 14 0ZM6 8c.552 0 1-.672 1-1.5S6.552 5 6 5s-1 .672-1 1.5S5.448 8 6 8Zm5-1.5c0 .828-.448 1.5-1 1.5s-1-.672-1-1.5S9.448 5 10 5s1 .672 1 1.5Zm-6.005 5.805a.75.75 0 0 0 1.06 0 2.75 2.75 0 0 1 3.89 0 .75.75 0 0 0 1.06-1.06 4.25 4.25 0 0 0-6.01 0 .75.75 0 0 0 0 1.06Z\" clip-rule=\"evenodd\"></path></svg></div></div>")
131
51
if templ_7745c5c3_Err != nil {
132
52
return templ_7745c5c3_Err
133
53
}
+1
-1
main.go
+1
-1
main.go
+4
models/post.go
+4
models/post.go
+14
-29
services/indieauth.go
+14
-29
services/indieauth.go
···
26
26
Server *indieauth.Server
27
27
}
28
28
29
-
// storeAuthorization stores the authorization request and returns a code for it.
30
-
// Something such as JWT tokens could be used in a production environment.
31
29
func (i *IndieAuth) storeAuthorization(req *indieauth.AuthenticationRequest) string {
32
30
code := nanoid.New()
33
31
···
47
45
scopesContextKey contextKey = "scopes"
48
46
)
49
47
50
-
// HandleAuthGET handles the GET method for the authorization endpoint.
51
48
func (i *IndieAuth) HandleAuthGET(w http.ResponseWriter, r *http.Request) {
52
-
// In a production server, this page would usually be protected. In order for
53
-
// the user to authorize this request, they must be authenticated. This could
54
-
// be done in different ways: username/password, passkeys, etc.
55
-
56
-
// Parse the authorization request.
57
49
req, err := i.Server.ParseAuthorization(r)
58
50
if err != nil {
59
51
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
60
52
return
61
53
}
62
54
63
-
// Do a best effort attempt at fetching more information about the application
64
-
// that we can show to the user. Not all applications provide this sort of
65
-
// information.
66
55
app, _ := i.Server.DiscoverApplicationMetadata(r.Context(), req.ClientID)
67
56
68
-
// Here, we just display a small HTML document where the user has to press
69
-
// to authorize this request. Please note that this template contains a form
70
-
// where we dump all the request information. This makes it possible to reuse
71
-
// [indieauth.Server.ParseAuthorization] when the user authorizes the request.
72
-
layouts.RenderDefault(pages.Auth(req, app)).ServeHTTP(w, r)
57
+
nonceId, nonce := nanoid.New(), nanoid.New()
58
+
storage.NonceCache().Set(nonceId, nonce, 0)
59
+
60
+
layouts.RenderDefault("authorize", pages.Auth(req, app, nonceId, nonce)).ServeHTTP(w, r)
73
61
}
74
62
75
-
// HandleAuthPOST handles the POST method for the authorization endpoint.
76
63
func (i *IndieAuth) HandleAuthPOST(w http.ResponseWriter, r *http.Request) {
77
64
i.authorizationCodeExchange(w, r, false)
78
65
}
79
66
80
-
// HandleToken handles the token endpoint. In our case, we only accept the default
81
-
// type which is exchanging an authorization code for a token.
82
67
func (i *IndieAuth) HandleToken(w http.ResponseWriter, r *http.Request) {
83
68
if r.Method != http.MethodPost {
84
69
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
···
103
88
ExpiresIn int64 `json:"expires_in,omitempty"`
104
89
}
105
90
106
-
// authorizationCodeExchange handles the authorization code exchange. It is used by
107
-
// both the authorization handler to exchange the code for the user's profile URL,
108
-
// and by the token endpoint, to exchange the code by a token.
109
91
func (i *IndieAuth) authorizationCodeExchange(w http.ResponseWriter, r *http.Request, withToken bool) {
110
92
if err := r.ParseForm(); err != nil {
111
93
SendErrorJSON(w, http.StatusBadRequest, "invalid_request", err.Error())
···
163
145
}
164
146
165
147
func (i *IndieAuth) HandleAuthApproval(w http.ResponseWriter, r *http.Request) {
166
-
// Parse authorization information. This only works because our authorization page
167
-
// includes all the required information. This can be done in other ways: database,
168
-
// whether temporary or not, cookies, etc.
148
+
id := r.FormValue("nonce_id")
149
+
nonce := r.FormValue("nonce")
150
+
151
+
stored, ok := storage.NonceCache().GetAndDelete(id)
152
+
if !ok {
153
+
SendErrorJSON(w, http.StatusBadRequest, "bad_request", "nonce does not match")
154
+
} else if stored.Value() != nonce {
155
+
SendErrorJSON(w, http.StatusBadRequest, "bad_request", "nonce does not match")
156
+
}
157
+
169
158
req, err := i.Server.ParseAuthorization(r)
170
159
if err != nil {
171
160
SendErrorJSON(w, http.StatusBadRequest, "invalid_request", err.Error())
172
161
return
173
162
}
174
163
175
-
// Generate a random code and persist the information associated to that code.
176
-
// You could do this in other ways: database, or JWT tokens, or both, for example.
177
164
code := i.storeAuthorization(req)
178
165
179
166
// Redirect to client callback.
···
183
170
http.Redirect(w, r, req.RedirectURI+"?"+query.Encode(), http.StatusFound)
184
171
}
185
172
186
-
// MustAuth is a middleware to ensure that the request is authorized. The way this
187
-
// works depends on the implementation. It then stores the scopes in the context.
188
173
func MustAuth(next http.Handler) http.Handler {
189
174
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
190
175
tokenStr := r.Header.Get("Authorization")
+18
-1
storage/cache.go
+18
-1
storage/cache.go
···
8
8
)
9
9
10
10
var authCache *ttlcache.Cache[string, *indieauth.AuthenticationRequest]
11
+
var nonceCache *ttlcache.Cache[string, string]
11
12
12
-
func CleanupAuthCache() {
13
+
func CleanupCaches() {
13
14
AuthCache().Stop()
14
15
}
15
16
···
28
29
29
30
return cache
30
31
}
32
+
33
+
func NonceCache() *ttlcache.Cache[string, string] {
34
+
if nonceCache != nil {
35
+
return nonceCache
36
+
}
37
+
38
+
cache := ttlcache.New(
39
+
ttlcache.WithTTL[string, string](5 * time.Minute),
40
+
)
41
+
42
+
go cache.Start()
43
+
44
+
nonceCache = cache
45
+
46
+
return cache
47
+
}