Files
geek-life/app/main.go

292 lines
6.9 KiB
Go

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)
}