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:
co-authored by
Claude Opus 4.6
parent
2d8cdcc60a
commit
9a11c48487
@@ -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,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user