Add GPG key
This commit is contained in:
parent
37fba2f602
commit
790ba15725
9 changed files with 361 additions and 116 deletions
194
astro.config.mjs
194
astro.config.mjs
|
|
@ -12,44 +12,162 @@ export default defineConfig({
|
|||
htaccessIntegration({
|
||||
generateHtaccessFile: import.meta.env.APACHE_CONFIG === "true",
|
||||
redirects: [
|
||||
{ match: /^\/@\/(aryion|ekas?(portal)?)\b/, url: "https://aryion.com/g4/user/BadManners" },
|
||||
{ match: /^\/@\/(blue[_-]?sky|bsky)\b/, url: "https://bsky.app/profile/badmanners.xyz" },
|
||||
{ match: /^\/@\/(buymeacoffee|buy_me_a_coffee|buy-me-a-coffee|bmac)\b/, url: "https://www.buymeacoffee.com/BadMannersXYZ" },
|
||||
{ match: /^\/@\/carrd\b/, url: "https://badmanners.carrd.co" },
|
||||
{ match: /^\/@\/code[_-]?berg\b/, url: "https://codeberg.org/BadManners" },
|
||||
{ match: /^\/@\/co[_-]?host\b/, url: "https://cohost.org/BadManners" },
|
||||
{ match: /^\/@\/discord\b/, url: "/#discord" },
|
||||
{ match: /^\/@\/e[_-]?mail\b/, url: "/#e-mail" },
|
||||
{ match: /^\/@\/(fur[_-]?affinity|fa)\b/, url: "https://www.furaffinity.net/user/badmanners" },
|
||||
{ match: /^\/@\/(gallery|stor(y|ies)|games?)\b/, url: "https://gallery.badmanners.xyz" },
|
||||
{ match: /^\/@\/(git[_-]?hub|gh)\b/, url: "https://github.com/BadMannersXYZ" },
|
||||
{ match: /^\/@\/git[_-]?lab\b/, url: "https://gitlab.com/Bad_Manners" },
|
||||
{ match: /^\/@\/(git[_-]?gud|sapp?hire)\b/, url: "https://gitgud.io/BadMannersXYZ" },
|
||||
{ match: /^\/@\/(google|g[_-]?mail)\b/, url: "/#gmail" },
|
||||
{ match: /^\/@\/gum[_-]?road\b/, url: "https://badmanners.gumroad.com" },
|
||||
{ match: /^\/@\/(ink[_-]?bunny|ib)\b/, url: "https://inkbunny.net/BadManners" },
|
||||
{ match: /^\/@\/itaku\b/, url: "https://itaku.ee/profile/badmanners" },
|
||||
{ match: /^\/@\/itch\b/, url: "https://bad-manners.itch.io" },
|
||||
{ match: /^\/@\/keybase\b/, url: "https://keybase.io/badmanners" },
|
||||
{ match: /^\/@\/keyoxide\b/, url: "https://keyoxide.org/aspe:keyoxide.org:UWYBVFCBFXTVUF2U6FS6AYJHLU" },
|
||||
{ match: /^\/@\/ko[._-]?fi\b/, url: "https://ko-fi.com/badmanners" },
|
||||
{ match: /^\/@\/(mastodon|meow[._-]?social|gulp[._-]?cafe)\b/, url: "https://meow.social/@BadManners" },
|
||||
{ match: /^\/@\/neo[_-]?cities\b/, url: "https://badmanners.neocities.org" },
|
||||
{ match: /^\/@\/picarto\b/, url: "https://www.picarto.tv/BadManners" },
|
||||
{ match: /^\/@\/pillow[_-]?fort\b/, url: "https://www.pillowfort.social/BadManners" },
|
||||
{ match: /^\/@\/pronouns?\b/, url: "https://pronouns.cc/@BadManners" },
|
||||
{ match: /^\/@\/redd\.?it\b/, url: "https://www.reddit.com/user/BadManners_" },
|
||||
{ match: /^\/@\/signal\b/, url: "https://signal.me/#eu/ytt_rk0fFmAB2JAW-x2PbUiJyc_H3kYmfL_Pq4QNh5QIDsiFtjdFHaqFRs1D36tB" },
|
||||
{ match: /^\/@\/(so[_-]?furry|sf)\b/, url: "https://bad-manners.sofurry.com" },
|
||||
{ match: /^\/@\/ssh\b/, url: "/ssh.pub" },
|
||||
{ match: /^\/@\/steam(community|powered)?\b/, url: "https://steamcommunity.com/id/badmanners_" },
|
||||
{ match: /^\/@\/subscribe[_-]?star\b/, url: "https://subscribestar.adult/bad-manners" },
|
||||
{ match: /^\/@\/(telegram|t\.me)\b/, url: "https://t.me/bad_manners" },
|
||||
{ match: /^\/@\/tumblr\b/, url: "https://www.tumblr.com/badmannersxyz" },
|
||||
{ match: /^\/@\/twitch\b/, url: "https://www.twitch.tv/bad__manners" },
|
||||
{ match: /^\/@\/weasyl\b/, url: "https://www.weasyl.com/~badmanners" },
|
||||
{ match: /^\/@\/(x|twitter)\b/, url: "https://x.com/BadManners__" },
|
||||
{ match: /^\/@\/(you[_-]?tube|youtu\.be|yt)\b/, url: "https://www.youtube.com/@BadMannersXYZ" },
|
||||
{
|
||||
match: /^\/@\/(aryion|ekas?(portal)?)\b/,
|
||||
url: "https://aryion.com/g4/user/BadManners",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/(blue[_-]?sky|bsky)\b/,
|
||||
url: "https://bsky.app/profile/badmanners.xyz",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/(buymeacoffee|buy_me_a_coffee|buy-me-a-coffee|bmac)\b/,
|
||||
url: "https://www.buymeacoffee.com/BadMannersXYZ",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/carrd\b/,
|
||||
url: "https://badmanners.carrd.co",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/code[_-]?berg\b/,
|
||||
url: "https://codeberg.org/BadManners",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/co[_-]?host\b/,
|
||||
url: "https://cohost.org/BadManners",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/discord\b/,
|
||||
url: "/#discord",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/e[_-]?mail\b/,
|
||||
url: "/#e-mail",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/(fur[_-]?affinity|fa)\b/,
|
||||
url: "https://www.furaffinity.net/user/badmanners",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/(gallery|stor(y|ies)|games?)\b/,
|
||||
url: "https://gallery.badmanners.xyz",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/(git[_-]?hub|gh)\b/,
|
||||
url: "https://github.com/BadMannersXYZ",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/git[_-]?lab\b/,
|
||||
url: "https://gitlab.com/Bad_Manners",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/(git[_-]?gud|sapp?hire)\b/,
|
||||
url: "https://gitgud.io/BadMannersXYZ",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/(google|g[_-]?mail)\b/,
|
||||
url: "/#gmail",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/(gpg|pgp)\b/,
|
||||
url: "/gpg.pub",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/gum[_-]?road\b/,
|
||||
url: "https://badmanners.gumroad.com",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/(ink[_-]?bunny|ib)\b/,
|
||||
url: "https://inkbunny.net/BadManners",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/itaku\b/,
|
||||
url: "https://itaku.ee/profile/badmanners",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/itch\b/,
|
||||
url: "https://bad-manners.itch.io",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/keybase\b/,
|
||||
url: "https://keybase.io/badmanners",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/keyoxide\b/,
|
||||
url: "https://keyoxide.org/aspe:keyoxide.org:UWYBVFCBFXTVUF2U6FS6AYJHLU",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/ko[._-]?fi\b/,
|
||||
url: "https://ko-fi.com/badmanners",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/(mastodon|meow[._-]?social|gulp[._-]?cafe)\b/,
|
||||
url: "https://meow.social/@BadManners",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/neo[_-]?cities\b/,
|
||||
url: "https://badmanners.neocities.org",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/picarto\b/,
|
||||
url: "https://www.picarto.tv/BadManners",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/pillow[_-]?fort\b/,
|
||||
url: "https://www.pillowfort.social/BadManners",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/pronouns?\b/,
|
||||
url: "https://pronouns.cc/@BadManners",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/redd\.?it\b/,
|
||||
url: "https://www.reddit.com/user/BadManners_",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/signal\b/,
|
||||
url: "https://signal.me/#eu/ytt_rk0fFmAB2JAW-x2PbUiJyc_H3kYmfL_Pq4QNh5QIDsiFtjdFHaqFRs1D36tB",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/(so[_-]?furry|sf)\b/,
|
||||
url: "https://bad-manners.sofurry.com",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/ssh\b/,
|
||||
url: "/ssh.pub",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/steam(community|powered)?\b/,
|
||||
url: "https://steamcommunity.com/id/badmanners_",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/subscribe[_-]?star\b/,
|
||||
url: "https://subscribestar.adult/bad-manners",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/(telegram|t\.me)\b/,
|
||||
url: "https://t.me/bad_manners",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/tumblr\b/,
|
||||
url: "https://www.tumblr.com/badmannersxyz",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/twitch\b/,
|
||||
url: "https://www.twitch.tv/bad__manners",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/weasyl\b/,
|
||||
url: "https://www.weasyl.com/~badmanners",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/(x|twitter)\b/,
|
||||
url: "https://x.com/BadManners__",
|
||||
},
|
||||
{
|
||||
match: /^\/@\/(you[_-]?tube|youtu\.be|yt)\b/,
|
||||
url: "https://www.youtube.com/@BadMannersXYZ",
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
|
|
|
|||
12
package-lock.json
generated
12
package-lock.json
generated
|
|
@ -1,19 +1,19 @@
|
|||
{
|
||||
"name": "badmanners.xyz",
|
||||
"version": "2.1.9",
|
||||
"version": "2.1.10",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "badmanners.xyz",
|
||||
"version": "2.1.9",
|
||||
"version": "2.1.10",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@astrojs/check": "^0.9.2",
|
||||
"@astrojs/rss": "^4.0.7",
|
||||
"@astrojs/tailwind": "^5.1.0",
|
||||
"astro": "^4.13.3",
|
||||
"astro-htaccess": "^0.1.1",
|
||||
"astro-htaccess": "^0.1.2",
|
||||
"date-fns": "^3.6.0",
|
||||
"tailwindcss": "^3.4.9",
|
||||
"tippy.js": "^6.3.7",
|
||||
|
|
@ -2239,9 +2239,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/astro-htaccess": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/astro-htaccess/-/astro-htaccess-0.1.1.tgz",
|
||||
"integrity": "sha512-GbsQZkAk62DW1CzZ5KgEuRYuZXqZXFeKt9w8MMAacmJoV5F+BfKws4x4gszpKqUi1wXSKG6psaPRPQrYIGsu0Q==",
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/astro-htaccess/-/astro-htaccess-0.1.2.tgz",
|
||||
"integrity": "sha512-ki0y7bjhfeocMkPefQA7OT/BsFMYu6hgljkiK2No58FMgALn9w/nj+N9NMWykHMUe9pHUa209/EVtOuoO5UkQw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"astro": "^4.0.0"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "badmanners.xyz",
|
||||
"type": "module",
|
||||
"version": "2.1.9",
|
||||
"version": "2.1.10",
|
||||
"scripts": {
|
||||
"postinstall": "astro sync",
|
||||
"dev": "astro dev",
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
"@astrojs/rss": "^4.0.7",
|
||||
"@astrojs/tailwind": "^5.1.0",
|
||||
"astro": "^4.13.3",
|
||||
"astro-htaccess": "^0.1.1",
|
||||
"astro-htaccess": "^0.1.2",
|
||||
"date-fns": "^3.6.0",
|
||||
"tailwindcss": "^3.4.9",
|
||||
"tippy.js": "^6.3.7",
|
||||
|
|
|
|||
52
public/gpg.pub
Normal file
52
public/gpg.pub
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
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
|
||||
ASNiWqvMfg==
|
||||
=VLiL
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
15
src/components/icons/IconKey.astro
Normal file
15
src/components/icons/IconKey.astro
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
import SVGIcon from "./SVGIcon.astro";
|
||||
|
||||
type Props = {
|
||||
width: string;
|
||||
height: string;
|
||||
class?: string;
|
||||
};
|
||||
---
|
||||
|
||||
<SVGIcon {...Astro.props} viewBox="0 0 512 512">
|
||||
<path
|
||||
d="M336 352c97.2 0 176-78.8 176-176S433.2 0 336 0S160 78.8 160 176c0 18.7 2.9 36.8 8.3 53.7L7 391c-4.5 4.5-7 10.6-7 17l0 80c0 13.3 10.7 24 24 24l80 0c13.3 0 24-10.7 24-24l0-40 40 0c13.3 0 24-10.7 24-24l0-40 40 0c6.4 0 12.5-2.5 17-7l33.3-33.3c16.9 5.4 35 8.3 53.7 8.3zM376 96a40 40 0 1 1 0 80 40 40 0 1 1 0-80z"
|
||||
></path>
|
||||
</SVGIcon>
|
||||
|
|
@ -2,6 +2,7 @@ export { default as IconArrowUpRightFromSquare } from "./IconArrowUpRightFromSqu
|
|||
export { default as IconBriefcase } from "./IconBriefcase.astro";
|
||||
export { default as IconCommentDots } from "./IconCommentDots.astro";
|
||||
export { default as IconEnvelope } from "./IconEnvelope.astro";
|
||||
export { default as IconKey } from "./IconKey.astro";
|
||||
export { default as IconLink } from "./IconLink.astro";
|
||||
export { default as IconMoon } from "./IconMoon.astro";
|
||||
export { default as IconSquareRSS } from "./IconSquareRSS.astro";
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ type = "logo"
|
|||
notes = "The briefcase logo is copyrighted and trademarked by me. All rights reserved."
|
||||
|
||||
[[copyright.additional]]
|
||||
title = "Sam Brendan"
|
||||
type = "character"
|
||||
notes = "The character/fursona Sam Brendan is copyrighted and trademarked by me. All rights reserved."
|
||||
|
||||
|
|
@ -108,6 +107,7 @@ items = [
|
|||
"briefcase",
|
||||
"comment-dots",
|
||||
"envelope",
|
||||
"key",
|
||||
"link",
|
||||
"moon",
|
||||
"square-rss",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
import { readFile } from "node:fs/promises";
|
||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||
import { IconEnvelope, IconBriefcase, IconLink, IconCommentDots, IconSSH } from "../components/icons";
|
||||
import { IconEnvelope, IconBriefcase, IconKey, IconLink, IconCommentDots, IconSSH } from "../components/icons";
|
||||
import {
|
||||
IconBluesky,
|
||||
IconCodeberg,
|
||||
|
|
@ -33,6 +34,9 @@ import {
|
|||
IconX,
|
||||
IconYouTube,
|
||||
} from "../components/icons/brands";
|
||||
|
||||
const gpgKey = await readFile("./public/gpg.pub", { encoding: "utf-8" });
|
||||
const sshKey = await readFile("./public/ssh.pub", { encoding: "utf-8" });
|
||||
---
|
||||
|
||||
<BaseLayout>
|
||||
|
|
@ -301,6 +305,23 @@ import {
|
|||
<p class="sr-only select-none">badmanners.vore@gmail.com</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
id="gpg"
|
||||
class="u-key text-link group block w-full py-2 transition-colors motion-reduce:transition-none"
|
||||
href="/gpg.pub"
|
||||
aria-label="GPG public key"
|
||||
data-clipboard={gpgKey}
|
||||
data-noun="GPG key"
|
||||
>
|
||||
<IconKey
|
||||
height="1.75rem"
|
||||
width="1.75rem"
|
||||
class="inline transition-transform group-hover:scale-150 group-focus:scale-150 motion-reduce:transition-none motion-reduce:group-hover:scale-100 motion-reduce:group-focus:scale-100"
|
||||
/>
|
||||
<p class="sr-only select-none">GPG public key</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
id="inkbunny"
|
||||
|
|
@ -378,7 +399,9 @@ import {
|
|||
width="1.75rem"
|
||||
class="inline transition-transform group-hover:scale-150 group-focus:scale-150 motion-reduce:transition-none motion-reduce:group-hover:scale-100 motion-reduce:group-focus:scale-100"
|
||||
/>
|
||||
<p class="sr-only select-none"><span class="p-uid">aspe:keyoxide.org:UWYBVFCBFXTVUF2U6FS6AYJHLU</span> on Keyoxide</p>
|
||||
<p class="sr-only select-none">
|
||||
<span class="p-uid">aspe:keyoxide.org:UWYBVFCBFXTVUF2U6FS6AYJHLU</span> on Keyoxide
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
|
|
@ -499,7 +522,7 @@ import {
|
|||
class="u-key text-link group block w-full py-2 transition-colors motion-reduce:transition-none"
|
||||
href="/ssh.pub"
|
||||
aria-label="SSH public key"
|
||||
data-clipboard="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ3QAZd3E95gxef2kiXppWa/xhcwBtnKMZJaW6s4d7Tm Bad Manners <me@badmanners.xyz>"
|
||||
data-clipboard={sshKey}
|
||||
data-noun="SSH key"
|
||||
>
|
||||
<IconSSH
|
||||
|
|
|
|||
|
|
@ -3,71 +3,88 @@ import { readFile } from "node:fs/promises";
|
|||
import { parse } from "toml";
|
||||
|
||||
/**
|
||||
* Verify attributions and copyright according to the [Creative Commons recommended practices](https://wiki.creativecommons.org/wiki/Recommended_practices_for_attribution)
|
||||
* Makes sure the copyright follows the TASL format. T = title (or description), A = author, S = source, L = license.
|
||||
* @param copyright
|
||||
*/
|
||||
function validateTASL(copyright: any) {
|
||||
const title = copyright.title ?? copyright.description;
|
||||
if (typeof title !== "string" || !title) {
|
||||
throw new Error(`Missing "title" and/or "description" for attribution (${JSON.stringify(copyright)})`);
|
||||
}
|
||||
// Author must be a valid string or object or list
|
||||
const authors = [copyright.author].flat();
|
||||
if (authors.length === 0) {
|
||||
throw new Error(`Missing "author" for attribution "${title}" (${JSON.stringify(copyright)})`);
|
||||
}
|
||||
authors.forEach((author) => {
|
||||
if (!author) {
|
||||
throw new Error(`Missing "author" for attribution "${title}" (${JSON.stringify(copyright)})`);
|
||||
}
|
||||
if (typeof author !== "object" && typeof author !== "string") {
|
||||
throw new Error(
|
||||
`Invalid "${typeof author}" type for "author"${JSON.stringify(author)} for attribution "${title}" (${JSON.stringify(copyright)})`,
|
||||
);
|
||||
}
|
||||
if (typeof author === "object" && !(author.name || author.url)) {
|
||||
throw new Error(
|
||||
`Missing both name and URL for "author" ${JSON.stringify(author)} for attribution "${title}" (${JSON.stringify(copyright)})`,
|
||||
);
|
||||
}
|
||||
});
|
||||
// Source must be a valid string or list of strings
|
||||
const sources = [copyright.source].flat();
|
||||
if (sources.length === 0) {
|
||||
throw new Error(`Missing "source" for attribution "${title}" (${JSON.stringify(copyright)})`);
|
||||
}
|
||||
sources.forEach((source) => {
|
||||
if (!source) {
|
||||
throw new Error(`Missing "source" for attribution "${title}" (${JSON.stringify(copyright)})`);
|
||||
}
|
||||
if (typeof source !== "object" && typeof source !== "string") {
|
||||
throw new Error(
|
||||
`Invalid "${typeof source}" type for "source"${JSON.stringify(source)} for attribution "${title}" (${JSON.stringify(copyright)})`,
|
||||
);
|
||||
}
|
||||
if (typeof source === "object" && !source.url) {
|
||||
throw new Error(
|
||||
`Missing URL for "source" ${JSON.stringify(source)} for attribution "${title}" (${JSON.stringify(copyright)})`,
|
||||
);
|
||||
}
|
||||
});
|
||||
// License must be a valid string or object or list
|
||||
const licenses = [copyright.license].flat();
|
||||
if (licenses.length === 0) {
|
||||
throw new Error(`Missing "license" for attribution "${title}" (${JSON.stringify(copyright)})`);
|
||||
}
|
||||
licenses.forEach((license) => {
|
||||
if (!license) {
|
||||
throw new Error(`Missing "license" for attribution "${title}" (${JSON.stringify(copyright)})`);
|
||||
}
|
||||
if (typeof license !== "object" && typeof license !== "string") {
|
||||
throw new Error(
|
||||
`Invalid "${typeof license}" type for "license"${JSON.stringify(license)} for attribution "${title}" (${JSON.stringify(copyright)})`,
|
||||
);
|
||||
}
|
||||
if (typeof license === "object" && !(license.name || license.url)) {
|
||||
throw new Error(
|
||||
`Missing both name and URL for "license" ${JSON.stringify(license)} for attribution "${title}" (${JSON.stringify(copyright)})`,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies attributions and copyright according to the [Creative Commons recommended practices](https://wiki.creativecommons.org/wiki/Recommended_practices_for_attribution)
|
||||
* @param copyright Unparsed TOML copyright information.
|
||||
*/
|
||||
function verifyAttributions(licenses: string) {
|
||||
const { copyright, attributions } = parse(licenses);
|
||||
// Make sure each copyright and attribution follows the TASL format.
|
||||
// - T: title (or description)
|
||||
// - A: author
|
||||
// - S: source
|
||||
// - L: license
|
||||
// - other fields that have custom validation: type, notes, items
|
||||
// Make sure each copyright and attribution follows the TASL format,
|
||||
// and that other fields (type, notes, items) pass their custom validation.
|
||||
[copyright, attributions].flat().forEach((value) => {
|
||||
// Title or description must be a valid string
|
||||
const title = value.title ?? value.description;
|
||||
if (typeof title !== "string" || !title) {
|
||||
throw new Error(`Missing "title" and/or "description" for attribution (${JSON.stringify(value)})`);
|
||||
}
|
||||
// Author must be a valid string or object or list
|
||||
const authors = [value.author].flat();
|
||||
if (authors.length === 0) {
|
||||
throw new Error(`Missing "author" for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
}
|
||||
authors.forEach((author) => {
|
||||
if (!author) {
|
||||
throw new Error(`Missing "author" for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
}
|
||||
if (typeof author !== "object" && typeof author !== "string") {
|
||||
throw new Error(`Invalid "${typeof author}" type for "author"${JSON.stringify(author)} for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
}
|
||||
if (typeof author === "object" && !(author.name || author.url)) {
|
||||
throw new Error(`Missing both name and URL for "author" ${JSON.stringify(author)} for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
}
|
||||
});
|
||||
// Source must be a valid string or list of strings
|
||||
const sources = [value.source].flat();
|
||||
if (sources.length === 0) {
|
||||
throw new Error(`Missing "source" for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
}
|
||||
sources.forEach((source) => {
|
||||
if (!source) {
|
||||
throw new Error(`Missing "source" for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
}
|
||||
if (typeof source !== "object" && typeof source !== "string") {
|
||||
throw new Error(`Invalid "${typeof source}" type for "source"${JSON.stringify(source)} for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
}
|
||||
if (typeof source === "object" && !(source.url)) {
|
||||
throw new Error(`Missing URL for "source" ${JSON.stringify(source)} for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
}
|
||||
});
|
||||
// License must be a valid string or object or list
|
||||
const licenses = [value.license].flat();
|
||||
if (licenses.length === 0) {
|
||||
throw new Error(`Missing "license" for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
}
|
||||
licenses.forEach((license) => {
|
||||
if (!license) {
|
||||
throw new Error(`Missing "license" for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
}
|
||||
if (typeof license !== "object" && typeof license !== "string") {
|
||||
throw new Error(`Invalid "${typeof license}" type for "license"${JSON.stringify(license)} for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
}
|
||||
if (typeof license === "object" && !(license.name || license.url)) {
|
||||
throw new Error(`Missing both name and URL for "license" ${JSON.stringify(license)} for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
}
|
||||
});
|
||||
// Validate TASL
|
||||
validateTASL(value);
|
||||
const title = copyright.title ?? copyright.description;
|
||||
// Validate extra optional fields
|
||||
// 1. Type must be a valid string
|
||||
if (typeof value.type !== "string") {
|
||||
|
|
@ -80,13 +97,17 @@ function verifyAttributions(licenses: string) {
|
|||
if ("items" in value) {
|
||||
const items = value.items;
|
||||
if (!Array.isArray(items)) {
|
||||
throw new Error(`Invalid non-array "items" ${JSON.stringify(items)} for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
throw new Error(
|
||||
`Invalid non-array "items" ${JSON.stringify(items)} for attribution "${title}" (${JSON.stringify(value)})`,
|
||||
);
|
||||
}
|
||||
items.forEach((item) => {
|
||||
if (!item) {
|
||||
throw new Error(`Invalid item ${JSON.stringify} in "items" for attribution "${title}" (${JSON.stringify(value)})`);
|
||||
throw new Error(
|
||||
`Invalid item ${JSON.stringify} in "items" for attribution "${title}" (${JSON.stringify(value)})`,
|
||||
);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
// 3. Type must be a valid string
|
||||
if ("notes" in value) {
|
||||
|
|
@ -106,18 +127,33 @@ function verifyAttributions(licenses: string) {
|
|||
if ("additional" in copyright) {
|
||||
const additionals = copyright.additional;
|
||||
if (!Array.isArray(additionals)) {
|
||||
throw new Error(`Invalid non-array "additional" ${JSON.stringify(additionals)} for copyright (${JSON.stringify(copyright)})`);
|
||||
throw new Error(
|
||||
`Invalid non-array "additional" ${JSON.stringify(additionals)} for copyright (${JSON.stringify(copyright)})`,
|
||||
);
|
||||
}
|
||||
additionals.forEach((additional) => {
|
||||
if (typeof additional.notes !== "string" || !additional.notes) {
|
||||
throw new Error(`Invalid "notes" for additional copyright (${JSON.stringify(additional)})`);
|
||||
}
|
||||
})
|
||||
if (typeof additional.type !== "string" || !additional.type) {
|
||||
throw new Error(`Invalid "type" for additional copyright (${JSON.stringify(additional)})`);
|
||||
}
|
||||
// Check TASL + date if title or description is present
|
||||
if (additional.title || additional.description) {
|
||||
validateTASL(additional);
|
||||
if (typeof additional.date !== "string") {
|
||||
throw new Error(`Invalid "date" for additional (${JSON.stringify(additional)})`);
|
||||
}
|
||||
if (!additional.date) {
|
||||
throw new Error(`Missing "date" for additional (${JSON.stringify(additional)})`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const GET: APIRoute = async () => {
|
||||
const licenses = await readFile("./src/data/licenses.toml", { encoding: "utf-8" });
|
||||
verifyAttributions(licenses);
|
||||
return new Response(licenses, { headers: { "Content-Type": "application/toml; charset=utf-8" } })
|
||||
return new Response(licenses, { headers: { "Content-Type": "application/toml; charset=utf-8" } });
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue