105 lines
3.1 KiB
TypeScript
105 lines
3.1 KiB
TypeScript
/**
|
|
* Repairs the rows 0011 got wrong. 0011 took any path containing "favicon",
|
|
* or ending in `.svg`, for an icon, so artwork such as
|
|
* `/wp-content/uploads/2022/04/mir-logo-favicon.png` lost its `thumbnailUrl`,
|
|
* and its dump showed as a pull-quote instead of an image in the journal
|
|
* mosaic.
|
|
*
|
|
* This rule judges the file name and its folder instead. It spares
|
|
* `mir-logo-favicon.png` and catches `icon_SEARCH.png`, which 0011 missed.
|
|
*
|
|
* @module
|
|
*/
|
|
|
|
import type { DatabaseSync } from "node:sqlite";
|
|
|
|
/** 0011's rule, copied as it shipped, to tell which rows it moved. */
|
|
function wasIconUrl0011(raw: string): boolean {
|
|
let pathname: string;
|
|
try {
|
|
pathname = new URL(raw).pathname;
|
|
} catch {
|
|
return false;
|
|
}
|
|
return /favicon|apple-touch-icon|\/icons?\//i.test(pathname) ||
|
|
/\.(ico|svg)$/i.test(pathname);
|
|
}
|
|
|
|
/**
|
|
* Checks whether a URL is a site icon. It is one when the file ends in `.ico`,
|
|
* sits in a `favicon(s)` or `icon(s)` folder, or is named `favicon`,
|
|
* `apple-touch-icon` or `icon`, alone or followed by a digit, `-`, `_` or `.`.
|
|
*/
|
|
function isIconUrl(raw: string): boolean {
|
|
let pathname: string;
|
|
try {
|
|
pathname = new URL(raw).pathname;
|
|
} catch {
|
|
return false;
|
|
}
|
|
const segments = pathname.split("/").filter(Boolean);
|
|
const filename = segments.pop() ?? "";
|
|
if (/\.ico$/i.test(filename)) return true;
|
|
if (segments.some((s) => /^(favicons?|icons?)$/i.test(s))) return true;
|
|
const basename = filename.replace(/\.[^.]+$/, "");
|
|
return /^(favicon|apple-touch-icon|icon)(?=$|[-_.\d])/i.test(basename);
|
|
}
|
|
|
|
/**
|
|
* Run once by `runMigrations`. It moves a `faviconUrl` back to `thumbnailUrl`
|
|
* only when 0011's rule takes it for an icon and this one doesn't. That also
|
|
* catches the ones a later fetch stored that this rule doesn't recognise, such
|
|
* as a `logo.svg`. It then moves the icons 0011 missed.
|
|
*/
|
|
export function up(db: DatabaseSync): void {
|
|
const rows = db.prepare(
|
|
`SELECT id, rich_content FROM dumps
|
|
WHERE kind = 'url' AND rich_content IS NOT NULL;`,
|
|
).all() as { id: string; rich_content: string }[];
|
|
|
|
const update = db.prepare(
|
|
`UPDATE dumps SET rich_content = ? WHERE id = ?;`,
|
|
);
|
|
|
|
let restored = 0;
|
|
let reclassified = 0;
|
|
for (const row of rows) {
|
|
let rich: { thumbnailUrl?: string; faviconUrl?: string };
|
|
try {
|
|
rich = JSON.parse(row.rich_content);
|
|
} catch {
|
|
continue;
|
|
}
|
|
|
|
if (
|
|
rich.faviconUrl && !rich.thumbnailUrl &&
|
|
wasIconUrl0011(rich.faviconUrl) && !isIconUrl(rich.faviconUrl)
|
|
) {
|
|
const { faviconUrl: _dropped, ...rest } = rich;
|
|
update.run(
|
|
JSON.stringify({ ...rest, thumbnailUrl: rich.faviconUrl }),
|
|
row.id,
|
|
);
|
|
restored++;
|
|
continue;
|
|
}
|
|
|
|
if (
|
|
rich.thumbnailUrl && !rich.faviconUrl && isIconUrl(rich.thumbnailUrl)
|
|
) {
|
|
const { thumbnailUrl: _dropped, ...rest } = rich;
|
|
update.run(
|
|
JSON.stringify({ ...rest, faviconUrl: rich.thumbnailUrl }),
|
|
row.id,
|
|
);
|
|
reclassified++;
|
|
}
|
|
}
|
|
|
|
if (restored > 0 || reclassified > 0) {
|
|
console.log(
|
|
`[migrate] 0012: restored ${restored} thumbnail(s), reclassified ${reclassified} icon(s)`,
|
|
);
|
|
}
|
|
}
|