My personal photography website steve.phot
portfolio photography svelte sveltekit

feat: added rss

+90
+7
bun.lock
··· 6 6 "name": "steve-photo-svelte", 7 7 "dependencies": { 8 8 "exifr": "^7.1.3", 9 + "feed": "^5.2.0", 9 10 "from": "^0.1.7", 10 11 "import": "^0.0.6", 11 12 }, ··· 300 301 301 302 "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], 302 303 304 + "feed": ["feed@5.2.0", "", { "dependencies": { "xml-js": "^1.6.11" } }, "sha512-hgH6CCb+7+0c8PBlakI2KubG6R+Rb1MhpNcdvqUXZTBwBHf32piwY255diAkAmkGZ6AWlywOU88AkOgP9q8Rdw=="], 305 + 303 306 "from": ["from@0.1.7", "", {}, "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g=="], 304 307 305 308 "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], ··· 372 375 373 376 "sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="], 374 377 378 + "sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="], 379 + 375 380 "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], 376 381 377 382 "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], ··· 417 422 "wrangler": ["wrangler@4.60.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.2", "@cloudflare/unenv-preset": "2.11.0", "blake3-wasm": "2.1.5", "esbuild": "0.27.0", "miniflare": "4.20260120.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260120.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20260120.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-n4kibm/xY0Qd5G2K/CbAQeVeOIlwPNVglmFjlDRCCYk3hZh8IggO/rg8AXt/vByK2Sxsugl5Z7yvgWxrUbmS6g=="], 418 423 419 424 "ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], 425 + 426 + "xml-js": ["xml-js@1.6.11", "", { "dependencies": { "sax": "^1.2.4" }, "bin": { "xml-js": "./bin/cli.js" } }, "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g=="], 420 427 421 428 "youch": ["youch@4.1.0-beta.10", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.4", "@speed-highlight/core": "^1.2.7", "cookie": "^1.0.2", "youch-core": "^0.3.3" } }, "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ=="], 422 429
+1
package.json
··· 26 26 }, 27 27 "dependencies": { 28 28 "exifr": "^7.1.3", 29 + "feed": "^5.2.0", 29 30 "from": "^0.1.7", 30 31 "import": "^0.0.6" 31 32 }
+32
src/lib/feed.ts
··· 1 + import type { ImageItem } from "$lib/types"; 2 + 3 + const R2_BASE_URL = "https://r2.steve.photo"; 4 + 5 + export async function getPhotos(platform: App.Platform | undefined): Promise<ImageItem[]> { 6 + const db = platform?.env?.DB; 7 + 8 + if (!db) { 9 + return []; 10 + } 11 + 12 + const result = await db.prepare("SELECT * FROM photos ORDER BY date DESC").all(); 13 + 14 + const photos: ImageItem[] = result.results.map((row: Record<string, unknown>) => ({ 15 + slug: row.slug as string, 16 + title: row.title as string, 17 + date: row.date as string, 18 + image: `${R2_BASE_URL}/${row.image_key}`, 19 + thumb: `${R2_BASE_URL}/${row.thumb_key}`, 20 + type: row.type as string, 21 + camera: row.camera as string, 22 + lens: row.lens as string, 23 + aperture: row.aperture as string, 24 + exposure: row.exposure as string, 25 + focalLength: row.focal_length as string, 26 + iso: row.iso as string, 27 + make: row.make as string, 28 + tags: [], 29 + })); 30 + 31 + return photos; 32 + }
+50
src/routes/rss.xml/+server.ts
··· 1 + import { Feed } from "feed"; 2 + import { getPhotos } from "$lib/feed"; 3 + import type { RequestHandler } from "./$types"; 4 + 5 + export const GET: RequestHandler = async ({ platform }) => { 6 + const feed = new Feed({ 7 + title: "steve.photo", 8 + description: "Personal photography portolio of Steve Simkins", 9 + id: "https://steve.photo", 10 + link: "https://steve.photo/", 11 + language: "en", 12 + favicon: "https://steve.photo/favicon.ico", 13 + copyright: `Copyright ${new Date().getFullYear().toString()}, Steve Simkins`, 14 + feedLinks: { 15 + rss: "https://steve.photo/rss.xml", 16 + }, 17 + author: { 18 + name: "Steve Simkins", 19 + email: "contact@stevedylan.dev", 20 + link: "https://stevedylan.dev", 21 + }, 22 + ttl: 60, 23 + }); 24 + 25 + const photos = await getPhotos(platform); 26 + 27 + for (const photo of photos) { 28 + feed.addItem({ 29 + title: photo.title, 30 + id: `https://steve.photo/photo/${photo.slug}`, 31 + link: `https://steve.photo/photo/${photo.slug}`, 32 + date: new Date(photo.date), 33 + image: photo.image, 34 + author: [ 35 + { 36 + name: "Steve Simkins", 37 + email: "contact@stevedylan.dev", 38 + link: "https://stevedylan.dev", 39 + }, 40 + ], 41 + content: `<img src="${photo.image}" alt="${photo.title}" /><p>Camera: ${photo.camera} | Lens: ${photo.lens} | ${photo.aperture} | ${photo.exposure} | ISO ${photo.iso}</p>`, 42 + }); 43 + } 44 + 45 + return new Response(feed.rss2(), { 46 + headers: { 47 + "Content-Type": "application/xml; charset=utf-8", 48 + }, 49 + }); 50 + };