Replace Prisma with better-sqlite3 for simpler deployment

This commit is contained in:
bitc0de committed 2025-12-17 15:32:10 +01:00
1 parent fae4fb179a
commit 06e2962fc5
11 files changed
+125 -112

No files matched your search

+2 -13
View File
@@ -5,7 +5,6 @@ WORKDIR /app
# Copy package files
COPY package*.json ./
COPY prisma ./prisma/
# Install dependencies
RUN npm ci
@@ -13,9 +12,6 @@ RUN npm ci
# Copy source code
COPY . .
# Generate Prisma Client
RUN npx prisma generate
# Build Next.js application
RUN npm run build
@@ -37,19 +33,12 @@ ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Install Prisma CLI for db push command
RUN npm install -g prisma
# Copy necessary files from builder
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/.prisma ./node_modules/.prisma
# Copy Prisma schema and migrations
COPY --chown=nextjs:nodejs prisma ./prisma/
# Create database directory and set permissions
RUN mkdir -p /app/prisma && chown -R nextjs:nodejs /app
# Create data directory for database
RUN mkdir -p /app/data && chown -R nextjs:nodejs /app
USER nextjs
+13 -17
View File
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import db from '@/lib/db'
export async function DELETE(
request: NextRequest,
@@ -8,9 +8,7 @@ export async function DELETE(
try {
const { id } = await params
await prisma.playlist.delete({
where: { id },
})
db.prepare('DELETE FROM playlists WHERE id = ?').run(id)
console.log('[API] Playlist deleted:', id)
return NextResponse.json({ success: true })
@@ -30,19 +28,7 @@ export async function GET(
try {
const { id } = await params
const playlist = await prisma.playlist.findUnique({
where: { id },
include: {
playlistSongs: {
include: {
song: true,
},
orderBy: {
addedAt: 'asc',
},
},
},
})
const playlist = db.prepare('SELECT * FROM playlists WHERE id = ?').get(id) as any
if (!playlist) {
return NextResponse.json(
@@ -51,6 +37,16 @@ export async function GET(
)
}
// Get songs for this playlist
const songs = db.prepare(`
SELECT s.*, ps.addedAt FROM songs s
INNER JOIN playlistSongs ps ON s.id = ps.songId
WHERE ps.playlistId = ?
ORDER BY ps.addedAt ASC
`).all(id)
playlist.playlistSongs = songs.map((song: any) => ({ song }))
return NextResponse.json(playlist)
} catch (error) {
console.error('[API] Error fetching playlist:', error)
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import db from '@/lib/db'
export async function DELETE(
request: NextRequest,
@@ -8,14 +8,7 @@ export async function DELETE(
try {
const { id, songId } = await params
await prisma.playlistSong.delete({
where: {
playlistId_songId: {
playlistId: id,
songId: songId,
},
},
})
db.prepare('DELETE FROM playlistSongs WHERE playlistId = ? AND songId = ?').run(id, songId)
console.log('[API] Song removed from playlist')
return NextResponse.json({ success: true })
+15 -16
View File
@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import db from '@/lib/db'
import { randomBytes } from 'crypto'
export async function POST(
request: NextRequest,
@@ -17,14 +18,7 @@ export async function POST(
}
// Check if already in playlist
const existing = await prisma.playlistSong.findUnique({
where: {
playlistId_songId: {
playlistId: id,
songId: songId,
},
},
})
const existing = db.prepare('SELECT * FROM playlistSongs WHERE playlistId = ? AND songId = ?').get(id, songId)
if (existing) {
return NextResponse.json(
@@ -33,14 +27,19 @@ export async function POST(
)
}
const playlistSong = await prisma.playlistSong.create({
data: {
playlistId: id,
songId: songId,
},
})
// Get next position
const maxPosition = db.prepare('SELECT MAX(position) as max FROM playlistSongs WHERE playlistId = ?').get(id) as any
const position = (maxPosition?.max ?? -1) + 1
console.log('[API] Song added to playlist:', playlistSong.id)
const playlistSongId = randomBytes(16).toString('hex')
db.prepare(`
INSERT INTO playlistSongs (id, playlistId, songId, position)
VALUES (?, ?, ?, ?)
`).run(playlistSongId, id, songId, position)
const playlistSong = db.prepare('SELECT * FROM playlistSongs WHERE id = ?').get(playlistSongId)
console.log('[API] Song added to playlist:', playlistSongId)
return NextResponse.json(playlistSong)
} catch (error) {
console.error('[API] Error adding song to playlist:', error)
+23 -15
View File
@@ -1,20 +1,27 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import db from '@/lib/db'
import { randomBytes } from 'crypto'
export async function GET() {
try {
const playlists = await prisma.playlist.findMany({
orderBy: { createdAt: 'desc' },
include: {
playlistSongs: {
include: {
song: true,
},
},
},
const playlists = db.prepare('SELECT * FROM playlists ORDER BY createdAt DESC').all() as any[]
// Get songs for each playlist
const playlistsWithSongs = playlists.map(playlist => {
const songs = db.prepare(`
SELECT s.* FROM songs s
INNER JOIN playlistSongs ps ON s.id = ps.songId
WHERE ps.playlistId = ?
ORDER BY ps.position
`).all(playlist.id)
return {
...playlist,
playlistSongs: songs.map((song: any) => ({ song }))
}
})
return NextResponse.json(playlists)
return NextResponse.json(playlistsWithSongs)
} catch (error) {
console.error('[API] Error fetching playlists:', error)
return NextResponse.json(
@@ -35,11 +42,12 @@ export async function POST(request: NextRequest) {
)
}
const playlist = await prisma.playlist.create({
data: { name },
})
const playlistId = randomBytes(16).toString('hex')
db.prepare('INSERT INTO playlists (id, name) VALUES (?, ?)').run(playlistId, name)
const playlist = db.prepare('SELECT * FROM playlists WHERE id = ?').get(playlistId)
console.log('[API] Playlist created:', playlist.id)
console.log('[API] Playlist created:', playlistId)
return NextResponse.json(playlist)
} catch (error) {
console.error('[API] Error creating playlist:', error)
+2 -5
View File
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import db from '@/lib/db'
export async function DELETE(
request: NextRequest,
@@ -10,10 +10,7 @@ export async function DELETE(
// Instead of deleting, mark as not in library
// This keeps the song in playlists but removes it from main library
await prisma.song.update({
where: { id },
data: { isInLibrary: false },
})
db.prepare('UPDATE songs SET isInLibrary = 0 WHERE id = ?').run(id)
console.log('[API] Song removed from library:', id)
return NextResponse.json({ success: true })
+15 -24
View File
@@ -1,6 +1,7 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import db from '@/lib/db'
import { getYoutubeMetadata } from '@/lib/ytdlp'
import { randomBytes } from 'crypto'
export async function POST(request: NextRequest) {
try {
@@ -20,20 +21,16 @@ export async function POST(request: NextRequest) {
console.log('[API] Got metadata:', metadata)
// Check if song already exists
const existingSong = await prisma.song.findUnique({
where: { youtubeId: metadata.id },
})
const existingSong = db.prepare('SELECT * FROM songs WHERE youtubeId = ?').get(metadata.id) as any
if (existingSong) {
console.log('[API] Song already exists:', existingSong.id)
// If song was previously removed from library, restore it
if (!existingSong.isInLibrary) {
const updatedSong = await prisma.song.update({
where: { id: existingSong.id },
data: { isInLibrary: true },
})
console.log('[API] Song restored to library:', updatedSong.id)
db.prepare('UPDATE songs SET isInLibrary = 1 WHERE id = ?').run(existingSong.id)
const updatedSong = db.prepare('SELECT * FROM songs WHERE id = ?').get(existingSong.id)
console.log('[API] Song restored to library:', existingSong.id)
return NextResponse.json(updatedSong)
}
@@ -41,17 +38,15 @@ export async function POST(request: NextRequest) {
}
// Create new song in database
const song = await prisma.song.create({
data: {
youtubeId: metadata.id,
title: metadata.title,
channelName: metadata.channel,
thumbnail: metadata.thumbnail,
duration: metadata.duration,
},
})
const songId = randomBytes(16).toString('hex')
db.prepare(`
INSERT INTO songs (id, youtubeId, title, channelName, thumbnail, duration)
VALUES (?, ?, ?, ?, ?, ?)
`).run(songId, metadata.id, metadata.title, metadata.channel, metadata.thumbnail, metadata.duration)
console.log('[API] Song created successfully:', song.id)
const song = db.prepare('SELECT * FROM songs WHERE id = ?').get(songId)
console.log('[API] Song created successfully:', songId)
return NextResponse.json(song)
} catch (error) {
console.error('[API] Error adding song:', error)
@@ -64,11 +59,7 @@ export async function POST(request: NextRequest) {
export async function GET() {
try {
const songs = await prisma.song.findMany({
where: { isInLibrary: true },
orderBy: { addedAt: 'desc' },
})
const songs = db.prepare('SELECT * FROM songs WHERE isInLibrary = 1 ORDER BY createdAt DESC').all()
return NextResponse.json(songs)
} catch (error) {
console.error('Error fetching songs:', error)
+2 -4
View File
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import db from '@/lib/db'
import { getStreamUrl } from '@/lib/ytdlp'
import { streamCache } from '@/lib/cache'
@@ -11,9 +11,7 @@ export async function GET(
const { id } = await params
// Get song from database
const song = await prisma.song.findUnique({
where: { id },
})
const song = db.prepare('SELECT * FROM songs WHERE id = ?').get(id) as any
if (!song) {
return NextResponse.json(
+1 -3
View File
@@ -8,14 +8,12 @@ services:
- "3000:3000"
volumes:
# Persist database
- ./prisma:/app/prisma
- ./data:/app/data
# Optional: persist yt-dlp cache
- yt-dlp-cache:/home/nextjs/.cache/yt-dlp
environment:
- NODE_ENV=production
- DATABASE_URL=file:/app/prisma/dev.db
restart: unless-stopped
command: sh -c "npx prisma db push && node server.js"
volumes:
yt-dlp-cache:
+46
View File
@@ -0,0 +1,46 @@
import Database from 'better-sqlite3'
import { join } from 'path'
const dbPath = join(process.cwd(), 'data', 'bitify.db')
const db = new Database(dbPath)
// Enable foreign keys
db.pragma('foreign_keys = ON')
// Create tables
db.exec(`
CREATE TABLE IF NOT EXISTS songs (
id TEXT PRIMARY KEY,
youtubeId TEXT UNIQUE NOT NULL,
title TEXT NOT NULL,
channelName TEXT NOT NULL,
thumbnail TEXT NOT NULL,
duration INTEGER NOT NULL,
isInLibrary INTEGER DEFAULT 1,
createdAt INTEGER DEFAULT (unixepoch())
);
CREATE TABLE IF NOT EXISTS playlists (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
createdAt INTEGER DEFAULT (unixepoch())
);
CREATE TABLE IF NOT EXISTS playlistSongs (
id TEXT PRIMARY KEY,
playlistId TEXT NOT NULL,
songId TEXT NOT NULL,
position INTEGER NOT NULL,
addedAt INTEGER DEFAULT (unixepoch()),
FOREIGN KEY (playlistId) REFERENCES playlists(id) ON DELETE CASCADE,
FOREIGN KEY (songId) REFERENCES songs(id) ON DELETE CASCADE,
UNIQUE(playlistId, songId)
);
CREATE INDEX IF NOT EXISTS idx_songs_youtubeId ON songs(youtubeId);
CREATE INDEX IF NOT EXISTS idx_songs_isInLibrary ON songs(isInLibrary);
CREATE INDEX IF NOT EXISTS idx_playlistSongs_playlistId ON playlistSongs(playlistId);
CREATE INDEX IF NOT EXISTS idx_playlistSongs_songId ON playlistSongs(songId);
`)
export default db
+4 -6
View File
@@ -4,14 +4,12 @@
"private": true,
"scripts": {
"dev": "next dev",
"build": "prisma generate && next build",
"build": "next build",
"start": "next start",
"lint": "next lint",
"prisma:generate": "prisma generate",
"prisma:push": "prisma db push"
"lint": "next lint"
},
"dependencies": {
"@prisma/client": "^5.22.0",
"better-sqlite3": "^11.7.0",
"lucide-react": "^0.454.0",
"next": "^15.0.3",
"react": "^18.3.1",
@@ -19,11 +17,11 @@
},
"devDependencies": {
"@tailwindcss/postcss": "^4.0.0",
"@types/better-sqlite3": "^7.6.12",
"@types/node": "^22.9.0",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"postcss": "^8.4.49",
"prisma": "^5.22.0",
"tailwindcss": "^4.0.0",
"typescript": "^5.6.3"
}