fix: early name validation, fighter glow, reduced visual noise

- Add /api/auth/check-name endpoint for early name availability check
- JoinBoutPage checks name availability at naming step (not after webhook)
- FightCardPage: replace poster containers with circular glow behind sprites
- Reduce synthwave grid and CRT overlay opacity for cleaner backgrounds
- BotProfilePage: webhook management, owner-only fields
- useNostr: normalize bot data, guard auto-restore, clearAllState helper
- bots.ts: expose owner-only webhook info on stats endpoint

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-08 11:03:16 +00:00
co-authored by Claude Opus 4.6
parent 2d8cdcc60a
commit 9a11c48487
7 changed files with 405 additions and 120 deletions
+32 -6
View File
@@ -10,6 +10,19 @@ import { rateLimit } from '../middleware/rate-limit.js'
export const authRouter = new Hono()
// Check name availability
authRouter.get("/check-name/:name", async (c) => {
const name = c.req.param("name")?.trim().toLowerCase()
if (!name || name.length < 2 || name.length > 12) {
return c.json({ available: false, error: "Name must be 2-12 characters." })
}
const existing = await db.select({ id: schema.bots.id })
.from(schema.bots)
.where(eq(sql`LOWER(${schema.bots.name})`, name))
.limit(1)
return c.json({ available: existing.length === 0 })
})
// Login with Nostr pubkey
authRouter.post('/login', async (c) => {
const body = await c.req.json()
@@ -33,6 +46,9 @@ authRouter.post('/login', async (c) => {
tier: schema.bots.tier,
isActive: schema.bots.isActive,
customization: schema.bots.customization,
webhookUrl: schema.bots.webhookUrl,
satsWon: schema.bots.satsWon,
satsWagered: schema.bots.satsWagered,
}).from(schema.bots).where(eq(schema.bots.publicKey, pubkey)).limit(1)
if (rows.length === 0) {
@@ -40,18 +56,28 @@ authRouter.post('/login', async (c) => {
}
const bot = rows[0]
// Check if this is a human player by loading webhookUrl
const webhookRows = await db.select({ webhookUrl: schema.bots.webhookUrl })
.from(schema.bots).where(eq(schema.bots.id, bot.id)).limit(1)
const isHuman = webhookRows[0]?.webhookUrl === 'http://human.local/'
const isHuman = bot.webhookUrl === 'http://human.local/'
return c.json({
exists: true,
bot: {
...bot,
id: bot.id,
name: bot.name,
avatarSeed: bot.avatarSeed,
archetype: bot.archetype,
profilePicUrl: bot.profilePicUrl,
eloRating: bot.eloRating,
wins: bot.wins,
losses: bot.losses,
winStreak: bot.winStreak,
bestStreak: bot.bestStreak,
tier: bot.tier,
isActive: bot.isActive,
isHuman,
customization: bot.customization ? JSON.parse(bot.customization) : null,
satsWon: bot.satsWon ?? 0,
satsWagered: bot.satsWagered ?? 0,
hasWallet: false,
},
})
})
+19 -1
View File
@@ -151,6 +151,7 @@ botsRouter.get('/:name', async (c) => {
// Get bot stats -- full account page data
botsRouter.get('/:name/stats', async (c) => {
const name = c.req.param('name')
const ownerPubkey = c.req.query('pubkey')
const botRows = await db.select({
id: schema.bots.id,
name: schema.bots.name,
@@ -167,6 +168,11 @@ botsRouter.get('/:name/stats', async (c) => {
customization: schema.bots.customization,
botType: schema.bots.botType,
createdAt: schema.bots.createdAt,
satsWon: schema.bots.satsWon,
satsWagered: schema.bots.satsWagered,
publicKey: schema.bots.publicKey,
webhookUrl: schema.bots.webhookUrl,
consecutiveErrors: schema.bots.consecutiveErrors,
}).from(schema.bots).where(eq(sql`LOWER(${schema.bots.name})`, name.toLowerCase())).limit(1)
if (botRows.length === 0) {
@@ -249,8 +255,19 @@ botsRouter.get('/:name/stats', async (c) => {
eloRating: bot.eloRating,
}, allFights)
// Owner-only fields: webhook info (only if pubkey matches)
const isOwnerRequest = ownerPubkey && ownerPubkey === rawBot.publicKey
const ownerFields = isOwnerRequest ? {
webhookUrl: rawBot.webhookUrl,
consecutiveErrors: rawBot.consecutiveErrors ?? 0,
isHuman: rawBot.webhookUrl === 'http://human.local/',
} : {}
// Strip internal fields from public response
const { publicKey: _pk, webhookUrl: _wh, consecutiveErrors: _ce, ...publicBot } = bot
return c.json({
...bot,
...publicBot,
tierName: TIER_NAMES[bot.tier] || 'BABY',
tierColor: TIER_COLORS[bot.tier] || '#888',
winRate,
@@ -259,6 +276,7 @@ botsRouter.get('/:name/stats', async (c) => {
totalBots: rankedBots.length,
recentFights,
achievements,
...ownerFields,
})
})