feat: devserver safebooru pull and ui fixes

This commit is contained in:
2025-01-05 20:50:03 +01:00
parent f3e3077662
commit 67e7b53860
5 changed files with 160 additions and 15 deletions

View File

@@ -24,6 +24,8 @@
"@radix-ui/react-slot": "^1.1.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.0",
"cron": "^3.3.2",
"glob": "^11.0.0",
"lucia": "^3.1.1",
"lucide-react": "^0.368.0",
"next": "^15.1.2",

View File

@@ -11,20 +11,29 @@ export default async function Home() {
<p className="text-sm text-muted-foreground italic">
(very unstable and not feature complete!)
</p>
<div className="flex gap-4 p-4">
{Boolean(process.env.SAFEBOORU_PULL) && (
<p className="text-red-300">
Development instance is pulling the first 30 safebooru images it finds for demonstration
purposes.
</p>
)}
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-4 p-4">
{posts.map((post) => (
<div key={post.id}>
<Link href={`/post/${post.id}`}>
<Image
width={176}
height={176}
src={post.imageUrl}
alt={''}
className="aspect-square object-contain border-2 rounded-md border-dashed"
blurDataURL={`data:image/jpeg;base64,${post.previewHash}`}
placeholder="blur"
/>
</Link>
<div key={post.id} className="relative aspect-square">
<Image
width={176}
height={176}
src={post.imageUrl}
alt={''}
className="w-full h-full object-contain border-2 rounded-md border-dashed pointer-events-none"
blurDataURL={`data:image/jpeg;base64,${post.previewHash}`}
placeholder="blur"
/>
<Link
href={`/post/${post.id}`}
className="absolute inset-0 z-10 hover:bg-black/5"
aria-label="View post details"
/>
</div>
))}
</div>

View File

@@ -71,9 +71,13 @@ export default async function Page({ params }: { params: Promise<{ id: string }>
<Separator className="my-6" />
<div className="grid gap-4">
<h3 className="text-xl font-bold">Tags</h3>
<div className="flex gap-2">
<div className="flex flex-wrap gap-2">
{post.tags.map((tag) => (
<Badge key={tag} variant={'secondary'} className="select-none cursor-pointer">
<Badge
key={tag}
variant={'secondary'}
className="select-none cursor-pointer"
>
{tag}
</Badge>
))}

63
src/instrumentation.ts Normal file
View File

@@ -0,0 +1,63 @@
import prisma from './lib/db';
export async function register() {
if (process.env.SAFEBOORU_PULL !== 'true') return;
if (process.env.NEXT_RUNTIME === 'nodejs') {
const { CronJob } = await import('cron');
const crypto = await import('crypto');
const { generateId } = await import('lucia');
const fs = await import('fs/promises');
const { default: hashImage } = await import('@/lib/hashImage');
const { glob } = await import('glob');
const job = async () => {
console.log('Deleting prior safebooru posts and accounts...');
await prisma.post.deleteMany({
where: { author: { username: { startsWith: 'safebooru-' } } },
});
await prisma.user.deleteMany({ where: { username: { startsWith: 'safebooru-' } } });
const files = await glob('public/uploads/safebooru-*.jpg');
await Promise.all(files.map((file) => fs.rm(file, { force: true })));
console.log('Pulling safebooru images...');
console.log('Fetching...');
const res = await fetch(
'https://safebooru.org/index.php?page=dapi&s=post&q=index&json=1&limit=30',
{ headers: { 'User-Agent': 'nextbooru' } }
);
const posts = await res.json();
const genId = generateId(6);
console.log('Creating account...');
const account = await prisma.user.create({
data: {
username: `safebooru-${genId}`,
hashed_password: crypto.randomUUID(),
},
});
console.log('Downloading all images...');
for (const post of posts) {
const imageUrl = await fetch(post.file_url).then((res) => res.arrayBuffer());
const savedFilename = `public/uploads/safebooru-${genId}-${post.id}.jpg`;
await fs.writeFile(savedFilename, new Uint8Array(imageUrl));
const previewHash = await hashImage(Buffer.from(imageUrl));
await prisma.post.create({
data: {
imageUrl: savedFilename.replace('public', ''),
previewHash,
authorId: account.id,
tags: post.tags.split(' '),
caption: post.source,
},
});
console.log(`Downloaded id ${post.id}`);
}
};
await job();
new CronJob('0 */2 * * *', async () => await job(), null, true);
}
}

View File

@@ -1290,6 +1290,11 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
"@types/luxon@~3.4.0":
version "3.4.2"
resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-3.4.2.tgz#e4fc7214a420173cea47739c33cdf10874694db7"
integrity sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==
"@types/node@^20":
version "20.12.7"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.7.tgz#04080362fa3dd6c5822061aa3124f5c152cff384"
@@ -1814,6 +1819,14 @@ cosmiconfig@^8.1.3:
parse-json "^5.2.0"
path-type "^4.0.0"
cron@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/cron/-/cron-3.3.2.tgz#d98843f1db6d4cfcf3a4cb028fbc71231c5261f1"
integrity sha512-7o2PH9vKRd4PxB8c2GsHRozfHYT+gIhZG0DI+vzGOdWo42mofO/ooYnyU0CCh27aKzCrUKMAwAwi7xJ84xKSug==
dependencies:
"@types/luxon" "~3.4.0"
luxon "~3.5.0"
cross-spawn@^7.0.0, cross-spawn@^7.0.2:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@@ -2601,6 +2614,18 @@ glob@^10.3.10:
minipass "^7.0.4"
path-scurry "^1.10.2"
glob@^11.0.0:
version "11.0.0"
resolved "https://registry.yarnpkg.com/glob/-/glob-11.0.0.tgz#6031df0d7b65eaa1ccb9b29b5ced16cea658e77e"
integrity sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==
dependencies:
foreground-child "^3.1.0"
jackspeak "^4.0.1"
minimatch "^10.0.0"
minipass "^7.1.2"
package-json-from-dist "^1.0.0"
path-scurry "^2.0.0"
glob@^7.1.3:
version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
@@ -3014,6 +3039,13 @@ jackspeak@^2.3.5, jackspeak@^2.3.6:
optionalDependencies:
"@pkgjs/parseargs" "^0.11.0"
jackspeak@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.0.2.tgz#11f9468a3730c6ff6f56823a820d7e3be9bef015"
integrity sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==
dependencies:
"@isaacs/cliui" "^8.0.2"
jiti@^1.21.0:
version "1.21.0"
resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d"
@@ -3191,6 +3223,11 @@ lru-cache@^10.2.0:
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.1.tgz#e8d901141f22937968e45a6533d52824070151e4"
integrity sha512-tS24spDe/zXhWbNPErCHs/AGOzbKGHT+ybSBqmdLm8WZ1xXLWvH8Qn71QPAlqVhd0qUTWjy+Kl9JmISgDdEjsA==
lru-cache@^11.0.0:
version "11.0.2"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.0.2.tgz#fbd8e7cf8211f5e7e5d91905c415a3f55755ca39"
integrity sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==
lru-cache@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
@@ -3217,6 +3254,11 @@ lucide-react@^0.368.0:
resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.368.0.tgz#3c0ee63f4f7d30ae63b621b2b8f04f9e409ee6e7"
integrity sha512-soryVrCjheZs8rbXKdINw9B8iPi5OajBJZMJ1HORig89ljcOcEokKKAgGbg3QWxSXel7JwHOfDFUdDHAKyUAMw==
luxon@~3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.5.0.tgz#6b6f65c5cd1d61d1fd19dbf07ee87a50bf4b8e20"
integrity sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==
memfs-browser@^3.4.13000:
version "3.5.10302"
resolved "https://registry.yarnpkg.com/memfs-browser/-/memfs-browser-3.5.10302.tgz#2067baf616a1b3a8e8023a033e5ead434a7ea0c0"
@@ -3266,6 +3308,13 @@ minimatch@9.0.3:
dependencies:
brace-expansion "^2.0.1"
minimatch@^10.0.0:
version "10.0.1"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.1.tgz#ce0521856b453c86e25f2c4c0d03e6ff7ddc440b"
integrity sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==
dependencies:
brace-expansion "^2.0.1"
minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
@@ -3297,6 +3346,11 @@ minimist@^1.2.0, minimist@^1.2.6:
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c"
integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==
minipass@^7.1.2:
version "7.1.2"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
mkdirp@^2.1.6:
version "2.1.6"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-2.1.6.tgz#964fbcb12b2d8c5d6fbc62a963ac95a273e2cc19"
@@ -3536,6 +3590,11 @@ p-locate@^5.0.0:
dependencies:
p-limit "^3.0.2"
package-json-from-dist@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505"
integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==
parent-module@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
@@ -3591,6 +3650,14 @@ path-scurry@^1.10.1, path-scurry@^1.10.2:
lru-cache "^10.2.0"
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
path-scurry@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.0.tgz#9f052289f23ad8bf9397a2a0425e7b8615c58580"
integrity sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==
dependencies:
lru-cache "^11.0.0"
minipass "^7.1.2"
path-type@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"