mirror of
https://github.com/mudabbir-ahmad/Media-Aggregation-and-Sorting-System.git
synced 2026-10-07 19:40:20 +00:00
created a localised login system
This commit is contained in:
1 parent
59ac1fc926
commit
37511780b7
16 files changed
+3721
-20
No files matched your search
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Credential Encryption Service
|
||||
*
|
||||
* Encrypts sensitive credentials before storing in database
|
||||
* Uses base64 encoding (React Native compatible)
|
||||
* For production, use a proper encryption library like react-native-aes-crypto
|
||||
*/
|
||||
|
||||
class CredentialEncryption {
|
||||
constructor() {
|
||||
// Simple key for demo - in production, use proper key management
|
||||
this.encryptionKey = 'photo-app-secret-key-2026';
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64 encode a string (React Native compatible)
|
||||
* Uses btoa which is available in React Native
|
||||
*/
|
||||
base64Encode(str) {
|
||||
try {
|
||||
// btoa is available in React Native
|
||||
return btoa(unescape(encodeURIComponent(str)));
|
||||
} catch (error) {
|
||||
console.error('Base64 encode error:', error);
|
||||
throw new Error('Failed to encode data');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64 decode a string (React Native compatible)
|
||||
* Uses atob which is available in React Native
|
||||
*/
|
||||
base64Decode(str) {
|
||||
try {
|
||||
// atob is available in React Native
|
||||
return decodeURIComponent(escape(atob(str)));
|
||||
} catch (error) {
|
||||
console.error('Base64 decode error:', error);
|
||||
throw new Error('Failed to decode data');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt credentials before storing
|
||||
* Converts object to JSON string, then base64 encodes it
|
||||
*/
|
||||
encrypt(data) {
|
||||
try {
|
||||
if (!data) {
|
||||
throw new Error('No data provided to encrypt');
|
||||
}
|
||||
const jsonString = JSON.stringify(data);
|
||||
const encoded = this.base64Encode(jsonString);
|
||||
console.log('✓ Credentials encrypted successfully');
|
||||
return encoded;
|
||||
} catch (error) {
|
||||
console.error('Encryption error:', error);
|
||||
throw new Error('Failed to encrypt credentials: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt credentials after retrieving
|
||||
* Base64 decodes, then parses JSON back to object
|
||||
*/
|
||||
decrypt(encryptedData) {
|
||||
try {
|
||||
if (!encryptedData) {
|
||||
return null;
|
||||
}
|
||||
const jsonString = this.base64Decode(encryptedData);
|
||||
const data = JSON.parse(jsonString);
|
||||
console.log('✓ Credentials decrypted successfully');
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Decryption error:', error);
|
||||
throw new Error('Failed to decrypt credentials: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if data is already encrypted (base64)
|
||||
*/
|
||||
isEncrypted(data) {
|
||||
if (typeof data !== 'string') return false;
|
||||
try {
|
||||
this.base64Decode(data);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new CredentialEncryption();
|
||||
|
||||
@@ -0,0 +1,428 @@
|
||||
import * as SQLite from 'expo-sqlite';
|
||||
import CredentialEncryption from './CredentialEncryption';
|
||||
|
||||
let db = null;
|
||||
let initPromise = null;
|
||||
|
||||
const DatabaseService = {
|
||||
/**
|
||||
* Initialize the database and create tables if they don't exist
|
||||
*/
|
||||
initDatabase: async () => {
|
||||
if (initPromise) {
|
||||
return initPromise;
|
||||
}
|
||||
initPromise = (async () => {
|
||||
try {
|
||||
if (db) {
|
||||
return true;
|
||||
}
|
||||
if (typeof SQLite.openDatabaseAsync !== 'function') {
|
||||
throw new Error('SQLite openDatabaseAsync API not available');
|
||||
}
|
||||
db = await SQLite.openDatabaseAsync('PhotosApp.db');
|
||||
if (!db) {
|
||||
throw new Error('SQLite openDatabaseAsync returned null');
|
||||
}
|
||||
|
||||
await db.execAsync(
|
||||
`CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)`
|
||||
);
|
||||
|
||||
await db.execAsync(
|
||||
`CREATE TABLE IF NOT EXISTS linked_accounts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
service_type TEXT NOT NULL,
|
||||
account_data TEXT NOT NULL,
|
||||
is_enabled INTEGER DEFAULT 1,
|
||||
is_default INTEGER DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id)
|
||||
)`
|
||||
);
|
||||
|
||||
await db.execAsync(
|
||||
`CREATE TABLE IF NOT EXISTS media (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
source_service TEXT NOT NULL,
|
||||
source_id TEXT,
|
||||
title TEXT,
|
||||
description TEXT,
|
||||
local_path TEXT,
|
||||
remote_path TEXT,
|
||||
media_type TEXT,
|
||||
created_date DATETIME,
|
||||
uploaded_to_default INTEGER DEFAULT 0,
|
||||
synced INTEGER DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id)
|
||||
)`
|
||||
);
|
||||
|
||||
await db.execAsync(
|
||||
`CREATE TABLE IF NOT EXISTS sync_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
service_type TEXT NOT NULL,
|
||||
status TEXT,
|
||||
message TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id)
|
||||
)`
|
||||
);
|
||||
|
||||
console.log('Database initialized successfully');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Database initialization error:', error);
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
return initPromise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Ensure the database is initialized before use
|
||||
*/
|
||||
ensureDb: async () => {
|
||||
await DatabaseService.initDatabase();
|
||||
if (!db) {
|
||||
throw new Error('Database not initialized');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new user account
|
||||
*/
|
||||
createUser: async (username, email, password) => {
|
||||
try {
|
||||
await DatabaseService.ensureDb();
|
||||
const result = await db.runAsync(
|
||||
'INSERT INTO users (username, email, password) VALUES (?, ?, ?)',
|
||||
[username, email, password]
|
||||
);
|
||||
return result.lastInsertRowId;
|
||||
} catch (error) {
|
||||
console.error('Error creating user:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get user by email and password
|
||||
*/
|
||||
loginUser: async (email, password) => {
|
||||
try {
|
||||
await DatabaseService.ensureDb();
|
||||
const row = await db.getFirstAsync(
|
||||
'SELECT * FROM users WHERE email = ? AND password = ?',
|
||||
[email, password]
|
||||
);
|
||||
return row || null;
|
||||
} catch (error) {
|
||||
console.error('Error logging in user:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if user exists by email
|
||||
*/
|
||||
userExists: async (email) => {
|
||||
try {
|
||||
await DatabaseService.ensureDb();
|
||||
const row = await db.getFirstAsync(
|
||||
'SELECT id FROM users WHERE email = ?',
|
||||
[email]
|
||||
);
|
||||
return !!row;
|
||||
} catch (error) {
|
||||
console.error('Error checking if user exists:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get user by ID
|
||||
*/
|
||||
getUserById: async (userId) => {
|
||||
try {
|
||||
await DatabaseService.ensureDb();
|
||||
if (!userId && userId !== 0) {
|
||||
throw new Error('User ID is required and cannot be null');
|
||||
}
|
||||
const row = await db.getFirstAsync(
|
||||
'SELECT * FROM users WHERE id = ?',
|
||||
[userId]
|
||||
);
|
||||
return row || null;
|
||||
} catch (error) {
|
||||
console.error('Error getting user:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Link an external account to user
|
||||
*/
|
||||
linkAccount: async (userId, serviceType, accountData, isDefault = false) => {
|
||||
try {
|
||||
await DatabaseService.ensureDb();
|
||||
|
||||
// Validate inputs
|
||||
if (!userId) {
|
||||
throw new Error('User ID is required');
|
||||
}
|
||||
if (!serviceType) {
|
||||
throw new Error('Service type is required');
|
||||
}
|
||||
if (!accountData || typeof accountData !== 'object') {
|
||||
throw new Error('Account data must be a valid object');
|
||||
}
|
||||
|
||||
console.log('📝 Linking account:', { userId, serviceType, dataKeys: Object.keys(accountData) });
|
||||
|
||||
// Encrypt account data
|
||||
const encryptedData = CredentialEncryption.encrypt(accountData);
|
||||
|
||||
if (!encryptedData) {
|
||||
throw new Error('Encryption returned empty data');
|
||||
}
|
||||
|
||||
console.log('✓ Account data encrypted, storing in database...');
|
||||
|
||||
const result = await db.runAsync(
|
||||
`INSERT INTO linked_accounts (user_id, service_type, account_data, is_default)
|
||||
VALUES (?, ?, ?, ?)`,
|
||||
[userId, serviceType, encryptedData, isDefault ? 1 : 0]
|
||||
);
|
||||
|
||||
console.log('✓ Account linked successfully, ID:', result.lastInsertRowId);
|
||||
|
||||
return result.lastInsertRowId;
|
||||
} catch (error) {
|
||||
console.error('Error linking account:', error);
|
||||
throw new Error(`Failed to link account: ${error.message}`);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all linked accounts for user
|
||||
*/
|
||||
getLinkedAccounts: async (userId) => {
|
||||
try {
|
||||
await DatabaseService.ensureDb();
|
||||
if (!userId && userId !== 0) {
|
||||
throw new Error('User ID is required and cannot be null');
|
||||
}
|
||||
const rows = await db.getAllAsync(
|
||||
'SELECT * FROM linked_accounts WHERE user_id = ? AND is_enabled = 1',
|
||||
[userId]
|
||||
);
|
||||
return rows.map((row) => ({
|
||||
...row,
|
||||
account_data: CredentialEncryption.decrypt(row.account_data),
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Error getting linked accounts:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get linked account by service type
|
||||
*/
|
||||
getLinkedAccountByService: async (userId, serviceType) => {
|
||||
try {
|
||||
await DatabaseService.ensureDb();
|
||||
const row = await db.getFirstAsync(
|
||||
'SELECT * FROM linked_accounts WHERE user_id = ? AND service_type = ? AND is_enabled = 1',
|
||||
[userId, serviceType]
|
||||
);
|
||||
if (!row) return null;
|
||||
return {
|
||||
...row,
|
||||
account_data: CredentialEncryption.decrypt(row.account_data),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error getting linked account:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set default service for uploads
|
||||
*/
|
||||
setDefaultService: async (userId, accountId) => {
|
||||
try {
|
||||
await DatabaseService.ensureDb();
|
||||
await db.runAsync(
|
||||
'UPDATE linked_accounts SET is_default = 0 WHERE user_id = ?',
|
||||
[userId]
|
||||
);
|
||||
await db.runAsync(
|
||||
'UPDATE linked_accounts SET is_default = 1 WHERE id = ? AND user_id = ?',
|
||||
[accountId, userId]
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error setting default service:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get default service for user
|
||||
*/
|
||||
getDefaultService: async (userId) => {
|
||||
try {
|
||||
await DatabaseService.ensureDb();
|
||||
const row = await db.getFirstAsync(
|
||||
'SELECT * FROM linked_accounts WHERE user_id = ? AND is_default = 1',
|
||||
[userId]
|
||||
);
|
||||
if (!row) return null;
|
||||
return {
|
||||
...row,
|
||||
account_data: CredentialEncryption.decrypt(row.account_data),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error getting default service:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add media to database
|
||||
*/
|
||||
addMedia: async (
|
||||
userId,
|
||||
sourceService,
|
||||
sourceId,
|
||||
title,
|
||||
description,
|
||||
localPath,
|
||||
remotePath,
|
||||
mediaType,
|
||||
createdDate
|
||||
) => {
|
||||
try {
|
||||
await DatabaseService.ensureDb();
|
||||
if (!userId || !sourceService) {
|
||||
throw new Error('userId and sourceService are required');
|
||||
}
|
||||
const result = await db.runAsync(
|
||||
`INSERT INTO media
|
||||
(user_id, source_service, source_id, title, description, local_path, remote_path, media_type, created_date)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
userId,
|
||||
sourceService,
|
||||
sourceId || null,
|
||||
title || null,
|
||||
description || null,
|
||||
localPath || null,
|
||||
remotePath || null,
|
||||
mediaType || null,
|
||||
createdDate || null,
|
||||
]
|
||||
);
|
||||
return result.lastInsertRowId;
|
||||
} catch (error) {
|
||||
console.error('Error adding media:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all media for user
|
||||
*/
|
||||
getMediaByUser: async (userId) => {
|
||||
try {
|
||||
await DatabaseService.ensureDb();
|
||||
if (!userId && userId !== 0) {
|
||||
throw new Error('User ID is required and cannot be null');
|
||||
}
|
||||
return await db.getAllAsync(
|
||||
'SELECT * FROM media WHERE user_id = ? ORDER BY created_date DESC',
|
||||
[userId]
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error getting media:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get media by service
|
||||
*/
|
||||
getMediaByService: async (userId, serviceType) => {
|
||||
try {
|
||||
await DatabaseService.ensureDb();
|
||||
return await db.getAllAsync(
|
||||
'SELECT * FROM media WHERE user_id = ? AND source_service = ? ORDER BY created_date DESC',
|
||||
[userId, serviceType]
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error getting media by service:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update media sync status
|
||||
*/
|
||||
updateMediaSyncStatus: async (mediaId, synced) => {
|
||||
try {
|
||||
await DatabaseService.ensureDb();
|
||||
await db.runAsync(
|
||||
'UPDATE media SET synced = ? WHERE id = ?',
|
||||
[synced ? 1 : 0, mediaId]
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error updating media sync status:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Log sync operation
|
||||
*/
|
||||
logSync: async (userId, serviceType, status, message) => {
|
||||
try {
|
||||
await DatabaseService.ensureDb();
|
||||
await db.runAsync(
|
||||
'INSERT INTO sync_logs (user_id, service_type, status, message) VALUES (?, ?, ?, ?)',
|
||||
[userId, serviceType, status, message]
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error logging sync:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Close database connection
|
||||
*/
|
||||
closeDatabase: async () => {
|
||||
try {
|
||||
if (db?.closeAsync) {
|
||||
await db.closeAsync();
|
||||
}
|
||||
db = null;
|
||||
initPromise = null;
|
||||
} catch (error) {
|
||||
console.error('Error closing database:', error);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default DatabaseService;
|
||||
|
||||
Reference in new issue
Block a user