package main import ( "fmt" "log" "os" "path/filepath" "github.com/gdamore/tcell/v2" "github.com/jmoiron/sqlx" "github.com/rivo/tview" flag "github.com/spf13/pflag" _ "github.com/lib/pq" "github.com/ajaxray/geek-life/auth" "github.com/ajaxray/geek-life/config" "github.com/ajaxray/geek-life/migration" "github.com/ajaxray/geek-life/model" "github.com/ajaxray/geek-life/repository" "github.com/ajaxray/geek-life/repository/postgres" "github.com/ajaxray/geek-life/util" ) var ( app *tview.Application layout, contents *tview.Flex statusBar *StatusBar projectPane *ProjectPane taskPane *TaskPane taskDetailPane *TaskDetailPane projectDetailPane *ProjectDetailPane db *sqlx.DB projectRepo repository.ProjectRepository taskRepo repository.TaskRepository authService *auth.AuthService userContext *model.UserContext // Configuration cfg *config.Config // Flag variables migrate bool ) func init() { flag.BoolVarP(&migrate, "migrate", "m", false, "Run database migrations") } func main() { flag.Parse() // Load configuration var err error cfg, err = config.Load() if err != nil { log.Fatalf("Failed to load configuration: %v", err) } // Connect to PostgreSQL db, err = sqlx.Connect("postgres", cfg.GetDSN()) if err != nil { log.Fatalf("Failed to connect to database: %v", err) } defer db.Close() // Run migrations if requested if migrate { migrator := migration.NewMigrator(db, "migrations") if err := migrator.Run(); err != nil { log.Fatalf("Failed to run migrations: %v", err) } fmt.Println("Migrations completed successfully") return } // Initialize repositories userRepo := postgres.NewUserRepository(db) tenantRepo := postgres.NewTenantRepository(db) sessionRepo := postgres.NewSessionRepository(db) projectRepo = postgres.NewProjectRepository(db) taskRepo = postgres.NewTaskRepository(db) // Initialize auth service authService = auth.NewAuthService(userRepo, tenantRepo, sessionRepo, cfg.Auth.SessionDuration) // Check for existing session sessionToken := loadSessionToken() if sessionToken != "" { userContext, err = authService.ValidateSession(sessionToken) if err != nil { // Invalid session, remove it removeSessionToken() sessionToken = "" } } // If no valid session, show login screen if userContext == nil { showAuthScreen() return } // Initialize and start the main application startMainApp() } func showAuthScreen() { app = tview.NewApplication() // Create auth form form := tview.NewForm() form.SetBorder(true).SetTitle("Geek-Life Authentication") var tenantName, username, password string var isLogin bool = true form.AddInputField("Tenant", "", 20, nil, func(text string) { tenantName = text }) form.AddInputField("Username/Email", "", 20, nil, func(text string) { username = text }) form.AddPasswordField("Password", "", 20, '*', func(text string) { password = text }) form.AddButton("Login", func() { if tenantName == "" || username == "" || password == "" { showError("All fields are required") return } ctx, token, err := authService.Login(tenantName, username, password) if err != nil { showError(fmt.Sprintf("Login failed: %v", err)) return } userContext = ctx saveSessionToken(token) app.Stop() startMainApp() }) form.AddButton("Register User", func() { if tenantName == "" || username == "" || password == "" { showError("All fields are required") return } ctx, token, err := authService.RegisterUser(tenantName, username, username, password) if err != nil { showError(fmt.Sprintf("Registration failed: %v", err)) return } userContext = ctx saveSessionToken(token) app.Stop() startMainApp() }) form.AddButton("Register Tenant", func() { if tenantName == "" || username == "" || password == "" { showError("All fields are required") return } ctx, token, err := authService.RegisterTenant(tenantName, username, username, password) if err != nil { showError(fmt.Sprintf("Tenant registration failed: %v", err)) return } userContext = ctx saveSessionToken(token) app.Stop() startMainApp() }) form.AddButton("Quit", func() { app.Stop() }) // Create info text info := tview.NewTextView() info.SetText("Welcome to Geek-Life!\n\n" + "• Login: Use existing tenant and user credentials\n" + "• Register User: Create a new user in an existing tenant\n" + "• Register Tenant: Create a new tenant with admin user\n\n" + "Use Tab to navigate between fields") info.SetBorder(true).SetTitle("Instructions") // Layout flex := tview.NewFlex().SetDirection(tview.FlexRow) flex.AddItem(info, 8, 0, false) flex.AddItem(form, 0, 1, true) if err := app.SetRoot(flex, true).EnableMouse(true).Run(); err != nil { panic(err) } } func startMainApp() { app = tview.NewApplication() statusBar = NewStatusBar(fmt.Sprintf("User: %s | Tenant: %s", userContext.User.Username, userContext.Tenant.Name)) projectPane = NewProjectPane(userContext) taskPane = NewTaskPane(userContext) taskDetailPane = NewTaskDetailPane(userContext) projectDetailPane = NewProjectDetailPane(userContext) layout = tview.NewFlex().SetDirection(tview.FlexRow). AddItem(tview.NewFlex().SetDirection(tview.FlexColumn). AddItem(projectPane.GetView(), 25, 1, true). AddItem(tview.NewFlex().SetDirection(tview.FlexRow). AddItem(taskPane.GetView(), 0, 7, false). AddItem(taskDetailPane.GetView(), 0, 3, false), 0, 5, false). AddItem(projectDetailPane.GetView(), 0, 2, false), 0, 1, true). AddItem(statusBar.GetView(), 1, 1, false) app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyCtrlC { app.Stop() return nil } if event.Key() == tcell.KeyCtrlL { // Logout sessionToken := loadSessionToken() if sessionToken != "" { authService.Logout(sessionToken) removeSessionToken() } app.Stop() userContext = nil showAuthScreen() return nil } return event }) if err := app.SetRoot(layout, true).EnableMouse(true).Run(); err != nil { panic(err) } } func showError(message string) { modal := tview.NewModal(). SetText(message). AddButtons([]string{"OK"}). SetDoneFunc(func(buttonIndex int, buttonLabel string) { app.SetRoot(app.GetRoot(), true) }) app.SetRoot(modal, false) } func saveSessionToken(token string) { homeDir, err := os.UserHomeDir() if err != nil { return } sessionFile := filepath.Join(homeDir, ".geek-life-session") os.WriteFile(sessionFile, []byte(token), 0600) } func loadSessionToken() string { homeDir, err := os.UserHomeDir() if err != nil { return "" } sessionFile := filepath.Join(homeDir, ".geek-life-session") data, err := os.ReadFile(sessionFile) if err != nil { return "" } return string(data) } func removeSessionToken() { homeDir, err := os.UserHomeDir() if err != nil { return } sessionFile := filepath.Join(homeDir, ".geek-life-session") os.Remove(sessionFile) }