Add new admin pages and update XP configuration endpoint

Introduces new API endpoints for XP configuration and quests, along with frontend updates in dashboard.html for styling and field name consistency.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Event-Id: 4b20cda7-6144-4154-8c0b-880fe406cb59
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/xfdSNeM
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
sirpiglr 2025-12-09 03:04:32 +00:00
parent c73092d455
commit cbb7619552
3 changed files with 1645 additions and 16 deletions

View file

@ -21,6 +21,10 @@ externalPort = 80
localPort = 8080
externalPort = 8080
[[ports]]
localPort = 36555
externalPort = 3000
[workflows]
runButton = "Project"

File diff suppressed because it is too large Load diff

View file

@ -481,6 +481,550 @@ function createWebServer(discordClient, supabase, options = {}) {
}
});
app.post('/api/guild/:guildId/xp-config', async (req, res) => {
if (!supabase) {
return res.status(503).json({ error: 'Database not available' });
}
const userId = req.session.user?.id;
if (!userId) {
return res.status(401).json({ error: 'Not authenticated' });
}
const { guildId } = req.params;
const userGuild = req.session.user.guilds.find(g => g.id === guildId);
if (!userGuild || !userGuild.isAdmin) {
return res.status(403).json({ error: 'No admin access to this server' });
}
const {
message_xp_min,
message_xp_max,
message_cooldown,
reaction_xp,
voice_xp_per_minute,
daily_xp,
xp_base,
levelup_channel_id,
levelup_enabled,
levelup_dm,
xp_multiplier,
weekend_multiplier
} = req.body;
try {
const { data: existing } = await supabase
.from('xp_config')
.select('id')
.eq('guild_id', guildId)
.maybeSingle();
const configData = {
guild_id: guildId,
message_xp_min: message_xp_min ?? 15,
message_xp_max: message_xp_max ?? 25,
message_cooldown: message_cooldown ?? 60,
reaction_xp: reaction_xp ?? 5,
voice_xp_per_minute: voice_xp_per_minute ?? 2,
daily_xp: daily_xp ?? 100,
xp_base: xp_base ?? 100,
levelup_channel_id: levelup_channel_id || null,
levelup_enabled: levelup_enabled !== false,
levelup_dm: levelup_dm === true,
xp_multiplier: xp_multiplier ?? 1.0,
weekend_multiplier: weekend_multiplier ?? 1.5,
updated_at: new Date().toISOString()
};
if (existing) {
const { error } = await supabase
.from('xp_config')
.update(configData)
.eq('guild_id', guildId);
if (error) throw error;
} else {
configData.created_at = new Date().toISOString();
const { error } = await supabase
.from('xp_config')
.insert(configData);
if (error) throw error;
}
res.json({ success: true });
} catch (error) {
console.error('Failed to save XP config:', error);
res.status(500).json({ error: 'Failed to save settings' });
}
});
app.get('/api/guild/:guildId/quests', async (req, res) => {
if (!supabase) {
return res.status(503).json({ error: 'Database not available' });
}
const userId = req.session.user?.id;
if (!userId) {
return res.status(401).json({ error: 'Not authenticated' });
}
const { guildId } = req.params;
const userGuild = req.session.user.guilds.find(g => g.id === guildId);
if (!userGuild || !userGuild.isAdmin) {
return res.status(403).json({ error: 'No admin access' });
}
try {
const { data: quests } = await supabase
.from('quests')
.select('*')
.eq('guild_id', guildId)
.order('created_at', { ascending: false });
res.json({ quests: quests || [] });
} catch (error) {
res.status(500).json({ error: 'Failed to fetch quests' });
}
});
app.post('/api/guild/:guildId/quests', async (req, res) => {
if (!supabase) {
return res.status(503).json({ error: 'Database not available' });
}
const userId = req.session.user?.id;
if (!userId) {
return res.status(401).json({ error: 'Not authenticated' });
}
const { guildId } = req.params;
const userGuild = req.session.user.guilds.find(g => g.id === guildId);
if (!userGuild || !userGuild.isAdmin) {
return res.status(403).json({ error: 'No admin access' });
}
const { name, description, quest_type, objective, target_value, xp_reward, duration_hours, repeatable, active } = req.body;
try {
const questData = {
guild_id: guildId,
name,
description: description || null,
quest_type: quest_type || 'daily',
objective: objective || 'messages',
target_value: target_value || 10,
xp_reward: xp_reward || 100,
repeatable: repeatable || false,
active: active !== false,
created_at: new Date().toISOString()
};
if (duration_hours && duration_hours > 0) {
const expiry = new Date();
expiry.setHours(expiry.getHours() + duration_hours);
questData.expires_at = expiry.toISOString();
}
const { error } = await supabase.from('quests').insert(questData);
if (error) throw error;
res.json({ success: true });
} catch (error) {
console.error('Failed to create quest:', error);
res.status(500).json({ error: 'Failed to create quest' });
}
});
app.put('/api/guild/:guildId/quests/:questId', async (req, res) => {
if (!supabase) {
return res.status(503).json({ error: 'Database not available' });
}
const userId = req.session.user?.id;
if (!userId) {
return res.status(401).json({ error: 'Not authenticated' });
}
const { guildId, questId } = req.params;
const userGuild = req.session.user.guilds.find(g => g.id === guildId);
if (!userGuild || !userGuild.isAdmin) {
return res.status(403).json({ error: 'No admin access' });
}
const { name, description, quest_type, objective, target_value, xp_reward, repeatable, active } = req.body;
try {
const { error } = await supabase
.from('quests')
.update({
name,
description: description || null,
quest_type,
objective,
target_value,
xp_reward,
repeatable: repeatable || false,
active
})
.eq('id', questId)
.eq('guild_id', guildId);
if (error) throw error;
res.json({ success: true });
} catch (error) {
console.error('Failed to update quest:', error);
res.status(500).json({ error: 'Failed to update quest' });
}
});
app.delete('/api/guild/:guildId/quests/:questId', async (req, res) => {
if (!supabase) {
return res.status(503).json({ error: 'Database not available' });
}
const userId = req.session.user?.id;
if (!userId) {
return res.status(401).json({ error: 'Not authenticated' });
}
const { guildId, questId } = req.params;
const userGuild = req.session.user.guilds.find(g => g.id === guildId);
if (!userGuild || !userGuild.isAdmin) {
return res.status(403).json({ error: 'No admin access' });
}
try {
const { error } = await supabase
.from('quests')
.delete()
.eq('id', questId)
.eq('guild_id', guildId);
if (error) throw error;
res.json({ success: true });
} catch (error) {
console.error('Failed to delete quest:', error);
res.status(500).json({ error: 'Failed to delete quest' });
}
});
// ============ ACHIEVEMENTS ADMIN API ============
app.get('/api/guild/:guildId/achievements', async (req, res) => {
if (!supabase) {
return res.status(503).json({ error: 'Database not available' });
}
const userId = req.session.user?.id;
if (!userId) {
return res.status(401).json({ error: 'Not authenticated' });
}
const { guildId } = req.params;
const userGuild = req.session.user.guilds.find(g => g.id === guildId);
if (!userGuild || !userGuild.isAdmin) {
return res.status(403).json({ error: 'No admin access' });
}
try {
const { data: achievements } = await supabase
.from('achievements')
.select('*')
.eq('guild_id', guildId)
.order('created_at', { ascending: false });
res.json({ achievements: achievements || [] });
} catch (error) {
console.error('Failed to fetch achievements:', error);
res.status(500).json({ error: 'Failed to fetch achievements' });
}
});
app.post('/api/guild/:guildId/achievements', async (req, res) => {
if (!supabase) {
return res.status(503).json({ error: 'Database not available' });
}
const userId = req.session.user?.id;
if (!userId) {
return res.status(401).json({ error: 'Not authenticated' });
}
const { guildId } = req.params;
const userGuild = req.session.user.guilds.find(g => g.id === guildId);
if (!userGuild || !userGuild.isAdmin) {
return res.status(403).json({ error: 'No admin access' });
}
const { name, icon, description, trigger_type, trigger_value, reward_xp, reward_role_id, hidden } = req.body;
try {
const achievementData = {
guild_id: guildId,
name,
icon: icon || '🏆',
description: description || null,
trigger_type: trigger_type || 'level',
trigger_value: trigger_value || 1,
reward_xp: reward_xp || 0,
reward_role_id: reward_role_id || null,
hidden: hidden || false,
created_at: new Date().toISOString()
};
const { error } = await supabase.from('achievements').insert(achievementData);
if (error) throw error;
res.json({ success: true });
} catch (error) {
console.error('Failed to create achievement:', error);
res.status(500).json({ error: 'Failed to create achievement' });
}
});
app.put('/api/guild/:guildId/achievements/:achievementId', async (req, res) => {
if (!supabase) {
return res.status(503).json({ error: 'Database not available' });
}
const userId = req.session.user?.id;
if (!userId) {
return res.status(401).json({ error: 'Not authenticated' });
}
const { guildId, achievementId } = req.params;
const userGuild = req.session.user.guilds.find(g => g.id === guildId);
if (!userGuild || !userGuild.isAdmin) {
return res.status(403).json({ error: 'No admin access' });
}
const { name, icon, description, trigger_type, trigger_value, reward_xp, reward_role_id, hidden } = req.body;
try {
const { error } = await supabase
.from('achievements')
.update({
name,
icon: icon || '🏆',
description: description || null,
trigger_type,
trigger_value,
reward_xp: reward_xp || 0,
reward_role_id: reward_role_id || null,
hidden: hidden || false
})
.eq('id', achievementId)
.eq('guild_id', guildId);
if (error) throw error;
res.json({ success: true });
} catch (error) {
console.error('Failed to update achievement:', error);
res.status(500).json({ error: 'Failed to update achievement' });
}
});
app.delete('/api/guild/:guildId/achievements/:achievementId', async (req, res) => {
if (!supabase) {
return res.status(503).json({ error: 'Database not available' });
}
const userId = req.session.user?.id;
if (!userId) {
return res.status(401).json({ error: 'Not authenticated' });
}
const { guildId, achievementId } = req.params;
const userGuild = req.session.user.guilds.find(g => g.id === guildId);
if (!userGuild || !userGuild.isAdmin) {
return res.status(403).json({ error: 'No admin access' });
}
try {
const { error } = await supabase
.from('achievements')
.delete()
.eq('id', achievementId)
.eq('guild_id', guildId);
if (error) throw error;
res.json({ success: true });
} catch (error) {
console.error('Failed to delete achievement:', error);
res.status(500).json({ error: 'Failed to delete achievement' });
}
});
// ============ SHOP ADMIN API ============
app.get('/api/guild/:guildId/shop/admin', async (req, res) => {
if (!supabase) {
return res.status(503).json({ error: 'Database not available' });
}
const userId = req.session.user?.id;
if (!userId) {
return res.status(401).json({ error: 'Not authenticated' });
}
const { guildId } = req.params;
const userGuild = req.session.user.guilds.find(g => g.id === guildId);
if (!userGuild || !userGuild.isAdmin) {
return res.status(403).json({ error: 'No admin access' });
}
try {
const { data: items } = await supabase
.from('shop_items')
.select('*')
.eq('guild_id', guildId)
.order('created_at', { ascending: false });
res.json({ items: items || [] });
} catch (error) {
console.error('Failed to fetch shop items:', error);
res.status(500).json({ error: 'Failed to fetch shop items' });
}
});
app.post('/api/guild/:guildId/shop', async (req, res) => {
if (!supabase) {
return res.status(503).json({ error: 'Database not available' });
}
const userId = req.session.user?.id;
if (!userId) {
return res.status(401).json({ error: 'Not authenticated' });
}
const { guildId } = req.params;
const userGuild = req.session.user.guilds.find(g => g.id === guildId);
if (!userGuild || !userGuild.isAdmin) {
return res.status(403).json({ error: 'No admin access' });
}
const { name, item_type, description, price, stock, level_required, prestige_required, available } = req.body;
try {
const itemData = {
guild_id: guildId,
name,
item_type: item_type || 'cosmetic',
description: description || null,
price: price || 100,
stock: stock || null,
level_required: level_required || 0,
prestige_required: prestige_required || 0,
available: available !== false,
created_at: new Date().toISOString()
};
const { error } = await supabase.from('shop_items').insert(itemData);
if (error) throw error;
res.json({ success: true });
} catch (error) {
console.error('Failed to create shop item:', error);
res.status(500).json({ error: 'Failed to create shop item' });
}
});
app.put('/api/guild/:guildId/shop/:itemId', async (req, res) => {
if (!supabase) {
return res.status(503).json({ error: 'Database not available' });
}
const userId = req.session.user?.id;
if (!userId) {
return res.status(401).json({ error: 'Not authenticated' });
}
const { guildId, itemId } = req.params;
const userGuild = req.session.user.guilds.find(g => g.id === guildId);
if (!userGuild || !userGuild.isAdmin) {
return res.status(403).json({ error: 'No admin access' });
}
const { name, item_type, description, price, stock, level_required, prestige_required, available } = req.body;
try {
const { error } = await supabase
.from('shop_items')
.update({
name,
item_type: item_type || 'cosmetic',
description: description || null,
price: price || 100,
stock: stock || null,
level_required: level_required || 0,
prestige_required: prestige_required || 0,
available: available !== false
})
.eq('id', itemId)
.eq('guild_id', guildId);
if (error) throw error;
res.json({ success: true });
} catch (error) {
console.error('Failed to update shop item:', error);
res.status(500).json({ error: 'Failed to update shop item' });
}
});
app.delete('/api/guild/:guildId/shop/:itemId', async (req, res) => {
if (!supabase) {
return res.status(503).json({ error: 'Database not available' });
}
const userId = req.session.user?.id;
if (!userId) {
return res.status(401).json({ error: 'Not authenticated' });
}
const { guildId, itemId } = req.params;
const userGuild = req.session.user.guilds.find(g => g.id === guildId);
if (!userGuild || !userGuild.isAdmin) {
return res.status(403).json({ error: 'No admin access' });
}
try {
const { error } = await supabase
.from('shop_items')
.delete()
.eq('id', itemId)
.eq('guild_id', guildId);
if (error) throw error;
res.json({ success: true });
} catch (error) {
console.error('Failed to delete shop item:', error);
res.status(500).json({ error: 'Failed to delete shop item' });
}
});
app.get('/health', (req, res) => {
res.json({
status: 'ok',