Horoskop in kozmicni dogodki: dnevno AI generiranje + scheduler

- Nov dnevni scheduler (server/scheduler.ts): vsak dan ob zagonu in vsakih 6h
  preveri/generira horoskope in kozmicne dogodke (prej samo enkrat ob zagonu)
- Kozmicni dogodki so zdaj AI-generirani in dnevni (nova tabela cosmic_events
  + /api/cosmic-events), namesto hardcoded fiksnih datumov iz feb/mar 2026
- Naslovni horoskop widget bere pravi AI horoskop za danes (prej staticni tekst)
- Frontend: staleTime 30min + refetchOnWindowFocus za dnevno osvezevanje
This commit is contained in:
Folx Ops
2026-06-07 15:05:20 +00:00
parent 6c34d44cea
commit 365da96f5b
6 changed files with 204 additions and 8 deletions
+100 -1
View File
@@ -1,6 +1,6 @@
import OpenAI from "openai";
import { db } from "./db";
import { dailyHoroscopes } from "@shared/schema";
import { dailyHoroscopes, cosmicEvents } from "@shared/schema";
import { eq, and } from "drizzle-orm";
const openai = new OpenAI({
@@ -110,3 +110,102 @@ export async function getOrGenerateHoroscope(signIndex: number): Promise<any | n
return null;
}
const VALID_ICONS = ["mercury", "venus", "moon", "newmoon", "sun", "saturn", "jupiter", "mars"];
const VALID_TYPES = ["retrograde", "moon", "transit", "season"];
export async function getCosmicEventsForToday(): Promise<any[]> {
const today = getTodayStr();
const rows = await db.select().from(cosmicEvents)
.where(eq(cosmicEvents.dateStr, today));
return rows
.sort((a, b) => a.position - b.position)
.map((r) => ({
title: r.title,
description: r.description,
dateRange: r.dateRange,
icon: r.icon,
type: r.type,
affectedSigns: JSON.parse(r.affectedSigns || "[]"),
}));
}
export async function generateCosmicEvents(): Promise<void> {
const today = getTodayStr();
const existing = await db.select().from(cosmicEvents)
.where(eq(cosmicEvents.dateStr, today));
if (existing.length >= 6) {
console.log(`Cosmic events for ${today} already exist.`);
return;
}
console.log(`Generating cosmic events for ${today}...`);
const d = new Date();
const monthName = d.toLocaleDateString("de-DE", { month: "long", year: "numeric" });
try {
const response = await openai.chat.completions.create({
model: "gpt-5-mini",
messages: [
{
role: "system",
content: `Du bist ein erfahrener Astrologe, der die aktuellen kosmischen Ereignisse (Planetenkonstellationen, Mondphasen, Retrograden, Tierkreis-Saison) für eine deutschsprachige Volksmusik- und Schlager-Website beschreibt. Du kennst die realen astronomischen Daten. Heute ist der ${today} (${monthName}). Beschreibe ausschließlich Ereignisse, die JETZT, rund um dieses Datum tatsächlich aktiv oder relevant sind. Verwende realistische, dem aktuellen Datum entsprechende Datumsangaben. Schreibe warm und zugänglich auf Deutsch.`,
},
{
role: "user",
content: `Erstelle die 6 wichtigsten aktuellen kosmischen Ereignisse für heute (${today}). Dazu sollten gehören: die aktuelle Tierkreis-Saison (Sonne im Zeichen), die aktuelle Mondphase, eventuelle Retrograden, sowie relevante Planetentransite (Venus, Mars, Saturn, Jupiter).
Antworte NUR mit einem JSON-Array von genau 6 Objekten (kein Markdown, keine Erklärung) in diesem exakten Format:
[
{
"title": "Kurzer Titel, z.B. 'Vollmond im Skorpion' oder 'Zwillinge-Saison'",
"description": "3-4 Sätze über die Bedeutung dieses Ereignisses und seinen Einfluss.",
"dateRange": "Realistische Datumsangabe passend zum heutigen Datum, z.B. '21. Mai – 20. Juni 2026' oder '11. Juni 2026'",
"icon": "EINER VON: mercury, venus, moon, newmoon, sun, saturn, jupiter, mars",
"type": "EINER VON: retrograde, moon, transit, season",
"affectedSigns": ["3-4 betroffene Sternzeichen auf Deutsch, z.B. Widder, Stier, Zwillinge, Krebs, Löwe, Jungfrau, Waage, Skorpion, Schütze, Steinbock, Wassermann, Fische"]
}
]`,
},
],
max_completion_tokens: 2500,
});
const content = response.choices[0]?.message?.content || "";
const jsonMatch = content.match(/\[[\s\S]*\]/);
if (!jsonMatch) {
console.error("Failed to parse cosmic events");
return;
}
const parsed = JSON.parse(jsonMatch[0]);
if (!Array.isArray(parsed) || parsed.length === 0) {
console.error("Cosmic events response is not a valid array");
return;
}
for (let i = 0; i < parsed.length; i++) {
const ev = parsed[i];
const icon = VALID_ICONS.includes(ev.icon) ? ev.icon : "moon";
const type = VALID_TYPES.includes(ev.type) ? ev.type : "transit";
const signs = Array.isArray(ev.affectedSigns) ? ev.affectedSigns : [];
await db.insert(cosmicEvents).values({
dateStr: today,
position: i,
title: String(ev.title || "").slice(0, 120),
description: String(ev.description || ""),
dateRange: String(ev.dateRange || "").slice(0, 120),
icon,
type,
affectedSigns: JSON.stringify(signs),
});
}
console.log(`Generated ${parsed.length} cosmic events for ${today}.`);
} catch (err: any) {
console.error("Error generating cosmic events:", err.message);
}
}
+23 -4
View File
@@ -3,7 +3,8 @@ import { createServer, type Server } from "http";
import { storage } from "./storage";
import { insertArticleSchema } from "@shared/schema";
import { seedDatabase } from "./seed";
import { generateDailyHoroscopes, getHoroscopesForToday, getOrGenerateHoroscope } from "./horoscope-generator";
import { generateDailyHoroscopes, getHoroscopesForToday, getOrGenerateHoroscope, getCosmicEventsForToday, generateCosmicEvents } from "./horoscope-generator";
import { startDailyScheduler } from "./scheduler";
import { analyzeAllArticleImages, getCachedFocalPoints } from "./focal-point";
import { optimizeImage } from "./image-optimizer";
import { getAuthUrl, exchangeCodeForTokens, isConnected, fetchGalleryFromDropbox, getValidAccessToken, migrateGalleryToCloudinary } from "./dropbox";
@@ -58,9 +59,7 @@ export async function registerRoutes(
): Promise<Server> {
await seedDatabase();
generateDailyHoroscopes().catch((err) =>
console.error("Background horoscope generation failed:", err.message)
);
startDailyScheduler();
storage.getArticles().then((articles) => {
analyzeAllArticleImages(articles).catch(err =>
@@ -682,6 +681,26 @@ export async function registerRoutes(
}
});
// Cosmic events API
app.get("/api/cosmic-events", async (_req, res) => {
try {
const events = await getCosmicEventsForToday();
res.json(events);
} catch (err: any) {
res.status(500).json({ message: err.message });
}
});
app.post("/api/cosmic-events/generate", async (_req, res) => {
try {
await generateCosmicEvents();
const events = await getCosmicEventsForToday();
res.json({ generated: events.length, events });
} catch (err: any) {
res.status(500).json({ message: err.message });
}
});
function parseRssItems(xml: string, maxAgeDays?: number): { title: string; link: string; source: string; pubDate: string }[] {
const channelTitle = xml.match(/<channel>[\s\S]*?<title>(.*?)<\/title>/)?.[1]?.replace(/<!\[CDATA\[|\]\]>/g, "").trim() || "";
const items: { title: string; link: string; source: string; pubDate: string }[] = [];
+28
View File
@@ -0,0 +1,28 @@
import { generateDailyHoroscopes, generateCosmicEvents } from "./horoscope-generator";
async function runDailyGeneration() {
try {
await generateDailyHoroscopes();
} catch (err: any) {
console.error("[scheduler] Horoscope generation failed:", err.message);
}
try {
await generateCosmicEvents();
} catch (err: any) {
console.error("[scheduler] Cosmic events generation failed:", err.message);
}
}
export function startDailyScheduler() {
// Run once at startup (generators are idempotent — they skip if today already exists).
runDailyGeneration();
// Re-check every 6 hours. Covers the day rollover without relying on container restarts.
const SIX_HOURS = 6 * 60 * 60 * 1000;
setInterval(() => {
console.log("[scheduler] Running scheduled daily generation check...");
runDailyGeneration();
}, SIX_HOURS);
console.log("[scheduler] Daily horoscope + cosmic events scheduler started.");
}