update
This commit is contained in:
237
lib/prisma.ts
Normal file
237
lib/prisma.ts
Normal file
@@ -0,0 +1,237 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const globalForPrisma = globalThis as unknown as {
|
||||
prisma: PrismaClient | undefined;
|
||||
};
|
||||
|
||||
export const prisma = globalForPrisma.prisma ?? new PrismaClient({
|
||||
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
|
||||
|
||||
// Database service functions
|
||||
export const projectService = {
|
||||
// Get all projects with pagination and filtering
|
||||
async getAllProjects(options: {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
category?: string;
|
||||
featured?: boolean;
|
||||
published?: boolean;
|
||||
difficulty?: string;
|
||||
search?: string;
|
||||
} = {}) {
|
||||
const { page = 1, limit = 50, category, featured, published, difficulty, search } = options;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const where: any = {};
|
||||
|
||||
if (category) where.category = category;
|
||||
if (featured !== undefined) where.featured = featured;
|
||||
if (published !== undefined) where.published = published;
|
||||
if (difficulty) where.difficulty = difficulty;
|
||||
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ title: { contains: search, mode: 'insensitive' } },
|
||||
{ description: { contains: search, mode: 'insensitive' } },
|
||||
{ tags: { hasSome: [search] } },
|
||||
{ content: { contains: search, mode: 'insensitive' } }
|
||||
];
|
||||
}
|
||||
|
||||
const [projects, total] = await Promise.all([
|
||||
prisma.project.findMany({
|
||||
where,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
skip,
|
||||
take: limit,
|
||||
include: {
|
||||
_count: {
|
||||
select: {
|
||||
pageViews: true,
|
||||
userInteractions: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
prisma.project.count({ where })
|
||||
]);
|
||||
|
||||
return {
|
||||
projects,
|
||||
total,
|
||||
pages: Math.ceil(total / limit),
|
||||
currentPage: page
|
||||
};
|
||||
},
|
||||
|
||||
// Get project by ID
|
||||
async getProjectById(id: number) {
|
||||
return prisma.project.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
_count: {
|
||||
select: {
|
||||
pageViews: true,
|
||||
userInteractions: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Create new project
|
||||
async createProject(data: any) {
|
||||
return prisma.project.create({
|
||||
data: {
|
||||
...data,
|
||||
performance: data.performance || { lighthouse: 90, bundleSize: '50KB', loadTime: '1.5s' },
|
||||
analytics: data.analytics || { views: 0, likes: 0, shares: 0 }
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Update project
|
||||
async updateProject(id: number, data: any) {
|
||||
return prisma.project.update({
|
||||
where: { id },
|
||||
data: { ...data, updatedAt: new Date() }
|
||||
});
|
||||
},
|
||||
|
||||
// Delete project
|
||||
async deleteProject(id: number) {
|
||||
return prisma.project.delete({
|
||||
where: { id }
|
||||
});
|
||||
},
|
||||
|
||||
// Get featured projects
|
||||
async getFeaturedProjects(limit: number = 6) {
|
||||
return prisma.project.findMany({
|
||||
where: { featured: true, published: true },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: limit
|
||||
});
|
||||
},
|
||||
|
||||
// Get projects by category
|
||||
async getProjectsByCategory(category: string, limit: number = 10) {
|
||||
return prisma.project.findMany({
|
||||
where: { category, published: true },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: limit
|
||||
});
|
||||
},
|
||||
|
||||
// Search projects
|
||||
async searchProjects(query: string, limit: number = 20) {
|
||||
return prisma.project.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ title: { contains: query, mode: 'insensitive' } },
|
||||
{ description: { contains: query, mode: 'insensitive' } },
|
||||
{ tags: { hasSome: [query] } },
|
||||
{ content: { contains: query, mode: 'insensitive' } }
|
||||
],
|
||||
published: true
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: limit
|
||||
});
|
||||
},
|
||||
|
||||
// Track page view
|
||||
async trackPageView(projectId: number | null, page: string, ip?: string, userAgent?: string, referrer?: string) {
|
||||
return prisma.pageView.create({
|
||||
data: {
|
||||
projectId,
|
||||
page,
|
||||
ip,
|
||||
userAgent,
|
||||
referrer
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Track user interaction
|
||||
async trackUserInteraction(projectId: number, type: string, ip?: string, userAgent?: string) {
|
||||
return prisma.userInteraction.create({
|
||||
data: {
|
||||
projectId,
|
||||
type: type as any,
|
||||
ip,
|
||||
userAgent
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Get analytics
|
||||
async getAnalytics(projectId: number) {
|
||||
const [pageViews, interactions] = await Promise.all([
|
||||
prisma.pageView.count({ where: { projectId } }),
|
||||
prisma.userInteraction.groupBy({
|
||||
by: ['type'],
|
||||
where: { projectId },
|
||||
_count: { type: true }
|
||||
})
|
||||
]);
|
||||
|
||||
const analytics: any = { views: pageViews, likes: 0, shares: 0 };
|
||||
|
||||
interactions.forEach(interaction => {
|
||||
if (interaction.type === 'LIKE') analytics.likes = interaction._count.type;
|
||||
if (interaction.type === 'SHARE') analytics.shares = interaction._count.type;
|
||||
});
|
||||
|
||||
return analytics;
|
||||
},
|
||||
|
||||
// Get performance stats
|
||||
async getPerformanceStats() {
|
||||
const projects = await prisma.project.findMany({
|
||||
select: {
|
||||
performance: true,
|
||||
analytics: true,
|
||||
category: true,
|
||||
difficulty: true
|
||||
}
|
||||
});
|
||||
|
||||
const stats = {
|
||||
totalProjects: projects.length,
|
||||
avgLighthouse: 0,
|
||||
totalViews: 0,
|
||||
totalLikes: 0,
|
||||
totalShares: 0,
|
||||
byCategory: {} as any,
|
||||
byDifficulty: {} as any
|
||||
};
|
||||
|
||||
projects.forEach(project => {
|
||||
const perf = project.performance as any;
|
||||
const analytics = project.analytics as any;
|
||||
|
||||
stats.avgLighthouse += perf?.lighthouse || 0;
|
||||
stats.totalViews += analytics?.views || 0;
|
||||
stats.totalLikes += analytics?.likes || 0;
|
||||
stats.totalShares += analytics?.shares || 0;
|
||||
|
||||
// Category stats
|
||||
if (!stats.byCategory[project.category]) stats.byCategory[project.category] = 0;
|
||||
stats.byCategory[project.category]++;
|
||||
|
||||
// Difficulty stats
|
||||
if (!stats.byDifficulty[project.difficulty]) stats.byDifficulty[project.difficulty] = 0;
|
||||
stats.byDifficulty[project.difficulty]++;
|
||||
});
|
||||
|
||||
if (stats.totalProjects > 0) {
|
||||
stats.avgLighthouse = Math.round(stats.avgLighthouse / stats.totalProjects);
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
};
|
||||
138
lib/supabase.ts
Normal file
138
lib/supabase.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
||||
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
|
||||
|
||||
export const supabase = createClient(supabaseUrl, supabaseAnonKey);
|
||||
|
||||
// Database types
|
||||
export interface DatabaseProject {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
content: string;
|
||||
tags: string[];
|
||||
featured: boolean;
|
||||
category: string;
|
||||
date: string;
|
||||
github?: string;
|
||||
live?: string;
|
||||
published: boolean;
|
||||
imageUrl?: string;
|
||||
metaDescription?: string;
|
||||
keywords?: string;
|
||||
ogImage?: string;
|
||||
schema?: Record<string, unknown>;
|
||||
difficulty: 'Beginner' | 'Intermediate' | 'Advanced' | 'Expert';
|
||||
timeToComplete?: string;
|
||||
technologies: string[];
|
||||
challenges: string[];
|
||||
lessonsLearned: string[];
|
||||
futureImprovements: string[];
|
||||
demoVideo?: string;
|
||||
screenshots: string[];
|
||||
colorScheme: string;
|
||||
accessibility: boolean;
|
||||
performance: {
|
||||
lighthouse: number;
|
||||
bundleSize: string;
|
||||
loadTime: string;
|
||||
};
|
||||
analytics: {
|
||||
views: number;
|
||||
likes: number;
|
||||
shares: number;
|
||||
};
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
// Database operations
|
||||
export const projectService = {
|
||||
async getAllProjects(): Promise<DatabaseProject[]> {
|
||||
const { data, error } = await supabase
|
||||
.from('projects')
|
||||
.select('*')
|
||||
.order('created_at', { ascending: false });
|
||||
|
||||
if (error) throw error;
|
||||
return data || [];
|
||||
},
|
||||
|
||||
async getProjectById(id: number): Promise<DatabaseProject | null> {
|
||||
const { data, error } = await supabase
|
||||
.from('projects')
|
||||
.select('*')
|
||||
.eq('id', id)
|
||||
.single();
|
||||
|
||||
if (error) throw error;
|
||||
return data;
|
||||
},
|
||||
|
||||
async createProject(project: Omit<DatabaseProject, 'id' | 'created_at' | 'updated_at'>): Promise<DatabaseProject> {
|
||||
const { data, error } = await supabase
|
||||
.from('projects')
|
||||
.insert([project])
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) throw error;
|
||||
return data;
|
||||
},
|
||||
|
||||
async updateProject(id: number, updates: Partial<DatabaseProject>): Promise<DatabaseProject> {
|
||||
const { data, error } = await supabase
|
||||
.from('projects')
|
||||
.update({ ...updates, updated_at: new Date().toISOString() })
|
||||
.eq('id', id)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) throw error;
|
||||
return data;
|
||||
},
|
||||
|
||||
async deleteProject(id: number): Promise<void> {
|
||||
const { error } = await supabase
|
||||
.from('projects')
|
||||
.delete()
|
||||
.eq('id', id);
|
||||
|
||||
if (error) throw error;
|
||||
},
|
||||
|
||||
async searchProjects(query: string): Promise<DatabaseProject[]> {
|
||||
const { data, error } = await supabase
|
||||
.from('projects')
|
||||
.select('*')
|
||||
.or(`title.ilike.%${query}%,description.ilike.%${query}%,content.ilike.%${query}%,tags.cs.{${query}}`)
|
||||
.order('created_at', { ascending: false });
|
||||
|
||||
if (error) throw error;
|
||||
return data || [];
|
||||
},
|
||||
|
||||
async getProjectsByCategory(category: string): Promise<DatabaseProject[]> {
|
||||
const { data, error } = await supabase
|
||||
.from('projects')
|
||||
.select('*')
|
||||
.eq('category', category)
|
||||
.order('created_at', { ascending: false });
|
||||
|
||||
if (error) throw error;
|
||||
return data || [];
|
||||
},
|
||||
|
||||
async getFeaturedProjects(): Promise<DatabaseProject[]> {
|
||||
const { data, error } = await supabase
|
||||
.from('projects')
|
||||
.select('*')
|
||||
.eq('featured', true)
|
||||
.eq('published', true)
|
||||
.order('created_at', { ascending: false });
|
||||
|
||||
if (error) throw error;
|
||||
return data || [];
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user