Nicholai 16cee69250 __Admin dashboard scaffolded with D1 database and R2 file uploads__
This commit implements the core admin dashboard functionality including NextAuth authentication, Cloudflare D1 database integration with complete schema, and Cloudflare R2 file upload system for portfolio images. Features include artist management, appointment scheduling, and data migration capabilities.
2025-09-17 16:08:34 -06:00

597 lines
16 KiB
JavaScript

'use strict';
function parseContentType(str) {
if (str.length === 0)
return;
const params = Object.create(null);
let i = 0;
// Parse type
for (; i < str.length; ++i) {
const code = str.charCodeAt(i);
if (TOKEN[code] !== 1) {
if (code !== 47/* '/' */ || i === 0)
return;
break;
}
}
// Check for type without subtype
if (i === str.length)
return;
const type = str.slice(0, i).toLowerCase();
// Parse subtype
const subtypeStart = ++i;
for (; i < str.length; ++i) {
const code = str.charCodeAt(i);
if (TOKEN[code] !== 1) {
// Make sure we have a subtype
if (i === subtypeStart)
return;
if (parseContentTypeParams(str, i, params) === undefined)
return;
break;
}
}
// Make sure we have a subtype
if (i === subtypeStart)
return;
const subtype = str.slice(subtypeStart, i).toLowerCase();
return { type, subtype, params };
}
function parseContentTypeParams(str, i, params) {
while (i < str.length) {
// Consume whitespace
for (; i < str.length; ++i) {
const code = str.charCodeAt(i);
if (code !== 32/* ' ' */ && code !== 9/* '\t' */)
break;
}
// Ended on whitespace
if (i === str.length)
break;
// Check for malformed parameter
if (str.charCodeAt(i++) !== 59/* ';' */)
return;
// Consume whitespace
for (; i < str.length; ++i) {
const code = str.charCodeAt(i);
if (code !== 32/* ' ' */ && code !== 9/* '\t' */)
break;
}
// Ended on whitespace (malformed)
if (i === str.length)
return;
let name;
const nameStart = i;
// Parse parameter name
for (; i < str.length; ++i) {
const code = str.charCodeAt(i);
if (TOKEN[code] !== 1) {
if (code !== 61/* '=' */)
return;
break;
}
}
// No value (malformed)
if (i === str.length)
return;
name = str.slice(nameStart, i);
++i; // Skip over '='
// No value (malformed)
if (i === str.length)
return;
let value = '';
let valueStart;
if (str.charCodeAt(i) === 34/* '"' */) {
valueStart = ++i;
let escaping = false;
// Parse quoted value
for (; i < str.length; ++i) {
const code = str.charCodeAt(i);
if (code === 92/* '\\' */) {
if (escaping) {
valueStart = i;
escaping = false;
} else {
value += str.slice(valueStart, i);
escaping = true;
}
continue;
}
if (code === 34/* '"' */) {
if (escaping) {
valueStart = i;
escaping = false;
continue;
}
value += str.slice(valueStart, i);
break;
}
if (escaping) {
valueStart = i - 1;
escaping = false;
}
// Invalid unescaped quoted character (malformed)
if (QDTEXT[code] !== 1)
return;
}
// No end quote (malformed)
if (i === str.length)
return;
++i; // Skip over double quote
} else {
valueStart = i;
// Parse unquoted value
for (; i < str.length; ++i) {
const code = str.charCodeAt(i);
if (TOKEN[code] !== 1) {
// No value (malformed)
if (i === valueStart)
return;
break;
}
}
value = str.slice(valueStart, i);
}
name = name.toLowerCase();
if (params[name] === undefined)
params[name] = value;
}
return params;
}
function parseDisposition(str, defDecoder) {
if (str.length === 0)
return;
const params = Object.create(null);
let i = 0;
for (; i < str.length; ++i) {
const code = str.charCodeAt(i);
if (TOKEN[code] !== 1) {
if (parseDispositionParams(str, i, params, defDecoder) === undefined)
return;
break;
}
}
const type = str.slice(0, i).toLowerCase();
return { type, params };
}
function parseDispositionParams(str, i, params, defDecoder) {
while (i < str.length) {
// Consume whitespace
for (; i < str.length; ++i) {
const code = str.charCodeAt(i);
if (code !== 32/* ' ' */ && code !== 9/* '\t' */)
break;
}
// Ended on whitespace
if (i === str.length)
break;
// Check for malformed parameter
if (str.charCodeAt(i++) !== 59/* ';' */)
return;
// Consume whitespace
for (; i < str.length; ++i) {
const code = str.charCodeAt(i);
if (code !== 32/* ' ' */ && code !== 9/* '\t' */)
break;
}
// Ended on whitespace (malformed)
if (i === str.length)
return;
let name;
const nameStart = i;
// Parse parameter name
for (; i < str.length; ++i) {
const code = str.charCodeAt(i);
if (TOKEN[code] !== 1) {
if (code === 61/* '=' */)
break;
return;
}
}
// No value (malformed)
if (i === str.length)
return;
let value = '';
let valueStart;
let charset;
//~ let lang;
name = str.slice(nameStart, i);
if (name.charCodeAt(name.length - 1) === 42/* '*' */) {
// Extended value
const charsetStart = ++i;
// Parse charset name
for (; i < str.length; ++i) {
const code = str.charCodeAt(i);
if (CHARSET[code] !== 1) {
if (code !== 39/* '\'' */)
return;
break;
}
}
// Incomplete charset (malformed)
if (i === str.length)
return;
charset = str.slice(charsetStart, i);
++i; // Skip over the '\''
//~ const langStart = ++i;
// Parse language name
for (; i < str.length; ++i) {
const code = str.charCodeAt(i);
if (code === 39/* '\'' */)
break;
}
// Incomplete language (malformed)
if (i === str.length)
return;
//~ lang = str.slice(langStart, i);
++i; // Skip over the '\''
// No value (malformed)
if (i === str.length)
return;
valueStart = i;
let encode = 0;
// Parse value
for (; i < str.length; ++i) {
const code = str.charCodeAt(i);
if (EXTENDED_VALUE[code] !== 1) {
if (code === 37/* '%' */) {
let hexUpper;
let hexLower;
if (i + 2 < str.length
&& (hexUpper = HEX_VALUES[str.charCodeAt(i + 1)]) !== -1
&& (hexLower = HEX_VALUES[str.charCodeAt(i + 2)]) !== -1) {
const byteVal = (hexUpper << 4) + hexLower;
value += str.slice(valueStart, i);
value += String.fromCharCode(byteVal);
i += 2;
valueStart = i + 1;
if (byteVal >= 128)
encode = 2;
else if (encode === 0)
encode = 1;
continue;
}
// '%' disallowed in non-percent encoded contexts (malformed)
return;
}
break;
}
}
value += str.slice(valueStart, i);
value = convertToUTF8(value, charset, encode);
if (value === undefined)
return;
} else {
// Non-extended value
++i; // Skip over '='
// No value (malformed)
if (i === str.length)
return;
if (str.charCodeAt(i) === 34/* '"' */) {
valueStart = ++i;
let escaping = false;
// Parse quoted value
for (; i < str.length; ++i) {
const code = str.charCodeAt(i);
if (code === 92/* '\\' */) {
if (escaping) {
valueStart = i;
escaping = false;
} else {
value += str.slice(valueStart, i);
escaping = true;
}
continue;
}
if (code === 34/* '"' */) {
if (escaping) {
valueStart = i;
escaping = false;
continue;
}
value += str.slice(valueStart, i);
break;
}
if (escaping) {
valueStart = i - 1;
escaping = false;
}
// Invalid unescaped quoted character (malformed)
if (QDTEXT[code] !== 1)
return;
}
// No end quote (malformed)
if (i === str.length)
return;
++i; // Skip over double quote
} else {
valueStart = i;
// Parse unquoted value
for (; i < str.length; ++i) {
const code = str.charCodeAt(i);
if (TOKEN[code] !== 1) {
// No value (malformed)
if (i === valueStart)
return;
break;
}
}
value = str.slice(valueStart, i);
}
value = defDecoder(value, 2);
if (value === undefined)
return;
}
name = name.toLowerCase();
if (params[name] === undefined)
params[name] = value;
}
return params;
}
function getDecoder(charset) {
let lc;
while (true) {
switch (charset) {
case 'utf-8':
case 'utf8':
return decoders.utf8;
case 'latin1':
case 'ascii': // TODO: Make these a separate, strict decoder?
case 'us-ascii':
case 'iso-8859-1':
case 'iso8859-1':
case 'iso88591':
case 'iso_8859-1':
case 'windows-1252':
case 'iso_8859-1:1987':
case 'cp1252':
case 'x-cp1252':
return decoders.latin1;
case 'utf16le':
case 'utf-16le':
case 'ucs2':
case 'ucs-2':
return decoders.utf16le;
case 'base64':
return decoders.base64;
default:
if (lc === undefined) {
lc = true;
charset = charset.toLowerCase();
continue;
}
return decoders.other.bind(charset);
}
}
}
const decoders = {
utf8: (data, hint) => {
if (data.length === 0)
return '';
if (typeof data === 'string') {
// If `data` never had any percent-encoded bytes or never had any that
// were outside of the ASCII range, then we can safely just return the
// input since UTF-8 is ASCII compatible
if (hint < 2)
return data;
data = Buffer.from(data, 'latin1');
}
return data.utf8Slice(0, data.length);
},
latin1: (data, hint) => {
if (data.length === 0)
return '';
if (typeof data === 'string')
return data;
return data.latin1Slice(0, data.length);
},
utf16le: (data, hint) => {
if (data.length === 0)
return '';
if (typeof data === 'string')
data = Buffer.from(data, 'latin1');
return data.ucs2Slice(0, data.length);
},
base64: (data, hint) => {
if (data.length === 0)
return '';
if (typeof data === 'string')
data = Buffer.from(data, 'latin1');
return data.base64Slice(0, data.length);
},
other: (data, hint) => {
if (data.length === 0)
return '';
if (typeof data === 'string')
data = Buffer.from(data, 'latin1');
try {
const decoder = new TextDecoder(this);
return decoder.decode(data);
} catch {}
},
};
function convertToUTF8(data, charset, hint) {
const decode = getDecoder(charset);
if (decode)
return decode(data, hint);
}
function basename(path) {
if (typeof path !== 'string')
return '';
for (let i = path.length - 1; i >= 0; --i) {
switch (path.charCodeAt(i)) {
case 0x2F: // '/'
case 0x5C: // '\'
path = path.slice(i + 1);
return (path === '..' || path === '.' ? '' : path);
}
}
return (path === '..' || path === '.' ? '' : path);
}
const TOKEN = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
const QDTEXT = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
];
const CHARSET = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
const EXTENDED_VALUE = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
/* eslint-disable no-multi-spaces */
const HEX_VALUES = [
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
];
/* eslint-enable no-multi-spaces */
module.exports = {
basename,
convertToUTF8,
getDecoder,
parseContentType,
parseDisposition,
};