import { Injectable } from '@nestjs/common';
import { ConfigService as NestConfigService } from '@nestjs/config';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
export interface Project {
id: number;
name: string;
}
export interface Module {
id: number;
name: string;
project_id: number;
description?: string;
}
export interface Activity {
id: number;
name: string;
}
export interface UserConfig {
apiUrl?: string;
token?: string;
user?: {
id: number;
username: string;
name: string;
email?: string;
roles?: string[];
managerId?: number;
reporting_manager_id?: number;
};
// Available data (fetched from API on login)
availableProjects?: Project[];
availableModules?: Module[];
availableActivities?: Activity[];
// User configured assignments
userAssignedProject?: Project;
userAssignedModules?: Module[];
// Other
geminiApiKey?: string;
}
@Injectable()
export class TimesheetConfigService {
private readonly configDir: string;
private readonly configFile: string;
private userConfig: UserConfig = {};
constructor(private readonly nestConfigService: NestConfigService) {
this.configDir = path.join(os.homedir(), '.config', 'timesheet-mcp');
this.configFile = path.join(this.configDir, 'config.json');
this.loadUserConfig();
}
/**
* Load user configuration from ~/.config/timesheet-mcp/config.json
*/
private loadUserConfig(): void {
try {
if (fs.existsSync(this.configFile)) {
const data = fs.readFileSync(this.configFile, 'utf-8');
this.userConfig = JSON.parse(data);
}
} catch (error) {
console.warn('Failed to load user config:', error.message);
}
}
/**
* Save user configuration to file
*/
saveUserConfig(config: Partial<UserConfig>): void {
try {
// Ensure directory exists
if (!fs.existsSync(this.configDir)) {
fs.mkdirSync(this.configDir, { recursive: true, mode: 0o700 });
}
// Merge with existing config
this.userConfig = { ...this.userConfig, ...config };
// Write to file
fs.writeFileSync(
this.configFile,
JSON.stringify(this.userConfig, null, 2),
{ mode: 0o600 },
);
} catch (error) {
throw new Error(`Failed to save user config: ${error.message}`);
}
}
/**
* Get the full user configuration
*/
getUserConfig(): UserConfig {
return this.userConfig;
}
/**
* Get JWT token
*/
getToken(): string | undefined {
return this.userConfig.token;
}
/**
* Set JWT token
*/
setToken(token: string): void {
this.saveUserConfig({ token });
}
/**
* Get user profile
*/
getUser(): UserConfig['user'] | undefined {
return this.userConfig.user;
}
/**
* Set user profile
*/
setUser(user: UserConfig['user']): void {
this.saveUserConfig({ user });
}
/**
* Get API URL
*/
getApiUrl(): string {
return (
this.userConfig.apiUrl ||
this.nestConfigService.get<string>('API_BASE_URL') ||
'https://timesheet.pinnacle.in:3000/api'
);
}
/**
* Get Gemini API key
*/
getGeminiApiKey(): string | undefined {
return (
this.userConfig.geminiApiKey ||
this.nestConfigService.get<string>('GEMINI_API_KEY')
);
}
/**
* Get available projects (fetched from API)
*/
getAvailableProjects(): Project[] {
return this.userConfig.availableProjects || [];
}
/**
* Set available projects
*/
setAvailableProjects(projects: Project[]): void {
this.saveUserConfig({ availableProjects: projects });
}
/**
* Get available modules (fetched from API)
*/
getAvailableModules(): Module[] {
return this.userConfig.availableModules || [];
}
/**
* Set available modules
*/
setAvailableModules(modules: Module[]): void {
this.saveUserConfig({ availableModules: modules });
}
/**
* Get available activities (fetched from API)
*/
getAvailableActivities(): Activity[] {
return this.userConfig.availableActivities || [];
}
/**
* Set available activities
*/
setAvailableActivities(activities: Activity[]): void {
this.saveUserConfig({ availableActivities: activities });
}
/**
* Get user's assigned project
*/
getUserAssignedProject(): Project | undefined {
return this.userConfig.userAssignedProject;
}
/**
* Set user's assigned project
*/
setUserAssignedProject(project: Project): void {
this.saveUserConfig({ userAssignedProject: project });
}
/**
* Get user's assigned modules
*/
getUserAssignedModules(): Module[] {
return this.userConfig.userAssignedModules || [];
}
/**
* Set user's assigned modules
*/
setUserAssignedModules(modules: Module[]): void {
this.saveUserConfig({ userAssignedModules: modules });
}
/**
* Check if user is authenticated (has token)
*/
isConfigured(): boolean {
return !!this.userConfig.token;
}
/**
* Check if user has configured their project and modules
*/
isUserConfigured(): boolean {
return (
!!this.userConfig.userAssignedProject &&
!!this.userConfig.userAssignedModules &&
this.userConfig.userAssignedModules.length > 0
);
}
/**
* Check what configuration is missing
*/
needsConfiguration(): { project: boolean; modules: boolean } {
return {
project: !this.userConfig.userAssignedProject,
modules:
!this.userConfig.userAssignedModules ||
this.userConfig.userAssignedModules.length === 0,
};
}
/**
* Clear all configuration (logout)
*/
clearConfig(): void {
this.userConfig = {};
try {
if (fs.existsSync(this.configFile)) {
fs.unlinkSync(this.configFile);
}
} catch (error) {
console.warn('Failed to clear config:', error.message);
}
}
}