Add mining resource system with background updates
Implements automatic resource production with database persistence: - Mining table with timestamp, value, and rate tracking - Background ticker updates values every minute - API endpoint calculates real-time values from database snapshots - Frontend displays live-updating current value and database history - Rate-based accumulation system for efficient resource management 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
07daead293
commit
8a7f9a62e5
3 changed files with 267 additions and 3 deletions
|
@ -31,6 +31,10 @@
|
|||
<a href="account.html">Account Information</a>
|
||||
<div class="description">Authentication test page - displays user info and headers from Authelia (requires authentication).</div>
|
||||
</li>
|
||||
<li>
|
||||
<a href="mining.html">Mining Resources</a>
|
||||
<div class="description">Background resource production demo - displays live-calculated mining values with automatic database updates every minute.</div>
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
|
166
main.go
166
main.go
|
@ -29,6 +29,19 @@ type UserInfo struct {
|
|||
Headers map[string]string `json:"headers"`
|
||||
}
|
||||
|
||||
type MiningRecord struct {
|
||||
ID int `json:"id"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Value float64 `json:"value"`
|
||||
Rate float64 `json:"rate"`
|
||||
}
|
||||
|
||||
type MiningData struct {
|
||||
CurrentValue float64 `json:"current_value"`
|
||||
Rate float64 `json:"rate"`
|
||||
History []MiningRecord `json:"history"`
|
||||
}
|
||||
|
||||
var db *sql.DB
|
||||
|
||||
func initDB() {
|
||||
|
@ -38,15 +51,43 @@ func initDB() {
|
|||
log.Fatal("Failed to open database:", err)
|
||||
}
|
||||
|
||||
createTable := `
|
||||
createItemsTable := `
|
||||
CREATE TABLE IF NOT EXISTS items (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL
|
||||
);`
|
||||
|
||||
_, err = db.Exec(createTable)
|
||||
_, err = db.Exec(createItemsTable)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to create table:", err)
|
||||
log.Fatal("Failed to create items table:", err)
|
||||
}
|
||||
|
||||
createMiningTable := `
|
||||
CREATE TABLE IF NOT EXISTS mining (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
timestamp DATETIME NOT NULL,
|
||||
value REAL NOT NULL,
|
||||
rate REAL NOT NULL
|
||||
);`
|
||||
|
||||
_, err = db.Exec(createMiningTable)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to create mining table:", err)
|
||||
}
|
||||
|
||||
// Insert initial mining record if table is empty
|
||||
var count int
|
||||
err = db.QueryRow("SELECT COUNT(*) FROM mining").Scan(&count)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to check mining table:", err)
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
_, err = db.Exec("INSERT INTO mining (timestamp, value, rate) VALUES (?, ?, ?)",
|
||||
time.Now(), 100.0, 2.5)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to insert initial mining record:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,6 +101,9 @@ func main() {
|
|||
initDB()
|
||||
defer db.Close()
|
||||
|
||||
// Start mining ticker
|
||||
go startMiningTicker()
|
||||
|
||||
// Your actual API
|
||||
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
http.HandleFunc("/randomnumber", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -90,6 +134,9 @@ func main() {
|
|||
http.HandleFunc("/items", handleItems)
|
||||
http.HandleFunc("/items/", handleItemDelete)
|
||||
|
||||
// Mining endpoints
|
||||
http.HandleFunc("/mining", handleMining)
|
||||
|
||||
// Auth endpoints
|
||||
http.HandleFunc("/auth/user", handleAuthUser)
|
||||
|
||||
|
@ -262,3 +309,116 @@ func handleAuthUser(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
json.NewEncoder(w).Encode(userInfo)
|
||||
}
|
||||
|
||||
func handleMining(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if r.Method != "GET" {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
json.NewEncoder(w).Encode(map[string]string{"error": "Method not allowed"})
|
||||
return
|
||||
}
|
||||
|
||||
// Get the latest record to calculate current value
|
||||
var latest MiningRecord
|
||||
err := db.QueryRow(`
|
||||
SELECT id, timestamp, value, rate
|
||||
FROM mining
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT 1
|
||||
`).Scan(&latest.ID, &latest.Timestamp, &latest.Value, &latest.Rate)
|
||||
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(map[string]string{"error": "Failed to get latest mining data"})
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate current value based on time elapsed and rate
|
||||
now := time.Now()
|
||||
minutesElapsed := now.Sub(latest.Timestamp).Minutes()
|
||||
currentValue := latest.Value + (latest.Rate * minutesElapsed)
|
||||
|
||||
// Get recent history (last 5 records)
|
||||
rows, err := db.Query(`
|
||||
SELECT id, timestamp, value, rate
|
||||
FROM mining
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT 5
|
||||
`)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(map[string]string{"error": "Failed to get mining history"})
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var history []MiningRecord
|
||||
for rows.Next() {
|
||||
var record MiningRecord
|
||||
err := rows.Scan(&record.ID, &record.Timestamp, &record.Value, &record.Rate)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(map[string]string{"error": "Failed to scan mining record"})
|
||||
return
|
||||
}
|
||||
history = append(history, record)
|
||||
}
|
||||
|
||||
response := MiningData{
|
||||
CurrentValue: currentValue,
|
||||
Rate: latest.Rate,
|
||||
History: history,
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
func startMiningTicker() {
|
||||
ticker := time.NewTicker(1 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
log.Println("Mining ticker started - updating every minute")
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
updateMiningValue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateMiningValue() {
|
||||
// Get the latest record
|
||||
var latest MiningRecord
|
||||
err := db.QueryRow(`
|
||||
SELECT id, timestamp, value, rate
|
||||
FROM mining
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT 1
|
||||
`).Scan(&latest.ID, &latest.Timestamp, &latest.Value, &latest.Rate)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Failed to get latest mining record: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate new value based on time elapsed and rate
|
||||
now := time.Now()
|
||||
minutesElapsed := now.Sub(latest.Timestamp).Minutes()
|
||||
newValue := latest.Value + (latest.Rate * minutesElapsed)
|
||||
|
||||
// Insert new record
|
||||
_, err = db.Exec(`
|
||||
INSERT INTO mining (timestamp, value, rate)
|
||||
VALUES (?, ?, ?)
|
||||
`, now, newValue, latest.Rate)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Failed to insert new mining record: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Mining updated: %.2f -> %.2f (rate: %.2f/min, elapsed: %.2f min)",
|
||||
latest.Value, newValue, latest.Rate, minutesElapsed)
|
||||
}
|
||||
|
|
100
mining.html
Normal file
100
mining.html
Normal file
|
@ -0,0 +1,100 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Mining Resources - Playground</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Mining Resources</h1>
|
||||
|
||||
<div class="section">
|
||||
<h3>Current Resources</h3>
|
||||
<div class="display-large" id="current-value">Loading...</div>
|
||||
<div class="info">
|
||||
Rate: <span id="rate">0</span> per minute
|
||||
</div>
|
||||
<div class="timestamp" id="last-update">Last updated: Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>Database History (Last 5 Updates)</h3>
|
||||
<div id="history-container">
|
||||
<div class="loading">Loading history...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="back-link">
|
||||
<a href="index.html" class="back-button">← Back to Main</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let miningData = null;
|
||||
let lastFetchTime = null;
|
||||
|
||||
function formatNumber(num) {
|
||||
return Math.floor(num * 100) / 100; // Round to 2 decimal places
|
||||
}
|
||||
|
||||
function formatTimestamp(timestamp) {
|
||||
return new Date(timestamp).toLocaleString();
|
||||
}
|
||||
|
||||
function calculateCurrentValue() {
|
||||
if (!miningData || !lastFetchTime) return 0;
|
||||
|
||||
const now = new Date();
|
||||
const minutesElapsed = (now - lastFetchTime) / (1000 * 60);
|
||||
return miningData.current_value + (miningData.rate * minutesElapsed);
|
||||
}
|
||||
|
||||
function updateDisplay() {
|
||||
const currentValue = calculateCurrentValue();
|
||||
document.getElementById('current-value').textContent = formatNumber(currentValue);
|
||||
document.getElementById('last-update').textContent = 'Last updated: ' + new Date().toLocaleString();
|
||||
}
|
||||
|
||||
function fetchMiningData() {
|
||||
fetch('/api/mining')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
miningData = data;
|
||||
lastFetchTime = new Date();
|
||||
|
||||
document.getElementById('rate').textContent = formatNumber(data.rate);
|
||||
|
||||
// Update history display
|
||||
const historyContainer = document.getElementById('history-container');
|
||||
if (data.history && data.history.length > 0) {
|
||||
historyContainer.innerHTML = data.history.map(record => `
|
||||
<div class="item-row">
|
||||
<div class="item-name">Value: ${formatNumber(record.value)}</div>
|
||||
<div class="item-id">Rate: ${formatNumber(record.rate)}/min</div>
|
||||
<div class="timestamp">${formatTimestamp(record.timestamp)}</div>
|
||||
</div>
|
||||
`).join('');
|
||||
} else {
|
||||
historyContainer.innerHTML = '<div class="empty">No mining history available</div>';
|
||||
}
|
||||
|
||||
updateDisplay();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching mining data:', error);
|
||||
document.getElementById('current-value').textContent = 'Error loading data';
|
||||
document.getElementById('current-value').className = 'display-large error';
|
||||
});
|
||||
}
|
||||
|
||||
// Initial fetch
|
||||
fetchMiningData();
|
||||
|
||||
// Update current value display every second
|
||||
setInterval(updateDisplay, 1000);
|
||||
|
||||
// Fetch fresh data from server every 30 seconds
|
||||
setInterval(fetchMiningData, 30000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Reference in a new issue