@geekity/cms 0.35.0 → 0.36.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/admin/components/job-status.njk +15 -10
  2. package/admin/components/upload.njk +26 -0
  3. package/admin/pages/documents/editor.njk +34 -19
  4. package/admin/pages/federation/followers.njk +2 -1
  5. package/admin/pages/media/file.njk +5 -4
  6. package/admin/pages/media/library.njk +7 -3
  7. package/admin/pages/settings/general.njk +4 -1
  8. package/admin/pages/tools/export.njk +54 -51
  9. package/admin/pages/tools/maintenance.njk +2 -1
  10. package/admin/pages/users/edit.njk +3 -0
  11. package/admin/src/admin.css +1 -0
  12. package/admin/static/admin.css +74 -0
  13. package/admin/static/job-status.js +158 -0
  14. package/admin/static/upload-drop.js +225 -0
  15. package/dist/admin/documents.d.ts.map +1 -1
  16. package/dist/admin/documents.js +0 -2
  17. package/dist/admin/documents.js.map +1 -1
  18. package/dist/admin/index.d.ts +1 -1
  19. package/dist/admin/index.d.ts.map +1 -1
  20. package/dist/admin/index.js +1 -1
  21. package/dist/admin/index.js.map +1 -1
  22. package/dist/admin/job-status.d.ts +1 -0
  23. package/dist/admin/job-status.d.ts.map +1 -1
  24. package/dist/admin/job-status.js +1 -0
  25. package/dist/admin/job-status.js.map +1 -1
  26. package/dist/admin/media.d.ts +6 -9
  27. package/dist/admin/media.d.ts.map +1 -1
  28. package/dist/admin/media.js +85 -27
  29. package/dist/admin/media.js.map +1 -1
  30. package/dist/admin/routes.d.ts.map +1 -1
  31. package/dist/admin/routes.js +1 -0
  32. package/dist/admin/routes.js.map +1 -1
  33. package/dist/admin/uploads.d.ts +2 -1
  34. package/dist/admin/uploads.d.ts.map +1 -1
  35. package/dist/admin/uploads.js +5 -2
  36. package/dist/admin/uploads.js.map +1 -1
  37. package/dist/media/library.d.ts +9 -0
  38. package/dist/media/library.d.ts.map +1 -1
  39. package/dist/media/library.js +25 -1
  40. package/dist/media/library.js.map +1 -1
  41. package/package.json +6 -1
  42. package/dist/comments/routes.d.ts +0 -65
  43. package/dist/comments/routes.d.ts.map +0 -1
  44. package/dist/comments/routes.js +0 -235
  45. package/dist/comments/routes.js.map +0 -1
  46. package/dist/content/media.d.ts +0 -85
  47. package/dist/content/media.d.ts.map +0 -1
  48. package/dist/content/media.js +0 -237
  49. package/dist/content/media.js.map +0 -1
  50. package/dist/content/read-form.d.ts +0 -31
  51. package/dist/content/read-form.d.ts.map +0 -1
  52. package/dist/content/read-form.js +0 -157
  53. package/dist/content/read-form.js.map +0 -1
  54. package/dist/content/read.d.ts +0 -29
  55. package/dist/content/read.d.ts.map +0 -1
  56. package/dist/content/read.js +0 -82
  57. package/dist/content/read.js.map +0 -1
  58. package/dist/export/export-archives.d.ts +0 -29
  59. package/dist/export/export-archives.d.ts.map +0 -1
  60. package/dist/export/export-archives.js +0 -88
  61. package/dist/export/export-archives.js.map +0 -1
  62. package/dist/federation/mount.d.ts +0 -53
  63. package/dist/federation/mount.d.ts.map +0 -1
  64. package/dist/federation/mount.js +0 -344
  65. package/dist/federation/mount.js.map +0 -1
  66. package/dist/mail/credentials.d.ts +0 -62
  67. package/dist/mail/credentials.d.ts.map +0 -1
  68. package/dist/mail/credentials.js +0 -86
  69. package/dist/mail/credentials.js.map +0 -1
  70. package/dist/site/setting-rows.d.ts +0 -45
  71. package/dist/site/setting-rows.d.ts.map +0 -1
  72. package/dist/site/setting-rows.js +0 -361
  73. package/dist/site/setting-rows.js.map +0 -1
  74. package/dist/site/settings-form.d.ts +0 -30
  75. package/dist/site/settings-form.d.ts.map +0 -1
  76. package/dist/site/settings-form.js +0 -54
  77. package/dist/site/settings-form.js.map +0 -1
  78. package/dist/site/site-json.d.ts +0 -38
  79. package/dist/site/site-json.d.ts.map +0 -1
  80. package/dist/site/site-json.js +0 -69
  81. package/dist/site/site-json.js.map +0 -1
  82. package/dist/webmention/routes.d.ts +0 -37
  83. package/dist/webmention/routes.d.ts.map +0 -1
  84. package/dist/webmention/routes.js +0 -75
  85. package/dist/webmention/routes.js.map +0 -1
@@ -1,344 +0,0 @@
1
- import { respondWithObject } from '@fedify/fedify';
2
- import { federation as federationMiddleware } from '@fedify/hono';
3
- import { isGone, isFederatedDocument } from '../content/served.js';
4
- import { isTrashedPath } from '../content/store.js';
5
- import { listUsers } from '../site/users.js';
6
- import { absoluteUrl } from '../util/url.js';
7
- import { authorHref, parseAuthorPath, accountOf } from '../content/authors.js';
8
- import { goneDocumentAt, publicDocumentAt } from '../content/permalinks.js';
9
- import { prefersActivityStreams } from '../web/negotiate.js';
10
- import { notFound, requestPath } from '../web/request.js';
11
- import { actorAliases, actorId, userActor } from './actor.js';
12
- import { postObject, postTombstone } from './article.js';
13
- import { handleHref } from './paths.js';
14
- /**
15
- * Put Fedify in front of the rest of the app.
16
- *
17
- * The middleware only answers the paths Fedify registered — the actor, its
18
- * collections, WebFinger and NodeInfo; everything else falls through to
19
- * `next()` and reaches the admin and the public site as before. That is why it
20
- * is mounted first: the public site claims every unmatched path in its
21
- * not-found handler, so anything registered after it never sees a request.
22
- *
23
- * A post's object is not one of Fedify's paths at all. decision-13 makes a
24
- * post's ActivityStreams id its permalink, so {@link activityStreamsDocument}
25
- * is what serves the `Article`: one URL, answering a browser with the page and
26
- * a peer with the object. The same handler answers at the `activitypub.id` a
27
- * migrated post's file names, and sends a browser from there to the permalink.
28
- *
29
- * The dispatchers are handed the stores off the Hono context rather than
30
- * closing over them, so one federation object could serve more than one CMS
31
- * and so a dispatcher is never reading a store the request did not come with.
32
- */
33
- export function mountFederation(app, federation, options = {}) {
34
- // WebFinger is ours rather than Fedify's, and so has to be registered before
35
- // the middleware that would otherwise answer it. Fedify hard-codes the
36
- // `self` link to the dispatcher path and computes `aliases` from the
37
- // resource, neither of which can be added to, so an actor served under the
38
- // id it had elsewhere could never be discovered by it (doc-8). Owning the
39
- // route is a dozen lines and gives complete control of `subject`, `aliases`
40
- // and `links`.
41
- app.get(WEBFINGER_PATH, (c) => {
42
- const resource = c.req.query('resource') ?? '';
43
- const user = webFingerSubject(c, resource);
44
- if (user === undefined)
45
- return c.notFound();
46
- const context = federation.createContext(c.req.raw, contextData(c));
47
- const { baseUrl } = c.var.config;
48
- const aliases = actorAliases(context, user, baseUrl);
49
- return c.json({
50
- subject: acctOf(user.username, c.var.config.baseUrl),
51
- aliases: aliases.map((alias) => alias.href),
52
- links: [
53
- {
54
- rel: 'self',
55
- type: 'application/activity+json',
56
- href: actorId(context, user).href,
57
- },
58
- {
59
- rel: 'http://webfinger.net/rel/profile-page',
60
- type: 'text/html',
61
- href: absoluteUrl(authorHref(user.username), baseUrl),
62
- },
63
- ],
64
- }, 200, {
65
- // The media type RFC 7033 defines, and the header that lets a browser
66
- // application read the answer, which is what every other WebFinger
67
- // server sends.
68
- 'content-type': 'application/jrd+json; charset=utf-8',
69
- 'access-control-allow-origin': '*',
70
- });
71
- });
72
- // `/@ada`, the short URL WordPress publishes beside the author archive and
73
- // the one a person is most likely to type. It is an alias rather than a
74
- // second identity, so it goes where the identity is.
75
- app.get('/:handle{@.+}', async (c, next) => {
76
- // The pattern is a regex rather than `/@:username`, because Hono's path
77
- // parameters are whole segments: the `@` has to be matched inside one.
78
- const handle = (c.req.param('handle') ?? '').slice(1);
79
- // An alias is only an alias of somebody. The route used to send every
80
- // handle to `/author/<whatever was typed>/`, so a profile link with a
81
- // menu item's flag on the end of it — `https://shll.me/@a | me` — landed a
82
- // reader on `/author/a%20%7C%20me/` instead of saying there is no such
83
- // person (TASK-112). Spelled exactly, the way WebFinger matches: `Ada` and
84
- // `ada` are two accounts on a screen that can tell them apart, and only
85
- // one of them exists.
86
- const user = listUsers(c.var.config.dataDir).find((account) => account.username === handle);
87
- // The site's own not-found handler answers rather than a bare 404 here, so
88
- // a handle nobody has gets the theme's 404 page like any other URL that is
89
- // not there.
90
- if (user === undefined)
91
- return await next();
92
- return c.redirect(authorHref(user.username), 301);
93
- });
94
- // `@fedify/hono` types its context as the two properties it actually reads,
95
- // which is looser than Hono's own; the cast is what puts `c.var` back.
96
- const fedify = federationMiddleware(federation, (context) => {
97
- const c = context;
98
- return contextData(c);
99
- });
100
- // The one path where Fedify and the public site both have a claim: a user's
101
- // author archive is their actor id (decision-14), and Fedify answers a bare
102
- // `Accept: application/json` as an ActivityStreams request while doc-3 gives
103
- // that spelling the listing's own JSON. The CMS's rule decides, so a peer
104
- // asking for `activity+json` or `ld+json` gets the actor and everything else
105
- // — a browser, a reader, a feed tool — reaches the archive it asked for.
106
- // Every other Fedify path is left exactly as it was.
107
- const federationGate = async (c, next) => {
108
- const isArchive = parseAuthorPath(requestPath(c)) !== undefined;
109
- if (isArchive && !prefersActivityStreams(c.req.header('accept')))
110
- return await next();
111
- return await fedify(c, next);
112
- };
113
- app.use('*', federationGate);
114
- if (options.plugins !== undefined)
115
- app.use('*', options.plugins);
116
- // Named, and typed as a middleware, so the Hono context arrives with its
117
- // path parameters resolved rather than as `any`.
118
- const activityStreams = async (c, next) => {
119
- const response = (await activityStreamsActor(c, federation)) ?? (await activityStreamsDocument(c, federation));
120
- if (response !== undefined)
121
- return response;
122
- await next();
123
- };
124
- app.use('*', activityStreams);
125
- }
126
- /**
127
- * The person this request is about, when it asks at the id they were published
128
- * under somewhere else: the `Person` for a peer, a redirect to the author
129
- * archive for a browser, and `undefined` for everything else.
130
- *
131
- * The twin of {@link activityStreamsDocument}, and for the same reason
132
- * (decision-14): a stored actor id is identity. A follower's server keys the
133
- * account by the URL it first saw, so that URL has to keep answering with this
134
- * person's actor for as long as the account exists, or every follow out there
135
- * points at nothing.
136
- *
137
- * Fedify cannot serve it — its router matches paths, and a stored id is
138
- * commonly a query string on the site root (doc-8) — so it is served here,
139
- * ahead of the public site, exactly as a post's stored `activitypub.id` is.
140
- */
141
- async function activityStreamsActor(c, federation) {
142
- const user = storedActorAt(c);
143
- if (user === undefined)
144
- return undefined;
145
- if (!prefersActivityStreams(c.req.header('accept'))) {
146
- return c.redirect(authorHref(user.username), 301);
147
- }
148
- const context = federation.createContext(c.req.raw, contextData(c));
149
- const actor = await userActor(context, user, { baseUrl: c.var.config.baseUrl });
150
- return await respondWithObject(actor, { contextLoader: context.contextLoader });
151
- }
152
- /**
153
- * The user whose stored actor id is the URL this request asks for, or
154
- * `undefined`.
155
- *
156
- * Built the way {@link storedObjectAt} builds a post's candidate — the
157
- * request's path and query on the site's base URL, rather than `request.url`,
158
- * which behind a proxy is the internal address — and matched on the whole URL,
159
- * query string and all, which is what decision-14 asks for.
160
- *
161
- * A user whose stored id happens to be their author URL is not one of these:
162
- * the actor dispatcher has already answered for that URL, and answering again
163
- * here would only redirect a browser to where it already is.
164
- */
165
- function storedActorAt(c) {
166
- const { search } = new URL(c.req.url);
167
- const candidate = `${absoluteUrl(requestPath(c), c.var.config.baseUrl)}${search}`;
168
- const user = listUsers(c.var.config.dataDir).find((entry) => entry.actorId === candidate);
169
- if (user === undefined)
170
- return undefined;
171
- if (absoluteUrl(authorHref(user.username), c.var.config.baseUrl) === candidate)
172
- return undefined;
173
- return user;
174
- }
175
- /**
176
- * The post this request is about, when it is about one: the `Article` for a
177
- * peer, a redirect to the permalink for a browser at a stored id, and
178
- * `undefined` for everything else.
179
- *
180
- * Two URLs can name a post. Its permalink always does — that is the whole of
181
- * decision-13, and a peer that dereferences a link somebody shared lands on
182
- * the object rather than on a page it cannot read. The other is the
183
- * `activitypub.id` a migrated post's file carries: the name its followers,
184
- * its replies and, unless it sets a `guid`, its RSS subscribers already hold,
185
- * which has to keep answering or every copy out there points at nothing. A
186
- * browser that follows one of those old links is sent on to the permalink,
187
- * because that is the URL a person should end up at.
188
- *
189
- * Anything else — a browser at a permalink, a `.md` or `.json` request, a
190
- * page, a listing — gets `undefined` and falls through to the public site
191
- * untouched.
192
- */
193
- async function activityStreamsDocument(c, federation) {
194
- const wantsObject = prefersActivityStreams(c.req.header('accept'));
195
- if (wantsObject) {
196
- const atPermalink = publicDocumentAt(c.var.store, requestPath(c));
197
- if (atPermalink !== undefined && isFederatedDocument(atPermalink, c.var.store.now())) {
198
- return await article(c, federation, atPermalink);
199
- }
200
- }
201
- const stored = storedObjectAt(c);
202
- if (wantsObject) {
203
- const trashed = stored ?? goneDocumentAt(c.var.store, requestPath(c));
204
- if (trashed?.type === 'post' && isTrashedPath(trashed.path)) {
205
- return isGone(trashed, c.var.store.now())
206
- ? await tombstone(c, federation, trashed)
207
- : undefined;
208
- }
209
- }
210
- if (stored === undefined)
211
- return undefined;
212
- const now = c.var.store.now();
213
- if (!isFederatedDocument(stored, now) && !isGone(stored, now)) {
214
- return wantsObject ? notFound(c) : undefined;
215
- }
216
- if (wantsObject)
217
- return await article(c, federation, stored);
218
- // A renamed post's old URL is its stored id (decision-20). Once a new
219
- // document takes that URL over, a browser there is reading the new one; the
220
- // id keeps answering peers, who hold it as a name rather than a page.
221
- if (publicDocumentAt(c.var.store, requestPath(c)) !== undefined)
222
- return undefined;
223
- return c.redirect(stored.permalink, 301);
224
- }
225
- /**
226
- * The post whose `activitypub.id` is the URL this request asks for, or
227
- * `undefined`.
228
- *
229
- * The candidate is built the way the ids themselves are — the request's path
230
- * and query on the site's base URL — rather than taken from `request.url`,
231
- * which behind a proxy is the internal address. A post whose stored id is its
232
- * permalink is not one of these: the permalink lookup has already answered for
233
- * it, and answering again here would only invite a redirect to itself.
234
- */
235
- function storedObjectAt(c) {
236
- const { search } = new URL(c.req.url);
237
- const candidate = `${absoluteUrl(requestPath(c), c.var.config.baseUrl)}${search}`;
238
- const document = c.var.store.getByStoredObjectId(candidate);
239
- if (document === undefined || document.type !== 'post')
240
- return undefined;
241
- if (absoluteUrl(document.permalink, c.var.config.baseUrl) === candidate)
242
- return undefined;
243
- return document;
244
- }
245
- /**
246
- * One post as the ActivityStreams response a peer asked for.
247
- *
248
- * The object is built through the same {@link postObject} the outbox uses,
249
- * off a context Fedify makes for this request, so the object a peer fetches
250
- * and the one it is delivered cannot drift apart.
251
- */
252
- async function article(c, federation, document) {
253
- const context = federation.createContext(c.req.raw, contextData(c));
254
- if (listUsers(c.var.config.dataDir).length === 0)
255
- return notFound(c);
256
- return await respondWithObject(postObject(context, document), {
257
- contextLoader: context.contextLoader,
258
- });
259
- }
260
- async function tombstone(c, federation, document) {
261
- const context = federation.createContext(c.req.raw, contextData(c));
262
- const response = await respondWithObject(postTombstone(context, document), {
263
- contextLoader: context.contextLoader,
264
- });
265
- return new Response(response.body, { status: 410, headers: response.headers });
266
- }
267
- /** Where WebFinger lives, which is defined on the host rather than under a site. */
268
- export const WEBFINGER_PATH = '/.well-known/webfinger';
269
- /** `acct:{username}@{host}`, the handle a person types into a search box. */
270
- export function acctOf(username, baseUrl) {
271
- return `acct:${accountOf(username, baseUrl)}`;
272
- }
273
- /**
274
- * The user a WebFinger `resource` asks about, or `undefined` for one this site
275
- * answers for nobody.
276
- *
277
- * Every spelling of a person resolves, and they are the ones decision-14 lists
278
- * as their aliases: the `acct:` handle, the author archive, `/@{username}` and
279
- * — for somebody who was published elsewhere first — the id they were
280
- * published under. Matching every one of them is what lets a peer that holds
281
- * any one URL for this person find the others, which is the whole job of
282
- * WebFinger, and it is how a server holding only `?author=2` discovers that
283
- * the account it follows is still here.
284
- *
285
- * Both sides of the comparison go through {@link canonicalResource} first, so
286
- * the spellings that differ only in how they were typed are one spelling by
287
- * the time they are matched, and no spelling needs a branch of its own.
288
- */
289
- export function webFingerSubject(c, resource) {
290
- const wanted = canonicalResource(resource);
291
- if (wanted === '')
292
- return undefined;
293
- const { baseUrl } = c.var.config;
294
- return listUsers(c.var.config.dataDir).find((user) => [
295
- acctOf(user.username, baseUrl),
296
- absoluteUrl(authorHref(user.username), baseUrl),
297
- absoluteUrl(handleHref(user.username), baseUrl),
298
- ...(user.actorId === undefined ? [] : [user.actorId]),
299
- ].some((spelling) => canonicalResource(spelling) === wanted));
300
- }
301
- /**
302
- * One resource in the spelling every equivalent one reduces to.
303
- *
304
- * A host is case-insensitive — in DNS, in RFC 3986 and in RFC 7033 — so
305
- * `acct:ada@EXAMPLE.COM` names the same person as `acct:ada@example.com` and
306
- * has to find them. A username is not: Geekity compares one case sensitively
307
- * (see {@link User.username}), so the case is left exactly as it was asked
308
- * for and `acct:Ada@example.com` stays somebody this site does not have.
309
- *
310
- * A bare `user@host` is not what RFC 7033 asks for, but it is what enough
311
- * clients send that refusing it would only look like a missing account, and
312
- * the same goes for the leading `@` of `@ada@example.com`, which is how the
313
- * handle is written everywhere a person reads it. Both are spelled back out
314
- * as the `acct:` URI they meant.
315
- */
316
- function canonicalResource(resource) {
317
- const trimmed = resource.trim();
318
- // A URL carries its own rules, and `URL` applies them: the scheme and the
319
- // host come back lower-cased, the path untouched.
320
- if (/^[a-z][a-z\d+.-]*:\/\//i.test(trimmed)) {
321
- try {
322
- return new URL(trimmed).href;
323
- }
324
- catch {
325
- return trimmed;
326
- }
327
- }
328
- const handle = trimmed.replace(/^acct:/i, '').replace(/^@/, '');
329
- const at = handle.lastIndexOf('@');
330
- if (at <= 0)
331
- return handle;
332
- return `acct:${handle.slice(0, at)}@${handle.slice(at + 1).toLowerCase()}`;
333
- }
334
- /** What the dispatchers are handed, off the Hono context. */
335
- export function contextData(c) {
336
- return {
337
- admin: c.var.admin,
338
- store: c.var.store,
339
- config: c.var.config,
340
- actorProfiles: c.var.actorProfiles,
341
- cited: (url) => c.var.replyContexts.read(url),
342
- };
343
- }
344
- //# sourceMappingURL=mount.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mount.js","sourceRoot":"","sources":["../../src/federation/mount.ts"],"names":[],"mappings":"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"}
@@ -1,62 +0,0 @@
1
- /**
2
- * What a site needs to prove it may send: `data/mail.json`.
3
- *
4
- * A credential, not a setting, so it lives where the Akismet key and the
5
- * password hashes live — under `data/` at mode `0600` — and never in
6
- * `content/_data/site.json`, which is public, in git and published with the
7
- * site. Which provider is in use, who mail is from and where a reply goes are
8
- * settings and are in `site.json`; the key and the SMTP password are here.
9
- *
10
- * Read on every send, like the Akismet key, so a credential pasted into the
11
- * settings screen sends the next message and one removed stops sending at
12
- * once, neither of them needing a restart.
13
- */
14
- /** Where the credentials live, under `dataDir`. */
15
- export declare const MAIL_CREDENTIALS_FILE = "mail.json";
16
- /** Brevo's half: one API key. */
17
- export interface BrevoCredential {
18
- /** The account's API key, `xkeysib-…`. */
19
- readonly apiKey: string;
20
- }
21
- /** SMTP's half: the whole connection, because all of it is a credential. */
22
- export interface SmtpCredential {
23
- /** The server's hostname. */
24
- readonly host: string;
25
- /** The port: 587 for submission with STARTTLS, 465 for implicit TLS. */
26
- readonly port: number;
27
- /** Whether the connection is TLS from the first byte, which is port 465. */
28
- readonly secure: boolean;
29
- /** The username. Empty for a server that wants no credentials. */
30
- readonly user: string;
31
- /** The password. */
32
- readonly password: string;
33
- }
34
- /**
35
- * The file's whole contents: at most one entry per provider.
36
- *
37
- * Both may be present at once, and that is deliberate — a site trying SMTP
38
- * after Brevo should be able to switch the `mailProvider` setting back without
39
- * having to paste a key in again.
40
- */
41
- export interface MailCredentials {
42
- /** Brevo's key, when the site has one. */
43
- readonly brevo?: BrevoCredential | undefined;
44
- /** The SMTP connection, when the site has one. */
45
- readonly smtp?: SmtpCredential | undefined;
46
- }
47
- /** Where one site's credential file is. */
48
- export declare function mailCredentialsPath(dataDir: string): string;
49
- /**
50
- * The stored credentials, or an empty object when the site has none.
51
- *
52
- * Read tolerantly and synchronously, like every other file this CMS treats as
53
- * truth: a file that will not parse, or an entry missing the one field that
54
- * makes it usable, is a site with no mail rather than a site that will not
55
- * boot.
56
- */
57
- export declare function readMailCredentials(dataDir: string): MailCredentials;
58
- /** Write the credentials, private to the account the site runs as. */
59
- export declare function writeMailCredentials(dataDir: string, credentials: MailCredentials): Promise<void>;
60
- /** Forget them, which is how a site stops sending mail entirely. */
61
- export declare function removeMailCredentials(dataDir: string): Promise<void>;
62
- //# sourceMappingURL=credentials.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/mail/credentials.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;GAYG;AAEH,mDAAmD;AACnD,eAAO,MAAM,qBAAqB,cAAc,CAAC;AAEjD,iCAAiC;AACjC,MAAM,WAAW,eAAe;IAC9B,0CAA0C;IAC1C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,4EAA4E;AAC5E,MAAM,WAAW,cAAc;IAC7B,6BAA6B;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oBAAoB;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,0CAA0C;IAC1C,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC;IAC7C,kDAAkD;IAClD,QAAQ,CAAC,IAAI,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;CAC5C;AAED,2CAA2C;AAC3C,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,CAkBpE;AAED,sEAAsE;AACtE,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,eAAe,GAC3B,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,oEAAoE;AACpE,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1E"}
@@ -1,86 +0,0 @@
1
- import { rm } from 'node:fs/promises';
2
- import path from 'node:path';
3
- import { readFileIfPresentSync, writeFileAtomically } from '../files/atomic.js';
4
- /**
5
- * What a site needs to prove it may send: `data/mail.json`.
6
- *
7
- * A credential, not a setting, so it lives where the Akismet key and the
8
- * password hashes live — under `data/` at mode `0600` — and never in
9
- * `content/_data/site.json`, which is public, in git and published with the
10
- * site. Which provider is in use, who mail is from and where a reply goes are
11
- * settings and are in `site.json`; the key and the SMTP password are here.
12
- *
13
- * Read on every send, like the Akismet key, so a credential pasted into the
14
- * settings screen sends the next message and one removed stops sending at
15
- * once, neither of them needing a restart.
16
- */
17
- /** Where the credentials live, under `dataDir`. */
18
- export const MAIL_CREDENTIALS_FILE = 'mail.json';
19
- /** Where one site's credential file is. */
20
- export function mailCredentialsPath(dataDir) {
21
- return path.join(dataDir, MAIL_CREDENTIALS_FILE);
22
- }
23
- /**
24
- * The stored credentials, or an empty object when the site has none.
25
- *
26
- * Read tolerantly and synchronously, like every other file this CMS treats as
27
- * truth: a file that will not parse, or an entry missing the one field that
28
- * makes it usable, is a site with no mail rather than a site that will not
29
- * boot.
30
- */
31
- export function readMailCredentials(dataDir) {
32
- const raw = readFileIfPresentSync(mailCredentialsPath(dataDir));
33
- if (raw === undefined)
34
- return {};
35
- let parsed;
36
- try {
37
- parsed = JSON.parse(raw);
38
- }
39
- catch {
40
- return {};
41
- }
42
- if (typeof parsed !== 'object' || parsed === null)
43
- return {};
44
- const file = parsed;
45
- return {
46
- ...(brevoIn(file['brevo']) === undefined ? {} : { brevo: brevoIn(file['brevo']) }),
47
- ...(smtpIn(file['smtp']) === undefined ? {} : { smtp: smtpIn(file['smtp']) }),
48
- };
49
- }
50
- /** Write the credentials, private to the account the site runs as. */
51
- export async function writeMailCredentials(dataDir, credentials) {
52
- await writeFileAtomically(mailCredentialsPath(dataDir), `${JSON.stringify(credentials, null, 2)}\n`, { mode: 0o600 });
53
- }
54
- /** Forget them, which is how a site stops sending mail entirely. */
55
- export async function removeMailCredentials(dataDir) {
56
- await rm(mailCredentialsPath(dataDir), { force: true });
57
- }
58
- /** One Brevo entry, or nothing when it carries no key. */
59
- function brevoIn(value) {
60
- if (typeof value !== 'object' || value === null)
61
- return undefined;
62
- const entry = value;
63
- const apiKey = typeof entry['apiKey'] === 'string' ? entry['apiKey'].trim() : '';
64
- return apiKey === '' ? undefined : { apiKey };
65
- }
66
- /** One SMTP entry, or nothing when it names no host to connect to. */
67
- function smtpIn(value) {
68
- if (typeof value !== 'object' || value === null)
69
- return undefined;
70
- const entry = value;
71
- const host = typeof entry['host'] === 'string' ? entry['host'].trim() : '';
72
- if (host === '')
73
- return undefined;
74
- const port = Number(entry['port']);
75
- const secure = entry['secure'] === true;
76
- return {
77
- host,
78
- // 465 when the connection is TLS from the first byte, 587 otherwise:
79
- // the two submission ports, so a file that lost its port still connects.
80
- port: Number.isInteger(port) && port > 0 && port < 65536 ? port : secure ? 465 : 587,
81
- secure,
82
- user: typeof entry['user'] === 'string' ? entry['user'] : '',
83
- password: typeof entry['password'] === 'string' ? entry['password'] : '',
84
- };
85
- }
86
- //# sourceMappingURL=credentials.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/mail/credentials.ts"],"names":[],"mappings":"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"}
@@ -1,45 +0,0 @@
1
- import type { SiteLicense, SiteSettings } from './settings.ts';
2
- export type CarriedField = 'taxonomy_redirects' | 'menus' | 'index_now_key' | 'clients_without_pkce' | 'avatar';
3
- type FormKey = Exclude<keyof SiteSettings, CarriedField>;
4
- export type SettingsField = FormKey | 'license_url' | 'license_name';
5
- export type CarriedSettings = Partial<Pick<SiteSettings, CarriedField>>;
6
- /** A submitted settings form, before it is known to be valid. */
7
- export type SettingsForm = Record<SettingsField, string>;
8
- export interface SettingsDirs {
9
- readonly themesDir: string;
10
- readonly contentDir: string;
11
- }
12
- export type SettingsContext = {
13
- readonly [D in keyof SettingsDirs]?: string | undefined;
14
- };
15
- export interface OnDiskCheck {
16
- readonly dir: keyof SettingsDirs;
17
- readonly problem: (value: string, dir: string) => string | undefined;
18
- }
19
- export interface SettingRow<V> {
20
- readonly submitted: (form: SettingsForm, onFile: CarriedSettings) => V;
21
- }
22
- export interface FieldRow<V> {
23
- readonly field: string;
24
- readonly shown: (value: V) => string;
25
- readonly check: (form: SettingsForm) => string | undefined;
26
- readonly onDisk?: OnDiskCheck;
27
- }
28
- export type FormSettingRow<V> = SettingRow<V> & FieldRow<V>;
29
- export type FieldValue<F extends SettingsField> = F extends keyof SiteSettings ? SiteSettings[F] : SiteLicense;
30
- export declare function requiredWholeDays(typed: string): number | undefined;
31
- export type FrontPage = Pick<SiteSettings, 'homepage' | 'posts_page'>;
32
- export declare function frontPageOf(form: SettingsForm): FrontPage;
33
- export declare function frontPageProblems(form: SettingsForm): {
34
- readonly [K in keyof FrontPage]?: string | undefined;
35
- };
36
- export declare const ROWS: {
37
- readonly [K in keyof SiteSettings]: SettingRow<SiteSettings[K]>;
38
- };
39
- export declare const FORM_ROWS: {
40
- readonly [F in SettingsField]: FieldRow<FieldValue<F>>;
41
- };
42
- export declare const SETTING_KEYS: readonly (keyof SiteSettings)[];
43
- export declare const SETTINGS_FIELD_NAMES: readonly SettingsField[];
44
- export {};
45
- //# sourceMappingURL=setting-rows.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"setting-rows.d.ts","sourceRoot":"","sources":["../../src/site/setting-rows.ts"],"names":[],"mappings":"AAkCA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE/D,MAAM,MAAM,YAAY,GACtB,oBAAoB,GAAG,OAAO,GAAG,eAAe,GAAG,sBAAsB,GAAG,QAAQ,CAAC;AAEvF,KAAK,OAAO,GAAG,OAAO,CAAC,MAAM,YAAY,EAAE,YAAY,CAAC,CAAC;AAEzD,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,aAAa,GAAG,cAAc,CAAC;AAErE,MAAM,MAAM,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC;AAExE,iEAAiE;AACjE,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;AAEzD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,MAAM,eAAe,GAAG;IAAE,QAAQ,EAAE,CAAC,IAAI,MAAM,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS;CAAE,CAAC;AAE1F,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,YAAY,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;CACtE;AAED,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,eAAe,KAAK,CAAC,CAAC;CACxE;AAED,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,MAAM,GAAG,SAAS,CAAC;IAC3D,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;AAE5D,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,aAAa,IAAI,CAAC,SAAS,MAAM,YAAY,GAC1E,YAAY,CAAC,CAAC,CAAC,GACf,WAAW,CAAC;AAqEhB,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAKnE;AAqED,MAAM,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,EAAE,UAAU,GAAG,YAAY,CAAC,CAAC;AAEtE,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,SAAS,CAGzD;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,YAAY,GAAG;IACrD,QAAQ,EAAE,CAAC,IAAI,MAAM,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS;CACrD,CAMA;AA0VD,eAAO,MAAM,IAAI,EAAE;IAAE,QAAQ,EAAE,CAAC,IAAI,MAAM,YAAY,GAAG,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;CACtE,CAAC;AAEf,eAAO,MAAM,SAAS,EAAE;IAAE,QAAQ,EAAE,CAAC,IAAI,aAAa,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;CAG/E,CAAC;AAEF,eAAO,MAAM,YAAY,EAAgC,SAAS,CAAC,MAAM,YAAY,CAAC,EAAE,CAAC;AAMzF,eAAO,MAAM,oBAAoB,EAAE,SAAS,aAAa,EAIxD,CAAC"}