182 lines
4.9 KiB
Go
182 lines
4.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"os"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
_ "github.com/go-sql-driver/mysql" // MySQL Driver
|
|
"golang.org/x/crypto/bcrypt"
|
|
|
|
// CHANGE THIS to match your go.mod name + /dba
|
|
"skyrama_clone_backend/backend/db"
|
|
)
|
|
|
|
// ============================================================================
|
|
// 1. STATE DEFINITIONS
|
|
// ============================================================================
|
|
type sessionState int
|
|
|
|
const (
|
|
stateLogin sessionState = iota
|
|
stateHomepage
|
|
)
|
|
|
|
// ============================================================================
|
|
// 2. THE MASTER MODEL (The Router)
|
|
// ============================================================================
|
|
type mainModel struct {
|
|
state sessionState
|
|
queries *db.Queries // <-- NEW: Holds our database queries
|
|
|
|
login loginModel
|
|
homepage homepageModel
|
|
}
|
|
|
|
func initialModel(q *db.Queries) mainModel {
|
|
return mainModel{
|
|
state: stateLogin,
|
|
queries: q,
|
|
login: initialModell(),
|
|
homepage: initialHomepageModel(),
|
|
}
|
|
}
|
|
|
|
func (m mainModel) Init() tea.Cmd {
|
|
return m.login.Init()
|
|
}
|
|
|
|
// ============================================================================
|
|
// 3. THE MASTER UPDATE (Traffic Cop & Authentication)
|
|
// ============================================================================
|
|
func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
if keyMsg, ok := msg.(tea.KeyMsg); ok && keyMsg.String() == "ctrl+c" {
|
|
return m, tea.Quit
|
|
}
|
|
|
|
//var cmd tea.Cmd
|
|
var cmds []tea.Cmd
|
|
ctx := context.Background()
|
|
|
|
switch m.state {
|
|
|
|
case stateLogin:
|
|
newLoginModel, loginCmd := m.login.Update(msg)
|
|
m.login = newLoginModel.(loginModel)
|
|
cmds = append(cmds, loginCmd)
|
|
|
|
// Did the user press Enter on the Submit button?
|
|
if m.login.isSubmitted {
|
|
username := m.login.inputs[0].Value()
|
|
password := m.login.inputs[1].Value()
|
|
|
|
// 1. Fetch the user from the database
|
|
user, err := m.queries.GetUserByUsername(ctx, username)
|
|
if err != nil {
|
|
m.login.errorMessage = "User not found."
|
|
m.login.isSubmitted = false // Reset so they can try again
|
|
return m, nil
|
|
}
|
|
|
|
// 2. Verify the password hash
|
|
err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password))
|
|
if err != nil {
|
|
m.login.errorMessage = "Invalid password."
|
|
m.login.isSubmitted = false // Reset so they can try again
|
|
return m, nil
|
|
}
|
|
|
|
// 3. Fetch their Airport Data
|
|
// Note: user.ID is int32 in sqlc (based on schema)
|
|
airport, err := m.queries.GetAirportByUserId(ctx, user.ID)
|
|
if err != nil {
|
|
m.login.errorMessage = "No airport found for this user."
|
|
m.login.isSubmitted = false
|
|
return m, nil
|
|
}
|
|
|
|
// 4. Fetch their Fleet Count
|
|
// Note: airport.ID is uint32, GetPlayerFleet expects the correct type
|
|
fleet, _ := m.queries.GetPlayerFleet(ctx, airport.ID)
|
|
|
|
// 5. SUCCESS! Push real data to the homepage and switch screens
|
|
m.homepage.playerName = user.Username
|
|
m.homepage.cash = int(airport.Cash)
|
|
m.homepage.fleetCount = len(fleet)
|
|
|
|
m.state = stateHomepage
|
|
cmds = append(cmds, m.homepage.Init())
|
|
} else if m.login.isCanceled {
|
|
return m, tea.Quit
|
|
}
|
|
|
|
case stateHomepage:
|
|
newHomepageModel, homeCmd := m.homepage.Update(msg)
|
|
m.homepage = newHomepageModel.(homepageModel)
|
|
cmds = append(cmds, homeCmd)
|
|
|
|
if m.homepage.selected != "" {
|
|
if m.homepage.selected == "🚪 Logout / Quit" {
|
|
return m, tea.Quit
|
|
}
|
|
// Placeholder for other menus
|
|
return m, tea.Quit
|
|
}
|
|
}
|
|
|
|
return m, tea.Batch(cmds...)
|
|
}
|
|
|
|
func (m mainModel) View() string {
|
|
switch m.state {
|
|
case stateLogin:
|
|
return m.login.View()
|
|
case stateHomepage:
|
|
return m.homepage.View()
|
|
default:
|
|
return "Error: Unknown state"
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// MAIN EXECUTION WITH DATABASE POOL
|
|
// ============================================================================
|
|
func main() {
|
|
// 1. Connect to MySQL Database
|
|
// Ensure ?parseTime=true is present!
|
|
dsn := "skyramaUser:Skyrama1234.@tcp(127.0.0.1:3306)/skyrama_clone?parseTime=true"
|
|
dbConn, err := sql.Open("mysql", dsn)
|
|
if err != nil {
|
|
fmt.Printf("Error opening database pool: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
defer dbConn.Close()
|
|
|
|
if err := dbConn.Ping(); err != nil {
|
|
fmt.Printf("Database unreachable: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// 2. Initialize sqlc queries
|
|
queries := db.New(dbConn)
|
|
|
|
// 3. Start the Bubble Tea UI, passing the database into the model
|
|
p := tea.NewProgram(initialModel(queries), tea.WithAltScreen())
|
|
|
|
finalModel, err := p.Run()
|
|
if err != nil {
|
|
fmt.Printf("Fatal error starting SkyRama: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Print("\033[H\033[2J") // Clear terminal on exit
|
|
if m, ok := finalModel.(mainModel); ok {
|
|
if m.state == stateHomepage && m.homepage.selected != "" && m.homepage.selected != "🚪 Logout / Quit" {
|
|
fmt.Printf("You clicked '%s', which we will build next!\n", m.homepage.selected)
|
|
} else {
|
|
fmt.Println("Gracefully disconnected from SkyRama servers. Goodbye!")
|
|
}
|
|
}
|
|
}
|