Upload files to "routes"
This commit is contained in:
104
routes/api.py
Normal file
104
routes/api.py
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
from flask import Blueprint, request, jsonify
|
||||||
|
from db import get_db
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
api = Blueprint('api', __name__)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# POST /api/state
|
||||||
|
# Tampermonkey pushes a full town snapshot every poll cycle.
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
@api.route('/api/state', methods=['POST'])
|
||||||
|
def receive_state():
|
||||||
|
data = request.get_json(silent=True)
|
||||||
|
if not data:
|
||||||
|
return jsonify({'error': 'no data'}), 400
|
||||||
|
|
||||||
|
towns = data.get('towns', [])
|
||||||
|
player = data.get('player', '')
|
||||||
|
world_id = data.get('world_id', '')
|
||||||
|
|
||||||
|
conn = get_db()
|
||||||
|
c = conn.cursor()
|
||||||
|
for town in towns:
|
||||||
|
c.execute('''
|
||||||
|
INSERT INTO town_state (town_id, town_name, player, world_id, data, updated_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
|
ON CONFLICT(town_id) DO UPDATE SET
|
||||||
|
town_name = excluded.town_name,
|
||||||
|
player = excluded.player,
|
||||||
|
world_id = excluded.world_id,
|
||||||
|
data = excluded.data,
|
||||||
|
updated_at = excluded.updated_at
|
||||||
|
''', (
|
||||||
|
str(town['town_id']),
|
||||||
|
town.get('town_name', ''),
|
||||||
|
player,
|
||||||
|
world_id,
|
||||||
|
json.dumps(town),
|
||||||
|
datetime.utcnow().isoformat()
|
||||||
|
))
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
return jsonify({'ok': True, 'towns_updated': len(towns)})
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# GET /api/commands/pending
|
||||||
|
# Tampermonkey polls this to get the next command to execute.
|
||||||
|
# Returns ONE command at a time, marks it as 'executing'.
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
@api.route('/api/commands/pending', methods=['GET'])
|
||||||
|
def get_pending_command():
|
||||||
|
conn = get_db()
|
||||||
|
c = conn.cursor()
|
||||||
|
row = c.execute('''
|
||||||
|
SELECT * FROM commands
|
||||||
|
WHERE status = 'pending'
|
||||||
|
ORDER BY id ASC
|
||||||
|
LIMIT 1
|
||||||
|
''').fetchone()
|
||||||
|
|
||||||
|
if not row:
|
||||||
|
conn.close()
|
||||||
|
return jsonify({'command': None})
|
||||||
|
|
||||||
|
c.execute('''
|
||||||
|
UPDATE commands
|
||||||
|
SET status = 'executing', updated_at = ?
|
||||||
|
WHERE id = ?
|
||||||
|
''', (datetime.utcnow().isoformat(), row['id']))
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'command': {
|
||||||
|
'id': row['id'],
|
||||||
|
'town_id': row['town_id'],
|
||||||
|
'type': row['type'],
|
||||||
|
'payload': json.loads(row['payload'])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# POST /api/commands/<id>/result
|
||||||
|
# Tampermonkey reports back whether the command succeeded or failed.
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
@api.route('/api/commands/<int:cmd_id>/result', methods=['POST'])
|
||||||
|
def command_result(cmd_id):
|
||||||
|
data = request.get_json(silent=True) or {}
|
||||||
|
status = data.get('status', 'done') # 'done' | 'failed'
|
||||||
|
msg = data.get('message', '')
|
||||||
|
|
||||||
|
conn = get_db()
|
||||||
|
conn.execute('''
|
||||||
|
UPDATE commands
|
||||||
|
SET status = ?, result_msg = ?, updated_at = ?
|
||||||
|
WHERE id = ?
|
||||||
|
''', (status, msg, datetime.utcnow().isoformat(), cmd_id))
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
return jsonify({'ok': True})
|
||||||
127
routes/dashboard.py
Normal file
127
routes/dashboard.py
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
from flask import Blueprint, render_template, request, jsonify
|
||||||
|
from db import get_db
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
dashboard = Blueprint('dashboard', __name__)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# GET /
|
||||||
|
# Serve the dashboard HTML
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
@dashboard.route('/')
|
||||||
|
def index():
|
||||||
|
return render_template('dashboard.html')
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# GET /dashboard/towns
|
||||||
|
# Returns all known towns with their latest state snapshot.
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
@dashboard.route('/dashboard/towns', methods=['GET'])
|
||||||
|
def get_towns():
|
||||||
|
conn = get_db()
|
||||||
|
rows = conn.execute('''
|
||||||
|
SELECT town_id, town_name, player, world_id, data, updated_at
|
||||||
|
FROM town_state
|
||||||
|
ORDER BY town_name ASC
|
||||||
|
''').fetchall()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
towns = []
|
||||||
|
for row in rows:
|
||||||
|
d = json.loads(row['data'])
|
||||||
|
towns.append({
|
||||||
|
'town_id': row['town_id'],
|
||||||
|
'town_name': row['town_name'],
|
||||||
|
'player': row['player'],
|
||||||
|
'world_id': row['world_id'],
|
||||||
|
'updated_at': row['updated_at'],
|
||||||
|
'resources': {
|
||||||
|
'wood': d.get('wood', 0),
|
||||||
|
'stone': d.get('stone', 0),
|
||||||
|
'iron': d.get('iron', 0),
|
||||||
|
'population': d.get('population', 0),
|
||||||
|
},
|
||||||
|
'buildings': d.get('buildings', {}),
|
||||||
|
'units': d.get('units', {}),
|
||||||
|
'points': d.get('points', 0),
|
||||||
|
'god': d.get('god', None),
|
||||||
|
'build_queue': d.get('buildingOrder', []),
|
||||||
|
})
|
||||||
|
return jsonify(towns)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# GET /dashboard/commands
|
||||||
|
# Returns command history (last 50) for the log panel.
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
@dashboard.route('/dashboard/commands', methods=['GET'])
|
||||||
|
def get_commands():
|
||||||
|
conn = get_db()
|
||||||
|
rows = conn.execute('''
|
||||||
|
SELECT id, town_id, town_name, type, payload, status, result_msg, created_at, updated_at
|
||||||
|
FROM commands
|
||||||
|
ORDER BY id DESC
|
||||||
|
LIMIT 50
|
||||||
|
''').fetchall()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return jsonify([dict(r) for r in rows])
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# POST /dashboard/commands
|
||||||
|
# Dashboard sends a new command.
|
||||||
|
# Body: { town_id, town_name, type: 'build'|'recruit', payload: {...} }
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
@dashboard.route('/dashboard/commands', methods=['POST'])
|
||||||
|
def create_command():
|
||||||
|
data = request.get_json(silent=True)
|
||||||
|
if not data:
|
||||||
|
return jsonify({'error': 'no data'}), 400
|
||||||
|
|
||||||
|
required = ['town_id', 'type', 'payload']
|
||||||
|
for field in required:
|
||||||
|
if field not in data:
|
||||||
|
return jsonify({'error': f'missing field: {field}'}), 400
|
||||||
|
|
||||||
|
cmd_type = data['type']
|
||||||
|
if cmd_type not in ('build', 'recruit'):
|
||||||
|
return jsonify({'error': 'type must be build or recruit'}), 400
|
||||||
|
|
||||||
|
conn = get_db()
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute('''
|
||||||
|
INSERT INTO commands (town_id, town_name, type, payload, status, created_at, updated_at)
|
||||||
|
VALUES (?, ?, ?, ?, 'pending', ?, ?)
|
||||||
|
''', (
|
||||||
|
str(data['town_id']),
|
||||||
|
data.get('town_name', ''),
|
||||||
|
cmd_type,
|
||||||
|
json.dumps(data['payload']),
|
||||||
|
datetime.utcnow().isoformat(),
|
||||||
|
datetime.utcnow().isoformat()
|
||||||
|
))
|
||||||
|
cmd_id = c.lastrowid
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return jsonify({'ok': True, 'id': cmd_id})
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# DELETE /dashboard/commands/<id>
|
||||||
|
# Cancel a pending command from the dashboard.
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
@dashboard.route('/dashboard/commands/<int:cmd_id>', methods=['DELETE'])
|
||||||
|
def cancel_command(cmd_id):
|
||||||
|
conn = get_db()
|
||||||
|
conn.execute('''
|
||||||
|
UPDATE commands SET status = 'cancelled', updated_at = ?
|
||||||
|
WHERE id = ? AND status = 'pending'
|
||||||
|
''', (datetime.utcnow().isoformat(), cmd_id))
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
return jsonify({'ok': True})
|
||||||
Reference in New Issue
Block a user