refactor(auth): migrate authentication from Firebase to (self-hosted) Appwrite
This commit is contained in:
parent
f967d29c52
commit
ad7d088e5c
6 changed files with 141 additions and 364 deletions
|
|
@ -1,39 +1,35 @@
|
|||
// js/accounts/auth.js
|
||||
import { auth, provider } from './config.js';
|
||||
import {
|
||||
signInWithPopup,
|
||||
signInWithRedirect,
|
||||
getRedirectResult,
|
||||
signOut as firebaseSignOut,
|
||||
onAuthStateChanged,
|
||||
signInWithEmailAndPassword,
|
||||
createUserWithEmailAndPassword,
|
||||
sendPasswordResetEmail,
|
||||
} from 'https://www.gstatic.com/firebasejs/10.7.1/firebase-auth.js';
|
||||
import { auth } from './config.js';
|
||||
|
||||
export class AuthManager {
|
||||
constructor() {
|
||||
this.user = null;
|
||||
this.unsubscribe = null;
|
||||
this.authListeners = [];
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
if (!auth) return;
|
||||
async init() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const userId = params.get('userId');
|
||||
const secret = params.get('secret');
|
||||
|
||||
this.unsubscribe = onAuthStateChanged(auth, (user) => {
|
||||
this.user = user;
|
||||
this.updateUI(user);
|
||||
if (userId && secret) {
|
||||
try {
|
||||
await auth.createSession(userId, secret);
|
||||
window.history.replaceState({}, '', window.location.pathname);
|
||||
} catch (error) {
|
||||
console.error('OAuth session creation failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
this.authListeners.forEach((listener) => listener(user));
|
||||
});
|
||||
|
||||
// Handle redirect result (for Linux/Mobile where popup might be blocked)
|
||||
getRedirectResult(auth).catch((error) => {
|
||||
console.error('Redirect Login failed:', error);
|
||||
alert(`Login failed: ${error.message}`);
|
||||
});
|
||||
try {
|
||||
this.user = await auth.get();
|
||||
this.updateUI(this.user);
|
||||
this.authListeners.forEach((listener) => listener(this.user));
|
||||
} catch (error) {
|
||||
this.user = null;
|
||||
this.updateUI(null);
|
||||
}
|
||||
}
|
||||
|
||||
onAuthStateChanged(callback) {
|
||||
|
|
@ -45,55 +41,25 @@ export class AuthManager {
|
|||
}
|
||||
|
||||
async signInWithGoogle() {
|
||||
if (!auth) {
|
||||
alert('Firebase is not configured. Please check console.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await signInWithPopup(auth, provider);
|
||||
|
||||
if (result.user) {
|
||||
console.log('Login successful:', result.user.email);
|
||||
this.user = result.user;
|
||||
this.updateUI(result.user);
|
||||
this.authListeners.forEach((listener) => listener(result.user));
|
||||
return result.user;
|
||||
}
|
||||
auth.createOAuth2Session(
|
||||
'google',
|
||||
window.location.origin + '/index.html',
|
||||
window.location.origin + '/login.html'
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Login failed:', error);
|
||||
|
||||
// On Linux, if popup is blocked or fails, we might be forced to redirect,
|
||||
// but we've seen it "bug the app", so we alert the user first.
|
||||
if (error.code === 'auth/popup-blocked' || error.code === 'auth/cancelled-popup-request') {
|
||||
if (
|
||||
confirm(
|
||||
'The login popup was blocked or failed to communicate. Would you like to try a redirect instead? Note: This may reload the application.'
|
||||
)
|
||||
) {
|
||||
try {
|
||||
await signInWithRedirect(auth, provider);
|
||||
return;
|
||||
} catch (redirectError) {
|
||||
console.error('Redirect fallback failed:', redirectError);
|
||||
alert(`Login failed: ${redirectError.message}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
alert(`Login failed: ${error.message}`);
|
||||
}
|
||||
throw error;
|
||||
alert(`Login failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async signInWithEmail(email, password) {
|
||||
if (!auth) {
|
||||
alert('Firebase is not configured.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = await signInWithEmailAndPassword(auth, email, password);
|
||||
return result.user;
|
||||
await auth.createEmailPasswordSession(email, password);
|
||||
this.user = await auth.get();
|
||||
this.updateUI(this.user);
|
||||
this.authListeners.forEach((listener) => listener(this.user));
|
||||
return this.user;
|
||||
} catch (error) {
|
||||
console.error('Email Login failed:', error);
|
||||
alert(`Login failed: ${error.message}`);
|
||||
|
|
@ -102,13 +68,13 @@ export class AuthManager {
|
|||
}
|
||||
|
||||
async signUpWithEmail(email, password) {
|
||||
if (!auth) {
|
||||
alert('Firebase is not configured.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = await createUserWithEmailAndPassword(auth, email, password);
|
||||
return result.user;
|
||||
await auth.create('unique()', email, password);
|
||||
await auth.createEmailPasswordSession(email, password);
|
||||
this.user = await auth.get();
|
||||
this.updateUI(this.user);
|
||||
this.authListeners.forEach((listener) => listener(this.user));
|
||||
return this.user;
|
||||
} catch (error) {
|
||||
console.error('Sign Up failed:', error);
|
||||
alert(`Sign Up failed: ${error.message}`);
|
||||
|
|
@ -117,12 +83,11 @@ export class AuthManager {
|
|||
}
|
||||
|
||||
async sendPasswordReset(email) {
|
||||
if (!auth) {
|
||||
alert('Firebase is not configured.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await sendPasswordResetEmail(auth, email);
|
||||
await auth.createRecovery(
|
||||
email,
|
||||
window.location.origin + '/reset-password.html'
|
||||
);
|
||||
alert(`Password reset email sent to ${email}`);
|
||||
} catch (error) {
|
||||
console.error('Password reset failed:', error);
|
||||
|
|
@ -132,17 +97,16 @@ export class AuthManager {
|
|||
}
|
||||
|
||||
async signOut() {
|
||||
if (!auth) return;
|
||||
|
||||
try {
|
||||
await firebaseSignOut(auth);
|
||||
await auth.deleteSession('current');
|
||||
this.user = null;
|
||||
this.updateUI(null);
|
||||
this.authListeners.forEach((listener) => listener(null));
|
||||
|
||||
if (window.__AUTH_GATE__) {
|
||||
try {
|
||||
await fetch('/api/auth/logout', { method: 'POST' });
|
||||
} catch {
|
||||
// Server endpoint may not exist in dev mode
|
||||
}
|
||||
window.location.href = '/login';
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Logout failed:', error);
|
||||
|
|
@ -151,9 +115,9 @@ export class AuthManager {
|
|||
}
|
||||
|
||||
updateUI(user) {
|
||||
const connectBtn = document.getElementById('firebase-connect-btn');
|
||||
const clearDataBtn = document.getElementById('firebase-clear-cloud-btn');
|
||||
const statusText = document.getElementById('firebase-status');
|
||||
const connectBtn = document.getElementById('firebase-connect-btn');
|
||||
const clearDataBtn = document.getElementById('firebase-clear-cloud-btn');
|
||||
const statusText = document.getElementById('firebase-status');
|
||||
const emailContainer = document.getElementById('email-auth-container');
|
||||
const emailToggleBtn = document.getElementById('toggle-email-auth-btn');
|
||||
|
||||
|
|
@ -164,17 +128,15 @@ export class AuthManager {
|
|||
connectBtn.textContent = 'Sign Out';
|
||||
connectBtn.classList.add('danger');
|
||||
connectBtn.onclick = () => this.signOut();
|
||||
if (clearDataBtn) clearDataBtn.style.display = 'none';
|
||||
if (clearDataBtn) clearDataBtn.style.display = 'none';
|
||||
if (emailContainer) emailContainer.style.display = 'none';
|
||||
if (emailToggleBtn) emailToggleBtn.style.display = 'none';
|
||||
if (statusText) statusText.textContent = user ? `Signed in as ${user.email}` : 'Signed in';
|
||||
if (statusText) statusText.textContent = user ? `Signed in as ${user.email}` : 'Signed in';
|
||||
|
||||
// Account page: clean up unnecessary text
|
||||
const accountPage = document.getElementById('page-account');
|
||||
if (accountPage) {
|
||||
const title = accountPage.querySelector('.section-title');
|
||||
if (title) title.textContent = 'Account';
|
||||
// Hide description + privacy paragraphs, keep only status
|
||||
accountPage.querySelectorAll('.account-content > p, .account-content > div').forEach((el) => {
|
||||
if (el.id !== 'firebase-status' && el.id !== 'auth-buttons-container') {
|
||||
el.style.display = 'none';
|
||||
|
|
@ -182,12 +144,10 @@ export class AuthManager {
|
|||
});
|
||||
}
|
||||
|
||||
// Settings page: hide custom DB/Auth config when fully server-configured
|
||||
const customDbBtn = document.getElementById('custom-db-btn');
|
||||
if (customDbBtn) {
|
||||
const fbFromEnv = !!window.__FIREBASE_CONFIG__;
|
||||
const pbFromEnv = !!window.__POCKETBASE_URL__;
|
||||
if (fbFromEnv && pbFromEnv) {
|
||||
if (pbFromEnv) {
|
||||
const settingItem = customDbBtn.closest('.setting-item');
|
||||
if (settingItem) settingItem.style.display = 'none';
|
||||
}
|
||||
|
|
@ -201,22 +161,20 @@ export class AuthManager {
|
|||
connectBtn.classList.add('danger');
|
||||
connectBtn.onclick = () => this.signOut();
|
||||
|
||||
if (clearDataBtn) clearDataBtn.style.display = 'block';
|
||||
if (clearDataBtn) clearDataBtn.style.display = 'block';
|
||||
if (emailContainer) emailContainer.style.display = 'none';
|
||||
if (emailToggleBtn) emailToggleBtn.style.display = 'none';
|
||||
|
||||
if (statusText) statusText.textContent = `Signed in as ${user.email}`;
|
||||
if (statusText) statusText.textContent = `Signed in as ${user.email}`;
|
||||
} else {
|
||||
connectBtn.textContent = 'Connect with Google';
|
||||
connectBtn.classList.remove('danger');
|
||||
connectBtn.onclick = () => this.signInWithGoogle();
|
||||
|
||||
if (clearDataBtn) clearDataBtn.style.display = 'none';
|
||||
if (clearDataBtn) clearDataBtn.style.display = 'none';
|
||||
if (emailToggleBtn) emailToggleBtn.style.display = 'inline-block';
|
||||
|
||||
if (statusText) statusText.textContent = 'Sync your library across devices';
|
||||
if (statusText) statusText.textContent = 'Sync your library across devices';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const authManager = new AuthManager();
|
||||
export const authManager = new AuthManager();
|
||||
|
|
@ -1,243 +1,12 @@
|
|||
//js/accounts/config.js
|
||||
import { initializeApp } from 'https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js';
|
||||
import { getAuth, GoogleAuthProvider } from 'https://www.gstatic.com/firebasejs/10.7.1/firebase-auth.js';
|
||||
import { getDatabase } from 'https://www.gstatic.com/firebasejs/10.7.1/firebase-database.js';
|
||||
// js/accounts/config.js
|
||||
import { Client, Account } from 'appwrite';
|
||||
|
||||
let app = null;
|
||||
let auth = null;
|
||||
let database = null;
|
||||
let provider = null;
|
||||
const client = new Client()
|
||||
.setEndpoint('https://auth.samidy.xyz/v1')
|
||||
.setProject('auth-for-monochrome');
|
||||
|
||||
const STORAGE_KEY = 'monochrome-firebase-config';
|
||||
const account = new Account(client);
|
||||
|
||||
const DEFAULT_CONFIG = {
|
||||
apiKey: 'AIzaSyDPU-unAjuLtQJt4IkGS5faG50UCF7lYyA',
|
||||
authDomain: 'monochrome-database.firebaseapp.com',
|
||||
projectId: 'monochrome-database',
|
||||
storageBucket: 'monochrome-database.firebasestorage.app',
|
||||
messagingSenderId: '895657412760',
|
||||
appId: '1:895657412760:web:e81c5044c7f4e9b799e8ed',
|
||||
};
|
||||
|
||||
function getStoredConfig() {
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
return stored ? JSON.parse(stored) : null;
|
||||
} catch (e) {
|
||||
console.warn('Failed to parse Firebase config from storage', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to initialize on load
|
||||
// Priority: server-injected env (auth gate) > localStorage > default
|
||||
const storedConfig = getStoredConfig();
|
||||
const config = window.__FIREBASE_CONFIG__ || storedConfig || DEFAULT_CONFIG;
|
||||
|
||||
if (config) {
|
||||
try {
|
||||
app = initializeApp(config);
|
||||
auth = getAuth(app);
|
||||
database = getDatabase(app);
|
||||
provider = new GoogleAuthProvider();
|
||||
console.log('Firebase initialized from ' + (storedConfig ? 'saved' : 'default') + ' config');
|
||||
} catch (error) {
|
||||
console.error('Error initializing Firebase:', error);
|
||||
}
|
||||
} else {
|
||||
console.log('No Firebase config found.');
|
||||
}
|
||||
|
||||
export function saveFirebaseConfig(configObj) {
|
||||
if (!configObj) return;
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(configObj));
|
||||
}
|
||||
|
||||
export function clearFirebaseConfig() {
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a shareable URL containing the encoded configuration.
|
||||
* @param {Object} config - The Firebase configuration object.
|
||||
* @returns {string} The full URL with the config hash.
|
||||
*/
|
||||
export function generateShareLink(config) {
|
||||
if (!config) return null;
|
||||
try {
|
||||
const json = JSON.stringify(config);
|
||||
// Base64 encode (safe for URL hash)
|
||||
const encoded = btoa(json);
|
||||
const url = new URL(window.location.href);
|
||||
url.hash = `#setup_firebase=${encoded}`;
|
||||
return url.toString();
|
||||
} catch (e) {
|
||||
console.error('Failed to generate share link:', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the current URL for a shared configuration.
|
||||
* If found, prompts the user to import it.
|
||||
* @returns {boolean} True if a config was handled/processed.
|
||||
*/
|
||||
export function checkAndImportConfig() {
|
||||
const hash = window.location.hash;
|
||||
if (!hash.startsWith('#setup_firebase=')) return false;
|
||||
|
||||
const encoded = hash.split('#setup_firebase=')[1];
|
||||
if (!encoded) return false;
|
||||
|
||||
try {
|
||||
const json = atob(encoded);
|
||||
const config = JSON.parse(json);
|
||||
|
||||
// Validate basic structure
|
||||
if (!config.apiKey || !config.authDomain) {
|
||||
alert('The shared configuration link appears to be invalid.');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (confirm('A Firebase configuration was detected in the link. Do you want to import it and enable Sync?')) {
|
||||
saveFirebaseConfig(config);
|
||||
// Clean URL
|
||||
window.history.replaceState(null, null, window.location.pathname);
|
||||
alert('Configuration imported successfully! The app will now reload.');
|
||||
window.location.reload();
|
||||
return true;
|
||||
} else {
|
||||
// User rejected, clean URL anyway to avoid re-prompting
|
||||
window.history.replaceState(null, null, window.location.pathname + '#settings');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to parse shared config:', e);
|
||||
alert('Failed to read configuration from link. The link might be corrupted.');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function initializeFirebaseSettingsUI() {
|
||||
// Check for shared config in URL first
|
||||
checkAndImportConfig();
|
||||
|
||||
const firebaseConfigInput = document.getElementById('firebase-config-input');
|
||||
const saveFirebaseConfigBtn = document.getElementById('save-firebase-config-btn');
|
||||
const clearFirebaseConfigBtn = document.getElementById('clear-firebase-config-btn');
|
||||
const shareFirebaseConfigBtn = document.getElementById('share-firebase-config-btn');
|
||||
const toggleFirebaseConfigBtn = document.getElementById('toggle-firebase-config-btn');
|
||||
const customFirebaseConfigContainer = document.getElementById('custom-firebase-config-container');
|
||||
|
||||
// Toggle Button Logic
|
||||
if (toggleFirebaseConfigBtn && customFirebaseConfigContainer) {
|
||||
toggleFirebaseConfigBtn.addEventListener('click', () => {
|
||||
const isVisible = customFirebaseConfigContainer.classList.contains('visible');
|
||||
if (isVisible) {
|
||||
customFirebaseConfigContainer.classList.remove('visible');
|
||||
toggleFirebaseConfigBtn.textContent = 'Custom Configuration';
|
||||
} else {
|
||||
customFirebaseConfigContainer.classList.add('visible');
|
||||
toggleFirebaseConfigBtn.textContent = 'Hide Custom Configuration';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Populate current config
|
||||
if (firebaseConfigInput) {
|
||||
const currentConfig = localStorage.getItem(STORAGE_KEY);
|
||||
if (currentConfig) {
|
||||
try {
|
||||
firebaseConfigInput.value = JSON.stringify(JSON.parse(currentConfig), null, 2);
|
||||
// If custom config exists, show the container
|
||||
if (customFirebaseConfigContainer && toggleFirebaseConfigBtn) {
|
||||
customFirebaseConfigContainer.classList.add('visible');
|
||||
toggleFirebaseConfigBtn.textContent = 'Hide Custom Configuration';
|
||||
}
|
||||
} catch {
|
||||
firebaseConfigInput.value = currentConfig;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Share Button
|
||||
if (shareFirebaseConfigBtn) {
|
||||
shareFirebaseConfigBtn.addEventListener('click', () => {
|
||||
const currentConfigStr = localStorage.getItem(STORAGE_KEY);
|
||||
if (!currentConfigStr) {
|
||||
alert('No configuration saved to share.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const config = JSON.parse(currentConfigStr);
|
||||
const link = generateShareLink(config);
|
||||
if (link) {
|
||||
navigator.clipboard
|
||||
.writeText(link)
|
||||
.then(() => {
|
||||
alert('Magic Link copied to clipboard! Send it to your other device.');
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Clipboard error:', err);
|
||||
prompt('Copy this link:', link);
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
alert('Invalid configuration found.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Save Button
|
||||
if (saveFirebaseConfigBtn) {
|
||||
saveFirebaseConfigBtn.addEventListener('click', () => {
|
||||
const inputVal = firebaseConfigInput.value.trim();
|
||||
if (!inputVal) {
|
||||
alert('Please enter a valid configuration.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let cleaned = inputVal;
|
||||
// Remove variable declaration if present (e.g., "const firebaseConfig = ")
|
||||
if (cleaned.includes('=')) {
|
||||
cleaned = cleaned.substring(cleaned.indexOf('=') + 1);
|
||||
}
|
||||
// Remove trailing semicolon
|
||||
cleaned = cleaned.trim();
|
||||
if (cleaned.endsWith(';')) {
|
||||
cleaned = cleaned.slice(0, -1);
|
||||
}
|
||||
|
||||
// Convert JS Object format to JSON format
|
||||
const jsonReady = cleaned
|
||||
.replace(/([{,]\s*)([a-zA-Z0-9_]+)\s*:/g, '$1"$2":') // Wrap keys in double quotes
|
||||
.replace(/:\s*'([^']*)'/g, ': "$1"') // Replace single-quoted values with double quotes
|
||||
.replace(/,\s*([}\]])/g, '$1'); // Remove trailing commas
|
||||
|
||||
const config = JSON.parse(jsonReady);
|
||||
saveFirebaseConfig(config);
|
||||
alert('Configuration saved. Reloading...');
|
||||
window.location.reload();
|
||||
} catch (error) {
|
||||
console.error('Invalid Config:', error);
|
||||
alert('Could not parse configuration. Please ensure it looks like a valid JSON or JS object.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Clear Button
|
||||
if (clearFirebaseConfigBtn) {
|
||||
clearFirebaseConfigBtn.addEventListener('click', () => {
|
||||
if (
|
||||
confirm(
|
||||
'Are you sure you want to remove the custom configuration? The app will revert to the shared default database.'
|
||||
)
|
||||
) {
|
||||
clearFirebaseConfig();
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { app, auth, database, provider };
|
||||
export { client, account as auth };
|
||||
export const saveFirebaseConfig = () => { console.log("ill fix this tomorrow"); };
|
||||
export const clearFirebaseConfig = () => { console.log("ill fix this tomorrow"); };
|
||||
|
|
@ -4,7 +4,7 @@ import { db } from '../db.js';
|
|||
import { authManager } from './auth.js';
|
||||
|
||||
const PUBLIC_COLLECTION = 'public_playlists';
|
||||
const DEFAULT_POCKETBASE_URL = 'https://monodb.samidy.com';
|
||||
const DEFAULT_POCKETBASE_URL = 'https://data.samidy.xyz';
|
||||
const POCKETBASE_URL = localStorage.getItem('monochrome-pocketbase-url') || DEFAULT_POCKETBASE_URL;
|
||||
|
||||
console.log('[PocketBase] Using URL:', POCKETBASE_URL);
|
||||
|
|
@ -57,7 +57,7 @@ const syncManager = {
|
|||
const user = authManager.user;
|
||||
if (!user) return null;
|
||||
|
||||
const record = await this._getUserRecord(user.uid);
|
||||
const record = await this._getUserRecord(user.$id);
|
||||
if (!record) return null;
|
||||
|
||||
const library = this.safeParseInternal(record.library, 'library', {});
|
||||
|
|
@ -143,7 +143,7 @@ const syncManager = {
|
|||
const user = authManager.user;
|
||||
if (!user) return;
|
||||
|
||||
const record = await this._getUserRecord(user.uid);
|
||||
const record = await this._getUserRecord(user.$id);
|
||||
if (!record) return;
|
||||
|
||||
let library = this.safeParseInternal(record.library, 'library', {});
|
||||
|
|
@ -161,7 +161,7 @@ const syncManager = {
|
|||
delete library[pluralType][key];
|
||||
}
|
||||
|
||||
await this._updateUserJSON(user.uid, 'library', library);
|
||||
await this._updateUserJSON(user.$id, 'library', library);
|
||||
},
|
||||
|
||||
_minifyItem(type, item) {
|
||||
|
|
@ -254,20 +254,20 @@ const syncManager = {
|
|||
const user = authManager.user;
|
||||
if (!user) return;
|
||||
|
||||
const record = await this._getUserRecord(user.uid);
|
||||
const record = await this._getUserRecord(user.$id);
|
||||
if (!record) return;
|
||||
|
||||
let history = this.safeParseInternal(record.history, 'history', []);
|
||||
|
||||
const newHistory = [historyEntry, ...history].slice(0, 100);
|
||||
await this._updateUserJSON(user.uid, 'history', newHistory);
|
||||
await this._updateUserJSON(user.$id, 'history', newHistory);
|
||||
},
|
||||
|
||||
async syncUserPlaylist(playlist, action) {
|
||||
const user = authManager.user;
|
||||
if (!user) return;
|
||||
|
||||
const record = await this._getUserRecord(user.uid);
|
||||
const record = await this._getUserRecord(user.$id);
|
||||
if (!record) return;
|
||||
|
||||
let userPlaylists = this.safeParseInternal(record.user_playlists, 'user_playlists', {});
|
||||
|
|
@ -293,14 +293,14 @@ const syncManager = {
|
|||
}
|
||||
}
|
||||
|
||||
await this._updateUserJSON(user.uid, 'user_playlists', userPlaylists);
|
||||
await this._updateUserJSON(user.$id, 'user_playlists', userPlaylists);
|
||||
},
|
||||
|
||||
async syncUserFolder(folder, action) {
|
||||
const user = authManager.user;
|
||||
if (!user) return;
|
||||
|
||||
const record = await this._getUserRecord(user.uid);
|
||||
const record = await this._getUserRecord(user.$id);
|
||||
if (!record) return;
|
||||
|
||||
let userFolders = this.safeParseInternal(record.user_folders, 'user_folders', {});
|
||||
|
|
@ -318,7 +318,7 @@ const syncManager = {
|
|||
};
|
||||
}
|
||||
|
||||
await this._updateUserJSON(user.uid, 'user_folders', userFolders);
|
||||
await this._updateUserJSON(user.$id, 'user_folders', userFolders);
|
||||
},
|
||||
|
||||
async getPublicPlaylist(uuid) {
|
||||
|
|
@ -391,7 +391,7 @@ const syncManager = {
|
|||
|
||||
async publishPlaylist(playlist) {
|
||||
if (!playlist || !playlist.id) return;
|
||||
const uid = authManager.user?.uid;
|
||||
const uid = authManager.user?.$id;
|
||||
if (!uid) return;
|
||||
|
||||
const data = {
|
||||
|
|
@ -431,7 +431,7 @@ const syncManager = {
|
|||
},
|
||||
|
||||
async unpublishPlaylist(uuid) {
|
||||
const uid = authManager.user?.uid;
|
||||
const uid = authManager.user?.$id;
|
||||
if (!uid) return;
|
||||
|
||||
try {
|
||||
|
|
@ -467,7 +467,7 @@ const syncManager = {
|
|||
async updateProfile(data) {
|
||||
const user = authManager.user;
|
||||
if (!user) return;
|
||||
const record = await this._getUserRecord(user.uid);
|
||||
const record = await this._getUserRecord(user.$id);
|
||||
if (!record) return;
|
||||
|
||||
const updateData = { ...data };
|
||||
|
|
@ -475,7 +475,7 @@ const syncManager = {
|
|||
updateData.privacy = JSON.stringify(updateData.privacy);
|
||||
}
|
||||
|
||||
await this.pb.collection('DB_users').update(record.id, updateData, { f_id: user.uid });
|
||||
await this.pb.collection('DB_users').update(record.id, updateData, { f_id: user.$id });
|
||||
if (this._userRecordCache) {
|
||||
this._userRecordCache = { ...this._userRecordCache, ...updateData };
|
||||
}
|
||||
|
|
@ -495,9 +495,9 @@ const syncManager = {
|
|||
if (!user) return;
|
||||
|
||||
try {
|
||||
const record = await this._getUserRecord(user.uid);
|
||||
const record = await this._getUserRecord(user.$id);
|
||||
if (record) {
|
||||
await this.pb.collection('DB_users').delete(record.id, { f_id: user.uid });
|
||||
await this.pb.collection('DB_users').delete(record.id, { f_id: user.$id });
|
||||
this._userRecordCache = null;
|
||||
alert('Cloud data cleared successfully.');
|
||||
}
|
||||
|
|
@ -606,10 +606,10 @@ const syncManager = {
|
|||
}
|
||||
|
||||
if (needsUpdate) {
|
||||
await this._updateUserJSON(user.uid, 'library', library);
|
||||
await this._updateUserJSON(user.uid, 'user_playlists', userPlaylists);
|
||||
await this._updateUserJSON(user.uid, 'user_folders', userFolders);
|
||||
await this._updateUserJSON(user.uid, 'history', history);
|
||||
await this._updateUserJSON(user.$id, 'library', library);
|
||||
await this._updateUserJSON(user.$id, 'user_playlists', userPlaylists);
|
||||
await this._updateUserJSON(user.$id, 'user_folders', userFolders);
|
||||
await this._updateUserJSON(user.$id, 'history', history);
|
||||
}
|
||||
|
||||
const convertedData = {
|
||||
|
|
|
|||
|
|
@ -2609,7 +2609,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
const headerAccountIcon = document.getElementById('header-account-icon');
|
||||
|
||||
// Temporarily disable accounts - show popup
|
||||
const isAccountsDisabled = true;
|
||||
const isAccountsDisabled = false;
|
||||
|
||||
if (headerAccountBtn && headerAccountDropdown) {
|
||||
if (isAccountsDisabled) {
|
||||
|
|
|
|||
51
package-lock.json
generated
51
package-lock.json
generated
|
|
@ -12,6 +12,7 @@
|
|||
"@ffmpeg/ffmpeg": "^0.12.15",
|
||||
"@ffmpeg/util": "^0.12.2",
|
||||
"@neutralinojs/lib": "^6.5.0",
|
||||
"appwrite": "^23.0.0",
|
||||
"butterchurn": "^2.6.7",
|
||||
"butterchurn-presets": "^2.4.7",
|
||||
"cookie-session": "^2.1.1",
|
||||
|
|
@ -23,7 +24,7 @@
|
|||
"@neutralinojs/neu": "^11.7.0",
|
||||
"eslint": "^9.39.3",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"globals": "^17.3.0",
|
||||
"globals": "^17.4.0",
|
||||
"htmlhint": "^1.9.1",
|
||||
"prettier": "^3.8.1",
|
||||
"stylelint": "^16.26.1",
|
||||
|
|
@ -3149,6 +3150,15 @@
|
|||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/appwrite": {
|
||||
"version": "23.0.0",
|
||||
"resolved": "https://registry.npmjs.org/appwrite/-/appwrite-23.0.0.tgz",
|
||||
"integrity": "sha512-K11a597npl3jsnxWKzjw163n4GguH4+/zBCOiU15yc1u+7QF0nP9mxsY4JxKrBU6bmQRtgtMTPv/6YOLSwp/QQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"json-bigint": "1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
|
|
@ -3351,6 +3361,15 @@
|
|||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/bignumber.js": {
|
||||
"version": "9.3.1",
|
||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
|
||||
"integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
|
|
@ -5923,6 +5942,15 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/json-bigint": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
|
||||
"integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bignumber.js": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/json-buffer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
|
||||
|
|
@ -11168,6 +11196,14 @@
|
|||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"appwrite": {
|
||||
"version": "23.0.0",
|
||||
"resolved": "https://registry.npmjs.org/appwrite/-/appwrite-23.0.0.tgz",
|
||||
"integrity": "sha512-K11a597npl3jsnxWKzjw163n4GguH4+/zBCOiU15yc1u+7QF0nP9mxsY4JxKrBU6bmQRtgtMTPv/6YOLSwp/QQ==",
|
||||
"requires": {
|
||||
"json-bigint": "1.0.0"
|
||||
}
|
||||
},
|
||||
"argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
|
|
@ -11313,6 +11349,11 @@
|
|||
"bcp-47-match": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"bignumber.js": {
|
||||
"version": "9.3.1",
|
||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
|
||||
"integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
|
|
@ -13143,6 +13184,14 @@
|
|||
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
||||
"dev": true
|
||||
},
|
||||
"json-bigint": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
|
||||
"integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
|
||||
"requires": {
|
||||
"bignumber.js": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"json-buffer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@
|
|||
"@ffmpeg/ffmpeg": "^0.12.15",
|
||||
"@ffmpeg/util": "^0.12.2",
|
||||
"@neutralinojs/lib": "^6.5.0",
|
||||
"appwrite": "^23.0.0",
|
||||
"butterchurn": "^2.6.7",
|
||||
"butterchurn-presets": "^2.4.7",
|
||||
"cookie-session": "^2.1.1",
|
||||
|
|
|
|||
Loading…
Reference in a new issue