emresar's picture
Upload folder using huggingface_hub
6678fa1 verified
/**
* Persistent mock database for development/testing
* Saves to JSON file so data persists across restarts
*/
import { nanoid } from "nanoid";
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Use HuggingFace Spaces persistent storage if available (/data is persistent)
// Otherwise use local data directory
function getDbPath(): string {
const hfPersistentPath = "/data/mock_db.json";
const localPath = path.resolve(__dirname, "../data/mock_db.json");
// Check if we're on HuggingFace Spaces
const isHfSpaces = process.env.SPACE_ID || process.env.SPACE_AUTHOR_NAME;
if (isHfSpaces) {
console.log("[MockDB] Running on HuggingFace Spaces");
// Check if /data exists and is writable
if (fs.existsSync("/data")) {
try {
fs.accessSync("/data", fs.constants.W_OK);
console.log("[MockDB] Using persistent storage: /data/mock_db.json");
return hfPersistentPath;
} catch (e) {
console.log("[MockDB] /data exists but not writable, using /app/data");
console.log("[MockDB] NOTE: Data will NOT persist across restarts!");
console.log("[MockDB] Enable persistent storage in Space settings for data persistence.");
}
} else {
console.log("[MockDB] /data directory not found, using /app/data");
console.log("[MockDB] NOTE: Data will NOT persist across restarts!");
}
}
return localPath;
}
const DB_FILE = getDbPath();
interface MockUser {
id: number;
openId: string;
name: string | null;
email: string | null;
loginMethod: string | null;
role: "user" | "admin";
createdAt: Date;
updatedAt: Date;
lastSignedIn: Date;
}
interface MockTrack {
id: number;
userId: number | null;
title: string;
artist: string | null;
trackType: "ai_generated" | "training_reference";
fileKey: string;
fileUrl: string;
fileSize: number | null;
mimeType: string | null;
duration: number | null;
status: "pending" | "processing" | "completed" | "failed";
errorMessage: string | null;
createdAt: Date;
updatedAt: Date;
}
interface MockStem {
id: number;
trackId: number;
stemType: "vocals" | "drums" | "bass" | "other";
fileKey: string;
fileUrl: string;
duration: number | null;
createdAt: Date;
}
interface MockFingerprint {
id: number;
stemId: number;
algorithm: string;
fingerprintData: string;
version: string | null;
createdAt: Date;
}
interface MockEmbedding {
id: number;
stemId: number;
model: string;
embeddingVector: number[];
dimension: number;
createdAt: Date;
}
interface MockAttributionResult {
id: number;
aiTrackId: number;
aiStemId: number | null;
trainingTrackId: number;
trainingStemId: number | null;
method: string;
score: number;
confidence: number | null;
metadata: Record<string, unknown> | null;
createdAt: Date;
}
interface MockProcessingJob {
id: number;
trackId: number;
jobType: "stem_separation" | "fingerprinting" | "embedding" | "attribution";
status: "pending" | "running" | "completed" | "failed";
progress: number;
errorMessage: string | null;
resultData: Record<string, unknown> | null;
startedAt: Date | null;
completedAt: Date | null;
createdAt: Date;
}
// Persistent storage with JSON file backup
class MockDatabase {
private users = new Map<number, MockUser>();
private tracks = new Map<number, MockTrack>();
private stems = new Map<number, MockStem>();
private fingerprints = new Map<number, MockFingerprint>();
private embeddings = new Map<number, MockEmbedding>();
private attributionResults = new Map<number, MockAttributionResult>();
private processingJobs = new Map<number, MockProcessingJob>();
private nextUserId = 1;
private nextTrackId = 1;
private nextStemId = 1;
private nextFingerprintId = 1;
private nextEmbeddingId = 1;
private nextAttributionId = 1;
private nextJobId = 1;
constructor() {
this.load();
}
private load(): void {
try {
if (fs.existsSync(DB_FILE)) {
const data = JSON.parse(fs.readFileSync(DB_FILE, 'utf-8'));
// Restore maps with date parsing
const parseDate = (obj: Record<string, unknown>) => {
for (const key of Object.keys(obj)) {
if (typeof obj[key] === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(obj[key] as string)) {
obj[key] = new Date(obj[key] as string);
}
}
return obj;
};
if (data.users) {
for (const u of data.users) {
this.users.set(u.id, parseDate(u) as MockUser);
}
}
if (data.tracks) {
for (const t of data.tracks) {
this.tracks.set(t.id, parseDate(t) as MockTrack);
}
}
if (data.stems) {
for (const s of data.stems) {
this.stems.set(s.id, parseDate(s) as MockStem);
}
}
if (data.fingerprints) {
for (const f of data.fingerprints) {
this.fingerprints.set(f.id, parseDate(f) as MockFingerprint);
}
}
if (data.embeddings) {
for (const e of data.embeddings) {
this.embeddings.set(e.id, parseDate(e) as MockEmbedding);
}
}
if (data.attributionResults) {
for (const a of data.attributionResults) {
this.attributionResults.set(a.id, parseDate(a) as MockAttributionResult);
}
}
if (data.processingJobs) {
for (const j of data.processingJobs) {
this.processingJobs.set(j.id, parseDate(j) as MockProcessingJob);
}
}
// Restore next IDs
this.nextUserId = data.nextUserId || 1;
this.nextTrackId = data.nextTrackId || 1;
this.nextStemId = data.nextStemId || 1;
this.nextFingerprintId = data.nextFingerprintId || 1;
this.nextEmbeddingId = data.nextEmbeddingId || 1;
this.nextAttributionId = data.nextAttributionId || 1;
this.nextJobId = data.nextJobId || 1;
console.log(`[MockDB] Loaded ${this.tracks.size} tracks, ${this.users.size} users from ${DB_FILE}`);
}
} catch (error) {
console.error('[MockDB] Failed to load database:', error);
}
}
private save(): void {
try {
// Ensure directory exists
const dir = path.dirname(DB_FILE);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
const data = {
users: Array.from(this.users.values()),
tracks: Array.from(this.tracks.values()),
stems: Array.from(this.stems.values()),
fingerprints: Array.from(this.fingerprints.values()),
embeddings: Array.from(this.embeddings.values()),
attributionResults: Array.from(this.attributionResults.values()),
processingJobs: Array.from(this.processingJobs.values()),
nextUserId: this.nextUserId,
nextTrackId: this.nextTrackId,
nextStemId: this.nextStemId,
nextFingerprintId: this.nextFingerprintId,
nextEmbeddingId: this.nextEmbeddingId,
nextAttributionId: this.nextAttributionId,
nextJobId: this.nextJobId,
};
fs.writeFileSync(DB_FILE, JSON.stringify(data, null, 2));
} catch (error) {
console.error('[MockDB] Failed to save database:', error);
}
}
// User operations
upsertUser(user: Partial<MockUser> & { openId: string }): MockUser {
// Find existing user by openId
let existing: MockUser | undefined;
for (const u of this.users.values()) {
if (u.openId === user.openId) {
existing = u;
break;
}
}
if (existing) {
const updated = {
...existing,
...user,
updatedAt: new Date(),
lastSignedIn: user.lastSignedIn || new Date(),
};
this.users.set(existing.id, updated);
this.save();
return updated;
}
const newUser: MockUser = {
id: this.nextUserId++,
openId: user.openId,
name: user.name || null,
email: user.email || null,
loginMethod: user.loginMethod || null,
role: user.role || "user",
createdAt: new Date(),
updatedAt: new Date(),
lastSignedIn: user.lastSignedIn || new Date(),
};
this.users.set(newUser.id, newUser);
this.save();
return newUser;
}
getUserByOpenId(openId: string): MockUser | undefined {
for (const user of this.users.values()) {
if (user.openId === openId) {
return user;
}
}
return undefined;
}
// Track operations
createTrack(track: Omit<MockTrack, "id" | "createdAt" | "updatedAt">): number {
const id = this.nextTrackId++;
const newTrack: MockTrack = {
id,
...track,
createdAt: new Date(),
updatedAt: new Date(),
};
this.tracks.set(id, newTrack);
this.save();
return id;
}
getTrackById(id: number): MockTrack | undefined {
return this.tracks.get(id);
}
getUserTracks(userId: number): MockTrack[] {
return Array.from(this.tracks.values())
.filter(t => t.userId === userId)
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
}
updateTrackStatus(id: number, status: MockTrack["status"], errorMessage?: string): void {
const track = this.tracks.get(id);
if (track) {
track.status = status;
if (errorMessage !== undefined) track.errorMessage = errorMessage;
track.updatedAt = new Date();
this.save();
}
}
getTrainingTracks(): MockTrack[] {
return Array.from(this.tracks.values())
.filter(t => t.trackType === "training_reference");
}
// Stem operations
createStem(stem: Omit<MockStem, "id" | "createdAt">): number {
const id = this.nextStemId++;
const newStem: MockStem = {
id,
...stem,
createdAt: new Date(),
};
this.stems.set(id, newStem);
this.save();
return id;
}
getTrackStems(trackId: number): MockStem[] {
return Array.from(this.stems.values()).filter(s => s.trackId === trackId);
}
// Fingerprint operations
createFingerprint(fp: Omit<MockFingerprint, "id" | "createdAt">): number {
const id = this.nextFingerprintId++;
const newFp: MockFingerprint = {
id,
...fp,
createdAt: new Date(),
};
this.fingerprints.set(id, newFp);
this.save();
return id;
}
// Embedding operations
createEmbedding(emb: Omit<MockEmbedding, "id" | "createdAt">): number {
const id = this.nextEmbeddingId++;
const newEmb: MockEmbedding = {
id,
...emb,
createdAt: new Date(),
};
this.embeddings.set(id, newEmb);
this.save();
return id;
}
// Attribution operations
createAttributionResult(attr: Omit<MockAttributionResult, "id" | "createdAt">): number {
const id = this.nextAttributionId++;
const newAttr: MockAttributionResult = {
id,
...attr,
createdAt: new Date(),
};
this.attributionResults.set(id, newAttr);
this.save();
return id;
}
getTrackAttributions(aiTrackId: number): Array<MockAttributionResult & { trainingTrack?: MockTrack }> {
return Array.from(this.attributionResults.values())
.filter(a => a.aiTrackId === aiTrackId)
.map(a => ({
...a,
trainingTrack: this.tracks.get(a.trainingTrackId),
}))
.sort((a, b) => b.score - a.score);
}
clearTrackAttributions(aiTrackId: number): number {
let count = 0;
for (const [id, attr] of this.attributionResults.entries()) {
if (attr.aiTrackId === aiTrackId) {
this.attributionResults.delete(id);
count++;
}
}
if (count > 0) {
this.save();
}
return count;
}
// Processing job operations
createProcessingJob(job: Omit<MockProcessingJob, "id" | "createdAt">): number {
const id = this.nextJobId++;
const newJob: MockProcessingJob = {
id,
...job,
createdAt: new Date(),
};
this.processingJobs.set(id, newJob);
this.save();
return id;
}
updateProcessingJob(id: number, updates: Partial<MockProcessingJob>): void {
const job = this.processingJobs.get(id);
if (job) {
Object.assign(job, updates);
this.save();
}
}
getTrackJobs(trackId: number): MockProcessingJob[] {
return Array.from(this.processingJobs.values())
.filter(j => j.trackId === trackId)
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
}
}
// Singleton instance
export const mockDb = new MockDatabase();
// Export type-compatible functions for use by the app
export const isMockDb = true;