commit d16c0c836fe06474d8baca796135e0eb80526538 Author: Eugene Shulga Date: Thu May 15 11:47:32 2025 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3d04b06 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM oven/bun:latest + +WORKDIR /app + +COPY package.json bun.lock ./ +RUN bun install --production + +COPY . . + +EXPOSE 3000 + +CMD ["bun", "index.js"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..8905909 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# rss-tools + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run +``` + +This project was created using `bun init` in bun v1.2.8. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/app.ts b/app.ts new file mode 100644 index 0000000..a9c2645 --- /dev/null +++ b/app.ts @@ -0,0 +1,71 @@ +import { Hono } from 'hono'; +import { serve } from 'bun'; +import Parser from 'rss-parser'; +import { Feed, type Item } from 'feed'; + +const app = new Hono(); +const parser = new Parser(); + +app.get('/group-by-day', async (c) => { + const feedUrl = c.req.query('feedUrl'); + if (!feedUrl) { + return c.text('Missing feedUrl query parameter', 400); + } + try { + const { title, feedUrl: feedId, link: feedLink, items } = await parser.parseURL(feedUrl); + + const grouped = items.reduce((acc, { pubDate, content, contentSnippet, summary, link }) => { + const day = new Date(pubDate as string).toISOString().slice(0, 10); + acc[day] = acc[day] || []; + acc[day].push({ + link, + content: content || contentSnippet || summary || '' + }); + return acc; + }, {}); + + const days = Object.keys(grouped).sort().reverse(); + const today = new Date(); + today.setHours(0, 0, 0, 0); + + const feedItems: Item[] = days.map(day => { + const dateStr = new Date(day).toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric' + }); + return { + title: `${title} - ${dateStr}`, + id: day, + date: new Date(day), + link: grouped[day][0].link, + content: grouped[day].map((p: any) => p.content).join('\n\n---------------------\n\n') + }; + }).filter(p => p.date < today); + + const outFeed = new Feed({ + title: title as string, + id: feedId as string, + link: feedLink, + copyright: '' + }); + + for (const item of feedItems) { + outFeed.addItem(item) + } + + const xml = outFeed.rss2(); + + return c.text(xml, 200, { + 'Content-Type': 'text/xml', + }); + } catch (err: any) { + return c.text(`Error fetching or processing feed: ${err.message}`, 500); + } +}); + +// Start Bun server on port 3000 +serve({ + fetch: app.fetch, + port: 3000 +}); diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..d71c1c5 --- /dev/null +++ b/bun.lock @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "rss-tools", + "dependencies": { + "feed": "^5.0.1", + "hono": "^4.7.9", + "rss-parser": "^3.13.0", + }, + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + }, + "packages": { + "@types/bun": ["@types/bun@1.2.13", "", { "dependencies": { "bun-types": "1.2.13" } }, "sha512-u6vXep/i9VBxoJl3GjZsl/BFIsvML8DfVDO0RYLEwtSZSp981kEO1V5NwRcO1CPJ7AmvpbnDCiMKo3JvbDEjAg=="], + + "@types/node": ["@types/node@22.15.18", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-v1DKRfUdyW+jJhZNEI1PYy29S2YRxMV5AOO/x/SjKmW0acCIOqmbj6Haf9eHAhsPmrhlHSxEhv/1WszcLWV4cg=="], + + "bun-types": ["bun-types@1.2.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-rRjA1T6n7wto4gxhAO/ErZEtOXyEZEmnIHQfl0Dt1QQSB4QV0iP6BZ9/YB5fZaHFQ2dwHFrmPaRQ9GGMX01k9Q=="], + + "entities": ["entities@2.2.0", "", {}, "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="], + + "feed": ["feed@5.0.1", "", { "dependencies": { "xml-js": "^1.6.11" } }, "sha512-kOveKLHucVZ6RI91bdWAts9O0L1N2mGzRGVCDQPRnHh4HmgqLdN66Cp/5dMeJZGn/qnBslWliwX37FEBq8DCIA=="], + + "hono": ["hono@4.7.9", "", {}, "sha512-/EsCoR5h7N4yu01TDu9GMCCJa6ZLk5ZJIWFFGNawAXmd1Tp53+Wir4xm0D2X19bbykWUlzQG0+BvPAji6p9E8Q=="], + + "rss-parser": ["rss-parser@3.13.0", "", { "dependencies": { "entities": "^2.0.3", "xml2js": "^0.5.0" } }, "sha512-7jWUBV5yGN3rqMMj7CZufl/291QAhvrrGpDNE4k/02ZchL0npisiYYqULF71jCEKoIiHvK/Q2e6IkDwPziT7+w=="], + + "sax": ["sax@1.4.1", "", {}, "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="], + + "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "xml-js": ["xml-js@1.6.11", "", { "dependencies": { "sax": "^1.2.4" }, "bin": { "xml-js": "./bin/cli.js" } }, "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g=="], + + "xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="], + + "xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="], + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..0d69f23 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ + +services: + app: + build: . + env_file: + - .env + environment: + - PORT=${PORT:-3000} + ports: + - "${PORT:-3000}:${PORT:-3000}" + restart: unless-stopped \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..77347ba --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "rss-tools", + "private": true, + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5" + }, + "dependencies": { + "feed": "^5.0.1", + "hono": "^4.7.9", + "rss-parser": "^3.13.0" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..9c62f74 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +}