Replace database with simple JSON file storage

This commit is contained in:
bitc0de committed 2025-12-17 15:49:34 +01:00
1 parent e86d4d7d60
commit 491ac6628a
9 files changed
+208 -127

No files matched your search

+171 -49
View File
@@ -1,60 +1,182 @@
import Database from 'better-sqlite3'
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'
import { join } from 'path'
import { mkdirSync, existsSync } from 'fs'
import { randomBytes } from 'crypto'
let db: Database.Database | null = null
interface Song {
id: string
youtubeId: string
title: string
channelName: string
thumbnail: string
duration: number
isInLibrary: boolean
createdAt: number
}
export function getDb() {
if (db) return db
interface Playlist {
id: string
name: string
createdAt: number
}
const dataDir = join(process.cwd(), 'data')
// Create data directory if it doesn't exist
interface PlaylistSong {
id: string
playlistId: string
songId: string
position: number
addedAt: number
}
interface Database {
songs: Song[]
playlists: Playlist[]
playlistSongs: PlaylistSong[]
}
const dataDir = join(process.cwd(), 'data')
const dbPath = join(dataDir, 'db.json')
function ensureDataDir() {
if (!existsSync(dataDir)) {
mkdirSync(dataDir, { recursive: true })
}
}
const dbPath = join(dataDir, 'bitify.db')
db = new Database(dbPath)
function readDb(): Database {
ensureDataDir()
if (!existsSync(dbPath)) {
const initialDb: Database = {
songs: [],
playlists: [],
playlistSongs: []
}
writeFileSync(dbPath, JSON.stringify(initialDb, null, 2))
return initialDb
}
return JSON.parse(readFileSync(dbPath, 'utf-8'))
}
// Enable foreign keys
db.pragma('foreign_keys = ON')
function writeDb(db: Database) {
ensureDataDir()
writeFileSync(dbPath, JSON.stringify(db, null, 2))
}
// 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);
`)
return db
export const db = {
// Songs
getSongs: () => {
const data = readDb()
return data.songs.filter(s => s.isInLibrary).sort((a, b) => b.createdAt - a.createdAt)
},
getSongById: (id: string) => {
const data = readDb()
return data.songs.find(s => s.id === id)
},
getSongByYoutubeId: (youtubeId: string) => {
const data = readDb()
return data.songs.find(s => s.youtubeId === youtubeId)
},
createSong: (song: Omit<Song, 'id' | 'createdAt' | 'isInLibrary'>) => {
const data = readDb()
const newSong: Song = {
...song,
id: randomBytes(16).toString('hex'),
isInLibrary: true,
createdAt: Date.now()
}
data.songs.push(newSong)
writeDb(data)
return newSong
},
updateSong: (id: string, updates: Partial<Song>) => {
const data = readDb()
const index = data.songs.findIndex(s => s.id === id)
if (index !== -1) {
data.songs[index] = { ...data.songs[index], ...updates }
writeDb(data)
return data.songs[index]
}
return null
},
// Playlists
getPlaylists: () => {
const data = readDb()
return data.playlists.sort((a, b) => b.createdAt - a.createdAt)
},
getPlaylistById: (id: string) => {
const data = readDb()
return data.playlists.find(p => p.id === id)
},
createPlaylist: (name: string) => {
const data = readDb()
const newPlaylist: Playlist = {
id: randomBytes(16).toString('hex'),
name,
createdAt: Date.now()
}
data.playlists.push(newPlaylist)
writeDb(data)
return newPlaylist
},
deletePlaylist: (id: string) => {
const data = readDb()
data.playlists = data.playlists.filter(p => p.id !== id)
data.playlistSongs = data.playlistSongs.filter(ps => ps.playlistId !== id)
writeDb(data)
},
// Playlist Songs
getPlaylistSongs: (playlistId: string) => {
const data = readDb()
const playlistSongs = data.playlistSongs
.filter(ps => ps.playlistId === playlistId)
.sort((a, b) => a.position - b.position)
return playlistSongs.map(ps => {
const song = data.songs.find(s => s.id === ps.songId)
return { ...ps, song }
}).filter(ps => ps.song)
},
addSongToPlaylist: (playlistId: string, songId: string) => {
const data = readDb()
// Check if already exists
if (data.playlistSongs.find(ps => ps.playlistId === playlistId && ps.songId === songId)) {
return null
}
// Get next position
const playlistSongs = data.playlistSongs.filter(ps => ps.playlistId === playlistId)
const maxPosition = playlistSongs.length > 0 ? Math.max(...playlistSongs.map(ps => ps.position)) : -1
const newPlaylistSong: PlaylistSong = {
id: randomBytes(16).toString('hex'),
playlistId,
songId,
position: maxPosition + 1,
addedAt: Date.now()
}
data.playlistSongs.push(newPlaylistSong)
writeDb(data)
return newPlaylistSong
},
removeSongFromPlaylist: (playlistId: string, songId: string) => {
const data = readDb()
data.playlistSongs = data.playlistSongs.filter(
ps => !(ps.playlistId === playlistId && ps.songId === songId)
)
writeDb(data)
}
}