Initial commit of working RSS Aggregator build

This commit is contained in:
2026-05-12 17:04:02 -03:00
parent ea3a2ca53e
commit 7ac2f6e384
4962 changed files with 1032666 additions and 0 deletions
View File
View File
View File
View File
+4
View File
@@ -0,0 +1,4 @@
export const config = {
redisUrl: process.env.REDIS_URL!,
port: process.env.PORT || 8080
};
+2
View File
@@ -0,0 +1,2 @@
import { PrismaClient } from "@prisma/client";
export const prisma = new PrismaClient();
View File
View File
View File
View File
View File
View File
View File
+40
View File
@@ -0,0 +1,40 @@
import express, { Request, Response } from "express";
import session from "express-session";
import RedisStore from "connect-redis";
import { createClient } from "redis";
import { prisma } from "./db.js";
import { feedQueue } from "./queue.js";
import { config } from "./config.js";
const app = express();
app.use(express.json());
// Redis client (NO top-level await)
const redis = createClient({ url: config.redisUrl });
redis.connect().catch(err => {
console.error("Redis connection failed:", err);
});
app.use(
session({
store: new RedisStore({ client: redis }),
secret: "dev-secret",
resave: false,
saveUninitialized: false
})
);
// Typed route handlers
app.get("/health", (_req: Request, res: Response) => {
res.json({ ok: true });
});
app.post("/feeds/:id/poll", async (req: Request, res: Response) => {
await feedQueue.add("poll-feed", { feedId: req.params.id });
res.json({ ok: true });
});
app.listen(config.port, () => {
console.log(`API running on port ${config.port}`);
});
+9
View File
@@ -0,0 +1,9 @@
import { Queue } from "bullmq";
// BullMQ v4 requires host + port, NOT url:
export const feedQueue = new Queue("feeds", {
connection: {
host: "redis",
port: 6379
}
});
+7
View File
@@ -0,0 +1,7 @@
import Parser from "rss-parser";
const parser = new Parser();
export default async function fetchFeed(url: string) {
return parser.parseURL(url);
}
View File
+23
View File
@@ -0,0 +1,23 @@
import { prisma } from "../db.js";
export default async function processEntries(feedId: string, parsed: any) {
for (const item of parsed.items) {
await prisma.entry.upsert({
where: {
guid_feedId: {
guid: item.guid || item.link,
feedId
}
},
update: {},
create: {
feedId,
guid: item.guid || item.link,
title: item.title || "Untitled",
link: item.link,
content: item.contentSnippet || item.content || "",
published: item.isoDate ? new Date(item.isoDate) : null
}
});
}
}
View File
View File
+26
View File
@@ -0,0 +1,26 @@
import { Worker } from "bullmq";
import { prisma } from "./db.js";
import fetchFeed from "./rss/fetchFeed.js";
import processEntries from "./rss/processEntries.js";
// BullMQ v4 requires host + port, NOT url
const connection = {
host: "redis",
port: 6379
};
new Worker(
"feeds",
async job => {
const feedId: string = job.data.feedId;
const feed = await prisma.feed.findUnique({ where: { id: feedId } });
if (!feed) return;
const parsed = await fetchFeed(feed.url);
await processEntries(feedId, parsed);
},
{ connection }
);
console.log("Worker running");