@jant/core 0.3.46 → 0.3.48
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.
- package/bin/commands/db/execute-file.js +12 -4
- package/bin/commands/db/rehearse.js +2 -2
- package/bin/commands/export.js +12 -4
- package/bin/commands/import-site.js +60 -267
- package/bin/commands/migrate.js +36 -69
- package/bin/commands/reset-password.js +10 -4
- package/bin/commands/site/export.js +59 -248
- package/bin/commands/site/snapshot/export.js +58 -45
- package/bin/commands/site/snapshot/import.js +104 -52
- package/bin/lib/node-env.js +100 -0
- package/bin/lib/runtime-target.js +64 -0
- package/bin/lib/site-snapshot.js +185 -54
- package/bin/lib/sql-export.js +19 -2
- package/dist/app-DU7dpJID.js +6 -0
- package/dist/{app-DB-P66E5.js → app-DdnIoX7y.js} +333 -191
- package/dist/client/.vite/manifest.json +2 -2
- package/dist/client/_assets/client-BoUn7xBo.css +2 -0
- package/dist/client/_assets/{client-auth-BLCUje4M.js → client-auth-Ce5WEAVS.js} +102 -49
- package/dist/{github-sync-CQ1x271f.js → export-ZBlfKSKm.js} +12 -439
- package/dist/github-sync-C593r22F.js +4 -0
- package/dist/github-sync-bL1hnx3Q.js +428 -0
- package/dist/index.js +3 -2
- package/dist/node.js +5 -4
- package/package.json +3 -2
- package/src/__tests__/helpers/export-fixtures.ts +0 -1
- package/src/client/components/__tests__/jant-settings-avatar.test.ts +2 -0
- package/src/client/components/__tests__/jant-settings-general.test.ts +70 -0
- package/src/client/components/jant-settings-general.ts +164 -22
- package/src/client/components/settings-types.ts +4 -6
- package/src/client-auth.ts +1 -1
- package/src/db/__tests__/demo-canonical-snapshot.test.ts +1 -1
- package/src/db/__tests__/migration-rehearsal.test.ts +2 -5
- package/src/db/backfills/0004_register_apple_touch_media_rows.sql +65 -0
- package/src/db/migrations/0021_thankful_phalanx.sql +16 -0
- package/src/db/migrations/meta/0021_snapshot.json +2121 -0
- package/src/db/migrations/meta/_journal.json +7 -0
- package/src/db/migrations/pg/0019_gray_natasha_romanoff.sql +20 -0
- package/src/db/migrations/pg/meta/0019_snapshot.json +2718 -0
- package/src/db/migrations/pg/meta/_journal.json +7 -0
- package/src/db/pg/schema.ts +21 -26
- package/src/db/rehearsal-fixtures/demo-current.json +1 -1
- package/src/db/schema.ts +16 -20
- package/src/i18n/__tests__/middleware.test.ts +43 -1
- package/src/i18n/coverage.generated.ts +17 -0
- package/src/i18n/i18n.ts +18 -2
- package/src/i18n/index.ts +3 -0
- package/src/i18n/locales/settings/en.po +16 -11
- package/src/i18n/locales/settings/en.ts +1 -1
- package/src/i18n/locales/settings/zh-Hans.po +17 -12
- package/src/i18n/locales/settings/zh-Hans.ts +1 -1
- package/src/i18n/locales/settings/zh-Hant.po +16 -11
- package/src/i18n/locales/settings/zh-Hant.ts +1 -1
- package/src/i18n/locales.ts +84 -2
- package/src/i18n/middleware.ts +25 -16
- package/src/i18n/supported-locales.ts +153 -0
- package/src/lib/__tests__/csp-builder.test.ts +19 -2
- package/src/lib/__tests__/feed.test.ts +242 -1
- package/src/lib/__tests__/post-meta.test.ts +0 -1
- package/src/lib/__tests__/view.test.ts +0 -1
- package/src/lib/api-posts.ts +9 -7
- package/src/lib/csp-builder.ts +28 -10
- package/src/lib/feed.ts +153 -3
- package/src/middleware/__tests__/secure-headers.test.ts +89 -0
- package/src/middleware/auth.ts +1 -1
- package/src/middleware/secure-headers.ts +47 -1
- package/src/node/__tests__/cli-runtime-target.test.ts +110 -2
- package/src/node/__tests__/cli-site-snapshot.test.ts +308 -13
- package/src/node/__tests__/cli-site-token-env.test.ts +2 -7
- package/src/node/__tests__/cli-snapshot-meta.test.ts +85 -0
- package/src/node/__tests__/cli-sql-export.test.ts +49 -0
- package/src/node/index.ts +1 -0
- package/src/preset.css +8 -2
- package/src/routes/api/__tests__/settings.test.ts +3 -2
- package/src/routes/api/github-sync.tsx +1 -1
- package/src/routes/api/settings.ts +4 -1
- package/src/routes/auth/signin.tsx +6 -0
- package/src/routes/pages/archive.tsx +4 -2
- package/src/services/__tests__/post.test.ts +19 -19
- package/src/services/__tests__/search.test.ts +0 -1
- package/src/services/__tests__/settings.test.ts +22 -3
- package/src/services/bootstrap.ts +7 -3
- package/src/services/collection.ts +3 -3
- package/src/services/export.ts +0 -3
- package/src/services/navigation.ts +0 -2
- package/src/services/path.ts +1 -38
- package/src/services/post.ts +32 -66
- package/src/services/search.ts +0 -6
- package/src/services/settings.ts +47 -6
- package/src/services/site-admin.ts +6 -1
- package/src/styles/ui.css +14 -25
- package/src/types/entities.ts +0 -1
- package/src/ui/color-themes.ts +1 -1
- package/src/ui/dash/settings/GeneralContent.tsx +17 -19
- package/src/ui/dash/settings/SettingsRootContent.tsx +17 -28
- package/src/ui/feed/NoteCard.tsx +1 -11
- package/src/ui/feed/__tests__/timeline-cards.test.ts +1 -1
- package/src/ui/pages/PostPage.tsx +2 -0
- package/bin/commands/collections.js +0 -268
- package/bin/commands/media.js +0 -302
- package/bin/commands/posts.js +0 -262
- package/bin/commands/search.js +0 -53
- package/bin/commands/settings.js +0 -93
- package/bin/lib/http-api.js +0 -223
- package/bin/lib/media-upload.js +0 -206
- package/dist/app-CM7sb3xO.js +0 -5
- package/dist/client/_assets/client-DDs6NzB3.css +0 -2
- package/src/__tests__/bin/content-cli.test.ts +0 -179
- package/src/__tests__/bin/media-cli.test.ts +0 -192
- /package/dist/{github-api-BkRWnqMx.js → github-api-Bh0PH3zr.js} +0 -0
- /package/dist/{github-app-WeadXMb8.js → github-app-D0GvNnqp.js} +0 -0
|
@@ -10,12 +10,16 @@
|
|
|
10
10
|
import { LitElement, html, nothing } from "lit";
|
|
11
11
|
import type { Editor } from "@tiptap/core";
|
|
12
12
|
import { MAX_SITE_NAME_LENGTH } from "../../types.js";
|
|
13
|
+
import {
|
|
14
|
+
getSupportedLocaleEntries,
|
|
15
|
+
getOrBuildEntry,
|
|
16
|
+
type LocaleEntry,
|
|
17
|
+
} from "../../i18n/supported-locales.js";
|
|
13
18
|
import type {
|
|
14
19
|
SettingsInitialData,
|
|
15
20
|
SettingsLabels,
|
|
16
21
|
SettingsTimezone,
|
|
17
22
|
SettingsCjkFont,
|
|
18
|
-
SettingsLanguage,
|
|
19
23
|
} from "./settings-types.js";
|
|
20
24
|
import { showToast } from "../toast.js";
|
|
21
25
|
import {
|
|
@@ -28,7 +32,6 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
28
32
|
labels: { type: Object },
|
|
29
33
|
timezones: { type: Array },
|
|
30
34
|
cjkFonts: { type: Array, attribute: "cjk-fonts" },
|
|
31
|
-
languages: { type: Array },
|
|
32
35
|
siteNameFallback: { type: String, attribute: "sitename-fallback" },
|
|
33
36
|
siteDescriptionFallback: {
|
|
34
37
|
type: String,
|
|
@@ -49,6 +52,8 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
49
52
|
|
|
50
53
|
// Language, CJK & time group
|
|
51
54
|
_siteLanguage: { state: true },
|
|
55
|
+
_localeOpen: { state: true },
|
|
56
|
+
_localeQuery: { state: true },
|
|
52
57
|
_cjkSerifFont: { state: true },
|
|
53
58
|
_timeZone: { state: true },
|
|
54
59
|
_origLocale: { state: true },
|
|
@@ -75,7 +80,6 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
75
80
|
declare labels: SettingsLabels;
|
|
76
81
|
declare timezones: SettingsTimezone[];
|
|
77
82
|
declare cjkFonts: SettingsCjkFont[];
|
|
78
|
-
declare languages: SettingsLanguage[];
|
|
79
83
|
declare siteNameFallback: string;
|
|
80
84
|
declare siteDescriptionFallback: string;
|
|
81
85
|
declare demoMode: boolean;
|
|
@@ -97,6 +101,10 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
97
101
|
|
|
98
102
|
// Language, CJK & time
|
|
99
103
|
declare _siteLanguage: string;
|
|
104
|
+
/** Whether the locale combobox dropdown is currently open. */
|
|
105
|
+
declare _localeOpen: boolean;
|
|
106
|
+
/** Search query inside the locale combobox. */
|
|
107
|
+
declare _localeQuery: string;
|
|
100
108
|
declare _cjkSerifFont: string;
|
|
101
109
|
declare _timeZone: string;
|
|
102
110
|
declare _origLocale: {
|
|
@@ -137,7 +145,6 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
137
145
|
this.labels = {} as SettingsLabels;
|
|
138
146
|
this.timezones = [];
|
|
139
147
|
this.cjkFonts = [];
|
|
140
|
-
this.languages = [];
|
|
141
148
|
this.siteNameFallback = "";
|
|
142
149
|
this.siteDescriptionFallback = "";
|
|
143
150
|
this.demoMode = false;
|
|
@@ -157,6 +164,8 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
157
164
|
this._siteLoading = false;
|
|
158
165
|
|
|
159
166
|
this._siteLanguage = "en";
|
|
167
|
+
this._localeOpen = false;
|
|
168
|
+
this._localeQuery = "";
|
|
160
169
|
this._cjkSerifFont = "off";
|
|
161
170
|
this._timeZone = "UTC";
|
|
162
171
|
this._origLocale = {
|
|
@@ -181,8 +190,16 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
181
190
|
this._searchLoading = false;
|
|
182
191
|
}
|
|
183
192
|
|
|
193
|
+
connectedCallback() {
|
|
194
|
+
super.connectedCallback();
|
|
195
|
+
document.addEventListener("click", this._onLocalePickerDocumentClick);
|
|
196
|
+
document.addEventListener("keydown", this._onLocalePickerKeydown);
|
|
197
|
+
}
|
|
198
|
+
|
|
184
199
|
disconnectedCallback() {
|
|
185
200
|
super.disconnectedCallback();
|
|
201
|
+
document.removeEventListener("click", this._onLocalePickerDocumentClick);
|
|
202
|
+
document.removeEventListener("keydown", this._onLocalePickerKeydown);
|
|
186
203
|
this._descEditor?.destroy();
|
|
187
204
|
this._descEditor = null;
|
|
188
205
|
this._footerEditor?.destroy();
|
|
@@ -387,6 +404,146 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
387
404
|
);
|
|
388
405
|
}
|
|
389
406
|
|
|
407
|
+
// ── Locale combobox ────────────────────────────────────────────────
|
|
408
|
+
|
|
409
|
+
private _filteredLocaleEntries(): LocaleEntry[] {
|
|
410
|
+
const all = getSupportedLocaleEntries();
|
|
411
|
+
const query = this._localeQuery.trim().toLowerCase();
|
|
412
|
+
if (!query) return all;
|
|
413
|
+
return all.filter(
|
|
414
|
+
(e) =>
|
|
415
|
+
e.tag.toLowerCase().includes(query) ||
|
|
416
|
+
e.native.toLowerCase().includes(query) ||
|
|
417
|
+
e.english.toLowerCase().includes(query),
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
private _toggleLocalePicker = () => {
|
|
422
|
+
this._localeOpen = !this._localeOpen;
|
|
423
|
+
if (!this._localeOpen) {
|
|
424
|
+
this._localeQuery = "";
|
|
425
|
+
} else {
|
|
426
|
+
// Focus the search input on next paint.
|
|
427
|
+
this.updateComplete.then(() => {
|
|
428
|
+
const input = this.querySelector<HTMLInputElement>(
|
|
429
|
+
"[data-locale-search]",
|
|
430
|
+
);
|
|
431
|
+
input?.focus();
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
private _selectLocale(tag: string) {
|
|
437
|
+
this._siteLanguage = tag;
|
|
438
|
+
this._localeOpen = false;
|
|
439
|
+
this._localeQuery = "";
|
|
440
|
+
this._syncLocaleDirty();
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
private _onLocalePickerDocumentClick = (e: Event) => {
|
|
444
|
+
if (!this._localeOpen) return;
|
|
445
|
+
const target = e.target as Node | null;
|
|
446
|
+
const picker = this.querySelector("[data-locale-picker]");
|
|
447
|
+
if (picker && target && !picker.contains(target)) {
|
|
448
|
+
this._localeOpen = false;
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
private _onLocalePickerKeydown = (e: KeyboardEvent) => {
|
|
453
|
+
if (e.key === "Escape" && this._localeOpen) {
|
|
454
|
+
this._localeOpen = false;
|
|
455
|
+
this._localeQuery = "";
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
private _renderLanguagePicker() {
|
|
460
|
+
const current = getOrBuildEntry(this._siteLanguage || "en");
|
|
461
|
+
const filtered = this._filteredLocaleEntries();
|
|
462
|
+
const searchPlaceholder =
|
|
463
|
+
this.labels.siteLanguageSearchPlaceholder || "Search…";
|
|
464
|
+
const noMatches = this.labels.siteLanguageNoMatches || "No matches.";
|
|
465
|
+
|
|
466
|
+
return html`
|
|
467
|
+
<div class="relative" data-locale-picker>
|
|
468
|
+
<button
|
|
469
|
+
type="button"
|
|
470
|
+
class="input flex w-full items-center justify-between text-left"
|
|
471
|
+
aria-expanded=${this._localeOpen ? "true" : "false"}
|
|
472
|
+
aria-haspopup="listbox"
|
|
473
|
+
aria-labelledby="site-language-label"
|
|
474
|
+
@click=${this._toggleLocalePicker}
|
|
475
|
+
>
|
|
476
|
+
<span class="truncate">
|
|
477
|
+
${current.native}
|
|
478
|
+
<span class="ml-2 text-xs text-muted-foreground">
|
|
479
|
+
${current.tag} · ${Math.round(current.coverage * 100)}% translated
|
|
480
|
+
</span>
|
|
481
|
+
</span>
|
|
482
|
+
<span class="ml-2 text-muted-foreground" aria-hidden="true">▾</span>
|
|
483
|
+
</button>
|
|
484
|
+
|
|
485
|
+
${this._localeOpen
|
|
486
|
+
? html`
|
|
487
|
+
<div
|
|
488
|
+
class="absolute left-0 right-0 top-full z-10 mt-1 max-h-72 overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md"
|
|
489
|
+
>
|
|
490
|
+
<div class="border-b p-2">
|
|
491
|
+
<input
|
|
492
|
+
type="text"
|
|
493
|
+
class="input w-full"
|
|
494
|
+
data-locale-search
|
|
495
|
+
placeholder=${searchPlaceholder}
|
|
496
|
+
autocomplete="off"
|
|
497
|
+
spellcheck="false"
|
|
498
|
+
.value=${this._localeQuery}
|
|
499
|
+
@input=${(e: Event) => {
|
|
500
|
+
this._localeQuery = (e.target as HTMLInputElement).value;
|
|
501
|
+
}}
|
|
502
|
+
/>
|
|
503
|
+
</div>
|
|
504
|
+
<div role="listbox" class="max-h-56 overflow-auto py-1">
|
|
505
|
+
${filtered.length === 0
|
|
506
|
+
? html`
|
|
507
|
+
<div class="px-3 py-2 text-sm text-muted-foreground">
|
|
508
|
+
${noMatches}
|
|
509
|
+
</div>
|
|
510
|
+
`
|
|
511
|
+
: filtered.map(
|
|
512
|
+
(entry) => html`
|
|
513
|
+
<button
|
|
514
|
+
type="button"
|
|
515
|
+
role="option"
|
|
516
|
+
aria-selected=${entry.tag === this._siteLanguage
|
|
517
|
+
? "true"
|
|
518
|
+
: "false"}
|
|
519
|
+
class=${[
|
|
520
|
+
"flex w-full items-center justify-between gap-3 px-3 py-2 text-left text-sm hover:bg-accent",
|
|
521
|
+
entry.tag === this._siteLanguage
|
|
522
|
+
? "bg-accent/60"
|
|
523
|
+
: "",
|
|
524
|
+
].join(" ")}
|
|
525
|
+
@click=${() => this._selectLocale(entry.tag)}
|
|
526
|
+
>
|
|
527
|
+
<span class="flex flex-col">
|
|
528
|
+
<span>${entry.native}</span>
|
|
529
|
+
<span class="text-xs text-muted-foreground">
|
|
530
|
+
${entry.tag} · ${entry.english}
|
|
531
|
+
</span>
|
|
532
|
+
</span>
|
|
533
|
+
<span class="text-xs text-muted-foreground">
|
|
534
|
+
${Math.round(entry.coverage * 100)}% translated
|
|
535
|
+
</span>
|
|
536
|
+
</button>
|
|
537
|
+
`,
|
|
538
|
+
)}
|
|
539
|
+
</div>
|
|
540
|
+
</div>
|
|
541
|
+
`
|
|
542
|
+
: nothing}
|
|
543
|
+
</div>
|
|
544
|
+
`;
|
|
545
|
+
}
|
|
546
|
+
|
|
390
547
|
// ── Feed group helpers ────────────────────────────────────────────
|
|
391
548
|
|
|
392
549
|
private _syncFeedDirty() {
|
|
@@ -652,25 +809,10 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
652
809
|
>
|
|
653
810
|
${this._renderSectionTitle(this.labels.languageAndTime)}
|
|
654
811
|
<div class="field">
|
|
655
|
-
<label class="label"
|
|
656
|
-
|
|
657
|
-
class="select"
|
|
658
|
-
@change=${(e: Event) => {
|
|
659
|
-
this._siteLanguage = (e.target as HTMLSelectElement).value;
|
|
660
|
-
this._syncLocaleDirty();
|
|
661
|
-
}}
|
|
812
|
+
<label id="site-language-label" class="label"
|
|
813
|
+
>${this.labels.siteLanguage}</label
|
|
662
814
|
>
|
|
663
|
-
|
|
664
|
-
(lang) => html`
|
|
665
|
-
<option
|
|
666
|
-
value=${lang.value}
|
|
667
|
-
?selected=${this._siteLanguage === lang.value}
|
|
668
|
-
>
|
|
669
|
-
${lang.label}
|
|
670
|
-
</option>
|
|
671
|
-
`,
|
|
672
|
-
)}
|
|
673
|
-
</select>
|
|
815
|
+
${this._renderLanguagePicker()}
|
|
674
816
|
<p class="text-sm text-muted-foreground mt-1">
|
|
675
817
|
${this.labels.siteLanguageHelp}
|
|
676
818
|
</p>
|
|
@@ -43,6 +43,10 @@ export interface SettingsLabels {
|
|
|
43
43
|
markdownSupported: string;
|
|
44
44
|
siteLanguage: string;
|
|
45
45
|
siteLanguageHelp: string;
|
|
46
|
+
/** Placeholder shown inside the locale combobox search field. */
|
|
47
|
+
siteLanguageSearchPlaceholder: string;
|
|
48
|
+
/** Empty-state message when the search filters out every option. */
|
|
49
|
+
siteLanguageNoMatches: string;
|
|
46
50
|
cjkFont: string;
|
|
47
51
|
cjkFontHelp: string;
|
|
48
52
|
timeZone: string;
|
|
@@ -71,12 +75,6 @@ export interface SettingsCjkFont {
|
|
|
71
75
|
label: string;
|
|
72
76
|
}
|
|
73
77
|
|
|
74
|
-
/** Site language option for the select dropdown */
|
|
75
|
-
export interface SettingsLanguage {
|
|
76
|
-
value: string;
|
|
77
|
-
label: string;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
78
|
export interface SettingsInitialData {
|
|
81
79
|
siteName: string;
|
|
82
80
|
siteDescription: string;
|
package/src/client-auth.ts
CHANGED
|
@@ -62,10 +62,7 @@ describe("migration rehearsal", () => {
|
|
|
62
62
|
);
|
|
63
63
|
|
|
64
64
|
expect(
|
|
65
|
-
queryLocalCount(
|
|
66
|
-
persistDir,
|
|
67
|
-
"SELECT COUNT(*) AS count FROM post WHERE deleted_at IS NULL",
|
|
68
|
-
),
|
|
65
|
+
queryLocalCount(persistDir, "SELECT COUNT(*) AS count FROM post"),
|
|
69
66
|
).toBeGreaterThan(0);
|
|
70
67
|
expect(
|
|
71
68
|
queryLocalCount(persistDir, "SELECT COUNT(*) AS count FROM post_fts"),
|
|
@@ -73,5 +70,5 @@ describe("migration rehearsal", () => {
|
|
|
73
70
|
} finally {
|
|
74
71
|
rmSync(persistDir, { recursive: true, force: true });
|
|
75
72
|
}
|
|
76
|
-
},
|
|
73
|
+
}, 600_000);
|
|
77
74
|
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
-- Register apple-touch icons in the media table.
|
|
2
|
+
--
|
|
3
|
+
-- Pre-JAN-6, apple-touch icons were uploaded to storage but only referenced
|
|
4
|
+
-- via the SITE_FAVICON_APPLE_TOUCH setting; they had no corresponding media
|
|
5
|
+
-- row. The snapshot logic compensated with a hardcoded list of "storage
|
|
6
|
+
-- setting keys", and the dashboard's media manager couldn't see the file.
|
|
7
|
+
--
|
|
8
|
+
-- JAN-6 makes apple-touch a first-class media entry so snapshot/admin
|
|
9
|
+
-- behavior is uniform with avatar. This backfill creates the missing media
|
|
10
|
+
-- rows for sites that already had apple-touch icons uploaded.
|
|
11
|
+
--
|
|
12
|
+
-- The size column is a placeholder (1). We have no way to read the real byte
|
|
13
|
+
-- length from pure SQL, and the field is only used for storage budgeting
|
|
14
|
+
-- that doesn't apply to favicon assets. Re-uploading the apple-touch icon
|
|
15
|
+
-- through the dashboard refreshes size to the accurate value.
|
|
16
|
+
--
|
|
17
|
+
-- The provider is borrowed from an existing media row in the same site so
|
|
18
|
+
-- the (provider, storage_key) unique index stays consistent with how that
|
|
19
|
+
-- site's other media is tracked.
|
|
20
|
+
INSERT INTO "media" (
|
|
21
|
+
"id",
|
|
22
|
+
"site_id",
|
|
23
|
+
"filename",
|
|
24
|
+
"original_name",
|
|
25
|
+
"mime_type",
|
|
26
|
+
"size",
|
|
27
|
+
"storage_key",
|
|
28
|
+
"provider",
|
|
29
|
+
"position",
|
|
30
|
+
"media_kind",
|
|
31
|
+
"created_at",
|
|
32
|
+
"updated_at"
|
|
33
|
+
)
|
|
34
|
+
SELECT
|
|
35
|
+
'med_apt_' || substr(s."site_id", 5),
|
|
36
|
+
s."site_id",
|
|
37
|
+
'apple-touch-icon.png',
|
|
38
|
+
'apple-touch-icon.png',
|
|
39
|
+
'image/png',
|
|
40
|
+
1,
|
|
41
|
+
s."value",
|
|
42
|
+
COALESCE(
|
|
43
|
+
(
|
|
44
|
+
SELECT m."provider"
|
|
45
|
+
FROM "media" m
|
|
46
|
+
WHERE m."site_id" = s."site_id"
|
|
47
|
+
ORDER BY m."created_at"
|
|
48
|
+
LIMIT 1
|
|
49
|
+
),
|
|
50
|
+
'r2'
|
|
51
|
+
),
|
|
52
|
+
'a0',
|
|
53
|
+
'image',
|
|
54
|
+
s."updated_at",
|
|
55
|
+
s."updated_at"
|
|
56
|
+
FROM "site_setting" s
|
|
57
|
+
WHERE s."key" = 'SITE_FAVICON_APPLE_TOUCH'
|
|
58
|
+
AND s."value" IS NOT NULL
|
|
59
|
+
AND trim(s."value") <> ''
|
|
60
|
+
AND NOT EXISTS (
|
|
61
|
+
SELECT 1
|
|
62
|
+
FROM "media" m
|
|
63
|
+
WHERE m."site_id" = s."site_id"
|
|
64
|
+
AND m."storage_key" = s."value"
|
|
65
|
+
);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
DELETE FROM `post` WHERE `deleted_at` IS NOT NULL;--> statement-breakpoint
|
|
2
|
+
DROP INDEX `idx_post_site_thread_live_created`;--> statement-breakpoint
|
|
3
|
+
DROP INDEX `idx_post_site_status_deleted_published`;--> statement-breakpoint
|
|
4
|
+
DROP INDEX `idx_post_site_status_deleted_activity`;--> statement-breakpoint
|
|
5
|
+
DROP INDEX `idx_post_site_root_live_published_activity`;--> statement-breakpoint
|
|
6
|
+
DROP INDEX `idx_post_site_root_live_draft_updated`;--> statement-breakpoint
|
|
7
|
+
DROP INDEX `idx_post_site_reply_live_thread_created`;--> statement-breakpoint
|
|
8
|
+
DROP INDEX `idx_post_site_featured_live_featured_at`;--> statement-breakpoint
|
|
9
|
+
CREATE INDEX `idx_post_site_thread_created` ON `post` (`site_id`,`thread_id`,`created_at`,`id`);--> statement-breakpoint
|
|
10
|
+
CREATE INDEX `idx_post_site_status_published` ON `post` (`site_id`,`status`,`published_at`);--> statement-breakpoint
|
|
11
|
+
CREATE INDEX `idx_post_site_status_activity` ON `post` (`site_id`,`status`,`last_activity_at`);--> statement-breakpoint
|
|
12
|
+
CREATE INDEX `idx_post_site_root_published_activity` ON `post` (`site_id`,`last_activity_at`,`id`) WHERE "post"."reply_to_id" IS NULL AND "post"."status" = 'published';--> statement-breakpoint
|
|
13
|
+
CREATE INDEX `idx_post_site_root_draft_updated` ON `post` (`site_id`,`updated_at`,`id`) WHERE "post"."reply_to_id" IS NULL AND "post"."status" = 'draft';--> statement-breakpoint
|
|
14
|
+
CREATE INDEX `idx_post_site_reply_thread_created` ON `post` (`site_id`,`thread_id`,`created_at`,`id`) WHERE "post"."reply_to_id" IS NOT NULL AND "post"."status" = 'published';--> statement-breakpoint
|
|
15
|
+
CREATE INDEX `idx_post_site_featured_featured_at` ON `post` (`site_id`,`featured_at`,`thread_id`,`id`) WHERE "post"."status" = 'published' AND "post"."featured_at" IS NOT NULL;--> statement-breakpoint
|
|
16
|
+
ALTER TABLE `post` DROP COLUMN `deleted_at`;
|