Add user data synchronization and database IPC handlers
- Introduce `db:user:sync` to synchronize user data with the local database during initialization. - Add `db:user:info` and `db:user:update` IPC handlers for fetching and updating user information. - Update `User` model and repository to support extended user fields and improved structure. - Dynamically import user models in main process to avoid circular dependencies. - Refactor database initialization in the app to include user sync logic with error handling.
This commit is contained in:
16
app/page.tsx
16
app/page.tsx
@@ -226,7 +226,21 @@ function ScribeContent() {
|
|||||||
const dbInitialized = await initializeDatabase(user.id);
|
const dbInitialized = await initializeDatabase(user.id);
|
||||||
if (dbInitialized) {
|
if (dbInitialized) {
|
||||||
console.log('Database initialized successfully');
|
console.log('Database initialized successfully');
|
||||||
// TODO: Sync initial des données du serveur vers la DB locale
|
|
||||||
|
// Sync user to local DB (only if not exists)
|
||||||
|
try {
|
||||||
|
await window.electron.invoke('db:user:sync', {
|
||||||
|
userId: user.id,
|
||||||
|
firstName: user.name,
|
||||||
|
lastName: user.lastName,
|
||||||
|
username: user.username,
|
||||||
|
email: user.email
|
||||||
|
});
|
||||||
|
console.log('User synced to local DB');
|
||||||
|
} catch (syncError) {
|
||||||
|
console.error('Failed to sync user to local DB:', syncError);
|
||||||
|
// Non-blocking error, continue anyway
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to initialize database:', error);
|
console.error('Failed to initialize database:', error);
|
||||||
|
|||||||
@@ -15,6 +15,26 @@ export interface GuideTour {
|
|||||||
[key: string]: boolean;
|
[key: string]: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface BookSummary {
|
||||||
|
bookId: string;
|
||||||
|
title: string;
|
||||||
|
subTitle?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserInfoResponse {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
lastName: string;
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
accountVerified: boolean;
|
||||||
|
authorName: string;
|
||||||
|
groupId: number;
|
||||||
|
termsAccepted: boolean;
|
||||||
|
guideTour: any[];
|
||||||
|
books: BookSummary[];
|
||||||
|
}
|
||||||
|
|
||||||
export default class User{
|
export default class User{
|
||||||
|
|
||||||
private readonly id:string;
|
private readonly id:string;
|
||||||
@@ -52,7 +72,7 @@ export default class User{
|
|||||||
this.termsAccepted = data.term_accepted === 1;
|
this.termsAccepted = data.term_accepted === 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async returnUserInfos(userId: string) {
|
public static async returnUserInfos(userId: string):Promise<UserInfoResponse> {
|
||||||
const user: User = new User(userId);
|
const user: User = new User(userId);
|
||||||
await user.getUserInfos();
|
await user.getUserInfos();
|
||||||
const books: BookProps[] = await Book.getBooks(userId);
|
const books: BookProps[] = await Book.getBooks(userId);
|
||||||
@@ -68,7 +88,7 @@ export default class User{
|
|||||||
groupId: user.getGroupId(),
|
groupId: user.getGroupId(),
|
||||||
termsAccepted: user.isTermsAccepted(),
|
termsAccepted: user.isTermsAccepted(),
|
||||||
guideTour: guideTour,
|
guideTour: guideTour,
|
||||||
books: books.map((book: BookProps) => {
|
books: books.map((book: BookProps):BookSummary => {
|
||||||
return {
|
return {
|
||||||
bookId: book.id,
|
bookId: book.id,
|
||||||
title: book.title,
|
title: book.title,
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ export interface UserInfosQueryResponse extends Record<string, SQLiteValue> {
|
|||||||
writing_level: number,
|
writing_level: number,
|
||||||
rite_points: number,
|
rite_points: number,
|
||||||
user_group: number,
|
user_group: number,
|
||||||
credits_balance: number,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CredentialResponse {
|
export interface CredentialResponse {
|
||||||
@@ -53,10 +52,23 @@ export default class UserRepo {
|
|||||||
try {
|
try {
|
||||||
const db: Database = System.getDb();
|
const db: Database = System.getDb();
|
||||||
const query = `INSERT INTO erit_users (user_id, first_name, last_name, username, email, origin_email,
|
const query = `INSERT INTO erit_users (user_id, first_name, last_name, username, email, origin_email,
|
||||||
origin_username, term_accepted,
|
origin_username, plateform, term_accepted,
|
||||||
account_verified)
|
account_verified, user_meta, reg_date)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, 0, 1)`;
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
|
||||||
const values: (string | null | number)[] = [uuId, firstName, lastName, username, email, originEmail, originUsername];
|
const values: (string | null | number)[] = [
|
||||||
|
uuId,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
username,
|
||||||
|
email,
|
||||||
|
originEmail,
|
||||||
|
originUsername,
|
||||||
|
'desktop', // plateform
|
||||||
|
0, // term_accepted
|
||||||
|
1, // account_verified
|
||||||
|
'{}', // user_meta (JSON empty object)
|
||||||
|
Date.now() // reg_date (current timestamp)
|
||||||
|
];
|
||||||
result = db.run(query, values);
|
result = db.run(query, values);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
if (e instanceof Error) {
|
if (e instanceof Error) {
|
||||||
@@ -78,7 +90,7 @@ export default class UserRepo {
|
|||||||
let result;
|
let result;
|
||||||
try {
|
try {
|
||||||
const db: Database = System.getDb();
|
const db: Database = System.getDb();
|
||||||
result = db.get('SELECT `first_name`, `last_name`, `username`, `email`, `plateform`, `term_accepted`, `account_verified`, user_meta, author_name, erite_points AS rite_points, user_group, credits_balance FROM `erit_users` AS users LEFT JOIN user_preferences AS preference ON users.user_id=preference.user_id WHERE users.user_id=?', [userId]);
|
result = db.get('SELECT `first_name`, `last_name`, `username`, `email`, `plateform`, `term_accepted`, `account_verified`, user_meta, author_name, erite_points AS rite_points, user_group FROM `erit_users` WHERE user_id=?', [userId]);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
if (e instanceof Error) {
|
if (e instanceof Error) {
|
||||||
console.error(`DB Error: ${e.message}`);
|
console.error(`DB Error: ${e.message}`);
|
||||||
|
|||||||
26
electron/ipc/user.ipc.ts
Normal file
26
electron/ipc/user.ipc.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { ipcMain } from 'electron';
|
||||||
|
import { createHandler } from '../database/LocalSystem.js';
|
||||||
|
import User, {UserInfoResponse} from '../database/models/User.js';
|
||||||
|
|
||||||
|
interface UpdateUserData {
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
authorName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /user/info - Get user info from local DB
|
||||||
|
ipcMain.handle('db:user:info', createHandler<void, UserInfoResponse>(
|
||||||
|
async function(userId: string, _body: void, _lang: 'fr' | 'en'):Promise<UserInfoResponse> {
|
||||||
|
return await User.returnUserInfos(userId);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// PUT /user/update - Update user info in local DB
|
||||||
|
ipcMain.handle('db:user:update', createHandler<UpdateUserData, boolean>(
|
||||||
|
async function(userId: string, data: UpdateUserData, lang: 'fr' | 'en'):Promise<boolean> {
|
||||||
|
const userKey = '';
|
||||||
|
return await User.updateUserInfos(userKey, userId, data.firstName, data.lastName, data.username, data.email, data.authorName, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
@@ -8,6 +8,7 @@ import { getDatabaseService } from './database/database.service.js';
|
|||||||
|
|
||||||
// Import IPC handlers
|
// Import IPC handlers
|
||||||
import './ipc/book.ipc.js';
|
import './ipc/book.ipc.js';
|
||||||
|
import './ipc/user.ipc.js';
|
||||||
|
|
||||||
// Fix pour __dirname en ES modules
|
// Fix pour __dirname en ES modules
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
@@ -169,6 +170,52 @@ ipcMain.on('logout', () => {
|
|||||||
createLoginWindow();
|
createLoginWindow();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ========== USER SYNC (PRE-AUTHENTICATION) ==========
|
||||||
|
|
||||||
|
interface SyncUserData {
|
||||||
|
userId: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcMain.handle('db:user:sync', async (_event, data: SyncUserData): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
// Import User models dynamically to avoid circular dependencies
|
||||||
|
const { default: User } = await import('./database/models/User.js');
|
||||||
|
const { default: UserRepo } = await import('./database/repositories/user.repository.js');
|
||||||
|
|
||||||
|
const lang: 'fr' | 'en' = 'fr';
|
||||||
|
|
||||||
|
// Check if user already exists in local DB
|
||||||
|
try {
|
||||||
|
UserRepo.fetchUserInfos(data.userId, lang);
|
||||||
|
// User exists, no need to sync
|
||||||
|
console.log(`[DB] User ${data.userId} already exists in local DB, skipping sync`);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
// User doesn't exist, create it
|
||||||
|
console.log(`[DB] User ${data.userId} not found, creating in local DB`);
|
||||||
|
|
||||||
|
await User.addUser(
|
||||||
|
data.userId,
|
||||||
|
data.firstName,
|
||||||
|
data.lastName,
|
||||||
|
data.username,
|
||||||
|
data.email,
|
||||||
|
'', // Password not needed for local DB
|
||||||
|
lang
|
||||||
|
);
|
||||||
|
console.log(`[DB] User ${data.userId} synced successfully`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[DB] Failed to sync user:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ========== DATABASE IPC HANDLERS ==========
|
// ========== DATABASE IPC HANDLERS ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user