apps/posts/app.go 5.6 K raw
1
package main
2
3
import (
4
	"database/sql"
5
	"embed"
6
	"html/template"
7
	"log/slog"
8
	"strings"
9
10
	poststorage "github.com/stevedylandev/andromeda/apps/posts/storage"
11
	"github.com/stevedylandev/andromeda/pkg/auth"
12
)
13
14
//go:embed templates/*.html static/*
15
var appFS embed.FS
16
17
type App struct {
18
	DB           *sql.DB
19
	Log          *slog.Logger
20
	Templates    map[string]*template.Template
21
	Sessions     *auth.Store
22
	AppPassword  string
23
	CookieSecure bool
24
	UploadsDir   string
25
	Storage      poststorage.Backend
26
	SiteURL      string
27
}
28
29
type Post struct {
30
	ID              int64
31
	ShortID         string
32
	Title           *string
33
	Slug            string
34
	Alias           *string
35
	CanonicalURL    *string
36
	PublishedDate   *string
37
	MetaDescription *string
38
	MetaImage       *string
39
	Lang            string
40
	Tags            *string
41
	Weather         *string
42
	Content         string
43
	Status          string
44
	CreatedAt       string
45
	UpdatedAt       string
46
}
47
48
func (p Post) DisplayTitle() string {
49
	if p.Title != nil {
50
		if t := strings.TrimSpace(*p.Title); t != "" {
51
			return t
52
		}
53
	}
54
	body := strings.ReplaceAll(strings.ReplaceAll(p.Content, "\n", ""), "\r", "")
55
	runes := []rune(body)
56
	n := 25
57
	if len(runes) < n {
58
		n = len(runes)
59
	}
60
	snip := strings.TrimSpace(string(runes[:n]))
61
	if snip == "" {
62
		return "Untitled"
63
	}
64
	if len([]rune(p.Content)) > 60 {
65
		return snip + "…"
66
	}
67
	return snip
68
}
69
70
func (p Post) TitleStr() string {
71
	if p.Title != nil {
72
		return *p.Title
73
	}
74
	return ""
75
}
76
77
func (p Post) HasTitle() bool {
78
	return p.Title != nil && strings.TrimSpace(*p.Title) != ""
79
}
80
81
func (p Post) PublishedDateStr() string {
82
	if p.PublishedDate != nil {
83
		return *p.PublishedDate
84
	}
85
	return ""
86
}
87
88
func (p Post) AliasStr() string {
89
	if p.Alias != nil {
90
		return *p.Alias
91
	}
92
	return ""
93
}
94
95
func (p Post) MetaDescriptionStr() string {
96
	if p.MetaDescription != nil {
97
		return *p.MetaDescription
98
	}
99
	return ""
100
}
101
102
func (p Post) MetaImageStr() string {
103
	if p.MetaImage != nil {
104
		return *p.MetaImage
105
	}
106
	return ""
107
}
108
109
func (p Post) CanonicalURLStr() string {
110
	if p.CanonicalURL != nil {
111
		return *p.CanonicalURL
112
	}
113
	return ""
114
}
115
116
func (p Post) TagsStr() string {
117
	if p.Tags != nil {
118
		return *p.Tags
119
	}
120
	return ""
121
}
122
123
func (p Post) TagList() []string {
124
	if p.Tags == nil {
125
		return nil
126
	}
127
	var out []string
128
	for _, t := range strings.Split(*p.Tags, ",") {
129
		if v := strings.TrimSpace(t); v != "" {
130
			out = append(out, v)
131
		}
132
	}
133
	return out
134
}
135
136
type Page struct {
137
	ID          int64
138
	ShortID     string
139
	Title       string
140
	Slug        string
141
	Content     string
142
	IsPublished bool
143
	NavOrder    int64
144
	CreatedAt   string
145
	UpdatedAt   string
146
}
147
148
type UploadedFile struct {
149
	ID             int64
150
	ShortID        string
151
	Filename       string
152
	OriginalName   string
153
	ContentType    string
154
	Size           int64
155
	CreatedAt      string
156
	StorageBackend string
157
}
158
159
func (f UploadedFile) SizeHuman() string {
160
	return humanSize(f.Size)
161
}
162
163
func (f UploadedFile) IsImage() bool {
164
	return strings.HasPrefix(f.ContentType, "image/")
165
}
166
167
func humanSize(n int64) string {
168
	const k = 1024
169
	if n < k {
170
		return formatInt(n) + " B"
171
	}
172
	v := float64(n) / float64(k)
173
	if v < k {
174
		return formatFloat1(v) + " KB"
175
	}
176
	v /= k
177
	if v < k {
178
		return formatFloat1(v) + " MB"
179
	}
180
	v /= k
181
	return formatFloat1(v) + " GB"
182
}
183
184
func formatInt(n int64) string {
185
	if n == 0 {
186
		return "0"
187
	}
188
	neg := n < 0
189
	if neg {
190
		n = -n
191
	}
192
	buf := [24]byte{}
193
	i := len(buf)
194
	for n > 0 {
195
		i--
196
		buf[i] = byte('0' + n%10)
197
		n /= 10
198
	}
199
	if neg {
200
		i--
201
		buf[i] = '-'
202
	}
203
	return string(buf[i:])
204
}
205
206
func formatFloat1(v float64) string {
207
	scaled := int64(v*10 + 0.5)
208
	whole := scaled / 10
209
	frac := scaled % 10
210
	return formatInt(whole) + "." + string(byte('0'+frac))
211
}
212
213
type NavLink struct {
214
	Label string
215
	URL   string
216
}
217
218
type siteContext struct {
219
	BlogTitle  string
220
	NavLinks   []NavLink
221
	FaviconURL string
222
	OGImageURL string
223
	SiteURL    string
224
	HeaderHTML template.HTML
225
	FooterHTML template.HTML
226
}
227
228
type loginPageData struct {
229
	Error string
230
}
231
232
type indexPageData struct {
233
	BlogTitle       string
234
	BlogDescription string
235
	IntroHTML       template.HTML
236
	Posts           []Post
237
	NavLinks        []NavLink
238
	FaviconURL      string
239
	OGImageURL      string
240
	SiteURL         string
241
	HeaderHTML      template.HTML
242
	FooterHTML      template.HTML
243
}
244
245
type postPageData struct {
246
	BlogTitle       string
247
	NavLinks        []NavLink
248
	Post            Post
249
	RenderedContent template.HTML
250
	FaviconURL      string
251
	OGImageURL      string
252
	SiteURL         string
253
	HeaderHTML      template.HTML
254
	FooterHTML      template.HTML
255
	Weather 				Weather
256
}
257
258
type pagePageData struct {
259
	BlogTitle       string
260
	NavLinks        []NavLink
261
	Page            Page
262
	RenderedContent template.HTML
263
	FaviconURL      string
264
	OGImageURL      string
265
	SiteURL         string
266
	HeaderHTML      template.HTML
267
	FooterHTML      template.HTML
268
}
269
270
type postsListPageData struct {
271
	BlogTitle  string
272
	NavLinks   []NavLink
273
	Posts      []Post
274
	FaviconURL string
275
	OGImageURL string
276
	SiteURL    string
277
	HeaderHTML template.HTML
278
	FooterHTML template.HTML
279
}
280
281
type adminIndexPageData struct {
282
	Posts []Post
283
}
284
285
type adminPostFormPageData struct {
286
	Post  *Post
287
	Error string
288
}
289
290
type adminPagesPageData struct {
291
	Pages []Page
292
}
293
294
type adminPageFormPageData struct {
295
	Page  *Page
296
	Error string
297
}
298
299
type adminSettingsPageData struct {
300
	BlogTitle       string
301
	BlogDescription string
302
	IntroContent    string
303
	NavLinksRaw     string
304
	CustomCSS       string
305
	DefaultCSS      string
306
	FaviconURL      string
307
	OGImageURL      string
308
	CustomHeader    string
309
	CustomFooter    string
310
	DefaultLocation string
311
	Success         bool
312
}
313
314
type adminFilesPageData struct {
315
	Files   []UploadedFile
316
	SiteURL string
317
	Error   string
318
	Success bool
319
}
320
321
type adminImportPageData struct {
322
	Error    string
323
	Imported *int
324
	Skipped  *int
325
}