-
Notifications
You must be signed in to change notification settings - Fork 2
/
api.go
160 lines (130 loc) · 3.88 KB
/
api.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package api
import (
"log/slog"
"net/http"
"os"
"regexp"
"github.com/a-h/templ"
"github.com/emarifer/gocms/internal/service"
"github.com/emarifer/gocms/settings"
"github.com/gin-contrib/gzip"
"github.com/gin-gonic/gin"
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/html"
"github.com/gomarkdown/markdown/parser"
)
type API struct {
serv service.Service
logger *slog.Logger
settings *settings.AppSettings
}
func New(
serv service.Service, logger *slog.Logger, settings *settings.AppSettings,
) *API {
return &API{
serv: serv,
logger: logger,
settings: settings,
}
}
type generator = func(*gin.Context) ([]byte, *customError)
var re = regexp.MustCompile(`Table|refused`)
func (a *API) Start(
e *gin.Engine, address string, cache *Cache,
) (*gin.Engine, error) {
e.Use(gzip.Gzip(gzip.DefaultCompression)) // gzip compression middleware
e.Use(a.globalErrorHandler()) // Error handler middleware
e.MaxMultipartMemory = 1 // 8 MiB max. request
e.Static("/assets", "./assets")
e.Static("/media", a.settings.ImageDirectory)
// e.LoadHTMLGlob("views/**/*") // Used for Go Html templates
a.registerRoutes(e, cache)
// SEE NOTE BELOW (this is hacky):
if os.Getenv("GO_ENV") == "testing" {
return e, nil
} else {
return nil, e.Run(address)
}
}
func (a *API) registerRoutes(e *gin.Engine, cache *Cache) {
// ↓ injected into the Start function ↓
// logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
// cache := MakeCache(4, time.Minute*1)
// All cache-able endpoints
a.addCachableHandler(e, "GET", "/", a.homeHandler, cache)
a.addCachableHandler(e, "GET", "/post/:id", a.postHandler, cache)
a.addCachableHandler(e, "GET", "/contact", a.contactHandler, cache)
// Do not cache as it needs to handle new form values
e.POST("/contact-send", a.contactFormHandler)
}
// mdToHTML converts markdown to HTML
func (a *API) mdToHTML(md []byte) []byte {
// create markdown parser with extensions
extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock
p := parser.NewWithExtensions(extensions)
doc := p.Parse(md)
// create HTML renderer with extensions
htmlFlags := html.CommonFlags | html.HrefTargetBlank
opts := html.RendererOptions{Flags: htmlFlags}
renderer := html.NewRenderer(opts)
return markdown.Render(doc, renderer)
}
// renderView will render the templ component into
// a gin context's Response Writer
func (a *API) renderView(
c *gin.Context, status int, cmp templ.Component,
) error {
c.Status(status)
return cmp.Render(c.Request.Context(), c.Writer)
}
func (a *API) addCachableHandler(
e *gin.Engine, method, endpoint string, gen generator, cache *Cache,
) {
handler := func(c *gin.Context) {
var errCache error
// if endpoint is cached, get it from cache
cachedEndpoint, errCache := (*cache).Get(c.Request.RequestURI)
if errCache == nil {
c.Data(
http.StatusOK,
"text/html; charset=utf-8",
cachedEndpoint.Contents,
)
return
} else {
a.logger.Info(
"cache info",
slog.String("could not get page from cache", errCache.Error()),
)
}
// If the endpoint data is not recovered, the handler (gen) is called
html_buffer, err := gen(c)
if err != nil {
c.Error(err)
return
}
// After handler call, add to cache
errCache = (*cache).Store(c.Request.RequestURI, html_buffer)
if errCache != nil {
a.logger.Warn(
"cache warning",
slog.String("could not add page to cache", errCache.Error()),
)
}
c.Data(http.StatusOK, "text/html; charset=utf-8", html_buffer)
}
// Hacky
switch method {
case "GET":
e.GET(endpoint, handler)
case "POST":
e.POST(endpoint, handler)
case "DELETE":
e.DELETE(endpoint, handler)
case "PUT":
e.PUT(endpoint, handler)
}
}
/* HOW DO I KNOW I'M RUNNING WITHIN "GO TEST". SEE:
https://stackoverflow.com/questions/14249217/how-do-i-know-im-running-within-go-test#59444829
*/