From ee173091d6442a4344b03ec0afcadc3eb7ae051b Mon Sep 17 00:00:00 2001 From: Sooraj-krishna Date: Tue, 13 Jan 2026 21:57:08 +0530 Subject: [PATCH 1/3] Database implimented --- .gitignore | 2 +- drizzle.config.ts | 16 ++++++ package-lock.json | 29 ++++++++++- package.json | 1 + src/app/api/contact/route.ts | 53 +++++++++++++++++++ src/app/api/projects/route.ts | 30 +++++++++++ src/app/api/team/route.ts | 22 ++++++++ src/app/contact/page.tsx | 18 +++++-- src/app/page.tsx | 13 +++-- src/lib/db-utils.ts | 45 +++++++++++++++++ src/lib/db.ts | 16 ++++++ src/lib/migrate.ts | 95 +++++++++++++++++++++++++++++++++++ src/lib/schema.ts | 92 +++++++++++++++++++++++++++++++++ 13 files changed, 423 insertions(+), 9 deletions(-) create mode 100644 drizzle.config.ts create mode 100644 src/app/api/contact/route.ts create mode 100644 src/app/api/projects/route.ts create mode 100644 src/app/api/team/route.ts create mode 100644 src/lib/db-utils.ts create mode 100644 src/lib/db.ts create mode 100644 src/lib/migrate.ts create mode 100644 src/lib/schema.ts diff --git a/.gitignore b/.gitignore index 5ef6a52..1b7a73a 100644 --- a/.gitignore +++ b/.gitignore @@ -31,7 +31,7 @@ yarn-error.log* .pnpm-debug.log* # env files (can opt-in for committing if needed) -.env* +# .env* # vercel .vercel diff --git a/drizzle.config.ts b/drizzle.config.ts new file mode 100644 index 0000000..1f5de66 --- /dev/null +++ b/drizzle.config.ts @@ -0,0 +1,16 @@ +import type { Config } from 'drizzle-kit'; + +if (!process.env.DATABASE_URL) { + throw new Error('DATABASE_URL environment variable is not set'); +} + +export default { + schema: './src/lib/schema.ts', + out: './drizzle', + dialect: 'postgresql', + dbCredentials: { + url: process.env.DATABASE_URL, + }, + verbose: true, + strict: true, +} satisfies Config; diff --git a/package-lock.json b/package-lock.json index 1868786..f1b765b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,7 @@ "cmdk": "^1.1.1", "connect-pg-simple": "^10.0.0", "date-fns": "^3.6.0", + "dotenv": "^17.2.3", "drizzle-orm": "^0.39.1", "drizzle-zod": "^0.7.0", "embla-carousel-react": "^8.6.0", @@ -1473,6 +1474,7 @@ "version": "0.10.4", "resolved": "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-0.10.4.tgz", "integrity": "sha512-2nZuh3VUO9voBauuh+IGYRhGU/MskWHt1IuZvHcJw6GLjDgtqj/KViKo7SIrLdGLdot7vFbiRRw+BgEy3wT9HA==", + "peer": true, "dependencies": { "@types/pg": "8.11.6" } @@ -3473,6 +3475,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", "devOptional": true, + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -3482,6 +3485,7 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz", "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", "devOptional": true, + "peer": true, "peerDependencies": { "@types/react": "^19.0.0" } @@ -3715,6 +3719,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001726", "electron-to-chromium": "^1.5.173", @@ -4211,6 +4216,18 @@ "csstype": "^3.0.2" } }, + "node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/drizzle-kit": { "version": "0.30.6", "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.30.6.tgz", @@ -4637,6 +4654,7 @@ "version": "0.39.3", "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.39.3.tgz", "integrity": "sha512-EZ8ZpYvDIvKU9C56JYLOmUskazhad+uXZCTCRN4OnRMsL+xAJ05dv1eCpAG5xzhsm1hqiuC5kAZUCS924u2DTw==", + "peer": true, "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", @@ -4790,7 +4808,8 @@ "node_modules/embla-carousel": { "version": "8.6.0", "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", - "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==" + "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", + "peer": true }, "node_modules/embla-carousel-react": { "version": "8.6.0", @@ -4883,6 +4902,7 @@ "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", "dev": true, "hasInstallScript": true, + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -6300,6 +6320,7 @@ "version": "8.16.3", "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", + "peer": true, "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", @@ -6487,6 +6508,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -6744,6 +6766,7 @@ "version": "19.1.0", "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -6781,6 +6804,7 @@ "version": "19.1.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -6792,6 +6816,7 @@ "version": "7.61.1", "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.61.1.tgz", "integrity": "sha512-2vbXUFDYgqEgM2RcXcAT2PwDW/80QARi+PKmHy5q2KhuKvOlG8iIYgf7eIlIANR5trW9fJbP4r5aub3a4egsew==", + "peer": true, "engines": { "node": ">=18.0.0" }, @@ -7499,6 +7524,7 @@ "version": "3.4.17", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", + "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -7999,6 +8025,7 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 9b5fd4a..81b04f0 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "cmdk": "^1.1.1", "connect-pg-simple": "^10.0.0", "date-fns": "^3.6.0", + "dotenv": "^17.2.3", "drizzle-orm": "^0.39.1", "drizzle-zod": "^0.7.0", "embla-carousel-react": "^8.6.0", diff --git a/src/app/api/contact/route.ts b/src/app/api/contact/route.ts new file mode 100644 index 0000000..0463cdf --- /dev/null +++ b/src/app/api/contact/route.ts @@ -0,0 +1,53 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { createContactMessage, handleDatabaseError } from '@/lib/db-utils'; +import { z } from 'zod'; + +const contactSchema = z.object({ + firstName: z.string().min(1, 'First name is required'), + lastName: z.string().min(1, 'Last name is required'), + email: z.string().email('Invalid email address'), + subject: z.string().min(1, 'Subject is required'), + message: z.string().min(10, 'Message must be at least 10 characters'), +}); + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + + // Validate the request body + const validatedData = contactSchema.parse(body); + + // Save to database + const contactMessage = await createContactMessage({ + firstName: validatedData.firstName, + lastName: validatedData.lastName, + email: validatedData.email, + subject: validatedData.subject, + message: validatedData.message, + status: 'new', + }); + + return NextResponse.json( + { + success: true, + message: 'Contact message received successfully', + id: contactMessage.id + }, + { status: 201 } + ); + } catch (error) { + console.error('Error saving contact message:', error); + + if (error instanceof z.ZodError) { + return NextResponse.json( + { error: 'Validation error', details: error.errors }, + { status: 400 } + ); + } + + return NextResponse.json( + { error: handleDatabaseError(error) }, + { status: 500 } + ); + } +} diff --git a/src/app/api/projects/route.ts b/src/app/api/projects/route.ts new file mode 100644 index 0000000..341ae27 --- /dev/null +++ b/src/app/api/projects/route.ts @@ -0,0 +1,30 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getAllProjects, getFeaturedProjects, handleDatabaseError } from '@/lib/db-utils'; + +export const dynamic = 'force-dynamic'; + +export async function GET(request: NextRequest) { + try { + const searchParams = request.nextUrl.searchParams; + const featured = searchParams.get('featured'); + + let projects; + if (featured === 'true') { + projects = await getFeaturedProjects(); + } else { + projects = await getAllProjects(); + } + + return NextResponse.json(projects, { + headers: { + 'Cache-Control': 'no-store, max-age=0', + }, + }); + } catch (error) { + console.error('Error fetching projects:', error); + return NextResponse.json( + { error: handleDatabaseError(error) }, + { status: 500 } + ); + } +} diff --git a/src/app/api/team/route.ts b/src/app/api/team/route.ts new file mode 100644 index 0000000..bf5c383 --- /dev/null +++ b/src/app/api/team/route.ts @@ -0,0 +1,22 @@ +import { NextResponse } from 'next/server'; +import { getAllTeamMembers, handleDatabaseError } from '@/lib/db-utils'; + +export const dynamic = 'force-dynamic'; + +export async function GET() { + try { + const teamMembers = await getAllTeamMembers(); + + return NextResponse.json(teamMembers, { + headers: { + 'Cache-Control': 'no-store, max-age=0', + }, + }); + } catch (error) { + console.error('Error fetching team members:', error); + return NextResponse.json( + { error: handleDatabaseError(error) }, + { status: 500 } + ); + } +} diff --git a/src/app/contact/page.tsx b/src/app/contact/page.tsx index 7183ef9..2fb0c30 100644 --- a/src/app/contact/page.tsx +++ b/src/app/contact/page.tsx @@ -25,8 +25,19 @@ export default function Contact() { setIsSubmitting(true); try { - // Simulate form submission - await new Promise(resolve => setTimeout(resolve, 1000)); + const response = await fetch('/api/contact', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(formData), + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || 'Failed to send message'); + } toast({ title: "Message sent successfully!", @@ -42,9 +53,10 @@ export default function Contact() { message: '', }); } catch (error) { + console.error('Contact form error:', error); toast({ title: "Failed to send message", - description: "Please try again later or contact us directly.", + description: error instanceof Error ? error.message : "Please try again later or contact us directly.", variant: "destructive", }); } finally { diff --git a/src/app/page.tsx b/src/app/page.tsx index 7fbe66c..e082780 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -19,13 +19,18 @@ import { useQuery } from "@tanstack/react-query"; import { Project } from "@/types/project"; export default function Home() { - // Fetch featured projects from JSON file + // Fetch featured projects from database API const basePath = process.env.NEXT_PUBLIC_BASE_PATH || ""; - const { data: allProjects = [] } = useQuery({ + const { data: allProjects = [], isLoading, error } = useQuery({ queryKey: ["projects"], queryFn: async (): Promise => { - const response = await fetch(`${basePath}/data/projects.json`); - if (!response.ok) throw new Error("Failed to fetch projects"); + const response = await fetch(`${basePath}/api/projects`); + if (!response.ok) { + // Fallback to JSON file if API fails + const fallbackResponse = await fetch(`${basePath}/data/projects.json`); + if (!fallbackResponse.ok) throw new Error("Failed to fetch projects"); + return fallbackResponse.json(); + } return response.json(); }, }); diff --git a/src/lib/db-utils.ts b/src/lib/db-utils.ts new file mode 100644 index 0000000..b428342 --- /dev/null +++ b/src/lib/db-utils.ts @@ -0,0 +1,45 @@ +import { db } from './db'; +import { projects, teamMembers, contactMessages, type NewContactMessage, type Project, type TeamMember } from './schema'; +import { eq } from 'drizzle-orm'; + +// Projects utilities +export async function getAllProjects(): Promise { + return await db.select().from(projects); +} + +export async function getFeaturedProjects(): Promise { + return await db.select().from(projects).where(eq(projects.featured, true)); +} + +export async function getProjectById(id: number): Promise { + const result = await db.select().from(projects).where(eq(projects.id, id)); + return result[0]; +} + +// Team utilities +export async function getAllTeamMembers(): Promise { + return await db.select().from(teamMembers); +} + +export async function getTeamMemberById(id: number): Promise { + const result = await db.select().from(teamMembers).where(eq(teamMembers.id, id)); + return result[0]; +} + +// Contact messages utilities +export async function createContactMessage(message: NewContactMessage) { + const result = await db.insert(contactMessages).values(message).returning(); + return result[0]; +} + +export async function getAllContactMessages() { + return await db.select().from(contactMessages); +} + +// Helper function to handle database errors +export function handleDatabaseError(error: unknown): string { + if (error instanceof Error) { + return error.message; + } + return 'An unknown database error occurred'; +} diff --git a/src/lib/db.ts b/src/lib/db.ts new file mode 100644 index 0000000..e8d78c3 --- /dev/null +++ b/src/lib/db.ts @@ -0,0 +1,16 @@ +import { drizzle } from 'drizzle-orm/neon-serverless'; +import { Pool } from '@neondatabase/serverless'; +import * as schema from './schema'; + +if (!process.env.DATABASE_URL) { + throw new Error('DATABASE_URL environment variable is not set'); +} + +// Create a connection pool +const pool = new Pool({ connectionString: process.env.DATABASE_URL }); + +// Create and export the drizzle instance +export const db = drizzle(pool, { schema }); + +// Export the pool for direct access if needed +export { pool }; diff --git a/src/lib/migrate.ts b/src/lib/migrate.ts new file mode 100644 index 0000000..30bf433 --- /dev/null +++ b/src/lib/migrate.ts @@ -0,0 +1,95 @@ +import { readFileSync } from 'fs'; +import { join, resolve } from 'path'; +import { config } from 'dotenv'; + +// Load environment variables FIRST before any other imports +config({ path: resolve(process.cwd(), '.env') }); + +// Now import database modules after env is loaded +import { db } from './db'; +import { projects, teamMembers } from './schema'; + +interface JsonProject { + id: number; + name: string; + description: string; + type: 'free' | 'premium'; + featured: boolean; + stars: number; + language: string; + license: string; + githubUrl: string; + tags: string[]; + category: 'developer' | 'student'; +} + +interface JsonTeamMember { + id: number; + name: string; + role: string; + bio: string; + image: string; + github?: string; + linkedin?: string; + specialties: string[]; +} + +async function migrate() { + try { + console.log('šŸš€ Starting database migration...\n'); + + // Load projects from JSON + const projectsPath = join(process.cwd(), 'public', 'data', 'projects.json'); + const projectsData: JsonProject[] = JSON.parse(readFileSync(projectsPath, 'utf-8')); + + console.log(`šŸ“¦ Found ${projectsData.length} projects to migrate`); + + // Insert projects + for (const project of projectsData) { + await db.insert(projects).values({ + name: project.name, + description: project.description, + githubUrl: project.githubUrl, + language: project.language, + stars: project.stars, + license: project.license, + type: project.type, + featured: project.featured, + category: project.category, + tags: project.tags, + }); + console.log(` āœ“ Migrated project: ${project.name}`); + } + + // Load team members from JSON + const teamPath = join(process.cwd(), 'public', 'data', 'team.json'); + const teamData: JsonTeamMember[] = JSON.parse(readFileSync(teamPath, 'utf-8')); + + console.log(`\nšŸ‘„ Found ${teamData.length} team members to migrate`); + + // Insert team members + for (const member of teamData) { + await db.insert(teamMembers).values({ + name: member.name, + role: member.role, + bio: member.bio, + image: member.image, + github: member.github, + linkedin: member.linkedin, + specialties: member.specialties, + }); + console.log(` āœ“ Migrated team member: ${member.name}`); + } + + console.log('\nāœ… Migration completed successfully!'); + console.log(` - ${projectsData.length} projects migrated`); + console.log(` - ${teamData.length} team members migrated`); + + process.exit(0); + } catch (error) { + console.error('āŒ Migration failed:', error); + process.exit(1); + } +} + +migrate(); diff --git a/src/lib/schema.ts b/src/lib/schema.ts new file mode 100644 index 0000000..1b59fc5 --- /dev/null +++ b/src/lib/schema.ts @@ -0,0 +1,92 @@ +import { + pgTable, + serial, + text, + integer, + boolean, + timestamp, + pgEnum, + jsonb +} from 'drizzle-orm/pg-core'; + +// Enums +export const projectTypeEnum = pgEnum('project_type', ['free', 'premium']); +export const projectCategoryEnum = pgEnum('project_category', ['developer', 'student']); +export const contactStatusEnum = pgEnum('contact_status', ['new', 'read', 'responded', 'archived']); +export const donationFrequencyEnum = pgEnum('donation_frequency', ['one-time', 'monthly', 'yearly']); +export const donationPlatformEnum = pgEnum('donation_platform', ['github', 'kofi', 'other']); + +// Projects Table +export const projects = pgTable('projects', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + description: text('description').notNull(), + githubUrl: text('github_url').notNull(), + language: text('language').notNull(), + stars: integer('stars').default(0).notNull(), + license: text('license').notNull(), + type: projectTypeEnum('type').notNull(), + featured: boolean('featured').default(false).notNull(), + category: projectCategoryEnum('category').notNull(), + tags: jsonb('tags').$type().default([]), + createdAt: timestamp('created_at').defaultNow().notNull(), + updatedAt: timestamp('updated_at').defaultNow().notNull(), +}); + +// Team Members Table +export const teamMembers = pgTable('team_members', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + role: text('role').notNull(), + bio: text('bio').notNull(), + image: text('image').notNull(), + github: text('github'), + linkedin: text('linkedin'), + specialties: jsonb('specialties').$type().default([]), + createdAt: timestamp('created_at').defaultNow().notNull(), + updatedAt: timestamp('updated_at').defaultNow().notNull(), +}); + +// Contact Messages Table +export const contactMessages = pgTable('contact_messages', { + id: serial('id').primaryKey(), + firstName: text('first_name').notNull(), + lastName: text('last_name').notNull(), + email: text('email').notNull(), + subject: text('subject').notNull(), + message: text('message').notNull(), + status: contactStatusEnum('status').default('new').notNull(), + createdAt: timestamp('created_at').defaultNow().notNull(), +}); + +// Donations Table (future enhancement) +export const donations = pgTable('donations', { + id: serial('id').primaryKey(), + donorName: text('donor_name'), + donorEmail: text('donor_email'), + amount: integer('amount').notNull(), // Amount in cents + frequency: donationFrequencyEnum('frequency').notNull(), + platform: donationPlatformEnum('platform').notNull(), + message: text('message'), + createdAt: timestamp('created_at').defaultNow().notNull(), +}); + +// Newsletter Subscriptions Table (future enhancement) +export const newsletterSubscriptions = pgTable('newsletter_subscriptions', { + id: serial('id').primaryKey(), + email: text('email').notNull().unique(), + subscribedAt: timestamp('subscribed_at').defaultNow().notNull(), + isActive: boolean('is_active').default(true).notNull(), +}); + +// TypeScript types inferred from schema +export type Project = typeof projects.$inferSelect; +export type NewProject = typeof projects.$inferInsert; +export type TeamMember = typeof teamMembers.$inferSelect; +export type NewTeamMember = typeof teamMembers.$inferInsert; +export type ContactMessage = typeof contactMessages.$inferSelect; +export type NewContactMessage = typeof contactMessages.$inferInsert; +export type Donation = typeof donations.$inferSelect; +export type NewDonation = typeof donations.$inferInsert; +export type NewsletterSubscription = typeof newsletterSubscriptions.$inferSelect; +export type NewNewsletterSubscription = typeof newsletterSubscriptions.$inferInsert; From 4b4a599d3da3868bb5347b4e225607ed7980d0d0 Mon Sep 17 00:00:00 2001 From: Sooraj-krishna Date: Tue, 13 Jan 2026 21:58:31 +0530 Subject: [PATCH 2/3] env issue --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1b7a73a..5ef6a52 100644 --- a/.gitignore +++ b/.gitignore @@ -31,7 +31,7 @@ yarn-error.log* .pnpm-debug.log* # env files (can opt-in for committing if needed) -# .env* +.env* # vercel .vercel From 574fd81f7b277cb1fd679e1c2293759d2c26772a Mon Sep 17 00:00:00 2001 From: Sooraj-krishna Date: Tue, 13 Jan 2026 22:08:20 +0530 Subject: [PATCH 3/3] fix(netlify): Make database connection lazy and enable serverless deployment - Changed db.ts to use lazy initialization pattern to prevent build-time errors - Removed static export mode from next.config.ts to support API routes - Build now succeeds even when DATABASE_URL is not set during build time --- next.config.ts | 1 - src/lib/db.ts | 33 +++++++++++++++++++++++++-------- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/next.config.ts b/next.config.ts index 2b6913a..e9ffa30 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,6 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - output: "export", /* config options here */ }; diff --git a/src/lib/db.ts b/src/lib/db.ts index e8d78c3..eeedf39 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -2,15 +2,32 @@ import { drizzle } from 'drizzle-orm/neon-serverless'; import { Pool } from '@neondatabase/serverless'; import * as schema from './schema'; -if (!process.env.DATABASE_URL) { - throw new Error('DATABASE_URL environment variable is not set'); +// Lazy initialization - only create pool when needed +let pool: Pool | null = null; +let dbInstance: ReturnType | null = null; + +function getPool() { + if (!pool) { + if (!process.env.DATABASE_URL) { + throw new Error('DATABASE_URL environment variable is not set'); + } + pool = new Pool({ connectionString: process.env.DATABASE_URL }); + } + return pool; } -// Create a connection pool -const pool = new Pool({ connectionString: process.env.DATABASE_URL }); +function getDb() { + if (!dbInstance) { + dbInstance = drizzle(getPool(), { schema }); + } + return dbInstance; +} -// Create and export the drizzle instance -export const db = drizzle(pool, { schema }); +// Export getters instead of direct instances +export const db = new Proxy({} as ReturnType, { + get(target, prop) { + return getDb()[prop as keyof ReturnType]; + } +}); -// Export the pool for direct access if needed -export { pool }; +export { getPool as pool };