spotify/server/dist/lib/db.js
2025-10-16 13:07:44 +02:00

128 lines
4.2 KiB
JavaScript

import Database from 'better-sqlite3';
import path from 'path';
import fs from 'fs';
const DB_PATH = process.env.DB_PATH || path.resolve(process.cwd(), '..', 'spotify.db');
class DBWrapper {
constructor() {
this.instance = null;
}
get db() {
if (!this.instance) {
this.instance = new Database(DB_PATH);
this.instance.pragma('journal_mode = WAL');
this.instance.pragma('foreign_keys = ON');
}
return this.instance;
}
initialize() {
// Ensure file exists
if (!fs.existsSync(DB_PATH)) {
fs.writeFileSync(DB_PATH, '');
}
const sql = `
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
display_name TEXT,
email TEXT,
avatar_url TEXT,
country TEXT,
product TEXT,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
access_token TEXT,
refresh_token TEXT,
token_expires_at INTEGER,
last_synced_at INTEGER
);
CREATE TABLE IF NOT EXISTS friendships (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_a_id TEXT NOT NULL,
user_b_id TEXT NOT NULL,
created_at INTEGER NOT NULL,
UNIQUE(user_a_id, user_b_id)
);
CREATE TABLE IF NOT EXISTS friend_requests (
id INTEGER PRIMARY KEY AUTOINCREMENT,
from_user_id TEXT NOT NULL,
to_user_id TEXT NOT NULL,
status TEXT NOT NULL CHECK(status IN ('pending','accepted','declined')),
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
UNIQUE(from_user_id, to_user_id)
);
CREATE TABLE IF NOT EXISTS recently_played (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
played_at INTEGER NOT NULL,
track_json TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS top_tracks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id TEXT NOT NULL,
time_range TEXT NOT NULL,
rank INTEGER NOT NULL,
track_json TEXT NOT NULL,
UNIQUE(user_id, time_range, rank)
);
CREATE TABLE IF NOT EXISTS top_artists (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id TEXT NOT NULL,
time_range TEXT NOT NULL,
rank INTEGER NOT NULL,
artist_json TEXT NOT NULL,
UNIQUE(user_id, time_range, rank)
);
CREATE TABLE IF NOT EXISTS mixed_playlists (
id TEXT PRIMARY KEY,
creator_id TEXT NOT NULL,
partner_id TEXT NOT NULL,
name TEXT NOT NULL,
description TEXT,
vibe TEXT,
genres TEXT, -- JSON array of genres
track_uris TEXT, -- JSON array of track URIs
creator_spotify_id TEXT,
partner_spotify_id TEXT,
creator_spotify_url TEXT,
partner_spotify_url TEXT,
creator_spotify_image_url TEXT,
partner_spotify_image_url TEXT,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
FOREIGN KEY (creator_id) REFERENCES users(id),
FOREIGN KEY (partner_id) REFERENCES users(id)
);
`;
this.db.exec(sql);
// Migrations for existing DBs
try {
const cols = this.db.prepare("PRAGMA table_info(users)").all();
const hasLastSynced = cols.some(c => c.name === 'last_synced_at');
if (!hasLastSynced) {
this.db.exec('ALTER TABLE users ADD COLUMN last_synced_at INTEGER');
}
}
catch { }
// Migration for mixed_playlists table
try {
const playlistCols = this.db.prepare("PRAGMA table_info(mixed_playlists)").all();
const hasCreatorImageUrl = playlistCols.some(c => c.name === 'creator_spotify_image_url');
const hasPartnerImageUrl = playlistCols.some(c => c.name === 'partner_spotify_image_url');
if (!hasCreatorImageUrl) {
this.db.exec('ALTER TABLE mixed_playlists ADD COLUMN creator_spotify_image_url TEXT');
}
if (!hasPartnerImageUrl) {
this.db.exec('ALTER TABLE mixed_playlists ADD COLUMN partner_spotify_image_url TEXT');
}
}
catch { }
}
}
export const db = new DBWrapper();