stage 0 database schema ready
This commit is contained in:
parent
f463e71c28
commit
7720522a32
18 changed files with 5577 additions and 4 deletions
|
|
@ -1 +1,62 @@
|
|||
package backend
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"skyrama_clone_backend/backend/db"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql" // MySQL Driver
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func create_user(queries *db.Queries, ctx context.Context, username string, password string) {
|
||||
|
||||
hashedPass, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
|
||||
resultt, err := queries.CreateUser(ctx, db.CreateUserParams{
|
||||
Username: username,
|
||||
PasswordHash: string(hashedPass),
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("Error at operation : %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println(resultt)
|
||||
fmt.Printf("added user : %s ", username)
|
||||
}
|
||||
func update_user(queries *db.Queries, ctx context.Context, username string, password string) {
|
||||
|
||||
hashed_pass, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
|
||||
err := queries.UpdateUserPasswordByUserName(ctx, db.UpdateUserPasswordByUserNameParams{
|
||||
PasswordHash: string(hashed_pass),
|
||||
Username: username,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("Error at operation : %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("updated user : %s with %s \n", username, string(hashed_pass))
|
||||
fmt.Println("successfully updated user")
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
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)
|
||||
}
|
||||
ctx := context.Background()
|
||||
queries := db.New(dbConn)
|
||||
create_user(queries, ctx, "tester_adnan", "a")
|
||||
//update_user(queries, ctx, "tester_adnan", "a")
|
||||
}
|
||||
|
|
|
|||
336
backend/db/config/queries.sql
Normal file
336
backend/db/config/queries.sql
Normal file
|
|
@ -0,0 +1,336 @@
|
|||
-- ============================================================================
|
||||
-- 1. AUTHENTICATION & USERS
|
||||
-- ============================================================================
|
||||
|
||||
-- name: CreateUser :execresult
|
||||
INSERT INTO users (username, password_hash)
|
||||
VALUES (?, ?);
|
||||
|
||||
-- name: UpdateUserPasswordByUserName :exec
|
||||
UPDATE users
|
||||
SET password_hash = ?
|
||||
WHERE username = ?;
|
||||
|
||||
-- name: GetUserByUsername :one
|
||||
SELECT id, username, password_hash
|
||||
FROM users
|
||||
WHERE username = ? LIMIT 1;
|
||||
|
||||
-- name: GetUserIdByUsername :one
|
||||
SELECT id
|
||||
FROM users
|
||||
WHERE username = ? LIMIT 1;
|
||||
|
||||
-- name: DeleteUserByUsername :exec
|
||||
UPDATE users
|
||||
SET deleted_at = CURRENT_TIMESTAMP
|
||||
WHERE username = ?;
|
||||
|
||||
|
||||
-- name: DeleteAirportByUserName :exec
|
||||
UPDATE airports
|
||||
SET is_deleted = TRUE
|
||||
WHERE user_id = (SELECT id FROM users WHERE username = ?);
|
||||
|
||||
-- ============================================================================
|
||||
-- 2. AIRPORT CORE MANAGEMENT
|
||||
-- ============================================================================
|
||||
|
||||
-- airport row management
|
||||
|
||||
-- name: CreateAirportByUserName :execresult
|
||||
INSERT INTO airports (name, user_id, origin_country_id)
|
||||
VALUES (?, (SELECT id FROM users WHERE username = ?), ?);
|
||||
|
||||
|
||||
-- name: GetAirportByUserId :one
|
||||
SELECT id, name, user_id, origin_country_id, cash, current_passengers, max_passenger_capacity, current_fuel, max_fuel_capacity, is_available, player_level, experience_points, passenger_generation_rate, fuel_generation_rate, resources_last_updated_at, is_deleted
|
||||
FROM airports
|
||||
WHERE user_id = ? LIMIT 1;
|
||||
-- name: GetAirportByUserName :one
|
||||
SELECT id, name, user_id, origin_country_id, cash, current_passengers, max_passenger_capacity, current_fuel, max_fuel_capacity, is_available, player_level, experience_points ,passenger_generation_rate, fuel_generation_rate, resources_last_updated_at, is_deleted
|
||||
FROM airports
|
||||
WHERE user_id = (SELECT id FROM users WHERE username = ?) LIMIT 1;
|
||||
|
||||
|
||||
-- airport resource management
|
||||
|
||||
|
||||
-- name: UpdateAirportCurrencies :exec
|
||||
UPDATE airports
|
||||
SET cash = ?,
|
||||
current_passengers = ?,
|
||||
current_fuel = ?
|
||||
WHERE id = ?;
|
||||
|
||||
-- name: UpdateAirportResources :exec
|
||||
UPDATE airports
|
||||
SET
|
||||
current_fuel = LEAST(
|
||||
max_fuel_capacity,
|
||||
current_fuel + (fuel_generation_rate * TIMESTAMPDIFF(SECOND, resources_last_updated_at, NOW()))
|
||||
),
|
||||
current_passengers = LEAST(
|
||||
max_passenger_capacity,
|
||||
current_passengers + (passenger_generation_rate * TIMESTAMPDIFF(SECOND, resources_last_updated_at, NOW()))
|
||||
),
|
||||
resources_last_updated_at = NOW()
|
||||
WHERE id = ?;
|
||||
|
||||
-- name: UpdateAirportCash :exec
|
||||
UPDATE airports
|
||||
SET cash = ?
|
||||
WHERE id = ?;
|
||||
-- name: UpdateAirportpassengers :exec
|
||||
UPDATE airports
|
||||
SET current_passengers = ?
|
||||
WHERE id = ?;
|
||||
-- name: UpdateAirportFuel :exec
|
||||
UPDATE airports
|
||||
SET current_fuel = ?
|
||||
WHERE id = ?;
|
||||
-- name: UpdateAirportmaxPassengers :exec
|
||||
UPDATE airports
|
||||
SET max_passenger_capacity = ?
|
||||
WHERE id = ?;
|
||||
-- name: UpdateAirportMaxFuel :exec
|
||||
UPDATE airports
|
||||
SET max_fuel_capacity = ?
|
||||
WHERE id = ?;
|
||||
|
||||
-- name: UpdateAirportFuelRate :exec
|
||||
UPDATE airports
|
||||
SET fuel_generation_rate = ?
|
||||
WHERE id = ?;
|
||||
-- name: UpdateAirportPassengerRate :exec
|
||||
UPDATE airports
|
||||
SET passenger_generation_rate = ?
|
||||
WHERE id = ?;
|
||||
|
||||
-- name: UpdateAirportResourcesUpdated :exec
|
||||
update airports
|
||||
set resources_last_updated_at = ?
|
||||
where id = ?;
|
||||
|
||||
|
||||
-- name: updatePlayerExperience :exec
|
||||
UPDATE airports
|
||||
SET experience_points = ?
|
||||
WHERE id = ?;
|
||||
|
||||
-- name: updatePlayerLevel :exec
|
||||
UPDATE airports
|
||||
SET player_level = ?
|
||||
WHERE id = ?;
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- 3. WAREHOUSE & INVENTORY SYSTEM
|
||||
-- ============================================================================
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- 4. BUILDINGS & PRODUCTION ENGINE
|
||||
-- ============================================================================
|
||||
|
||||
-- name: addStorageBuilding :execresult
|
||||
INSERT INTO buildings (name,min_level,construction_cost_cash,capacity,storage_type,storage_subtype)
|
||||
VALUES (?, ?, ?, ?, ?, ?);
|
||||
|
||||
-- name: addOperationalBuilding :execresult
|
||||
INSERT INTO buildings (name,min_level,construction_cost_cash,operation_type,operation_size)
|
||||
VALUES (?, ?, ?, ?, ?);
|
||||
|
||||
-- name: addPassiveProductionBuilding :execresult
|
||||
INSERT INTO buildings (name,min_level,construction_cost_cash,passive_type,passive_rate)
|
||||
VALUES (?, ?, ?, ?, ?);
|
||||
|
||||
-- name: addBuilding :execresult
|
||||
INSERT INTO buildings (name,min_level,construction_cost_cash)
|
||||
VALUES (?, ?, ?);
|
||||
|
||||
|
||||
-- name: AddBuildingToAirportByUsername :execresult
|
||||
INSERT INTO airport_buildings (airport_id, building_id)
|
||||
VALUES ((select id from airports where user_id = (select id from users where username = ?)), ?);
|
||||
|
||||
-- name: GetAirportBuildingsByUsername :many
|
||||
SELECT ab.id AS airport_building_id,b.building_type , b.*
|
||||
FROM airport_buildings ab
|
||||
JOIN buildings b ON ab.building_id = b.id
|
||||
WHERE ab.airport_id = (select id from airports where user_id = (select id from users where username = ?));
|
||||
|
||||
|
||||
|
||||
-- planes & fleet management
|
||||
|
||||
-- name: addPlane :execresult
|
||||
INSERT INTO planes (name, aircraft_size,operation_type,aircraft_type ,min_level, operation_cost_fuel, operation_cost_cash, max_passengers, travel_time_inSeconds,buying_cost_cash, completed_flight_experience, completed_flight_cash_reward, completed_flight_cargo_reward)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||
|
||||
|
||||
-- name: GetPlaneById :one
|
||||
SELECT * FROM planes WHERE id = ?;
|
||||
|
||||
-- name: GetAllPlanes :many
|
||||
SELECT * FROM planes;
|
||||
|
||||
-- name: GetPlanesByOperationType :many
|
||||
SELECT * FROM planes WHERE operation_type = ?;
|
||||
|
||||
-- name: GetPlanesByMinLevel :many
|
||||
SELECT * FROM planes WHERE min_level <= ?;
|
||||
|
||||
-- name: GetPlanesByAircraftSize :many
|
||||
SELECT * FROM planes WHERE aircraft_size = ?;
|
||||
|
||||
-- name: GetPlanesByAircraftType :many
|
||||
SELECT * FROM planes WHERE aircraft_type = ?;
|
||||
|
||||
-- name: addPlaneToAirportByUsername :execresult
|
||||
INSERT INTO player_planes (airport_id, plane_id, assigned_hangar_id, current_building_id, status)
|
||||
VALUES ((select id from airports where user_id = (select id from users where username = ?)), ?, ?, ?, 'idle');
|
||||
|
||||
-- name: GetPlayerPlanesByUsername :many
|
||||
SELECT pp.id AS player_plane_id, pp.*
|
||||
FROM player_planes pp
|
||||
JOIN planes p ON pp.plane_id = p.id
|
||||
WHERE pp.airport_id = (select id from airports where user_id = (select id from users where username = ?));
|
||||
|
||||
|
||||
-- name: FindAvailableInfrastructureForPlane :one
|
||||
SELECT ab.id
|
||||
FROM airport_buildings ab
|
||||
JOIN buildings b ON ab.building_id = b.id
|
||||
LEFT JOIN player_planes pp ON ab.id = pp.current_building_id
|
||||
WHERE ab.airport_id = (select id from airports where user_id = (select id from users where username = ?))
|
||||
AND b.operation_type = ? -- 'runway' or 'service' (service bay)
|
||||
AND b.operation_size = ? -- matches plane's aircraft_size requirement
|
||||
AND pp.id IS NULL -- Ensures no other plane is physically here right now
|
||||
LIMIT 1;
|
||||
|
||||
-- name: UpdatePlayerPlaneState :exec
|
||||
UPDATE player_planes
|
||||
SET current_building_id = ?,
|
||||
status = ?,
|
||||
flight_start_time = ?
|
||||
WHERE id = ?;
|
||||
|
||||
-- name: CheckHangarOccupancyByUsername :one
|
||||
SELECT COUNT(*) AS total_parked
|
||||
FROM player_planes pp
|
||||
JOIN airport_buildings ab ON pp.current_building_id = ab.id
|
||||
WHERE ab.airport_id = (select id from airports where user_id = (select id from users where username = ?))
|
||||
AND ab.building_id = ?; -- specific hangar
|
||||
|
||||
--
|
||||
-- name: CheckRunwayOccupancyByUsername :one
|
||||
SELECT COUNT(*) AS total_occupied
|
||||
FROM player_planes pp
|
||||
JOIN airport_buildings ab ON pp.current_building_id = ab.id
|
||||
WHERE ab.airport_id = (select id from airports where user_id = (select id from users where username = ?))
|
||||
AND ab.building_id = ?; -- specific runway
|
||||
|
||||
-- name: GetPlanesReadyToLandByUsername :many
|
||||
SELECT pp.id AS player_plane_id, pp.airport_id, pp.assigned_hangar_id,
|
||||
p.completed_flight_experience, p.completed_flight_cash_reward, p.completed_flight_cargo_reward, p.operation_type
|
||||
FROM player_planes pp
|
||||
JOIN planes p ON pp.plane_id = p.id
|
||||
WHERE pp.status = 'flying'
|
||||
AND ADDTIME(pp.flight_start_time, p.travel_time) <= NOW()
|
||||
AND pp.airport_id = (select id from airports where user_id = (select id from users where username = ?));
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- 5. FLEET & DYNAMIC INFRASTRUCTURE OPERATIONS
|
||||
-- ============================================================================
|
||||
|
||||
-- name: BuyPlaneForPlayer :execresult
|
||||
INSERT INTO player_planes (airport_id, plane_id, assigned_hangar_id, current_building_id, status)
|
||||
VALUES (?, ?, ?, ?, 'idle');
|
||||
|
||||
-- name: GetPlayerFleet :many
|
||||
SELECT pp.id AS player_plane_id, pp.status, pp.updated_at, pp.flight_start_time, pp.current_building_id, pp.assigned_hangar_id,p.*
|
||||
FROM player_planes pp
|
||||
JOIN planes p ON pp.plane_id = p.id
|
||||
WHERE pp.airport_id = ?;
|
||||
|
||||
-- name: FindAvailableInfrastructure :one
|
||||
SELECT ab.id
|
||||
FROM airport_buildings ab
|
||||
JOIN buildings b ON ab.building_id = b.id
|
||||
LEFT JOIN player_planes pp ON ab.id = pp.current_building_id
|
||||
WHERE ab.airport_id = ?
|
||||
AND b.operation_type = ? -- 'runway' or 'service' (service bay)
|
||||
AND b.operation_size = ? -- matches plane's aircraft_size requirement
|
||||
AND pp.id IS NULL -- Ensures no other plane is physically here right now
|
||||
LIMIT 1;
|
||||
|
||||
-- name: UpdatePlaneState :exec
|
||||
UPDATE player_planes
|
||||
SET current_building_id = ?,
|
||||
status = ?,
|
||||
|
||||
flight_start_time = ?
|
||||
WHERE id = ?;
|
||||
|
||||
-- name: CheckHangarOccupancy :one
|
||||
SELECT COUNT(*) AS total_parked
|
||||
FROM player_planes
|
||||
WHERE current_building_id = ?;
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- 6. TIME-BASED FLIGHT ENGINE
|
||||
-- ============================================================================
|
||||
|
||||
-- name: GetPlanesReadyToLand :many
|
||||
SELECT pp.id AS player_plane_id, pp.airport_id, pp.assigned_hangar_id,
|
||||
p.completed_flight_experience, p.completed_flight_cash_reward, p.completed_flight_cargo_reward, p.operation_type
|
||||
FROM player_planes pp
|
||||
JOIN planes p ON pp.plane_id = p.id
|
||||
WHERE pp.status = 'flying'
|
||||
-- Uses your logic: flight_start_time + plane travel duration <= current clock
|
||||
AND ADDTIME(pp.flight_start_time, p.travel_time) <= NOW();
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- 7. PROGRESSION CONTRACTS & ECONOMY LOGS
|
||||
-- ============================================================================
|
||||
|
||||
-- name: GetActiveContracts :many
|
||||
SELECT id, title, required_product_id, required_amount, current_amount_delivered, is_completed, expires_at
|
||||
FROM airport_contracts
|
||||
WHERE airport_id = ? AND is_completed = FALSE AND expires_at > NOW();
|
||||
|
||||
-- name: UpdateContractDelivery :exec
|
||||
UPDATE airport_contracts
|
||||
SET current_amount_delivered = current_amount_delivered + ?
|
||||
WHERE id = ? AND is_completed = FALSE;
|
||||
|
||||
-- name: CompleteContract :exec
|
||||
UPDATE airport_contracts
|
||||
SET is_completed = TRUE
|
||||
WHERE id = ?;
|
||||
|
||||
-- name: LogCurrencyTransaction :exec
|
||||
INSERT INTO currency_transaction_logs (airport_id, amount_changed, currency_type, reason)
|
||||
VALUES (?, ?, ?, ?);
|
||||
|
||||
-- name: LogCompletedFlightRoute :exec
|
||||
INSERT INTO completed_flight_routes_log (origin_airport_id, destination_airport_id, start_time, end_time)
|
||||
VALUES (?, ?, ?, NOW());
|
||||
218
backend/db/config/queries.txt
Normal file
218
backend/db/config/queries.txt
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
-- ============================================================================
|
||||
-- 1. AUTHENTICATION & USERS
|
||||
-- ============================================================================
|
||||
|
||||
-- name: CreateUser :execresult
|
||||
INSERT INTO users (username, password_hash)
|
||||
VALUES (?, ?);
|
||||
|
||||
-- name: UpdateUserPasswordByUserName :exec
|
||||
UPDATE users
|
||||
SET password_hash = ?
|
||||
WHERE username = ?;
|
||||
|
||||
-- name: GetUserByUsername :one
|
||||
SELECT id, username, password_hash, created_at
|
||||
FROM users
|
||||
WHERE username = ? LIMIT 1;
|
||||
|
||||
-- name: GetUserIdByUsername :one
|
||||
SELECT id
|
||||
FROM users
|
||||
WHERE username = ? LIMIT 1;
|
||||
|
||||
-- ============================================================================
|
||||
-- 2. AIRPORT CORE MANAGEMENT
|
||||
-- ============================================================================
|
||||
|
||||
-- airport row management
|
||||
|
||||
-- name: CreateAirportByUserName :execresult
|
||||
INSERT INTO airports (name, user_id, origin_country_id)
|
||||
VALUES (?, (SELECT id FROM users WHERE username = ?), ?);
|
||||
|
||||
|
||||
-- name: GetAirportByUserId :one
|
||||
SELECT id, name, user_id, origin_country_id, cash, current_passengers, max_passenger_capacity, current_fuel, max_fuel_capacity, is_available, player_level, experience_points, passenger_generation_rate, fuel_generation_rate, resources_last_updated_at
|
||||
FROM airports
|
||||
WHERE user_id = ? LIMIT 1;
|
||||
-- name: GetAirportByUserName :one
|
||||
SELECT id, name, user_id, origin_country_id, cash, current_passengers, max_passenger_capacity, current_fuel, max_fuel_capacity, is_available, player_level, experience_points ,passenger_generation_rate, fuel_generation_rate, resources_last_updated_at
|
||||
FROM airports
|
||||
WHERE user_id = (SELECT id FROM users WHERE username = ?) LIMIT 1;
|
||||
|
||||
|
||||
-- airport resource management
|
||||
|
||||
|
||||
-- name: UpdateAirportCurrencies :exec
|
||||
UPDATE airports
|
||||
SET cash = ?,
|
||||
current_passengers = ?,
|
||||
current_fuel = ?
|
||||
WHERE id = ?;
|
||||
|
||||
-- name: UpdateAirportCash :exec
|
||||
UPDATE airports
|
||||
SET cash = ?
|
||||
WHERE id = ?;
|
||||
-- name: UpdateAirportpassengers :exec
|
||||
UPDATE airports
|
||||
SET current_passengers = ?
|
||||
WHERE id = ?;
|
||||
-- name: UpdateAirportFuel :exec
|
||||
UPDATE airports
|
||||
SET current_fuel = ?
|
||||
WHERE id = ?;
|
||||
-- name: UpdateAirportmaxPassengers :exec
|
||||
UPDATE airports
|
||||
SET max_passenger_capacity = ?
|
||||
WHERE id = ?;
|
||||
-- name: UpdateAirportMaxFuel :exec
|
||||
UPDATE airports
|
||||
SET max_fuel_capacity = ?
|
||||
WHERE id = ?;
|
||||
|
||||
-- name: UpdateAirportFuelRate :exec
|
||||
UPDATE airports
|
||||
SET fuel_generation_rate = ?
|
||||
WHERE id = ?;
|
||||
-- name: UpdateAirportPassengerRate :exec
|
||||
UPDATE airports
|
||||
SET passenger_generation_rate = ?
|
||||
WHERE id = ?;
|
||||
|
||||
-- name: UpdateAirportResourcesUpdated :exec
|
||||
update airports
|
||||
set resources_last_updated_at = ?
|
||||
where id = ?;
|
||||
|
||||
|
||||
-- name: updatePlayerExperience :exec
|
||||
UPDATE airports
|
||||
SET experience_points = ?,
|
||||
WHERE id = ?;
|
||||
|
||||
-- name: updatePlayerLevel :exec
|
||||
UPDATE airports
|
||||
SET player_level = ?
|
||||
WHERE id = ?;
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- 3. WAREHOUSE & INVENTORY SYSTEM
|
||||
-- ============================================================================
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- 4. BUILDINGS & PRODUCTION ENGINE
|
||||
-- ============================================================================
|
||||
|
||||
-- name: addStorageBuilding:execresult
|
||||
INSERT INTO buildings (name,min_level,construction_cost_cash,capacity,storage_type,storage_subtype)
|
||||
VALUES (?, ?, ?, ?, ?, ?);
|
||||
|
||||
-- name: addOperationalBuilding:execresult
|
||||
INSERT INTO buildings (name,min_level,construction_cost_cash,operation_type,operation_size)
|
||||
VALUES (?, ?, ?, ?, ?);
|
||||
|
||||
-- name: addPassiveProductionBuilding:execresult
|
||||
INSERT INTO buildings (name,min_level,construction_cost_cash,currency_type,income_rate)
|
||||
VALUES (?, ?, ?, ?, ?);
|
||||
|
||||
-- name: addProductionBuilding:execresult
|
||||
INSERT INTO buildings (name,min_level,construction_cost_cash,recipe_id,storage_1,storage_2,storage_3)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?);
|
||||
|
||||
|
||||
-- name: AddBuildingToAirport :execresult
|
||||
INSERT INTO airport_buildings (airport_id, building_id, level)
|
||||
VALUES ((select id from airports where user_id = (select id from users where username = ?)), ?, 1);
|
||||
|
||||
-- name: GetAirportBuildings :many
|
||||
SELECT ab.id AS airport_building_id, ab.level, b.*
|
||||
FROM airport_buildings ab
|
||||
JOIN buildings b ON ab.building_id = b.id
|
||||
WHERE ab.airport_id = (select id from airports where user_id = (select id from users where username = ?));
|
||||
|
||||
-- ============================================================================
|
||||
-- 5. FLEET & DYNAMIC INFRASTRUCTURE OPERATIONS
|
||||
-- ============================================================================
|
||||
|
||||
-- name: BuyPlaneForPlayer :execresult
|
||||
INSERT INTO player_planes (airport_id, plane_id, assigned_hangar_id, current_building_id, status)
|
||||
VALUES (?, ?, ?, ?, 'idle');
|
||||
|
||||
-- name: GetPlayerFleet :many
|
||||
SELECT pp.id AS player_plane_id, pp.status, pp.updated_at, pp.flight_start_time, pp.current_building_id, pp.assigned_hangar_id, pp.assigned_cargo_id,
|
||||
p.*
|
||||
FROM player_planes pp
|
||||
JOIN planes p ON pp.plane_id = p.id
|
||||
WHERE pp.airport_id = ?;
|
||||
|
||||
-- name: FindAvailableInfrastructure :one
|
||||
SELECT ab.id
|
||||
FROM airport_buildings ab
|
||||
JOIN buildings b ON ab.building_id = b.id
|
||||
LEFT JOIN player_planes pp ON ab.id = pp.current_building_id
|
||||
WHERE ab.airport_id = ?
|
||||
AND b.operation_type = ? -- 'runway' or 'service' (service bay)
|
||||
AND b.operation_size = ? -- matches plane's aircraft_size requirement
|
||||
AND pp.id IS NULL -- Ensures no other plane is physically here right now
|
||||
LIMIT 1;
|
||||
|
||||
-- name: UpdatePlaneState :exec
|
||||
UPDATE player_planes
|
||||
SET current_building_id = ?,
|
||||
status = ?,
|
||||
assigned_cargo_id = ?,
|
||||
flight_start_time = ?
|
||||
WHERE id = ?;
|
||||
|
||||
-- name: CheckHangarOccupancy :one
|
||||
SELECT COUNT(*) AS total_parked
|
||||
FROM player_planes
|
||||
WHERE current_building_id = ?;
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- 6. TIME-BASED FLIGHT ENGINE
|
||||
-- ============================================================================
|
||||
|
||||
-- name: GetPlanesReadyToLand :many
|
||||
SELECT pp.id AS player_plane_id, pp.airport_id, pp.assigned_hangar_id,
|
||||
p.completed_flight_experience, p.completed_flight_cash_reward, p.completed_flight_cargo_reward, p.operation_type, pp.assigned_cargo_id
|
||||
FROM player_planes pp
|
||||
JOIN planes p ON pp.plane_id = p.id
|
||||
WHERE pp.status = 'flying'
|
||||
-- Uses your logic: flight_start_time + plane travel duration <= current clock
|
||||
AND ADDTIME(pp.flight_start_time, p.travel_time) <= NOW();
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- 7. PROGRESSION CONTRACTS & ECONOMY LOGS
|
||||
-- ============================================================================
|
||||
|
||||
-- name: GetActiveContracts :many
|
||||
SELECT id, title, required_product_id, required_amount, current_amount_delivered, is_completed, expires_at
|
||||
FROM airport_contracts
|
||||
WHERE airport_id = ? AND is_completed = FALSE AND expires_at > NOW();
|
||||
|
||||
-- name: UpdateContractDelivery :exec
|
||||
UPDATE airport_contracts
|
||||
SET current_amount_delivered = current_amount_delivered + ?
|
||||
WHERE id = ? AND is_completed = FALSE;
|
||||
|
||||
-- name: CompleteContract :exec
|
||||
UPDATE airport_contracts
|
||||
SET is_completed = TRUE
|
||||
WHERE id = ?;
|
||||
|
||||
-- name: LogCurrencyTransaction :exec
|
||||
INSERT INTO currency_transaction_logs (airport_id, amount_changed, currency_type, reason)
|
||||
VALUES (?, ?, ?, ?);
|
||||
|
||||
-- name: LogCompletedFlightRoute :exec
|
||||
INSERT INTO completed_flight_routes_log (origin_airport_id, destination_country_id, start_time, end_time)
|
||||
VALUES (?, ?, ?, NOW());
|
||||
260
backend/db/config/schema.sql
Normal file
260
backend/db/config/schema.sql
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
-- stages
|
||||
-- stage 0 : launch planes to country's base airport (accepts everyhing sendds nothing back)
|
||||
-- stage 1 : add NPCs and cargo planes and shop buildings.
|
||||
-- stage 2 : add player progression, building upgrades, effects and more complex recipes.
|
||||
-- stage 3 : TBD
|
||||
|
||||
|
||||
|
||||
-- good
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
||||
username varchar(255) NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP DEFAULT NULL
|
||||
);
|
||||
|
||||
-- product table act as a product identifier ie egg, milk, flour etc
|
||||
create table if not exists products (
|
||||
id smallint unsigned primary key auto_increment,
|
||||
name varchar(255) not null
|
||||
);
|
||||
|
||||
-- for stage 2 will not be used
|
||||
-- increasing speed (crafting,currency type generation etc)),
|
||||
CREATE table if not exists effects (
|
||||
id smallint unsigned primary key auto_increment,
|
||||
name varchar(255) not null
|
||||
);
|
||||
|
||||
-- used airport grouping and for contracts, each country has 3 produce they can export there will not be cap
|
||||
create table if not exists countries (
|
||||
id smallint unsigned primary key auto_increment,
|
||||
name varchar(255) not null,
|
||||
continent ENUM('void','Africa', 'Antarctica', 'Asia', 'Europe', 'North America', 'Oceania', 'South America') NOT NULL, -- universal enum
|
||||
produce_1_id smallint unsigned not null,
|
||||
produce_2_id smallint unsigned not null,
|
||||
produce_3_id smallint unsigned not null,
|
||||
foreign key (produce_1_id) references products(id),
|
||||
foreign key (produce_2_id) references products(id),
|
||||
foreign key (produce_3_id) references products(id)
|
||||
);
|
||||
|
||||
-- main table
|
||||
-- primary access table act as a player identifier, each player can only have one airport, but they can have multiple planes and buildings
|
||||
create table if not exists airports (
|
||||
id integer unsigned primary key auto_increment,
|
||||
name varchar(255) not null,
|
||||
user_id integer unique not null,
|
||||
origin_country_id smallint unsigned not null,
|
||||
-- resources
|
||||
cash bigint unsigned not null default 100,
|
||||
current_passengers INTEGER UNSIGNED NOT NULL DEFAULT 100,
|
||||
max_passenger_capacity INTEGER UNSIGNED NOT NULL DEFAULT 100,
|
||||
current_fuel INTEGER UNSIGNED NOT NULL DEFAULT 500,
|
||||
max_fuel_capacity INTEGER UNSIGNED NOT NULL DEFAULT 1000,
|
||||
-- resource generation
|
||||
fuel_generation_rate INT UNSIGNED NOT NULL DEFAULT 20,
|
||||
passenger_generation_rate INT UNSIGNED NOT NULL DEFAULT 5,
|
||||
resources_last_updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
-- progression
|
||||
is_available boolean not null default true, -- if true can be sent planes from anyone
|
||||
player_level integer unsigned not null default 1,
|
||||
experience_points bigint unsigned not null default 0,
|
||||
is_deleted BOOLEAN DEFAULT FALSE,
|
||||
foreign key (origin_country_id) references countries(id),
|
||||
foreign key (user_id) references users(id),
|
||||
constraint chk_fuel_capacity CHECK (current_fuel <= max_fuel_capacity),
|
||||
constraint chk_positive_fuel CHECK (current_fuel >= 0),
|
||||
CONSTRAINT chk_passenger_capacity CHECK (current_passengers <= max_passenger_capacity),
|
||||
CONSTRAINT chk_positive_passenger CHECK (current_passengers >= 0)
|
||||
);
|
||||
|
||||
|
||||
-- for stage 1, used as template for recipes.
|
||||
create table if not exists recipes (
|
||||
id integer unsigned primary key auto_increment,
|
||||
name varchar(255) not null,
|
||||
ingredient_1_id smallint unsigned ,
|
||||
ingredient_1_amount integer unsigned ,
|
||||
ingredient_2_id smallint unsigned,
|
||||
ingredient_2_amount integer unsigned ,
|
||||
ingredient_3_id smallint unsigned ,
|
||||
ingredient_3_amount integer unsigned ,
|
||||
product_id smallint unsigned not null,
|
||||
yield_amount integer unsigned not null,
|
||||
foreign key (ingredient_1_id) references products(id),
|
||||
foreign key (ingredient_2_id) references products(id),
|
||||
foreign key (ingredient_3_id) references products(id),
|
||||
foreign key (product_id) references products(id)
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- BUILDNGS
|
||||
|
||||
-- hangars fuel tanks etc
|
||||
CREATE table if not exists buildings (
|
||||
-- shared
|
||||
id integer unsigned primary key auto_increment,
|
||||
name varchar(255) not null,
|
||||
min_level integer unsigned not null DEfAULT 1,
|
||||
construction_cost_cash integer unsigned not null default 100,
|
||||
upgrade_tier TINYINT UNSIGNED DEFAULT 0,
|
||||
upgrade_cost_cash_to_uptier integer unsigned default null,
|
||||
building_type ENUM('none','storage', 'support', 'operation', 'passive', 'shop', 'production') not null,
|
||||
-- production specific
|
||||
-- storage
|
||||
capacity integer unsigned ,
|
||||
storage_type ENUM( 'fuel' , 'plane', 'product'),
|
||||
storage_subtype ENUM('none', 'airplane', 'helicopter', 'seaplane','spaceship'),
|
||||
-- support
|
||||
effect_id smallint unsigned,
|
||||
effect_value integer,
|
||||
-- operation
|
||||
operation_size enum('small','medium' ,'large', 'huge'),
|
||||
operation_type enum('runway', 'service', 'production'),
|
||||
|
||||
-- passive income
|
||||
passive_type enum('passengers', 'fuel'),
|
||||
passive_rate integer ,
|
||||
|
||||
foreign key (effect_id) references effects(id)
|
||||
|
||||
);
|
||||
|
||||
-- stage 1
|
||||
create table if not exists building_products (
|
||||
id integer unsigned primary key auto_increment,
|
||||
building_id integer unsigned not null,
|
||||
|
||||
recipe_id integer unsigned,
|
||||
storage_product integer unsigned ,
|
||||
storage_1 integer unsigned ,
|
||||
storage_2 integer unsigned ,
|
||||
storage_3 integer unsigned ,
|
||||
foreign key (building_id) references buildings(id),
|
||||
foreign key (recipe_id) references recipes(id)
|
||||
);
|
||||
|
||||
|
||||
-- many to many relation between airport and buildings
|
||||
create table if not exists airport_buildings (
|
||||
id integer unsigned primary key auto_increment,
|
||||
airport_id integer unsigned not null,
|
||||
building_id integer unsigned not null,
|
||||
-- shop specific
|
||||
recipe_id integer unsigned ,
|
||||
storage_product integer unsigned ,
|
||||
storage_1 integer unsigned ,
|
||||
storage_2 integer unsigned ,
|
||||
storage_3 integer unsigned ,
|
||||
level integer unsigned not null default 1,
|
||||
foreign key (airport_id) references airports(id) ,
|
||||
foreign key (building_id) references buildings(id)
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
-- planes
|
||||
-- template for planes
|
||||
create table if not exists planes (
|
||||
id integer unsigned primary key auto_increment,
|
||||
name varchar(255) not null,
|
||||
min_level integer unsigned not null default 1,
|
||||
operation_cost_fuel integer unsigned not null,
|
||||
operation_cost_cash integer unsigned not null DEFAULT 10,
|
||||
max_passengers integer unsigned,
|
||||
travel_time_inSeconds integer unsigned not null,
|
||||
operation_type enum('passenger', 'cargo') not null,
|
||||
aircraft_size enum('small', 'medium', 'large', 'huge') not null,
|
||||
aircraft_type enum('plane', 'helicopter', 'seaplane','spaceship') not null,
|
||||
buying_cost_cash integer unsigned not null DEFAULT 100,
|
||||
completed_flight_experience integer unsigned not null DEFAULT 20,
|
||||
completed_flight_cash_reward integer unsigned DEFAULT 50,
|
||||
completed_flight_cargo_reward integer unsigned DEFAULT null
|
||||
|
||||
);
|
||||
|
||||
-- actual planes owned by players, tracks location, status, assigned hangar etc
|
||||
CREATE TABLE IF NOT EXISTS player_planes (
|
||||
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||
airport_id INT UNSIGNED NOT NULL,
|
||||
plane_id INTEGER UNSIGNED NOT NULL,
|
||||
|
||||
-- 1. assignment (Must always point to a hangar they own they move between suitable hangars)
|
||||
assigned_hangar_id INT UNSIGNED NOT NULL,
|
||||
|
||||
-- 2. Real-time physical location (stored, Service Bay, Runway, or NULL if flying)
|
||||
current_building_id INT UNSIGNED DEFAULT NULL,
|
||||
-- assigned_cargo_id INTEGER UNSIGNED DEFAULT NULL, -- stage 1 For cargo planes, track what they're carrying
|
||||
|
||||
status ENUM('idle', 'maintenance', 'boarding', 'taxiing', 'flying') NOT NULL DEFAULT 'idle',
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
flight_start_time TIMESTAMP NULL DEFAULT NULL,
|
||||
|
||||
|
||||
FOREIGN KEY (airport_id) REFERENCES airports(id),
|
||||
FOREIGN KEY (plane_id) REFERENCES planes(id),
|
||||
FOREIGN KEY (assigned_hangar_id) REFERENCES airport_buildings(id),
|
||||
FOREIGN KEY (current_building_id) REFERENCES airport_buildings(id)
|
||||
|
||||
);
|
||||
|
||||
-- stage 1
|
||||
CREATE TABLE IF NOT EXISTS airport_inventory (
|
||||
airport_id INTEGER unsigned NOT NULL,
|
||||
product_id SMALLINT UNSIGNED NOT NULL,
|
||||
quantity INTEGER UNSIGNED NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (airport_id, product_id),
|
||||
FOREIGN KEY (airport_id) REFERENCES airports(id),
|
||||
FOREIGN KEY (product_id) REFERENCES products(id)
|
||||
);
|
||||
|
||||
|
||||
-- stage ? tbd
|
||||
CREATE TABLE IF NOT EXISTS airport_contracts (
|
||||
id INTEGER UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||
airport_id INTEGER unsigned NOT NULL,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
|
||||
-- What they need to deliver
|
||||
required_product_id SMALLINT UNSIGNED NOT NULL,
|
||||
required_amount INTEGER UNSIGNED NOT NULL,
|
||||
current_amount_delivered INTEGER UNSIGNED NOT NULL DEFAULT 0,
|
||||
|
||||
|
||||
-- Status
|
||||
is_completed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
expires_at TIMESTAMP NOT NULL,
|
||||
|
||||
FOREIGN KEY (airport_id) REFERENCES airports(id),
|
||||
FOREIGN KEY (required_product_id) REFERENCES products(id)
|
||||
|
||||
);
|
||||
|
||||
|
||||
-- logs
|
||||
|
||||
create table if not exists completed_flight_routes_log (
|
||||
id integer unsigned primary key auto_increment,
|
||||
origin_airport_id integer unsigned not null,
|
||||
destination_airport_id integer unsigned not null,
|
||||
start_time timestamp not null default current_timestamp,
|
||||
end_time timestamp default null,
|
||||
foreign key (origin_airport_id) references airports(id),
|
||||
foreign key (destination_airport_id) references airports(id)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS currency_transaction_logs (
|
||||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||
airport_id INT UNSIGNED NOT NULL,
|
||||
amount_changed INT NOT NULL, -- Negative for buying, positive for earning
|
||||
currency_type ENUM('cash', 'fuel', 'passengers') NOT NULL,
|
||||
reason VARCHAR(255) NOT NULL, -- e.g., 'bought_plane_id_12', 'completed_flight_34'
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (airport_id) REFERENCES airports(id)
|
||||
);
|
||||
254
backend/db/config/schema.txt
Normal file
254
backend/db/config/schema.txt
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
-- stages
|
||||
-- stage 0 : launch planes to country's base airport (accepts everyhing sendds nothing back)
|
||||
-- stage 1 : add NPCs and cargo planes and shop buildings.
|
||||
-- stage 2 : add player progression, building upgrades, effects and more complex recipes.
|
||||
-- stage 3 : TBD
|
||||
|
||||
|
||||
|
||||
-- good
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
||||
username varchar(255) NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- product table act as a product identifier ie egg, milk, flour etc
|
||||
create table if not exists products (
|
||||
id smallint unsigned primary key auto_increment,
|
||||
name varchar(255) not null
|
||||
);
|
||||
|
||||
-- for stage 2 will not be used
|
||||
-- increasing speed (crafting,currency type generation etc)),
|
||||
CREATE table if not exists effects (
|
||||
id smallint unsigned primary key auto_increment,
|
||||
name varchar(255) not null
|
||||
);
|
||||
|
||||
-- used airport grouping and for contracts, each country has 3 produce they can export there will not be cap
|
||||
create table if not exists countries (
|
||||
id smallint unsigned primary key auto_increment,
|
||||
name varchar(255) not null,
|
||||
continent ENUM('void','Africa', 'Antarctica', 'Asia', 'Europe', 'North America', 'Oceania', 'South America') NOT NULL, -- universal enum
|
||||
produce_1_id smallint unsigned not null,
|
||||
produce_2_id smallint unsigned not null,
|
||||
produce_3_id smallint unsigned not null,
|
||||
foreign key (produce_1_id) references products(id),
|
||||
foreign key (produce_2_id) references products(id),
|
||||
foreign key (produce_3_id) references products(id)
|
||||
);
|
||||
|
||||
-- main table
|
||||
-- primary access table act as a player identifier, each player can only have one airport, but they can have multiple planes and buildings
|
||||
create table if not exists airports (
|
||||
id integer unsigned primary key auto_increment,
|
||||
name varchar(255) not null,
|
||||
user_id integer unique not null,
|
||||
origin_country_id smallint unsigned not null,
|
||||
-- resources
|
||||
cash bigint unsigned not null default 100,
|
||||
current_passengers INTEGER UNSIGNED NOT NULL DEFAULT 100,
|
||||
max_passenger_capacity INTEGER UNSIGNED NOT NULL DEFAULT 100,
|
||||
current_fuel INTEGER UNSIGNED NOT NULL DEFAULT 500,
|
||||
max_fuel_capacity INTEGER UNSIGNED NOT NULL DEFAULT 1000,
|
||||
-- resource generation
|
||||
fuel_generation_rate INT UNSIGNED NOT NULL DEFAULT 20,
|
||||
passenger_generation_rate INT UNSIGNED NOT NULL DEFAULT 5,
|
||||
resources_last_updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
-- progression
|
||||
is_available boolean not null default true, -- if true can be sent planes from anyone
|
||||
player_level integer unsigned not null default 1,
|
||||
experience_points bigint unsigned not null default 0,
|
||||
foreign key (origin_country_id) references countries(id),
|
||||
foreign key (user_id) references users(id),
|
||||
constraint chk_fuel_capacity CHECK (current_fuel <= max_fuel_capacity),
|
||||
constraint chk_positive_fuel CHECK (current_fuel >= 0)
|
||||
);
|
||||
|
||||
|
||||
-- for stage 1, used as template for recipes.
|
||||
create table if not exists recipes (
|
||||
id integer unsigned primary key auto_increment,
|
||||
name varchar(255) not null,
|
||||
ingredient_1_id smallint unsigned ,
|
||||
ingredient_1_amount integer unsigned ,
|
||||
ingredient_2_id smallint unsigned,
|
||||
ingredient_2_amount integer unsigned ,
|
||||
ingredient_3_id smallint unsigned ,
|
||||
ingredient_3_amount integer unsigned ,
|
||||
product_id smallint unsigned not null,
|
||||
yield_amount integer unsigned not null,
|
||||
foreign key (ingredient_1_id) references products(id),
|
||||
foreign key (ingredient_2_id) references products(id),
|
||||
foreign key (ingredient_3_id) references products(id),
|
||||
foreign key (product_id) references products(id)
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- BUILDNGS
|
||||
|
||||
-- hangars fuel tanks etc
|
||||
CREATE table if not exists buildings (
|
||||
-- shared
|
||||
id integer unsigned primary key auto_increment,
|
||||
name varchar(255) not null,
|
||||
min_level integer unsigned not null DEfAULT 1,
|
||||
construction_cost_cash integer unsigned not null default 100,
|
||||
upgrade_tier TINYINT UNSIGNED DEFAULT 0,
|
||||
upgrade_cost_cash_to_uptier integer unsigned default null,
|
||||
-- storage
|
||||
capacity integer unsigned ,
|
||||
storage_type ENUM( 'fuel' , 'plane', 'product'),
|
||||
storage_subtype ENUM('none', 'airplane', 'helicopter', 'seaplane','spaceship'),
|
||||
-- support
|
||||
effect_id smallint unsigned,
|
||||
effect_value integer,
|
||||
-- operation
|
||||
operation_size enum('small','medium' ,'large', 'huge'),
|
||||
operation_type enum('runway', 'service', 'production'),
|
||||
|
||||
-- passive income
|
||||
passive_type enum('passengers', 'fuel'),
|
||||
passive_rate integer ,
|
||||
|
||||
foreign key (effect_id) references effects(id)
|
||||
|
||||
);
|
||||
|
||||
--stage 1
|
||||
create table if not exists building_products (
|
||||
id integer unsigned primary key auto_increment,
|
||||
building_id integer unsigned not null,
|
||||
|
||||
recipe_id integer unsigned,
|
||||
storage_product integer unsigned ,
|
||||
storage_1 integer unsigned ,
|
||||
storage_2 integer unsigned ,
|
||||
storage_3 integer unsigned ,
|
||||
foreign key (building_id) references buildings(id),
|
||||
foreign key (recipe_id) references recipes(id)
|
||||
);
|
||||
|
||||
|
||||
-- many to many relation between airport and buildings
|
||||
create table if not exists airport_buildings (
|
||||
id integer unsigned primary key auto_increment,
|
||||
airport_id integer unsigned not null,
|
||||
building_id integer unsigned not null,
|
||||
-- shop specific
|
||||
recipe_id integer unsigned ,
|
||||
storage_product integer unsigned ,
|
||||
storage_1 integer unsigned ,
|
||||
storage_2 integer unsigned ,
|
||||
storage_3 integer unsigned ,
|
||||
level integer unsigned not null default 1,
|
||||
foreign key (airport_id) references airports(id) ,
|
||||
foreign key (building_id) references buildings(id)
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
-- planes
|
||||
-- template for planes
|
||||
create table if not exists planes (
|
||||
id integer unsigned primary key auto_increment,
|
||||
name varchar(255) not null,
|
||||
min_level integer unsigned not null default 1,
|
||||
operation_cost_fuel integer unsigned not null,
|
||||
operation_cost_cash integer unsigned not null DEFAULT 10,
|
||||
max_passengers integer unsigned,
|
||||
travel_time_inSeconds integer unsigned not null,
|
||||
operation_type enum('passenger', 'cargo') not null,
|
||||
aircraft_size enum('small', 'medium', 'large', 'huge') not null,
|
||||
aircraft_type enum('plane', 'helicopter', 'seaplane','spaceship') not null,
|
||||
buying_cost_cash integer unsigned not null DEFAULT 100,
|
||||
completed_flight_experience integer unsigned not null DEFAULT 20,
|
||||
completed_flight_cash_reward integer unsigned DEFAULT 50,
|
||||
completed_flight_cargo_reward integer unsigned DEFAULT 10
|
||||
|
||||
);
|
||||
|
||||
-- actual planes owned by players, tracks location, status, assigned hangar etc
|
||||
CREATE TABLE IF NOT EXISTS player_planes (
|
||||
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||
airport_id INT UNSIGNED NOT NULL,
|
||||
plane_id INTEGER UNSIGNED NOT NULL,
|
||||
|
||||
-- 1. assignment (Must always point to a hangar they own they move between suitable hangars)
|
||||
assigned_hangar_id INT UNSIGNED NOT NULL,
|
||||
|
||||
-- 2. Real-time physical location (stored, Service Bay, Runway, or NULL if flying)
|
||||
current_building_id INT UNSIGNED DEFAULT NULL,
|
||||
-- assigned_cargo_id INTEGER UNSIGNED DEFAULT NULL, -- stage 1 For cargo planes, track what they're carrying
|
||||
|
||||
status ENUM('idle', 'maintenance', 'boarding', 'taxiing', 'flying') NOT NULL DEFAULT 'idle',
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
flight_start_time TIMESTAMP NULL DEFAULT NULL,
|
||||
|
||||
|
||||
FOREIGN KEY (airport_id) REFERENCES airports(id),
|
||||
FOREIGN KEY (plane_id) REFERENCES planes(id),
|
||||
FOREIGN KEY (assigned_hangar_id) REFERENCES airport_buildings(id),
|
||||
FOREIGN KEY (current_building_id) REFERENCES airport_buildings(id)
|
||||
--foreign key (assigned_cargo_id) references products(id) -- stage 1
|
||||
);
|
||||
|
||||
-- stage 1
|
||||
CREATE TABLE IF NOT EXISTS airport_inventory (
|
||||
airport_id INTEGER unsigned NOT NULL,
|
||||
product_id SMALLINT UNSIGNED NOT NULL,
|
||||
quantity INTEGER UNSIGNED NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (airport_id, product_id),
|
||||
FOREIGN KEY (airport_id) REFERENCES airports(id),
|
||||
FOREIGN KEY (product_id) REFERENCES products(id)
|
||||
);
|
||||
|
||||
|
||||
-- stage ? tbd
|
||||
CREATE TABLE IF NOT EXISTS airport_contracts (
|
||||
id INTEGER UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||
airport_id INTEGER unsigned NOT NULL,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
|
||||
-- What they need to deliver
|
||||
required_product_id SMALLINT UNSIGNED NOT NULL,
|
||||
required_amount INTEGER UNSIGNED NOT NULL,
|
||||
current_amount_delivered INTEGER UNSIGNED NOT NULL DEFAULT 0,
|
||||
|
||||
|
||||
-- Status
|
||||
is_completed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
expires_at TIMESTAMP NOT NULL,
|
||||
|
||||
FOREIGN KEY (airport_id) REFERENCES airports(id),
|
||||
FOREIGN KEY (required_product_id) REFERENCES products(id)
|
||||
|
||||
);
|
||||
|
||||
|
||||
-- logs
|
||||
|
||||
create table if not exists completed_flight_routes_log (
|
||||
id integer unsigned primary key auto_increment,
|
||||
origin_airport_id integer unsigned not null,
|
||||
destination_airport_id integer unsigned not null,
|
||||
start_time timestamp not null default current_timestamp,
|
||||
end_time timestamp default null,
|
||||
foreign key (origin_airport_id) references airports(id),
|
||||
foreign key (destination_airport_id) references airports(id)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS currency_transaction_logs (
|
||||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||
airport_id INT UNSIGNED NOT NULL,
|
||||
amount_changed INT NOT NULL, -- Negative for buying, positive for earning
|
||||
currency_type ENUM('cash', 'fuel', 'passengers') NOT NULL,
|
||||
reason VARCHAR(255) NOT NULL, -- e.g., 'bought_plane_id_12', 'completed_flight_34'
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (airport_id) REFERENCES airports(id)
|
||||
);
|
||||
9
backend/db/config/sqlc.yml
Normal file
9
backend/db/config/sqlc.yml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
version: "2"
|
||||
sql:
|
||||
- engine: "mysql"
|
||||
queries: "queries.sql"
|
||||
schema: "schema.sql"
|
||||
gen:
|
||||
go:
|
||||
package: "db"
|
||||
out: "../"
|
||||
31
backend/db/db.go
Normal file
31
backend/db/db.go
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.31.1
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
type DBTX interface {
|
||||
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
|
||||
PrepareContext(context.Context, string) (*sql.Stmt, error)
|
||||
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
|
||||
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
|
||||
}
|
||||
|
||||
func New(db DBTX) *Queries {
|
||||
return &Queries{db: db}
|
||||
}
|
||||
|
||||
type Queries struct {
|
||||
db DBTX
|
||||
}
|
||||
|
||||
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
|
||||
return &Queries{
|
||||
db: tx,
|
||||
}
|
||||
}
|
||||
704
backend/db/models.go
Normal file
704
backend/db/models.go
Normal file
|
|
@ -0,0 +1,704 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.31.1
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type BuildingsBuildingType string
|
||||
|
||||
const (
|
||||
BuildingsBuildingTypeNone BuildingsBuildingType = "none"
|
||||
BuildingsBuildingTypeStorage BuildingsBuildingType = "storage"
|
||||
BuildingsBuildingTypeSupport BuildingsBuildingType = "support"
|
||||
BuildingsBuildingTypeOperation BuildingsBuildingType = "operation"
|
||||
BuildingsBuildingTypePassive BuildingsBuildingType = "passive"
|
||||
BuildingsBuildingTypeShop BuildingsBuildingType = "shop"
|
||||
BuildingsBuildingTypeProduction BuildingsBuildingType = "production"
|
||||
)
|
||||
|
||||
func (e *BuildingsBuildingType) Scan(src interface{}) error {
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*e = BuildingsBuildingType(s)
|
||||
case string:
|
||||
*e = BuildingsBuildingType(s)
|
||||
default:
|
||||
return fmt.Errorf("unsupported scan type for BuildingsBuildingType: %T", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NullBuildingsBuildingType struct {
|
||||
BuildingsBuildingType BuildingsBuildingType
|
||||
Valid bool // Valid is true if BuildingsBuildingType is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (ns *NullBuildingsBuildingType) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
ns.BuildingsBuildingType, ns.Valid = "", false
|
||||
return nil
|
||||
}
|
||||
ns.Valid = true
|
||||
return ns.BuildingsBuildingType.Scan(value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (ns NullBuildingsBuildingType) Value() (driver.Value, error) {
|
||||
if !ns.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return string(ns.BuildingsBuildingType), nil
|
||||
}
|
||||
|
||||
type BuildingsOperationSize string
|
||||
|
||||
const (
|
||||
BuildingsOperationSizeSmall BuildingsOperationSize = "small"
|
||||
BuildingsOperationSizeMedium BuildingsOperationSize = "medium"
|
||||
BuildingsOperationSizeLarge BuildingsOperationSize = "large"
|
||||
BuildingsOperationSizeHuge BuildingsOperationSize = "huge"
|
||||
)
|
||||
|
||||
func (e *BuildingsOperationSize) Scan(src interface{}) error {
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*e = BuildingsOperationSize(s)
|
||||
case string:
|
||||
*e = BuildingsOperationSize(s)
|
||||
default:
|
||||
return fmt.Errorf("unsupported scan type for BuildingsOperationSize: %T", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NullBuildingsOperationSize struct {
|
||||
BuildingsOperationSize BuildingsOperationSize
|
||||
Valid bool // Valid is true if BuildingsOperationSize is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (ns *NullBuildingsOperationSize) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
ns.BuildingsOperationSize, ns.Valid = "", false
|
||||
return nil
|
||||
}
|
||||
ns.Valid = true
|
||||
return ns.BuildingsOperationSize.Scan(value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (ns NullBuildingsOperationSize) Value() (driver.Value, error) {
|
||||
if !ns.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return string(ns.BuildingsOperationSize), nil
|
||||
}
|
||||
|
||||
type BuildingsOperationType string
|
||||
|
||||
const (
|
||||
BuildingsOperationTypeRunway BuildingsOperationType = "runway"
|
||||
BuildingsOperationTypeService BuildingsOperationType = "service"
|
||||
BuildingsOperationTypeProduction BuildingsOperationType = "production"
|
||||
)
|
||||
|
||||
func (e *BuildingsOperationType) Scan(src interface{}) error {
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*e = BuildingsOperationType(s)
|
||||
case string:
|
||||
*e = BuildingsOperationType(s)
|
||||
default:
|
||||
return fmt.Errorf("unsupported scan type for BuildingsOperationType: %T", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NullBuildingsOperationType struct {
|
||||
BuildingsOperationType BuildingsOperationType
|
||||
Valid bool // Valid is true if BuildingsOperationType is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (ns *NullBuildingsOperationType) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
ns.BuildingsOperationType, ns.Valid = "", false
|
||||
return nil
|
||||
}
|
||||
ns.Valid = true
|
||||
return ns.BuildingsOperationType.Scan(value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (ns NullBuildingsOperationType) Value() (driver.Value, error) {
|
||||
if !ns.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return string(ns.BuildingsOperationType), nil
|
||||
}
|
||||
|
||||
type BuildingsPassiveType string
|
||||
|
||||
const (
|
||||
BuildingsPassiveTypePassengers BuildingsPassiveType = "passengers"
|
||||
BuildingsPassiveTypeFuel BuildingsPassiveType = "fuel"
|
||||
)
|
||||
|
||||
func (e *BuildingsPassiveType) Scan(src interface{}) error {
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*e = BuildingsPassiveType(s)
|
||||
case string:
|
||||
*e = BuildingsPassiveType(s)
|
||||
default:
|
||||
return fmt.Errorf("unsupported scan type for BuildingsPassiveType: %T", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NullBuildingsPassiveType struct {
|
||||
BuildingsPassiveType BuildingsPassiveType
|
||||
Valid bool // Valid is true if BuildingsPassiveType is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (ns *NullBuildingsPassiveType) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
ns.BuildingsPassiveType, ns.Valid = "", false
|
||||
return nil
|
||||
}
|
||||
ns.Valid = true
|
||||
return ns.BuildingsPassiveType.Scan(value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (ns NullBuildingsPassiveType) Value() (driver.Value, error) {
|
||||
if !ns.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return string(ns.BuildingsPassiveType), nil
|
||||
}
|
||||
|
||||
type BuildingsStorageSubtype string
|
||||
|
||||
const (
|
||||
BuildingsStorageSubtypeNone BuildingsStorageSubtype = "none"
|
||||
BuildingsStorageSubtypeAirplane BuildingsStorageSubtype = "airplane"
|
||||
BuildingsStorageSubtypeHelicopter BuildingsStorageSubtype = "helicopter"
|
||||
BuildingsStorageSubtypeSeaplane BuildingsStorageSubtype = "seaplane"
|
||||
BuildingsStorageSubtypeSpaceship BuildingsStorageSubtype = "spaceship"
|
||||
)
|
||||
|
||||
func (e *BuildingsStorageSubtype) Scan(src interface{}) error {
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*e = BuildingsStorageSubtype(s)
|
||||
case string:
|
||||
*e = BuildingsStorageSubtype(s)
|
||||
default:
|
||||
return fmt.Errorf("unsupported scan type for BuildingsStorageSubtype: %T", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NullBuildingsStorageSubtype struct {
|
||||
BuildingsStorageSubtype BuildingsStorageSubtype
|
||||
Valid bool // Valid is true if BuildingsStorageSubtype is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (ns *NullBuildingsStorageSubtype) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
ns.BuildingsStorageSubtype, ns.Valid = "", false
|
||||
return nil
|
||||
}
|
||||
ns.Valid = true
|
||||
return ns.BuildingsStorageSubtype.Scan(value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (ns NullBuildingsStorageSubtype) Value() (driver.Value, error) {
|
||||
if !ns.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return string(ns.BuildingsStorageSubtype), nil
|
||||
}
|
||||
|
||||
type BuildingsStorageType string
|
||||
|
||||
const (
|
||||
BuildingsStorageTypeFuel BuildingsStorageType = "fuel"
|
||||
BuildingsStorageTypePlane BuildingsStorageType = "plane"
|
||||
BuildingsStorageTypeProduct BuildingsStorageType = "product"
|
||||
)
|
||||
|
||||
func (e *BuildingsStorageType) Scan(src interface{}) error {
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*e = BuildingsStorageType(s)
|
||||
case string:
|
||||
*e = BuildingsStorageType(s)
|
||||
default:
|
||||
return fmt.Errorf("unsupported scan type for BuildingsStorageType: %T", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NullBuildingsStorageType struct {
|
||||
BuildingsStorageType BuildingsStorageType
|
||||
Valid bool // Valid is true if BuildingsStorageType is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (ns *NullBuildingsStorageType) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
ns.BuildingsStorageType, ns.Valid = "", false
|
||||
return nil
|
||||
}
|
||||
ns.Valid = true
|
||||
return ns.BuildingsStorageType.Scan(value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (ns NullBuildingsStorageType) Value() (driver.Value, error) {
|
||||
if !ns.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return string(ns.BuildingsStorageType), nil
|
||||
}
|
||||
|
||||
type CountriesContinent string
|
||||
|
||||
const (
|
||||
CountriesContinentVoid CountriesContinent = "void"
|
||||
CountriesContinentAfrica CountriesContinent = "Africa"
|
||||
CountriesContinentAntarctica CountriesContinent = "Antarctica"
|
||||
CountriesContinentAsia CountriesContinent = "Asia"
|
||||
CountriesContinentEurope CountriesContinent = "Europe"
|
||||
CountriesContinentNorthAmerica CountriesContinent = "North America"
|
||||
CountriesContinentOceania CountriesContinent = "Oceania"
|
||||
CountriesContinentSouthAmerica CountriesContinent = "South America"
|
||||
)
|
||||
|
||||
func (e *CountriesContinent) Scan(src interface{}) error {
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*e = CountriesContinent(s)
|
||||
case string:
|
||||
*e = CountriesContinent(s)
|
||||
default:
|
||||
return fmt.Errorf("unsupported scan type for CountriesContinent: %T", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NullCountriesContinent struct {
|
||||
CountriesContinent CountriesContinent
|
||||
Valid bool // Valid is true if CountriesContinent is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (ns *NullCountriesContinent) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
ns.CountriesContinent, ns.Valid = "", false
|
||||
return nil
|
||||
}
|
||||
ns.Valid = true
|
||||
return ns.CountriesContinent.Scan(value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (ns NullCountriesContinent) Value() (driver.Value, error) {
|
||||
if !ns.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return string(ns.CountriesContinent), nil
|
||||
}
|
||||
|
||||
type CurrencyTransactionLogsCurrencyType string
|
||||
|
||||
const (
|
||||
CurrencyTransactionLogsCurrencyTypeCash CurrencyTransactionLogsCurrencyType = "cash"
|
||||
CurrencyTransactionLogsCurrencyTypeFuel CurrencyTransactionLogsCurrencyType = "fuel"
|
||||
CurrencyTransactionLogsCurrencyTypePassengers CurrencyTransactionLogsCurrencyType = "passengers"
|
||||
)
|
||||
|
||||
func (e *CurrencyTransactionLogsCurrencyType) Scan(src interface{}) error {
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*e = CurrencyTransactionLogsCurrencyType(s)
|
||||
case string:
|
||||
*e = CurrencyTransactionLogsCurrencyType(s)
|
||||
default:
|
||||
return fmt.Errorf("unsupported scan type for CurrencyTransactionLogsCurrencyType: %T", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NullCurrencyTransactionLogsCurrencyType struct {
|
||||
CurrencyTransactionLogsCurrencyType CurrencyTransactionLogsCurrencyType
|
||||
Valid bool // Valid is true if CurrencyTransactionLogsCurrencyType is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (ns *NullCurrencyTransactionLogsCurrencyType) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
ns.CurrencyTransactionLogsCurrencyType, ns.Valid = "", false
|
||||
return nil
|
||||
}
|
||||
ns.Valid = true
|
||||
return ns.CurrencyTransactionLogsCurrencyType.Scan(value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (ns NullCurrencyTransactionLogsCurrencyType) Value() (driver.Value, error) {
|
||||
if !ns.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return string(ns.CurrencyTransactionLogsCurrencyType), nil
|
||||
}
|
||||
|
||||
type PlanesAircraftSize string
|
||||
|
||||
const (
|
||||
PlanesAircraftSizeSmall PlanesAircraftSize = "small"
|
||||
PlanesAircraftSizeMedium PlanesAircraftSize = "medium"
|
||||
PlanesAircraftSizeLarge PlanesAircraftSize = "large"
|
||||
PlanesAircraftSizeHuge PlanesAircraftSize = "huge"
|
||||
)
|
||||
|
||||
func (e *PlanesAircraftSize) Scan(src interface{}) error {
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*e = PlanesAircraftSize(s)
|
||||
case string:
|
||||
*e = PlanesAircraftSize(s)
|
||||
default:
|
||||
return fmt.Errorf("unsupported scan type for PlanesAircraftSize: %T", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NullPlanesAircraftSize struct {
|
||||
PlanesAircraftSize PlanesAircraftSize
|
||||
Valid bool // Valid is true if PlanesAircraftSize is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (ns *NullPlanesAircraftSize) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
ns.PlanesAircraftSize, ns.Valid = "", false
|
||||
return nil
|
||||
}
|
||||
ns.Valid = true
|
||||
return ns.PlanesAircraftSize.Scan(value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (ns NullPlanesAircraftSize) Value() (driver.Value, error) {
|
||||
if !ns.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return string(ns.PlanesAircraftSize), nil
|
||||
}
|
||||
|
||||
type PlanesAircraftType string
|
||||
|
||||
const (
|
||||
PlanesAircraftTypePlane PlanesAircraftType = "plane"
|
||||
PlanesAircraftTypeHelicopter PlanesAircraftType = "helicopter"
|
||||
PlanesAircraftTypeSeaplane PlanesAircraftType = "seaplane"
|
||||
PlanesAircraftTypeSpaceship PlanesAircraftType = "spaceship"
|
||||
)
|
||||
|
||||
func (e *PlanesAircraftType) Scan(src interface{}) error {
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*e = PlanesAircraftType(s)
|
||||
case string:
|
||||
*e = PlanesAircraftType(s)
|
||||
default:
|
||||
return fmt.Errorf("unsupported scan type for PlanesAircraftType: %T", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NullPlanesAircraftType struct {
|
||||
PlanesAircraftType PlanesAircraftType
|
||||
Valid bool // Valid is true if PlanesAircraftType is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (ns *NullPlanesAircraftType) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
ns.PlanesAircraftType, ns.Valid = "", false
|
||||
return nil
|
||||
}
|
||||
ns.Valid = true
|
||||
return ns.PlanesAircraftType.Scan(value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (ns NullPlanesAircraftType) Value() (driver.Value, error) {
|
||||
if !ns.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return string(ns.PlanesAircraftType), nil
|
||||
}
|
||||
|
||||
type PlanesOperationType string
|
||||
|
||||
const (
|
||||
PlanesOperationTypePassenger PlanesOperationType = "passenger"
|
||||
PlanesOperationTypeCargo PlanesOperationType = "cargo"
|
||||
)
|
||||
|
||||
func (e *PlanesOperationType) Scan(src interface{}) error {
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*e = PlanesOperationType(s)
|
||||
case string:
|
||||
*e = PlanesOperationType(s)
|
||||
default:
|
||||
return fmt.Errorf("unsupported scan type for PlanesOperationType: %T", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NullPlanesOperationType struct {
|
||||
PlanesOperationType PlanesOperationType
|
||||
Valid bool // Valid is true if PlanesOperationType is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (ns *NullPlanesOperationType) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
ns.PlanesOperationType, ns.Valid = "", false
|
||||
return nil
|
||||
}
|
||||
ns.Valid = true
|
||||
return ns.PlanesOperationType.Scan(value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (ns NullPlanesOperationType) Value() (driver.Value, error) {
|
||||
if !ns.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return string(ns.PlanesOperationType), nil
|
||||
}
|
||||
|
||||
type PlayerPlanesStatus string
|
||||
|
||||
const (
|
||||
PlayerPlanesStatusIdle PlayerPlanesStatus = "idle"
|
||||
PlayerPlanesStatusMaintenance PlayerPlanesStatus = "maintenance"
|
||||
PlayerPlanesStatusBoarding PlayerPlanesStatus = "boarding"
|
||||
PlayerPlanesStatusTaxiing PlayerPlanesStatus = "taxiing"
|
||||
PlayerPlanesStatusFlying PlayerPlanesStatus = "flying"
|
||||
)
|
||||
|
||||
func (e *PlayerPlanesStatus) Scan(src interface{}) error {
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*e = PlayerPlanesStatus(s)
|
||||
case string:
|
||||
*e = PlayerPlanesStatus(s)
|
||||
default:
|
||||
return fmt.Errorf("unsupported scan type for PlayerPlanesStatus: %T", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NullPlayerPlanesStatus struct {
|
||||
PlayerPlanesStatus PlayerPlanesStatus
|
||||
Valid bool // Valid is true if PlayerPlanesStatus is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (ns *NullPlayerPlanesStatus) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
ns.PlayerPlanesStatus, ns.Valid = "", false
|
||||
return nil
|
||||
}
|
||||
ns.Valid = true
|
||||
return ns.PlayerPlanesStatus.Scan(value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (ns NullPlayerPlanesStatus) Value() (driver.Value, error) {
|
||||
if !ns.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return string(ns.PlayerPlanesStatus), nil
|
||||
}
|
||||
|
||||
type Airport struct {
|
||||
ID uint32
|
||||
Name string
|
||||
UserID int32
|
||||
OriginCountryID uint16
|
||||
Cash uint64
|
||||
CurrentPassengers uint32
|
||||
MaxPassengerCapacity uint32
|
||||
CurrentFuel uint32
|
||||
MaxFuelCapacity uint32
|
||||
FuelGenerationRate uint32
|
||||
PassengerGenerationRate uint32
|
||||
ResourcesLastUpdatedAt time.Time
|
||||
IsAvailable bool
|
||||
PlayerLevel uint32
|
||||
ExperiencePoints uint64
|
||||
IsDeleted sql.NullBool
|
||||
}
|
||||
|
||||
type AirportBuilding struct {
|
||||
ID uint32
|
||||
AirportID uint32
|
||||
BuildingID uint32
|
||||
RecipeID sql.NullInt32
|
||||
StorageProduct sql.NullInt32
|
||||
Storage1 sql.NullInt32
|
||||
Storage2 sql.NullInt32
|
||||
Storage3 sql.NullInt32
|
||||
Level uint32
|
||||
}
|
||||
|
||||
type AirportContract struct {
|
||||
ID uint32
|
||||
AirportID uint32
|
||||
Title string
|
||||
RequiredProductID uint16
|
||||
RequiredAmount uint32
|
||||
CurrentAmountDelivered uint32
|
||||
IsCompleted bool
|
||||
ExpiresAt time.Time
|
||||
}
|
||||
|
||||
type AirportInventory struct {
|
||||
AirportID uint32
|
||||
ProductID uint16
|
||||
Quantity uint32
|
||||
}
|
||||
|
||||
type Building struct {
|
||||
ID uint32
|
||||
Name string
|
||||
MinLevel uint32
|
||||
ConstructionCostCash uint32
|
||||
UpgradeTier sql.NullInt16
|
||||
UpgradeCostCashToUptier sql.NullInt32
|
||||
BuildingType BuildingsBuildingType
|
||||
Capacity sql.NullInt32
|
||||
StorageType NullBuildingsStorageType
|
||||
StorageSubtype NullBuildingsStorageSubtype
|
||||
EffectID sql.NullInt16
|
||||
EffectValue sql.NullInt32
|
||||
OperationSize NullBuildingsOperationSize
|
||||
OperationType NullBuildingsOperationType
|
||||
PassiveType NullBuildingsPassiveType
|
||||
PassiveRate sql.NullInt32
|
||||
}
|
||||
|
||||
type BuildingProduct struct {
|
||||
ID uint32
|
||||
BuildingID uint32
|
||||
RecipeID sql.NullInt32
|
||||
StorageProduct sql.NullInt32
|
||||
Storage1 sql.NullInt32
|
||||
Storage2 sql.NullInt32
|
||||
Storage3 sql.NullInt32
|
||||
}
|
||||
|
||||
type CompletedFlightRoutesLog struct {
|
||||
ID uint32
|
||||
OriginAirportID uint32
|
||||
DestinationAirportID uint32
|
||||
StartTime time.Time
|
||||
EndTime sql.NullTime
|
||||
}
|
||||
|
||||
type Country struct {
|
||||
ID uint16
|
||||
Name string
|
||||
Continent CountriesContinent
|
||||
Produce1ID uint16
|
||||
Produce2ID uint16
|
||||
Produce3ID uint16
|
||||
}
|
||||
|
||||
type CurrencyTransactionLog struct {
|
||||
ID uint64
|
||||
AirportID uint32
|
||||
AmountChanged int32
|
||||
CurrencyType CurrencyTransactionLogsCurrencyType
|
||||
Reason string
|
||||
CreatedAt sql.NullTime
|
||||
}
|
||||
|
||||
type Effect struct {
|
||||
ID uint16
|
||||
Name string
|
||||
}
|
||||
|
||||
type Plane struct {
|
||||
ID uint32
|
||||
Name string
|
||||
MinLevel uint32
|
||||
OperationCostFuel uint32
|
||||
OperationCostCash uint32
|
||||
MaxPassengers sql.NullInt32
|
||||
TravelTimeInseconds uint32
|
||||
OperationType PlanesOperationType
|
||||
AircraftSize PlanesAircraftSize
|
||||
AircraftType PlanesAircraftType
|
||||
BuyingCostCash uint32
|
||||
CompletedFlightExperience uint32
|
||||
CompletedFlightCashReward sql.NullInt32
|
||||
CompletedFlightCargoReward sql.NullInt32
|
||||
}
|
||||
|
||||
type PlayerPlane struct {
|
||||
ID uint32
|
||||
AirportID uint32
|
||||
PlaneID uint32
|
||||
AssignedHangarID uint32
|
||||
CurrentBuildingID sql.NullInt32
|
||||
Status PlayerPlanesStatus
|
||||
UpdatedAt sql.NullTime
|
||||
FlightStartTime sql.NullTime
|
||||
}
|
||||
|
||||
type Product struct {
|
||||
ID uint16
|
||||
Name string
|
||||
}
|
||||
|
||||
type Recipe struct {
|
||||
ID uint32
|
||||
Name string
|
||||
Ingredient1ID sql.NullInt16
|
||||
Ingredient1Amount sql.NullInt32
|
||||
Ingredient2ID sql.NullInt16
|
||||
Ingredient2Amount sql.NullInt32
|
||||
Ingredient3ID sql.NullInt16
|
||||
Ingredient3Amount sql.NullInt32
|
||||
ProductID uint16
|
||||
YieldAmount uint32
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID int32
|
||||
Username string
|
||||
PasswordHash string
|
||||
CreatedAt sql.NullTime
|
||||
DeletedAt sql.NullTime
|
||||
}
|
||||
1436
backend/db/queries.sql.go
Normal file
1436
backend/db/queries.sql.go
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1 +1,205 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// STYLING DEFINITIONS (Lipgloss)
|
||||
// ============================================================================
|
||||
var (
|
||||
// Colors
|
||||
skyBlue = lipgloss.Color("117")
|
||||
neonPink = lipgloss.Color("205")
|
||||
moneyGreen = lipgloss.Color("85")
|
||||
gray = lipgloss.Color("240")
|
||||
|
||||
// Component Styles
|
||||
titleStyle = lipgloss.NewStyle().
|
||||
Foreground(skyBlue).
|
||||
Bold(true).
|
||||
MarginTop(1).
|
||||
MarginBottom(1)
|
||||
|
||||
headerBoxStyle = lipgloss.NewStyle().
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderForeground(skyBlue).
|
||||
Padding(0, 2).
|
||||
MarginBottom(1)
|
||||
|
||||
listStyle = lipgloss.NewStyle().MarginLeft(2)
|
||||
|
||||
itemStyle = lipgloss.NewStyle().
|
||||
PaddingLeft(2).
|
||||
Foreground(lipgloss.Color("252"))
|
||||
|
||||
selectedItemStyle = lipgloss.NewStyle().
|
||||
PaddingLeft(2).
|
||||
Foreground(neonPink).
|
||||
Bold(true).
|
||||
Background(lipgloss.Color("236")) // Slight background highlight
|
||||
|
||||
footerStyle = lipgloss.NewStyle().
|
||||
Foreground(gray).
|
||||
MarginTop(2)
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// 1. THE MODEL
|
||||
// ============================================================================
|
||||
type homepageModel struct {
|
||||
cursor int
|
||||
choices []string
|
||||
selected string
|
||||
|
||||
// Mock Player Data (In a real app, pass this in via initialModel)
|
||||
playerName string
|
||||
cash int
|
||||
fleetCount int
|
||||
}
|
||||
|
||||
func initialHomepageModel() homepageModel {
|
||||
return homepageModel{
|
||||
// Our interactive menu options
|
||||
choices: []string{
|
||||
"🗄️ Manage Hangar Fleet",
|
||||
"🏢 View Airport Infrastructure",
|
||||
"🏁 Active Runways & Service Loop",
|
||||
"🚪 Logout / Quit",
|
||||
},
|
||||
playerName: "CaptainGo",
|
||||
cash: 14500,
|
||||
fleetCount: 12,
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 2. THE INIT FUNCTION
|
||||
// ============================================================================
|
||||
func (m homepageModel) Init() tea.Cmd {
|
||||
return nil // No background tasks to start immediately
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 3. THE UPDATE FUNCTION (Handles inputs)
|
||||
// ============================================================================
|
||||
func (m homepageModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
|
||||
// Handle keyboard events
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
|
||||
// Quit commands
|
||||
case "ctrl+c", "q", "esc":
|
||||
m.selected = "quit"
|
||||
return m, tea.Quit
|
||||
|
||||
// Move up the menu
|
||||
case "up", "k":
|
||||
if m.cursor > 0 {
|
||||
m.cursor--
|
||||
} else {
|
||||
m.cursor = len(m.choices) - 1 // Wrap around to bottom
|
||||
}
|
||||
|
||||
// Move down the menu
|
||||
case "down", "j":
|
||||
if m.cursor < len(m.choices)-1 {
|
||||
m.cursor++
|
||||
} else {
|
||||
m.cursor = 0 // Wrap around to top
|
||||
}
|
||||
|
||||
// Select an option
|
||||
case "enter", " ":
|
||||
m.selected = m.choices[m.cursor]
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 4. THE VIEW FUNCTION (Renders the UI)
|
||||
// ============================================================================
|
||||
func (m homepageModel) View() string {
|
||||
var b strings.Builder
|
||||
|
||||
// --- 1. TITLE ---
|
||||
b.WriteString(titleStyle.Render("✈️ SKYRAMA COMMAND CENTER"))
|
||||
b.WriteString("\n")
|
||||
|
||||
// --- 2. HEADER BAR (Player Stats) ---
|
||||
statsLine := fmt.Sprintf("[ 🔑 Player: %s ] [ 💰 Cash: %s ] [ 🛩️ Fleet: %d ]",
|
||||
lipgloss.NewStyle().Foreground(lipgloss.Color("252")).Bold(true).Render(m.playerName),
|
||||
lipgloss.NewStyle().Foreground(moneyGreen).Render(fmt.Sprintf("%d €", m.cash)),
|
||||
m.fleetCount,
|
||||
)
|
||||
b.WriteString(headerBoxStyle.Render(statsLine))
|
||||
b.WriteString("\n\n")
|
||||
|
||||
// --- 3. INTERACTIVE MENU ---
|
||||
b.WriteString(" Main Operations Menu:\n\n")
|
||||
|
||||
for i, choice := range m.choices {
|
||||
// Is the cursor pointing at this choice?
|
||||
cursorStr := " " // no cursor
|
||||
if m.cursor == i {
|
||||
cursorStr = lipgloss.NewStyle().Foreground(neonPink).Render(">")
|
||||
b.WriteString(fmt.Sprintf("%s %s\n", cursorStr, selectedItemStyle.Render(choice)))
|
||||
} else {
|
||||
b.WriteString(fmt.Sprintf("%s %s\n", cursorStr, itemStyle.Render(choice)))
|
||||
}
|
||||
}
|
||||
|
||||
// --- 4. FOOTER ---
|
||||
b.WriteString(footerStyle.Render("\n ↑/↓: Navigate • Enter: Select • q/esc: Quit"))
|
||||
|
||||
// Return the final drawn screen with a little left margin padding
|
||||
return lipgloss.NewStyle().MarginLeft(2).Render(b.String()) + "\n"
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// THE WRAPPER FUNCTION
|
||||
// ============================================================================
|
||||
// RunHomepageScreen starts the TUI and returns the menu string the user selected.
|
||||
func RunHomepageScreen() string {
|
||||
p := tea.NewProgram(initialHomepageModel(), tea.WithAltScreen()) // AltScreen creates a full-screen view
|
||||
|
||||
finalModel, err := p.Run()
|
||||
if err != nil {
|
||||
fmt.Printf("Error running dashboard: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if m, ok := finalModel.(homepageModel); ok {
|
||||
return m.selected
|
||||
}
|
||||
|
||||
return "quit"
|
||||
}
|
||||
|
||||
/*
|
||||
// Optional Main function to test it directly
|
||||
func main() {
|
||||
selection := RunHomepageScreen()
|
||||
|
||||
// Clear the screen on exit to print standard output
|
||||
fmt.Print("\033[H\033[2J")
|
||||
|
||||
if selection == "quit" || selection == "🚪 Logout / Quit" {
|
||||
fmt.Println("Gracefully disconnected from SkyRama servers. Goodbye!")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
fmt.Printf("You selected: %s\n", selection)
|
||||
fmt.Println("Here is where you would load the next Bubble Tea model for that screen!")
|
||||
}
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1 +1,206 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// STYLING DEFINITIONS (Lipgloss)
|
||||
// ============================================================================
|
||||
var (
|
||||
focusedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("205")).Bold(true)
|
||||
blurredStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
|
||||
cursorStyle = focusedStyle.Copy()
|
||||
noStyle = lipgloss.NewStyle()
|
||||
|
||||
// A nice box around the login screen
|
||||
dialogBoxStyle = lipgloss.NewStyle().
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderForeground(lipgloss.Color("87")).
|
||||
Padding(1, 4).
|
||||
MarginTop(1).
|
||||
MarginLeft(2)
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// 1. THE MODEL
|
||||
// ============================================================================
|
||||
type loginModel struct {
|
||||
focusIndex int
|
||||
inputs []textinput.Model
|
||||
cursorMode textinput.CursorMode
|
||||
|
||||
// Flags to return back to the main application
|
||||
isSubmitted bool
|
||||
isCanceled bool
|
||||
errorMessage string
|
||||
}
|
||||
|
||||
func initialModell() loginModel {
|
||||
m := loginModel{
|
||||
inputs: make([]textinput.Model, 2),
|
||||
}
|
||||
|
||||
var t textinput.Model
|
||||
for i := range m.inputs {
|
||||
t = textinput.New()
|
||||
t.Cursor.Style = cursorStyle
|
||||
t.CharLimit = 32
|
||||
|
||||
switch i {
|
||||
case 0:
|
||||
t.Placeholder = "Username"
|
||||
t.Focus()
|
||||
t.PromptStyle = focusedStyle
|
||||
t.TextStyle = focusedStyle
|
||||
case 1:
|
||||
t.Placeholder = "Password"
|
||||
t.EchoMode = textinput.EchoPassword // Masks the password with *
|
||||
t.EchoCharacter = '•'
|
||||
}
|
||||
|
||||
m.inputs[i] = t
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 2. THE INIT FUNCTION (Runs once on startup)
|
||||
// ============================================================================
|
||||
func (m loginModel) Init() tea.Cmd {
|
||||
return textinput.Blink
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 3. THE UPDATE FUNCTION (Handles keypresses)
|
||||
// ============================================================================
|
||||
func (m loginModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "ctrl+c", "esc":
|
||||
m.isCanceled = true
|
||||
return m, tea.Quit
|
||||
|
||||
// Handle switching between username and password fields
|
||||
case "tab", "shift+tab", "enter", "up", "down":
|
||||
s := msg.String()
|
||||
|
||||
// If they press Enter on the password field, submit the form
|
||||
if s == "enter" && m.focusIndex == len(m.inputs)-1 {
|
||||
m.isSubmitted = true
|
||||
return m, tea.Quit
|
||||
}
|
||||
|
||||
// Cycle through inputs
|
||||
if s == "up" || s == "shift+tab" {
|
||||
m.focusIndex--
|
||||
} else {
|
||||
m.focusIndex++
|
||||
}
|
||||
|
||||
if m.focusIndex > len(m.inputs)-1 {
|
||||
m.focusIndex = 0
|
||||
} else if m.focusIndex < 0 {
|
||||
m.focusIndex = len(m.inputs) - 1
|
||||
}
|
||||
|
||||
// Apply focus states
|
||||
cmds := make([]tea.Cmd, len(m.inputs))
|
||||
for i := 0; i <= len(m.inputs)-1; i++ {
|
||||
if i == m.focusIndex {
|
||||
cmds[i] = m.inputs[i].Focus()
|
||||
m.inputs[i].PromptStyle = focusedStyle
|
||||
m.inputs[i].TextStyle = focusedStyle
|
||||
continue
|
||||
}
|
||||
m.inputs[i].Blur()
|
||||
m.inputs[i].PromptStyle = noStyle
|
||||
m.inputs[i].TextStyle = noStyle
|
||||
}
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle character inputs and blinking
|
||||
cmd := m.updateInputs(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
func (m *loginModel) updateInputs(msg tea.Msg) tea.Cmd {
|
||||
cmds := make([]tea.Cmd, len(m.inputs))
|
||||
for i := range m.inputs {
|
||||
m.inputs[i], cmds[i] = m.inputs[i].Update(msg)
|
||||
}
|
||||
return tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 4. THE VIEW FUNCTION (Renders the UI)
|
||||
// ============================================================================
|
||||
func (m loginModel) View() string {
|
||||
// Build the UI string
|
||||
var b strings.Builder
|
||||
|
||||
b.WriteString(lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("78")).Render("✈️ SKYRAMA TERMINAL LOGIN\n\n"))
|
||||
|
||||
for i := range m.inputs {
|
||||
b.WriteString(m.inputs[i].View())
|
||||
if i < len(m.inputs)-1 {
|
||||
b.WriteRune('\n')
|
||||
}
|
||||
}
|
||||
|
||||
button := &lipgloss.Style{}
|
||||
if m.focusIndex == len(m.inputs) {
|
||||
button = &focusedStyle
|
||||
} else {
|
||||
button = &blurredStyle
|
||||
}
|
||||
|
||||
b.WriteString("\n")
|
||||
if m.errorMessage != "" {
|
||||
b.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("9")).Render("⚠️ " + m.errorMessage + "\n"))
|
||||
} else {
|
||||
b.WriteString("\n")
|
||||
}
|
||||
b.WriteString(button.Render("[ Submit ]"))
|
||||
b.WriteString(blurredStyle.Render("\n\n(esc to quit)"))
|
||||
|
||||
// Wrap the whole thing in our dialog box
|
||||
return dialogBoxStyle.Render(b.String()) + "\n"
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// THE WRAPPER FUNCTION (Call this from your app)
|
||||
// ============================================================================
|
||||
// RunLoginScreen starts the TUI and returns the username, password, and a boolean
|
||||
// indicating if the user completed the form (true) or pressed escape (false).
|
||||
func RunLoginScreen() (username string, password string, submitted bool) {
|
||||
|
||||
p := tea.NewProgram(initialModell())
|
||||
|
||||
// Run the Bubble Tea program synchronously
|
||||
finalModel, err := p.Run()
|
||||
if err != nil {
|
||||
fmt.Printf("Error running login screen: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Cast the returned tea.Model back into our specific loginModel
|
||||
if m, ok := finalModel.(loginModel); ok {
|
||||
if m.isSubmitted {
|
||||
return m.inputs[0].Value(), m.inputs[1].Value(), true
|
||||
}
|
||||
}
|
||||
|
||||
return "", "", false
|
||||
}
|
||||
|
|
|
|||
182
backend/server/main.go
Normal file
182
backend/server/main.go
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
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!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1,78 @@
|
|||
package utils
|
||||
|
||||
import "skyrama_clone_backend/backend/objects"
|
||||
|
||||
type Msg = isPacket_Msg
|
||||
|
||||
func NewChat(msg string) Msg {
|
||||
return &Packet_Chat{
|
||||
Chat: &ChatMessage{
|
||||
Msg: msg,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewId(id uint64) Msg {
|
||||
return &Packet_Id{
|
||||
Id: &IdMessage{
|
||||
Id: id,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewOkResponse() Msg {
|
||||
return &Packet_OkResponse{
|
||||
OkResponse: &OkResponseMessage{},
|
||||
}
|
||||
}
|
||||
|
||||
func NewDenyResponse(reason string) Msg {
|
||||
return &Packet_DenyResponse{
|
||||
DenyResponse: &DenyResponseMessage{
|
||||
Reason: reason,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewPlayer(id uint64, player *objects.Player) Msg {
|
||||
return &Packet_Player{
|
||||
Player: &PlayerMessage{
|
||||
Id: id,
|
||||
Name: player.Name,
|
||||
X: player.X,
|
||||
Y: player.Y,
|
||||
Radius: player.Radius,
|
||||
Direction: player.Direction,
|
||||
Speed: player.Speed,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newSporeMessage(id uint64, spore *objects.Spore) *SporeMessage {
|
||||
return &SporeMessage{
|
||||
Id: id,
|
||||
X: spore.X,
|
||||
Y: spore.Y,
|
||||
Radius: spore.Radius,
|
||||
}
|
||||
}
|
||||
func NewSpore(id uint64, spore *objects.Spore) Msg {
|
||||
return &Packet_Spore{
|
||||
newSporeMessage(id, spore),
|
||||
}
|
||||
}
|
||||
|
||||
func NewSporeBatches(spores map[uint64]*objects.Spore) Msg {
|
||||
|
||||
SporeMessages := make([]*SporeMessage, len(spores))
|
||||
for id, spore := range spores {
|
||||
SporeMessages = append(SporeMessages, newSporeMessage(id, spore))
|
||||
|
||||
}
|
||||
|
||||
return &Packet_SporeBatch{
|
||||
SporeBatch: &SporesBatchMessage{
|
||||
Spores: SporeMessages,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1441
backend/utils/packets.pb.go
Normal file
1441
backend/utils/packets.pb.go
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue