Horoskopska stran: slike znamenj (image_url + /api/horoscopes/images)
This commit is contained in:
parent
63f27e7236
commit
b61bfac5d3
@ -43,6 +43,7 @@ interface AIHoroscope {
|
|||||||
tip: string;
|
tip: string;
|
||||||
weekly: string;
|
weekly: string;
|
||||||
monthly: string;
|
monthly: string;
|
||||||
|
imageUrl?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function StarRating({ count }: { count: number }) {
|
function StarRating({ count }: { count: number }) {
|
||||||
@ -161,6 +162,14 @@ function SignGrid({ onSelect, selectedIndex, aiHoroscopes }: { onSelect: (i: num
|
|||||||
}`}
|
}`}
|
||||||
data-testid={`button-sign-grid-${sign.name.toLowerCase()}`}
|
data-testid={`button-sign-grid-${sign.name.toLowerCase()}`}
|
||||||
>
|
>
|
||||||
|
{(horoscope as any).imageUrl && (
|
||||||
|
<img
|
||||||
|
src={(horoscope as any).imageUrl}
|
||||||
|
alt={`Sternzeichen ${sign.name}`}
|
||||||
|
loading="lazy"
|
||||||
|
className="w-full h-24 object-cover rounded-lg mb-3"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<span className="text-2xl">{sign.symbol}</span>
|
<span className="text-2xl">{sign.symbol}</span>
|
||||||
<div>
|
<div>
|
||||||
@ -227,6 +236,15 @@ function SignDetail({ signIndex, onNavigate, aiHoroscopes }: { signIndex: number
|
|||||||
|
|
||||||
<div className="space-y-0">
|
<div className="space-y-0">
|
||||||
<div className="bg-card rounded-xl border border-card-border p-6 md:p-8" data-testid={`card-horoscope-${sign.name.toLowerCase()}`}>
|
<div className="bg-card rounded-xl border border-card-border p-6 md:p-8" data-testid={`card-horoscope-${sign.name.toLowerCase()}`}>
|
||||||
|
{(horoscope as any).imageUrl && (
|
||||||
|
<img
|
||||||
|
src={(horoscope as any).imageUrl}
|
||||||
|
alt={`Tageshoroskop ${sign.name}`}
|
||||||
|
loading="lazy"
|
||||||
|
className="w-full rounded-xl mb-6 object-cover aspect-[3/2]"
|
||||||
|
data-testid="img-horoscope-sign"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div className="flex items-start gap-4 mb-6">
|
<div className="flex items-start gap-4 mb-6">
|
||||||
<div className={`w-20 h-20 rounded-2xl ${ec.bg} border ${ec.border} flex items-center justify-center flex-shrink-0`}>
|
<div className={`w-20 h-20 rounded-2xl ${ec.bg} border ${ec.border} flex items-center justify-center flex-shrink-0`}>
|
||||||
<span className="text-5xl">{sign.symbol}</span>
|
<span className="text-5xl">{sign.symbol}</span>
|
||||||
|
|||||||
@ -25,6 +25,19 @@ export async function getHoroscopesForToday(): Promise<any[]> {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function setHoroscopeImages(dateStr: string, images: Record<string, string>): Promise<number> {
|
||||||
|
let n = 0;
|
||||||
|
for (const [key, url] of Object.entries(images || {})) {
|
||||||
|
const idx = parseInt(key, 10);
|
||||||
|
if (Number.isNaN(idx) || !url) continue;
|
||||||
|
await db.update(dailyHoroscopes)
|
||||||
|
.set({ imageUrl: url })
|
||||||
|
.where(and(eq(dailyHoroscopes.dateStr, dateStr), eq(dailyHoroscopes.signIndex, idx)));
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
export async function generateDailyHoroscopes(): Promise<void> {
|
export async function generateDailyHoroscopes(): Promise<void> {
|
||||||
const today = getTodayStr();
|
const today = getTodayStr();
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { createServer, type Server } from "http";
|
|||||||
import { storage } from "./storage";
|
import { storage } from "./storage";
|
||||||
import { insertArticleSchema } from "@shared/schema";
|
import { insertArticleSchema } from "@shared/schema";
|
||||||
import { seedDatabase } from "./seed";
|
import { seedDatabase } from "./seed";
|
||||||
import { generateDailyHoroscopes, getHoroscopesForToday, getOrGenerateHoroscope, getCosmicEventsForToday, generateCosmicEvents } from "./horoscope-generator";
|
import { generateDailyHoroscopes, getHoroscopesForToday, getOrGenerateHoroscope, getCosmicEventsForToday, generateCosmicEvents, setHoroscopeImages } from "./horoscope-generator";
|
||||||
import { startDailyScheduler } from "./scheduler";
|
import { startDailyScheduler } from "./scheduler";
|
||||||
import { analyzeAllArticleImages, getCachedFocalPoints } from "./focal-point";
|
import { analyzeAllArticleImages, getCachedFocalPoints } from "./focal-point";
|
||||||
import { optimizeImage } from "./image-optimizer";
|
import { optimizeImage } from "./image-optimizer";
|
||||||
@ -741,6 +741,19 @@ export async function registerRoutes(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Horoscope API
|
// Horoscope API
|
||||||
|
app.post("/api/horoscopes/images", requireApiKey, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { dateStr, images } = req.body || {};
|
||||||
|
if (!dateStr || typeof images !== "object" || images === null) {
|
||||||
|
return res.status(400).json({ message: "dateStr und images sind erforderlich" });
|
||||||
|
}
|
||||||
|
const updated = await setHoroscopeImages(dateStr, images);
|
||||||
|
res.json({ updated });
|
||||||
|
} catch (err: any) {
|
||||||
|
res.status(500).json({ message: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.get("/api/horoscopes/today", async (_req, res) => {
|
app.get("/api/horoscopes/today", async (_req, res) => {
|
||||||
try {
|
try {
|
||||||
const horoscopes = await getHoroscopesForToday();
|
const horoscopes = await getHoroscopesForToday();
|
||||||
|
|||||||
@ -45,6 +45,7 @@ export const dailyHoroscopes = pgTable("daily_horoscopes", {
|
|||||||
tip: text("tip").notNull(),
|
tip: text("tip").notNull(),
|
||||||
weekly: text("weekly").notNull(),
|
weekly: text("weekly").notNull(),
|
||||||
monthly: text("monthly").notNull(),
|
monthly: text("monthly").notNull(),
|
||||||
|
imageUrl: text("image_url"),
|
||||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user