forked from syntaxbullet/aurorabot
feat: implement lootdrop management endpoints and fix class api types
This commit is contained in:
30
docs/api.md
30
docs/api.md
@@ -350,6 +350,36 @@ List economy transactions.
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## Lootdrops
|
||||
|
||||
### `GET /api/lootdrops`
|
||||
List lootdrops (default limit 50, sorted by newest).
|
||||
|
||||
| Query Param | Type | Description |
|
||||
|-------------|------|-------------|
|
||||
| `limit` | number | Max results (default: 50) |
|
||||
|
||||
**Response:** `{ "lootdrops": [...] }`
|
||||
|
||||
### `POST /api/lootdrops`
|
||||
Spawn a lootdrop in a channel.
|
||||
|
||||
**Body:**
|
||||
```json
|
||||
{
|
||||
"channelId": "1234567890",
|
||||
"amount": 100,
|
||||
"currency": "Gold"
|
||||
}
|
||||
```
|
||||
|
||||
### `DELETE /api/lootdrops/:messageId`
|
||||
Cancel and delete a lootdrop.
|
||||
|
||||
---
|
||||
|
||||
## Quests
|
||||
|
||||
### `GET /api/quests`
|
||||
|
||||
@@ -93,11 +93,11 @@ class LootdropService {
|
||||
}
|
||||
}
|
||||
|
||||
private async spawnLootdrop(channel: TextChannel) {
|
||||
public async spawnLootdrop(channel: TextChannel, overrideReward?: number, overrideCurrency?: string) {
|
||||
const min = config.lootdrop.reward.min;
|
||||
const max = config.lootdrop.reward.max;
|
||||
const reward = Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
const currency = config.lootdrop.reward.currency;
|
||||
const reward = overrideReward ?? (Math.floor(Math.random() * (max - min + 1)) + min);
|
||||
const currency = overrideCurrency ?? config.lootdrop.reward.currency;
|
||||
|
||||
const { content, files, components } = await getLootdropMessage(reward, currency);
|
||||
|
||||
@@ -205,6 +205,36 @@ class LootdropService {
|
||||
this.channelCooldowns.clear();
|
||||
console.log("[LootdropService] Caches cleared via administrative action.");
|
||||
}
|
||||
public async deleteLootdrop(messageId: string): Promise<boolean> {
|
||||
try {
|
||||
// First fetch it to get channel info so we can delete the message
|
||||
const drop = await DrizzleClient.query.lootdrops.findFirst({
|
||||
where: eq(lootdrops.messageId, messageId)
|
||||
});
|
||||
|
||||
if (!drop) return false;
|
||||
|
||||
// Delete from DB
|
||||
await DrizzleClient.delete(lootdrops).where(eq(lootdrops.messageId, messageId));
|
||||
|
||||
// Try to delete from Discord
|
||||
try {
|
||||
const { AuroraClient } = await import("../../../bot/lib/BotClient");
|
||||
const channel = await AuroraClient.channels.fetch(drop.channelId) as TextChannel;
|
||||
if (channel) {
|
||||
const message = await channel.messages.fetch(messageId);
|
||||
if (message) await message.delete();
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Could not delete lootdrop message from Discord:", e);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error deleting lootdrop:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const lootdropService = new LootdropService();
|
||||
|
||||
@@ -951,7 +951,7 @@ export async function createWebServer(config: WebServerConfig = {}): Promise<Web
|
||||
const { classService } = await import("@shared/modules/class/class.service");
|
||||
const data = await req.json() as Record<string, any>;
|
||||
|
||||
if (!data.id || !data.name) {
|
||||
if (!data.id || !data.name || typeof data.name !== 'string') {
|
||||
return Response.json(
|
||||
{ error: "Missing required fields: id and name are required" },
|
||||
{ status: 400 }
|
||||
@@ -1206,6 +1206,96 @@ export async function createWebServer(config: WebServerConfig = {}): Promise<Web
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// Lootdrops API
|
||||
// =====================================
|
||||
|
||||
// GET /api/lootdrops - List lootdrops
|
||||
if (url.pathname === "/api/lootdrops" && req.method === "GET") {
|
||||
try {
|
||||
const { lootdrops } = await import("../../shared/db/schema");
|
||||
const { DrizzleClient } = await import("@shared/db/DrizzleClient");
|
||||
const { desc } = await import("drizzle-orm");
|
||||
|
||||
const limit = url.searchParams.get("limit") ? parseInt(url.searchParams.get("limit")!) : 50;
|
||||
|
||||
const result = await DrizzleClient.select()
|
||||
.from(lootdrops)
|
||||
.orderBy(desc(lootdrops.createdAt))
|
||||
.limit(limit);
|
||||
|
||||
const { jsonReplacer } = await import("@shared/lib/utils");
|
||||
return new Response(JSON.stringify({ lootdrops: result }, jsonReplacer), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("web", "Error fetching lootdrops", error);
|
||||
return Response.json(
|
||||
{ error: "Failed to fetch lootdrops", details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// POST /api/lootdrops - Spawn lootdrop
|
||||
if (url.pathname === "/api/lootdrops" && req.method === "POST") {
|
||||
try {
|
||||
const { lootdropService } = await import("@shared/modules/economy/lootdrop.service");
|
||||
const { AuroraClient } = await import("../../bot/lib/BotClient");
|
||||
const { TextChannel } = await import("discord.js");
|
||||
|
||||
const data = await req.json() as Record<string, any>;
|
||||
|
||||
if (!data.channelId) {
|
||||
return Response.json(
|
||||
{ error: "Missing required field: channelId" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const channel = await AuroraClient.channels.fetch(data.channelId);
|
||||
|
||||
if (!channel || !(channel instanceof TextChannel)) {
|
||||
return Response.json(
|
||||
{ error: "Invalid channel. Must be a TextChannel." },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
await lootdropService.spawnLootdrop(channel, data.amount, data.currency);
|
||||
|
||||
return Response.json({ success: true }, { status: 201 });
|
||||
} catch (error) {
|
||||
logger.error("web", "Error spawning lootdrop", error);
|
||||
return Response.json(
|
||||
{ error: "Failed to spawn lootdrop", details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE /api/lootdrops/:id - Cancel/Delete lootdrop
|
||||
if (url.pathname.match(/^\/api\/lootdrops\/[^\/]+$/) && req.method === "DELETE") {
|
||||
const messageId = url.pathname.split("/").pop()!;
|
||||
|
||||
try {
|
||||
const { lootdropService } = await import("@shared/modules/economy/lootdrop.service");
|
||||
const success = await lootdropService.deleteLootdrop(messageId);
|
||||
|
||||
if (!success) {
|
||||
return Response.json({ error: "Lootdrop not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
return new Response(null, { status: 204 });
|
||||
} catch (error) {
|
||||
logger.error("web", "Error deleting lootdrop", error);
|
||||
return Response.json(
|
||||
{ error: "Failed to delete lootdrop", details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// No frontend - return 404 for unknown routes
|
||||
return new Response("Not Found", { status: 404 });
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user