Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/main/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ export class ApiClient {
return this.request<T>('PUT', url, body);
}

async patch<T>(path: string, body?: unknown): Promise<ApiResponse<T>> {
const url = this.buildUrl(path);
return this.request<T>('PATCH', url, body);
}

async delete<T>(path: string): Promise<ApiResponse<T>> {
const url = this.buildUrl(path);
return this.request<T>('DELETE', url);
Expand Down
24 changes: 24 additions & 0 deletions src/main/api/users.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Users API
* Handles the authenticated user's account and interview configuration
* (full name, profile, context)
*/

import { UpdateInterviewConfigRequest, UserAccount } from '../types/account.js';
import { ApiClient, ApiResponse } from './client.js';

export class UsersApi extends ApiClient {
/**
* Get the authenticated user's account
*/
async getMe(): Promise<ApiResponse<UserAccount>> {
return this.get<UserAccount>('/api/users/me');
}

/**
* Replace the authenticated user's interview configuration
*/
async updateInterviewConfig(data: UpdateInterviewConfigRequest): Promise<ApiResponse<UserAccount>> {
return this.patch<UserAccount>('/api/users/me/interview-config', data);
}
}
2 changes: 2 additions & 0 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const __dirname = path.dirname(__filename);
// Import modules
import { MIN_HEIGHT, MIN_WIDTH } from './consts.js';
import { registerGlobalHotkeys, unregisterHotkeys } from './hotkeys.js';
import { registerAccountHandlers } from './ipc/account.js';
import { registerAppStateHandlers } from './ipc/app-state.js';
import { registerAuthHandlers } from './ipc/auth.js';
import { registerAutoUpdaterHandlers } from './ipc/auto-updater.js';
Expand Down Expand Up @@ -182,6 +183,7 @@ app.whenReady().then(async () => {
registerConfigHandlers();
registerAppStateHandlers();
registerAuthHandlers();
registerAccountHandlers();
registerPaymentHandlers();
registerLLMHandlers();
registerPermissionHandlers();
Expand Down
13 changes: 13 additions & 0 deletions src/main/ipc/account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ipcMain } from 'electron';

import { accountService } from '../services/account.service.js';

export function registerAccountHandlers(): void {
// Push local account config changes (full name, profile, context) to the backend
ipcMain.handle(
'account:update',
async (_event, fullName: string, profileData: string, context: string) => {
return accountService.updateConfig(fullName, profileData, context);
}
);
}
5 changes: 5 additions & 0 deletions src/main/preload.cts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ const electronApi = {
ipcRenderer.invoke('auth:change-password', currentPassword, newPassword),
},

account: {
update: (fullName: string, profileData: string, context: string) =>
ipcRenderer.invoke('account:update', fullName, profileData, context),
},

payment: {
getPlans: () => ipcRenderer.invoke('payment:get-plans'),
getCurrencies: () => ipcRenderer.invoke('payment:get-currencies'),
Expand Down
67 changes: 67 additions & 0 deletions src/main/services/account.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { UsersApi } from '../api/users.js';
import { appStateService } from './app-state.service.js';

/**
* AccountService
* Keeps the in-memory interview config (full name, profile, context) in sync with
* the account persisted on the backend. Not written to local disk - the backend is
* the only durable store, so this always reflects whatever was last fetched/saved
* this session.
*/
export class AccountService {
private client = new UsersApi();

/**
* Pull the authenticated user's persisted interview config from the backend
* into app state. Called after login so a device shows the same config the
* user last saved anywhere.
*/
async pullFromBackend(): Promise<{ success: boolean; error?: string }> {
try {
const response = await this.client.getMe();
if (response.error || !response.data) {
return { success: false, error: response.error?.message || 'Failed to fetch account' };
}

const interviewConfig = response.data.interview_config;
appStateService.updateState({
interviewConfig: {
fullName: interviewConfig?.full_name ?? '',
profileData: interviewConfig?.profile_data ?? '',
context: interviewConfig?.context ?? '',
},
});
return { success: true };
} catch {
return { success: false, error: 'Failed to fetch account' };
}
}

/**
* Push local interview config changes to the backend, then mirror the
* saved values into app state.
*/
async updateConfig(
fullName: string,
profileData: string,
context: string
): Promise<{ success: boolean; error?: string }> {
try {
const response = await this.client.updateInterviewConfig({
full_name: fullName,
profile_data: profileData,
context,
});
if (response.error) {
return { success: false, error: response.error.message || 'Failed to update account' };
}

appStateService.updateState({ interviewConfig: { fullName, profileData, context } });
return { success: true };
} catch {
return { success: false, error: 'Failed to update account' };
}
}
}

export const accountService = new AccountService();
1 change: 1 addition & 0 deletions src/main/services/app-state.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const DEFAULT_STATE: AppState = {
credits: undefined,
userRole: undefined,
providedLLMModel: undefined,
interviewConfig: { fullName: '', profileData: '', context: '' },
};

export class AppStateService {
Expand Down
4 changes: 4 additions & 0 deletions src/main/services/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AuthApi } from '../api/auth.js';
import { configStore } from '../store/config.store.js';
import { accountService } from './account.service.js';
import { appStateService } from './app-state.service.js';
import { disableStealth } from './window-control.service.js';

Expand Down Expand Up @@ -99,6 +100,9 @@ export class AuthService {
// update app state to logged in
appStateService.updateState({ isLoggedIn: true });

// pull this account's synced config (full name, profile, context) from the backend
await accountService.pullFromBackend();

return { success: true };
} catch {
return { success: false, error: 'Login failed' };
Expand Down
7 changes: 7 additions & 0 deletions src/main/services/health-check.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import { HealthCheckApi } from '../api/health-check.js';
import { safeSleep } from '../utils/sleep.js';
import { accountService } from './account.service.js';
import { appStateService } from './app-state.service.js';
import { authService } from './auth.service.js';
import { pushNotificationService } from './push-notification.service.js';
Expand Down Expand Up @@ -33,6 +34,12 @@ export class HealthCheckService {
userRole: res.data?.user_role,
providedLLMModel: res.data?.provided_llm_model,
});

// Remembered sessions log the user in here without going through authService.login(),
// so this is where a returning device needs to pull its synced account config.
if (res.status === 200) {
await accountService.pullFromBackend();
}
} catch (error) {
console.error('[HealthCheckService] Initial client ping error:', error);
appStateService.updateState({ isLoggedIn: false });
Expand Down
5 changes: 3 additions & 2 deletions src/main/services/suggestion-action.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,12 @@ export class ActionSuggestionService {
private async generateSuggestion(taskId: string, transcripts: Transcript[]): Promise<void> {
const timestamp = DateTimeUtil.now();
const conf = configStore.getConfig();
const interviewConfig = appStateService.getState().interviewConfig;

const payload: GenerateActionSuggestionRequest = {
config: conf.llmConf,
profile_data: conf.interviewConf.profileData,
context: conf.interviewConf.jobDescription,
profile_data: interviewConfig.profileData,
context: interviewConfig.context,
transcripts: transcripts,
image_names: [...this.uploadedImageNames],
};
Expand Down
5 changes: 3 additions & 2 deletions src/main/services/suggestion-live.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,11 @@ class LiveSuggestionService {

try {
const conf = configStore.getConfig();
const interviewConfig = appStateService.getState().interviewConfig;
const requestBody: GenerateLiveSuggestionRequest = {
config: conf.llmConf,
profile_data: conf.interviewConf.profileData,
context: conf.interviewConf.jobDescription,
profile_data: interviewConfig.profileData,
context: interviewConfig.context,
transcripts: transcripts,
};

Expand Down
2 changes: 1 addition & 1 deletion src/main/services/tools.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class ToolsService {

async exportTranscript(): Promise<string | null> {
// Prepare request data
const username = configStore.getConfig().interviewConf.username;
const username = appStateService.getState().interviewConfig.fullName;
const transcripts = appStateService.getState().transcripts;
const suggestions = appStateService.getState().liveSuggestions;

Expand Down
33 changes: 15 additions & 18 deletions src/main/store/config.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,6 @@ import { LLMConfig } from '../types/llm.js';

// Runtime configuration (matches Config type in frontend)
export interface RuntimeConfig {
interviewConf: {
username: string;
profileData: string;
jobDescription: string;
};
language: string;
sessionToken: string;
rememberMe: boolean;
Expand All @@ -32,11 +27,6 @@ export interface RuntimeConfig {

// Default runtime configuration
const DEFAULT_RUNTIME_CONFIG: RuntimeConfig = {
interviewConf: {
username: '',
profileData: '',
jobDescription: '',
},
language: 'en',
sessionToken: '',
rememberMe: true,
Expand Down Expand Up @@ -89,14 +79,6 @@ class ConfigStore {
const current = this.getConfig();
const updated = { ...current, ...updates };

// Deep merge interview_conf if it's being partially updated
if (updates.interviewConf) {
updated.interviewConf = {
...current.interviewConf,
...updates.interviewConf,
};
}

this.store.set('runtime', updated);
return updated;
}
Expand Down Expand Up @@ -200,3 +182,18 @@ export const configStore = new ConfigStore();
configStore.updateConfig(migration);
}
})(); // migration block

// interviewConf (full name, profile, context) used to be cached here, but it's now
// backend-persisted and lives only in-memory (see AccountService/AppStateService).
// Drop any leftover local copy so it doesn't linger in the store file.
(() => {
// eslint-disable-next-line
const raw = (configStore as any).store.get('runtime') as
| (Partial<RuntimeConfig> & { interviewConf?: unknown })
| undefined;
if (raw && 'interviewConf' in raw) {
delete raw.interviewConf;
// eslint-disable-next-line
(configStore as any).store.set('runtime', raw);
}
})(); // drop legacy local interviewConf
27 changes: 27 additions & 0 deletions src/main/types/account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Account Types
*/

export interface InterviewConfig {
full_name: string;
profile_data: string;
context: string;
}

export interface UserAccount {
_id: string;
username: string;
email: string;
role: string;
status: string;
credits: number;
interview_config: InterviewConfig | null;
created_at: number;
updated_at: number | null;
}

export interface UpdateInterviewConfigRequest {
full_name: string;
profile_data: string;
context: string;
}
7 changes: 7 additions & 0 deletions src/main/types/app-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ export interface ActionSuggestion {
error: string;
}

export interface InterviewConfig {
fullName: string;
profileData: string;
context: string;
}

export interface AppState {
isStealth: boolean;
isBackendLive: boolean | null;
Expand All @@ -62,4 +68,5 @@ export interface AppState {
credits?: number;
userRole?: UserRole;
providedLLMModel?: string;
interviewConfig: InterviewConfig;
}
Loading