123 lines
5.6 KiB
TypeScript
123 lines
5.6 KiB
TypeScript
import type { APIRoute } from "astro";
|
|
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)
|
|
* @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
|
|
[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 extra optional fields
|
|
// 1. Type must be a valid string
|
|
if (typeof value.type !== "string") {
|
|
throw new Error(`Invalid "type" for attribution "${title}" (${JSON.stringify(value)})`);
|
|
}
|
|
if (!value.type) {
|
|
throw new Error(`Missing "type" for attribution "${title}" (${JSON.stringify(value)})`);
|
|
}
|
|
// 2. Items must be a valid list of strings/objects
|
|
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)})`);
|
|
}
|
|
items.forEach((item) => {
|
|
if (!item) {
|
|
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) {
|
|
if (typeof value.notes !== "string" || !value.notes) {
|
|
throw new Error(`Invalid "notes" for attribution "${title}" (${JSON.stringify(value)})`);
|
|
}
|
|
}
|
|
});
|
|
// Check date for copyrights
|
|
if (typeof copyright.date !== "string") {
|
|
throw new Error(`Invalid "date" for copyright (${JSON.stringify(copyright)})`);
|
|
}
|
|
if (!copyright.date) {
|
|
throw new Error(`Missing "date" for copyright (${JSON.stringify(copyright)})`);
|
|
}
|
|
// Check notes for additional copyrights
|
|
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)})`);
|
|
}
|
|
additionals.forEach((additional) => {
|
|
if (typeof additional.notes !== "string" || !additional.notes) {
|
|
throw new Error(`Invalid "notes" for additional copyright (${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" } })
|
|
};
|