Restored to '026e19d3b6c2165ce9cdbfd9d513ddff3d15f57c'
Replit-Restored-To:026e19d3b6c2165ce9cdbfd9d513ddff3d15f57c
This commit is contained in:
@@ -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 };
|
||||
}
|
||||
@@ -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']
|
||||
|
||||
@@ -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>
|
||||
))
|
||||
) : (
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{pkgs}: {
|
||||
deps = [
|
||||
pkgs.postgresql
|
||||
];
|
||||
}
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user