tangled
alpha
login
or
join now
stevedylan.dev
/
steve.photo
2
fork
atom
My personal photography website
steve.phot
portfolio
photography
svelte
sveltekit
2
fork
atom
overview
issues
pulls
pipelines
feat: added rss
stevedylan.dev
1 month ago
fc8c359e
e0373bcf
+90
4 changed files
expand all
collapse all
unified
split
bun.lock
package.json
src
lib
feed.ts
routes
rss.xml
+server.ts
+7
bun.lock
···
6
6
"name": "steve-photo-svelte",
7
7
"dependencies": {
8
8
"exifr": "^7.1.3",
9
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
304
+
"feed": ["feed@5.2.0", "", { "dependencies": { "xml-js": "^1.6.11" } }, "sha512-hgH6CCb+7+0c8PBlakI2KubG6R+Rb1MhpNcdvqUXZTBwBHf32piwY255diAkAmkGZ6AWlywOU88AkOgP9q8Rdw=="],
305
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
378
+
"sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="],
379
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
425
+
426
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
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
1
+
import type { ImageItem } from "$lib/types";
2
2
+
3
3
+
const R2_BASE_URL = "https://r2.steve.photo";
4
4
+
5
5
+
export async function getPhotos(platform: App.Platform | undefined): Promise<ImageItem[]> {
6
6
+
const db = platform?.env?.DB;
7
7
+
8
8
+
if (!db) {
9
9
+
return [];
10
10
+
}
11
11
+
12
12
+
const result = await db.prepare("SELECT * FROM photos ORDER BY date DESC").all();
13
13
+
14
14
+
const photos: ImageItem[] = result.results.map((row: Record<string, unknown>) => ({
15
15
+
slug: row.slug as string,
16
16
+
title: row.title as string,
17
17
+
date: row.date as string,
18
18
+
image: `${R2_BASE_URL}/${row.image_key}`,
19
19
+
thumb: `${R2_BASE_URL}/${row.thumb_key}`,
20
20
+
type: row.type as string,
21
21
+
camera: row.camera as string,
22
22
+
lens: row.lens as string,
23
23
+
aperture: row.aperture as string,
24
24
+
exposure: row.exposure as string,
25
25
+
focalLength: row.focal_length as string,
26
26
+
iso: row.iso as string,
27
27
+
make: row.make as string,
28
28
+
tags: [],
29
29
+
}));
30
30
+
31
31
+
return photos;
32
32
+
}
+50
src/routes/rss.xml/+server.ts
···
1
1
+
import { Feed } from "feed";
2
2
+
import { getPhotos } from "$lib/feed";
3
3
+
import type { RequestHandler } from "./$types";
4
4
+
5
5
+
export const GET: RequestHandler = async ({ platform }) => {
6
6
+
const feed = new Feed({
7
7
+
title: "steve.photo",
8
8
+
description: "Personal photography portolio of Steve Simkins",
9
9
+
id: "https://steve.photo",
10
10
+
link: "https://steve.photo/",
11
11
+
language: "en",
12
12
+
favicon: "https://steve.photo/favicon.ico",
13
13
+
copyright: `Copyright ${new Date().getFullYear().toString()}, Steve Simkins`,
14
14
+
feedLinks: {
15
15
+
rss: "https://steve.photo/rss.xml",
16
16
+
},
17
17
+
author: {
18
18
+
name: "Steve Simkins",
19
19
+
email: "contact@stevedylan.dev",
20
20
+
link: "https://stevedylan.dev",
21
21
+
},
22
22
+
ttl: 60,
23
23
+
});
24
24
+
25
25
+
const photos = await getPhotos(platform);
26
26
+
27
27
+
for (const photo of photos) {
28
28
+
feed.addItem({
29
29
+
title: photo.title,
30
30
+
id: `https://steve.photo/photo/${photo.slug}`,
31
31
+
link: `https://steve.photo/photo/${photo.slug}`,
32
32
+
date: new Date(photo.date),
33
33
+
image: photo.image,
34
34
+
author: [
35
35
+
{
36
36
+
name: "Steve Simkins",
37
37
+
email: "contact@stevedylan.dev",
38
38
+
link: "https://stevedylan.dev",
39
39
+
},
40
40
+
],
41
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
42
+
});
43
43
+
}
44
44
+
45
45
+
return new Response(feed.rss2(), {
46
46
+
headers: {
47
47
+
"Content-Type": "application/xml; charset=utf-8",
48
48
+
},
49
49
+
});
50
50
+
};