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:
Lucas 2025-06-14 03:29:29 +02:00
parent 07daead293
commit 8a7f9a62e5
3 changed files with 267 additions and 3 deletions

View file

@ -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
View file

@ -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
View 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>