mongo -> supabase

This commit is contained in:
Austin Bennett
2025-05-14 07:45:07 -05:00
parent f0084a0f31
commit 4db5a689be
7 changed files with 633 additions and 157 deletions
+118 -69
View File
@@ -1,3 +1,4 @@
import { supabase } from '@/utils/supabase';
import React, { useState, useEffect } from 'react';
interface CommentFormProps {
@@ -5,9 +6,10 @@ interface CommentFormProps {
}
interface Comment {
id: number; // Supabase tables typically have an ID
name: string;
comment: string;
createdAt: string;
created_at: string; // Use the Supabase column name
}
const CommentSection: React.FC<CommentFormProps> = ({ postId }) => {
@@ -18,24 +20,48 @@ const CommentSection: React.FC<CommentFormProps> = ({ postId }) => {
const [isSubmitted, setIsSubmitted] = useState(false);
const [error, setError] = useState<string | null>(null);
const [comments, setComments] = useState<Comment[]>([]);
const [isLoadingComments, setIsLoadingComments] = useState(true); // Add loading state
// Fetch comments when the component mounts or postId changes
useEffect(() => {
const fetchComments = async () => {
try {
const response = await fetch(`/api/comments?postId=${postId}`);
if (response.ok) {
const data = await response.json();
setComments(data);
} else {
console.error('Failed to fetch comments');
}
} catch (err) {
console.error('Error fetching comments:', err);
setIsLoadingComments(true); // Set loading to true
const { data, error } = await supabase
.from('comments') // Replace 'comments' with your Supabase table name
.select('id, name, comment, created_at') // Select specific columns
.eq('post_id', postId) // Filter by post_id
.order('created_at', { ascending: false }); // Order by creation date
if (error) {
console.error('Error fetching comments:', error);
setError('Failed to load comments.'); // Display error to user
setComments([]); // Clear comments on error
} else {
setComments(data || []); // Set comments (handle case where data is null)
setError(null); // Clear any previous errors
}
setIsLoadingComments(false); // Set loading to false
};
fetchComments();
}, [postId]);
// Optional: Set up real-time subscriptions for new comments
// Be mindful of performance and resource usage with real-time subscriptions
// This is a basic example; you might need more sophisticated handling
const subscription = supabase
.channel(`comments:post_id=eq.${postId}`)
.on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'comments', filter: `post_id=eq.${postId}` }, (payload) => {
// Add the new comment to the beginning of the list
setComments((currentComments) => [payload.new as Comment, ...currentComments]);
})
.subscribe();
// Cleanup the subscription when the component unmounts or postId changes
return () => {
supabase.removeChannel(subscription);
};
}, [postId]); // Dependency array includes postId
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
@@ -49,33 +75,42 @@ const CommentSection: React.FC<CommentFormProps> = ({ postId }) => {
setError(null);
try {
const response = await fetch('/api/comments', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, email, comment, postId }),
});
const { data, error } = await supabase
.from('comments') // Replace 'comments' with your Supabase table name
.insert([
{
name,
email, // Storing email is dependent on your privacy policy and RLS
comment,
post_id: postId, // Assuming your Supabase column is named 'post_id'
// created_at will likely be automatically set by Supabase with a default value
},
])
.select('id, name, comment, created_at'); // Select the inserted data
if (error) {
console.error('Error inserting comment:', error);
setError(error.message || 'Something went wrong. Please try again later.');
} else {
// Assuming real-time subscription is active,
// the new comment will be added to the comments state automatically.
// If not using real-time, you would manually add the new comment here:
// if (data && data.length > 0) {
// setComments((currentComments) => [data[0], ...currentComments]);
// }
if (response.ok) {
setName('');
setEmail('');
setComment('');
setIsSubmitted(true);
setIsSubmitting(false);
// Fetch updated comments
const updatedComments = await fetch(`/api/comments?postId=${postId}`).then((res) =>
res.json()
);
setComments(updatedComments);
// No need to re-fetch all comments if using real-time subscriptions
setTimeout(() => setIsSubmitted(false), 5000);
} else {
const data = await response.json();
setError(data.message || 'Something went wrong. Please try again later.');
setIsSubmitting(false);
}
} catch (err) {
console.error('Unexpected error during submission:', err);
setError('Something went wrong. Please try again later.');
} finally {
setIsSubmitting(false);
}
};
@@ -152,30 +187,31 @@ const CommentSection: React.FC<CommentFormProps> = ({ postId }) => {
></textarea>
</div>
{/* <div className="flex items-center">
<input
type="checkbox"
id="save-info"
className="h-4 w-4 text-accent border-primary-300 rounded focus:ring-accent"
checked={typeof window !== 'undefined' && localStorage.getItem('saveInfo') === 'true'}
onChange={(e) => {
if (typeof window !== 'undefined') {
const saveInfo = e.target.checked;
localStorage.setItem('saveInfo', saveInfo.toString());
if (saveInfo) {
localStorage.setItem('name', name);
localStorage.setItem('email', email);
} else {
localStorage.removeItem('name');
localStorage.removeItem('email');
}
}
}}
/>
<label htmlFor="save-info" className="ml-2 block text-sm text-primary-600">
Save my name and email for the next time I comment
</label>
</div> */}
{/* The save info checkbox logic can remain as it interacts with localStorage */}
{/* <div className="flex items-center">
<input
type="checkbox"
id="save-info"
className="h-4 w-4 text-accent border-primary-300 rounded focus:ring-accent"
checked={typeof window !== 'undefined' && localStorage.getItem('saveInfo') === 'true'}
onChange={(e) => {
if (typeof window !== 'undefined') {
const saveInfo = e.target.checked;
localStorage.setItem('saveInfo', saveInfo.toString());
if (saveInfo) {
localStorage.setItem('name', name);
localStorage.setItem('email', email);
} else {
localStorage.removeItem('name');
localStorage.removeItem('email');
}
}
}}
/>
<label htmlFor="save-info" className="ml-2 block text-sm text-primary-600">
Save my name and email for the next time I comment
</label>
</div> */}
<button
type="submit"
@@ -189,24 +225,37 @@ const CommentSection: React.FC<CommentFormProps> = ({ postId }) => {
<div className="mt-12">
<h3 className="text-xl font-bold mb-6">Comments ({comments.length})</h3>
<div className="space-y-6">
{comments.map((comment) => (
<div
key={comment.createdAt}
className="bg-white p-6 rounded-md shadow-sm border border-primary-200"
>
<div className="flex justify-between items-start mb-4">
<div>
<h4 className="font-bold">{comment.name}</h4>
<p className="text-sm text-primary-500">
{new Date(comment.createdAt).toLocaleDateString()}
</p>
{isLoadingComments ? (
<p>Loading comments...</p>
) : comments.length === 0 ? (
<p>No comments yet. Be the first to leave one!</p>
) : (
<div className="space-y-6">
{comments.map((comment) => (
<div
// Use a more stable key than createdAt if possible, like comment.id from Supabase
key={comment.id || comment.created_at}
className="bg-white p-6 rounded-md shadow-sm border border-primary-200"
>
<div className="flex justify-between items-start mb-4">
<div>
<h4 className="font-bold">{comment.name}</h4>
<p className="text-sm text-primary-500">
{new Date(comment.created_at).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
})}
</p>
</div>
</div>
<p className="text-primary-700">{comment.comment}</p>
</div>
<p className="text-primary-700">{comment.comment}</p>
</div>
))}
</div>
))}
</div>
)}
</div>
</div>
);
+2 -1
View File
@@ -3,4 +3,5 @@ import type { AppProps } from 'next/app';
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
}
+51 -38
View File
@@ -1,55 +1,68 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { MongoClient } from 'mongodb';
import { supabase } from '@/utils/supabase';
import { NextRequest, NextResponse } from 'next/server'; // Use next/server for Edge runtime
export const runtime = 'edge';
const uri = process.env.MONGODB_URI || 'your-mongodb-uri';
const dbName = 'confessions-of-grace';
const client = new MongoClient(uri);
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === 'POST') {
const { name, email, comment, postId } = req.body;
export default async function POST(req: NextRequest) {
try {
const { name, email, comment, postId } = await req.json();
if (!name || !email || !comment || !postId) {
return res.status(400).json({ message: 'All fields are required' });
return NextResponse.json({ message: 'All fields are required' }, { status: 400 });
}
try {
if (!client.db(dbName)) await client.connect();
const db = client.db(dbName);
const collection = db.collection('comments');
// Insert data into the 'comments' table
const { data, error } = await supabase
.from('comments') // Replace 'comments' with your Supabase table name
.insert([
{
name,
email,
comment,
post_id: postId,
created_at: new Date().toISOString(), // Supabase often uses ISO strings for timestamps
},
]);
await collection.insertOne({ name, email, comment, postId, createdAt: new Date() });
res.status(201).json({ message: 'Comment submitted successfully' });
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Internal server error' });
if (error) {
console.error('Error inserting comment:', error);
return NextResponse.json({ message: 'Error submitting comment', error }, { status: 500 });
}
} else if (req.method === 'GET') {
const { postId } = req.query;
return NextResponse.json({ message: 'Comment submitted successfully', data }, { status: 201 });
} catch (error) {
console.error('Internal server error during POST:', error);
// Catching and returning a 500 for unexpected errors
return NextResponse.json({ message: 'Internal server error' }, { status: 500 });
}
}
export async function GET(req: NextRequest) {
try {
// Get query parameters from the URL
const { searchParams } = new URL(req.url);
const postId = searchParams.get('postId');
if (!postId) {
return res.status(400).json({ message: 'Post ID is required' });
return NextResponse.json({ message: 'Post ID is required' }, { status: 400 });
}
try {
if (!client.db(dbName)) await client.connect();
const db = client.db(dbName);
const collection = db.collection('comments');
// Fetch comments for a specific postId, ordered by creation date descending
const { data: comments, error } = await supabase
.from('comments') // Replace 'comments' with your Supabase table name
.select('*')
.eq('post_id', postId) // Assuming your Supabase column for post ID is 'post_id'
.order('created_at', { ascending: false }); // Assuming your timestamp column is 'created_at'
const comments = await collection
.find({ postId })
.sort({ createdAt: -1 })
.toArray();
res.status(200).json(comments);
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Internal server error' });
if (error) {
console.error('Error fetching comments:', error);
return NextResponse.json({ message: 'Error fetching comments', error }, { status: 500 });
}
} else {
res.status(405).json({ message: 'Method not allowed' });
return NextResponse.json(comments, { status: 200 });
} catch (error) {
console.error('Internal server error during GET:', error);
// Catching and returning a 500 for unexpected errors
return NextResponse.json({ message: 'Internal server error' }, { status: 500 });
}
}
+49 -45
View File
@@ -1,57 +1,61 @@
import { MongoClient } from 'mongodb';
import { NextApiRequest, NextApiResponse } from 'next';
import { supabase } from '@/utils/supabase';
import { NextRequest, NextResponse } from 'next/server'; // Use next/server for Edge runtime
export const runtime = 'edge';
const MONGODB_URI = process.env.MONGODB_URI as string; // Add this in your .env.local file
// Create a MongoClient instance
let cachedClient: MongoClient | null = null;
async function connectToDatabase() {
if (!cachedClient) {
cachedClient = new MongoClient(MONGODB_URI);
await cachedClient.connect();
}
return cachedClient.db(MONGODB_URI);
}
export default async function POST(req: NextRequest) {
// Only allow POST requests - this is handled by exporting the POST function
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
// Only allow POST requests
if (req.method !== 'POST') {
res.setHeader('Allow', ['POST']);
return res.status(405).json({ message: `Method ${req.method} Not Allowed` });
const { email } = await req.json(); // Get body from Edge request
// Check if email was provided
if (!email) {
return NextResponse.json({ message: 'Email is required.' }, { status: 400 });
}
// Validate email format
const emailRegex = /\S+@\S+\.\S+/;
if (!emailRegex.test(email)) {
return NextResponse.json({ message: 'Invalid email address.' }, { status: 400 });
}
try {
// Check if the email already exists in the 'subscriptions' table
const { data: existingEmails, error: selectError } = await supabase
.from('subscriptions') // Replace 'subscriptions' with your Supabase table name
.select('email')
.eq('email', email);
if (selectError) {
console.error('Error checking existing email:', selectError);
return NextResponse.json({ message: 'An unexpected error occurred while checking email.' }, { status: 500 });
}
const { email } = req.body;
// Check if email was provided
if (!email) {
return res.status(400).json({ message: 'Email is required.' });
if (existingEmails && existingEmails.length > 0) {
return NextResponse.json({ message: 'Email is already subscribed.' }, { status: 400 });
}
// Validate email format
const emailRegex = /\S+@\S+\.\S+/;
if (!emailRegex.test(email)) {
return res.status(400).json({ message: 'Invalid email address.' });
// Save email to the 'subscriptions' table
const { data: insertedData, error: insertError } = await supabase
.from('subscriptions') // Replace 'subscriptions' with your Supabase table name
.insert([
{
email,
created_at: new Date().toISOString(),
},
]);
if (insertError) {
console.error('Failed to save subscription:', insertError);
return NextResponse.json({ message: 'An unexpected error occurred while saving subscription.' }, { status: 500 });
}
try {
// Connect to the database
const db = await connectToDatabase();
// Respond with success
return NextResponse.json({ message: 'Successfully subscribed!' }, { status: 200 });
// Check if the email already exists
const existingEmail = await db.collection('subscriptions').findOne({ email });
if (existingEmail) {
return res.status(400).json({ message: 'Email is already subscribed.' });
}
// Save email to the database
await db.collection('subscriptions').insertOne({ email, subscribedAt: new Date() });
// Respond with success
return res.status(200).json({ message: 'Successfully subscribed!' });
} catch (error) {
// Log the error and respond with a server error status
console.error('Failed to save subscription:', error);
return res.status(500).json({ message: 'An unexpected error occurred.' });
}
} catch (error) {
// Log unexpected errors
console.error('An unexpected server error occurred:', error);
return NextResponse.json({ message: 'An unexpected error occurred.' }, { status: 500 });
}
}
+6
View File
@@ -0,0 +1,6 @@
import { createClient } from "@supabase/supabase-js";
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
export const supabase = createClient(supabaseUrl, supabaseKey);