Restored to '026e19d3b6c2165ce9cdbfd9d513ddff3d15f57c'

Replit-Restored-To:026e19d3b6c2165ce9cdbfd9d513ddff3d15f57c
This commit is contained in:
TerribleDev
2025-02-15 18:31:48 +00:00
parent 9c27c42e16
commit 00785d9df0
6 changed files with 72 additions and 163 deletions

View File

@@ -1,29 +0,0 @@
import { urlBase64ToUint8Array } from '../lib/utils';
export function usePushNotifications() {
const subscribe = async () => {
try {
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(import.meta.env.VITE_VAPID_PUBLIC_KEY)
});
await fetch('/api/subscriptions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(subscription)
});
return true;
} catch (err) {
console.error('Error subscribing to push notifications:', err);
return false;
}
};
return { subscribe };
}

View File

@@ -1,11 +1,6 @@
import { useQuery } from "@tanstack/react-query";
import type { Newsletter } from "@shared/schema";
export interface Newsletter {
// ... other properties
thumbnail: string | null;
}
export function useNewsletters() {
return useQuery<Newsletter[]>({
queryKey: ['/api/newsletters']

View File

@@ -11,10 +11,11 @@ import {
Calendar,
RefreshCw,
Share2,
Bell,
BellOff,
Twitter,
Facebook,
Rss,
ImageOff
Bell,
BellOff
} from "lucide-react";
import { useNewsletters, useNewsletterSearch } from "@/lib/newsletter-data";
import { useToast } from "@/hooks/use-toast";
@@ -24,34 +25,6 @@ import { motion, AnimatePresence } from "framer-motion";
const ITEMS_PER_PAGE = 20;
function NewsletterImage({ src, alt, onError }: { src: string | null, alt: string, onError: () => void }) {
const [error, setError] = useState(false);
const handleError = () => {
setError(true);
onError();
};
if (!src || error) {
return (
<div className="w-full h-40 bg-muted flex items-center justify-center rounded-md mb-4">
<ImageOff className="h-8 w-8 text-muted-foreground" />
</div>
);
}
return (
<img
src={src}
alt={alt}
className="w-full h-40 object-cover rounded-md mb-4"
onError={handleError}
loading="lazy"
crossOrigin="anonymous"
/>
);
}
export default function Home() {
const [searchQuery, setSearchQuery] = useState("");
const [isImporting, setIsImporting] = useState(false);
@@ -262,11 +235,17 @@ export default function Home() {
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
layout
>
<a
href={newsletter.url}
target="_blank"
rel="noopener noreferrer"
className="block"
>
<Card className="h-full hover:shadow-lg transition-all duration-300 cursor-pointer group">
<CardHeader>
<CardTitle className="flex items-center justify-between gap-2">
<span className="line-clamp-2">{newsletter.title}</span>
<span className="line-clamp-2 flex-1">{newsletter.title}</span>
<div className="flex items-center gap-2 shrink-0">
<Button
variant="ghost"
@@ -302,12 +281,10 @@ export default function Home() {
{(newsletter.thumbnail || newsletter.description) && (
<CardContent>
{newsletter.thumbnail && (
<NewsletterImage
<img
src={newsletter.thumbnail}
alt={newsletter.title}
onError={() => {
console.warn(`Failed to load image for newsletter: ${newsletter.id}`);
}}
className="w-full h-40 object-cover rounded-md mb-4"
/>
)}
{newsletter.description && (
@@ -318,6 +295,7 @@ export default function Home() {
</CardContent>
)}
</Card>
</a>
</motion.div>
))
) : (

View File

@@ -1,5 +0,0 @@
{pkgs}: {
deps = [
pkgs.postgresql
];
}

View File

@@ -6,21 +6,6 @@ const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
// Add security headers to prevent script injection and allow images from any domain
app.use((req, res, next) => {
// Prevent content injection but allow images from any domain
res.setHeader('Content-Security-Policy', "default-src 'self'; img-src * data: https:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';");
// Prevent browsers from MIME-sniffing
res.setHeader('X-Content-Type-Options', 'nosniff');
// XSS protection
res.setHeader('X-XSS-Protection', '1; mode=block');
// Allow CORS for images
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
app.use((req, res, next) => {
const start = Date.now();
const path = req.path;
@@ -62,12 +47,17 @@ app.use((req, res, next) => {
throw err;
});
// importantly only setup vite in development and after
// setting up all the other routes so the catch-all route
// doesn't interfere with the other routes
if (app.get("env") === "development") {
await setupVite(app, server);
} else {
serveStatic(app);
}
// ALWAYS serve the app on port 5000
// this serves both the API and the client
const PORT = 5000;
server.listen(PORT, "0.0.0.0", () => {
log(`serving on port ${PORT}`);

View File

@@ -137,26 +137,6 @@ export async function registerRoutes(app: Express): Promise<Server> {
}
});
app.get("/api/proxy-image", async (req, res) => {
try {
const imageUrl = req.query.url as string;
if (!imageUrl) {
return res.status(400).json({ message: "Image URL is required" });
}
const response = await axios.get(imageUrl, {
responseType: 'arraybuffer'
});
const contentType = response.headers['content-type'];
res.setHeader('Content-Type', contentType);
res.send(response.data);
} catch (error) {
console.error('Error proxying image:', error);
res.status(500).json({ message: "Failed to proxy image" });
}
});
app.get("/api/rss", async (_req, res) => {
try {
const newsletters = await storage.getNewsletters();