Skip to content

Commit 8c61465

Browse files
committed
feat(newsletters): add error handling for unauthorized and forbidden access
1 parent 7b318d2 commit 8c61465

File tree

4 files changed

+112
-9
lines changed

4 files changed

+112
-9
lines changed

apps/web/src/app/(main)/dashboard/newsletters/[slug]/page.tsx

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,23 +87,40 @@ export default function NewsletterPage() {
8787
const slug = params.slug as string;
8888
const [newsletter, setNewsletter] = useState<NewsletterData | null>(null);
8989
const [loading, setLoading] = useState(true);
90+
const [error, setError] = useState<'unauthorized' | 'forbidden' | 'not-found' | null>(null);
9091
const { isPaidUser, isLoading: subscriptionLoading } = useSubscription();
9192

9293
useEffect(() => {
9394
if (subscriptionLoading) return;
9495

9596
fetch(`/api/newsletters/${slug}`)
96-
.then((res) => res.json())
97+
.then(async (res) => {
98+
if (res.status === 401) {
99+
setError('unauthorized');
100+
setLoading(false);
101+
return null;
102+
}
103+
if (res.status === 403) {
104+
setError('forbidden');
105+
setLoading(false);
106+
return null;
107+
}
108+
if (!res.ok) {
109+
setError('not-found');
110+
setLoading(false);
111+
return null;
112+
}
113+
return res.json();
114+
})
97115
.then((data) => {
98-
if (data.error) {
99-
setNewsletter(null);
100-
} else {
116+
if (data && !data.error) {
101117
setNewsletter(data);
118+
setError(null);
102119
}
103120
setLoading(false);
104121
})
105122
.catch(() => {
106-
setNewsletter(null);
123+
setError('not-found');
107124
setLoading(false);
108125
});
109126
}, [slug, subscriptionLoading]);
@@ -119,10 +136,15 @@ export default function NewsletterPage() {
119136
);
120137
}
121138

122-
if (!isPaidUser) {
139+
if (!isPaidUser || error === 'forbidden') {
123140
return <PremiumUpgradePrompt />;
124141
}
125142

143+
if (error === 'unauthorized') {
144+
router.push('/login');
145+
return null;
146+
}
147+
126148
if (loading) {
127149
return (
128150
<div className="w-full h-full overflow-auto">

apps/web/src/app/(main)/dashboard/newsletters/page.tsx

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { useNewsletterFilters } from "@/hooks/useNewsletterFilters";
1616
export default function NewslettersPage() {
1717
const [newsletters, setNewsletters] = useState<Newsletter[]>([]);
1818
const [loading, setLoading] = useState(true);
19+
const [error, setError] = useState<'unauthorized' | 'forbidden' | null>(null);
1920
const [searchQuery, setSearchQuery] = useState("");
2021
const [timeFilter, setTimeFilter] = useState<TimeFilter>("all");
2122
const [sortFilter, setSortFilter] = useState<SortFilter>("newest");
@@ -28,9 +29,27 @@ export default function NewslettersPage() {
2829
if (subscriptionLoading) return;
2930

3031
fetch("/api/newsletters")
31-
.then((res) => res.json())
32+
.then(async (res) => {
33+
if (res.status === 401) {
34+
setError('unauthorized');
35+
setLoading(false);
36+
return null;
37+
}
38+
if (res.status === 403) {
39+
setError('forbidden');
40+
setLoading(false);
41+
return null;
42+
}
43+
if (!res.ok) {
44+
setLoading(false);
45+
return null;
46+
}
47+
return res.json();
48+
})
3249
.then((data) => {
33-
setNewsletters(data);
50+
if (data) {
51+
setNewsletters(data);
52+
}
3453
setLoading(false);
3554
})
3655
.catch(() => setLoading(false));
@@ -72,7 +91,7 @@ export default function NewslettersPage() {
7291
);
7392
}
7493

75-
if (!isPaidUser) {
94+
if (!isPaidUser || error === 'forbidden') {
7695
return <PremiumUpgradePrompt />;
7796
}
7897

apps/web/src/app/api/newsletters/[slug]/route.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import fs from "fs";
33
import path from "path";
44
import matter from "gray-matter";
55
import { marked } from "marked";
6+
import { getServerSession } from "next-auth";
7+
import { authConfig } from "@/lib/auth/config";
8+
import { serverTrpc } from "@/lib/trpc-server";
69

710
// Configure marked for rich markdown support
811
marked.setOptions({
@@ -18,6 +21,34 @@ export async function GET(
1821
_request: Request,
1922
{ params }: { params: Promise<{ slug: string }> }
2023
) {
24+
// Authenticate user
25+
const session = await getServerSession(authConfig);
26+
27+
if (!session || !session.user?.email) {
28+
return NextResponse.json(
29+
{ error: "Unauthorized - Please sign in" },
30+
{ status: 401 }
31+
);
32+
}
33+
34+
// Verify paid subscription
35+
try {
36+
const subscriptionStatus = await serverTrpc.user.subscriptionStatus.query();
37+
38+
if (!subscriptionStatus.isPaidUser) {
39+
return NextResponse.json(
40+
{ error: "Forbidden - Premium subscription required" },
41+
{ status: 403 }
42+
);
43+
}
44+
} catch (error) {
45+
console.error("Error checking subscription:", error);
46+
return NextResponse.json(
47+
{ error: "Failed to verify subscription status" },
48+
{ status: 500 }
49+
);
50+
}
51+
2152
const { slug } = await params;
2253
const now = Date.now();
2354
const cached = newsletterCache.get(slug);

apps/web/src/app/api/newsletters/route.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,44 @@ import { NextResponse } from "next/server";
22
import fs from "fs";
33
import path from "path";
44
import matter from "gray-matter";
5+
import { getServerSession } from "next-auth";
6+
import { authConfig } from "@/lib/auth/config";
7+
import { serverTrpc } from "@/lib/trpc-server";
58

69
// Cache newsletters in memory for faster subsequent loads
710
let cachedNewsletters: any[] | null = null;
811
let lastCacheTime = 0;
912
const CACHE_DURATION = 60000; // 1 minute cache
1013

1114
export async function GET() {
15+
// Authenticate user
16+
const session = await getServerSession(authConfig);
17+
18+
if (!session || !session.user?.email) {
19+
return NextResponse.json(
20+
{ error: "Unauthorized - Please sign in" },
21+
{ status: 401 }
22+
);
23+
}
24+
25+
// Verify paid subscription
26+
try {
27+
const subscriptionStatus = await serverTrpc.user.subscriptionStatus.query();
28+
29+
if (!subscriptionStatus.isPaidUser) {
30+
return NextResponse.json(
31+
{ error: "Forbidden - Premium subscription required" },
32+
{ status: 403 }
33+
);
34+
}
35+
} catch (error) {
36+
console.error("Error checking subscription:", error);
37+
return NextResponse.json(
38+
{ error: "Failed to verify subscription status" },
39+
{ status: 500 }
40+
);
41+
}
42+
1243
const now = Date.now();
1344

1445
// Return cached data if available and fresh

0 commit comments

Comments
 (0)