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:
@@ -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
@@ -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 }[] = [];
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
Reference in New Issue
Block a user