Merge pull request 'build multi tenant' (#1) from main into master
Reviewed-on: #1
This commit is contained in:
292
app/main.go
Normal file
292
app/main.go
Normal file
@@ -0,0 +1,292 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user