128 lines
4.2 KiB
JavaScript
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();
|