database-integration
SKILL.md
Database Integration
Purpose
Implement reliable and efficient database operations using the ORM, including queries, transactions, and migrations.
When to Use
- Data model implementation
- Query optimization
- Transaction handling
- Migration writing
Prerequisites
- Database schema defined
- ORM configured (Prisma)
- Connection pooling set up
Process
Step 1: Define Schema
Prisma schema:
- Models
- Relations
- Indexes
- Constraints
Step 2: Generate Client
Commands:
- prisma generate
- prisma migrate dev
Step 3: Implement Repository
Repository pattern:
- CRUD operations
- Complex queries
- Transactions
Step 4: Optimize Queries
Optimization:
- Select only needed fields
- Use indexes
- Avoid N+1 queries
- Use transactions
Step 5: Handle Errors
Error handling:
- Constraint violations
- Connection errors
- Deadlocks
- Timeouts
Inputs
| Input | Type | Required | Description |
|---|---|---|---|
| database_schema | SQL | Yes | Schema design |
| queries | Markdown | Yes | Required queries |
Outputs
| Output | Type | Description |
|---|---|---|
| schema.prisma | Prisma | Schema definition |
| migrations/ | SQL | Migration files |
| repositories/ | TypeScript | Data access |
StudyAbroad-Specific Considerations
- User data privacy
- Application audit trail
- Soft deletes for GDPR
- Efficient search queries
Integration Points
- Architecture Agent: Schema design
- Security Agent: Data protection
- DevOps Agent: Migrations
Examples
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(uuid())
email String @unique
passwordHash String?
firstName String
lastName String
dateOfBirth DateTime?
nationality String?
gdprConsent Boolean @default(false)
gdprConsentAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime? // Soft delete for GDPR
applications Application[]
documents Document[]
oauthAccounts OAuthAccount[]
@@index([email])
@@index([deletedAt])
}
model Application {
id String @id @default(uuid())
userId String
programId String
status String @default("draft")
progress Int @default(0)
submittedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
program Program @relation(fields: [programId], references: [id])
documents Document[]
statusHistory ApplicationStatusHistory[]
@@unique([userId, programId])
@@index([userId])
@@index([status])
}
model ApplicationStatusHistory {
id String @id @default(uuid())
applicationId String
oldStatus String?
newStatus String
changedBy String?
notes String?
createdAt DateTime @default(now())
application Application @relation(fields: [applicationId], references: [id])
@@index([applicationId])
}
// src/prisma/prisma.service.ts
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'
import { PrismaClient, Prisma } from '@prisma/client'
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
constructor() {
super({
log: [
{ emit: 'event', level: 'query' },
{ emit: 'stdout', level: 'error' },
{ emit: 'stdout', level: 'warn' }
]
})
}
async onModuleInit() {
await this.$connect()
// Soft delete middleware
this.$use(async (params, next) => {
if (params.model === 'User') {
if (params.action === 'delete') {
params.action = 'update'
params.args['data'] = { deletedAt: new Date() }
}
if (params.action === 'deleteMany') {
params.action = 'updateMany'
if (params.args.data !== undefined) {
params.args.data['deletedAt'] = new Date()
} else {
params.args['data'] = { deletedAt: new Date() }
}
}
// Filter out soft-deleted by default
if (params.action === 'findUnique' || params.action === 'findFirst') {
params.action = 'findFirst'
params.args.where['deletedAt'] = null
}
if (params.action === 'findMany') {
if (params.args.where) {
if (params.args.where.deletedAt === undefined) {
params.args.where['deletedAt'] = null
}
} else {
params.args['where'] = { deletedAt: null }
}
}
}
return next(params)
})
}
async onModuleDestroy() {
await this.$disconnect()
}
// Transaction helper
async executeInTransaction<T>(
fn: (prisma: Prisma.TransactionClient) => Promise<T>
): Promise<T> {
return this.$transaction(fn, {
maxWait: 5000,
timeout: 10000,
isolationLevel: Prisma.TransactionIsolationLevel.Serializable
})
}
}
// src/application/application.repository.ts
@Injectable()
export class ApplicationRepository {
constructor(private readonly prisma: PrismaService) {}
async findByUserWithPrograms(
userId: string,
options: { status?: string; page: number; limit: number }
) {
const { status, page, limit } = options
const where: Prisma.ApplicationWhereInput = { userId }
if (status) {
where.status = status
}
const [applications, total] = await Promise.all([
this.prisma.application.findMany({
where,
include: {
program: {
include: {
university: true
}
},
documents: {
select: { id: true, documentType: true, fileName: true }
}
},
skip: (page - 1) * limit,
take: limit,
orderBy: { createdAt: 'desc' }
}),
this.prisma.application.count({ where })
])
return { applications, total }
}
async submitWithHistory(
id: string,
userId: string
): Promise<Application> {
return this.prisma.executeInTransaction(async (tx) => {
// Get current application
const application = await tx.application.findFirst({
where: { id, userId }
})
if (!application) {
throw new NotFoundException()
}
// Update status
const updated = await tx.application.update({
where: { id },
data: {
status: 'submitted',
submittedAt: new Date()
}
})
// Create history record
await tx.applicationStatusHistory.create({
data: {
applicationId: id,
oldStatus: application.status,
newStatus: 'submitted',
changedBy: userId
}
})
return updated
})
}
}
Validation
- Schema matches design
- Migrations run cleanly
- Queries are efficient
- Transactions used correctly
- Errors handled properly
Weekly Installs
2
Repository
vihang-hub/inte…rameworkGitHub Stars
1
First Seen
1 day ago
Security Audits
Installed on
opencode2
gemini-cli2
antigravity2
claude-code2
github-copilot2
codex2