// ============================================
// Prisma Client Instance - db.ts
// ============================================
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
// ============================================
// Schema (schema.prisma)
// ============================================
/*
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql" // or mysql, sqlite, sqlserver
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String
password String // Hashed
role Role @default(USER)
posts Post[]
profile Profile?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([email])
}
model Profile {
id Int @id @default(autoincrement())
bio String?
userId Int @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
viewCount Int @default(0)
authorId Int
author User @relation(fields: [authorId], references: [id])
categories Category[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([authorId])
}
model Category {
id Int @id @default(autoincrement())
name String @unique
posts Post[]
}
enum Role {
USER
ADMIN
}
*/
// ============================================
// Services / Repositories
// ============================================
export const userService = {
async findByEmail(email: string) {
return prisma.user.findUnique({
where: { email },
include: { profile: true },
})
},
async create(data: { email: string; name: string; password: string }) {
return prisma.user.create({
data: {
...data,
profile: {
create: { bio: 'New user' },
},
},
})
},
async getFeed(userId: number) {
// Get posts from authors followed by user (example logic)
return prisma.post.findMany({
where: {
published: true,
authorId: { not: userId }, // Exclude own posts
},
include: {
author: {
select: { name: true, email: true },
},
_count: {
select: { categories: true },
},
},
orderBy: { createdAt: 'desc' },
take: 20,
})
},
}
export const postService = {
async create(userId: number, title: string, content?: string) {
return prisma.post.create({
data: {
title,
content,
authorId: userId,
},
})
},
async publish(postId: number, userId: number) {
// Verify ownership and update
const post = await prisma.post.findUnique({ where: { id: postId } })
if (!post) throw new Error('Post not found')
if (post.authorId !== userId) throw new Error('Not authorized')
return prisma.post.update({
where: { id: postId },
data: { published: true },
})
},
async delete(postId: number, userId: number) {
return prisma.post.deleteMany({
where: {
id: postId,
authorId: userId, // Ensure ownership
},
})
},
}
// ============================================
// Transaction Example
// ============================================
export async function transferOwnership(postId: number, newAuthorId: number) {
return prisma.$transaction(async (tx) => {
const post = await tx.post.findUniqueOrThrow({ where: { id: postId } })
// Log the transfer
console.log(`Transferring post ${post.id} to user ${newAuthorId}`)
const updated = await tx.post.update({
where: { id: postId },
data: { authorId: newAuthorId },
})
return updated
})
}
// ============================================
// Pagination Helper
// ============================================
export async function getPostsPaginated(cursor?: number, limit = 10) {
return prisma.post.findMany({
take: limit,
skip: cursor ? 1 : 0, // Skip the cursor itself
cursor: cursor ? { id: cursor } : undefined,
orderBy: { id: 'desc' },
where: { published: true },
})
}
// ============================================
// Middleware Example (Soft Delete)
// ============================================
/*
prisma.$use(async (params, next) => {
// Check incoming query type
if (params.model == 'Post' && params.action == 'delete') {
// Change action to an update
params.action = 'update'
params.args['data'] = { deleted: true }
}
return next(params)
})
*/
// ============================================
// Clean Shutdown
// ============================================
export async function disconnect() {
await prisma.$disconnect()
}