mirror of
https://github.com/xCyanGrizzly/DragonsStash.git
synced 2026-05-11 14:21:15 +00:00
feat: Docker audit + Telegram bot service + send UI
Docker:
- Harden docker-compose.yml: parameterized DB creds, required AUTH_SECRET,
health checks, resource limits, network isolation, removed exposed DB port
- Add profiles (telegram/bot/full) so base 'docker compose up' needs only AUTH_SECRET
- Fix docker-entrypoint.sh: AUTH_SECRET startup guard
- Fix Dockerfile: copy prisma.config.ts + dotenv into production image
- Update .env.example with all new variables
- Update .dockerignore
Telegram Bot Service (bot/):
- TDLib-based bot using bot token auth (not HTTP Bot API)
- Commands: /search, /latest, /package, /link, /unlink, /subscribe, /unsubscribe
- pg_notify listener for send requests (bot_send) and new packages (new_package)
- Subscription-based notifications when matching packages arrive
- Dockerfile with multi-stage build (bookworm-slim for glibc/TDLib)
API & Database:
- Prisma: TelegramLink, BotSendRequest, BotSubscription models + migration
- POST /api/telegram/bot/send - queue package delivery to linked TG account
- GET /api/telegram/bot/send/[id] - poll send request status
- Server actions: generateTelegramLinkCode, unlinkTelegram, getBotSendHistory
- Worker: emit pg_notify('new_package') after creating packages
Frontend:
- Settings: TelegramLinkCard for account linking via one-time code
- STL table + drawer: SendToTelegramButton with send dialog and status polling
- Telegram admin: Bot Sends tab with delivery history table
- Shared SendHistoryRow type
README: Updated with bot docs, profiles, config vars, project structure
This commit is contained in:
180
bot/src/db/queries.ts
Normal file
180
bot/src/db/queries.ts
Normal file
@@ -0,0 +1,180 @@
|
||||
import { db } from "./client.js";
|
||||
|
||||
// ── Link management ──
|
||||
|
||||
export async function findLinkByTelegramUserId(telegramUserId: bigint) {
|
||||
return db.telegramLink.findUnique({
|
||||
where: { telegramUserId },
|
||||
});
|
||||
}
|
||||
|
||||
export async function findLinkByUserId(userId: string) {
|
||||
return db.telegramLink.findUnique({
|
||||
where: { userId },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a link code stored in global_settings as `link_code:<code>`.
|
||||
* Returns the userId if the code is valid, null otherwise.
|
||||
*/
|
||||
export async function validateLinkCode(code: string): Promise<string | null> {
|
||||
const key = `link_code:${code}`;
|
||||
const setting = await db.globalSetting.findUnique({ where: { key } });
|
||||
return setting?.value ?? null;
|
||||
}
|
||||
|
||||
export async function deleteLinkCode(code: string): Promise<void> {
|
||||
const key = `link_code:${code}`;
|
||||
await db.globalSetting.delete({ where: { key } }).catch(() => {});
|
||||
}
|
||||
|
||||
export async function createTelegramLink(
|
||||
userId: string,
|
||||
telegramUserId: bigint,
|
||||
telegramName: string | null
|
||||
) {
|
||||
return db.telegramLink.upsert({
|
||||
where: { userId },
|
||||
create: { userId, telegramUserId, telegramName },
|
||||
update: { telegramUserId, telegramName },
|
||||
});
|
||||
}
|
||||
|
||||
// ── Package search ──
|
||||
|
||||
export async function searchPackages(query: string, limit = 10) {
|
||||
const packages = await db.package.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ fileName: { contains: query, mode: "insensitive" } },
|
||||
{ creator: { contains: query, mode: "insensitive" } },
|
||||
],
|
||||
},
|
||||
orderBy: { indexedAt: "desc" },
|
||||
take: limit,
|
||||
select: {
|
||||
id: true,
|
||||
fileName: true,
|
||||
fileSize: true,
|
||||
archiveType: true,
|
||||
fileCount: true,
|
||||
creator: true,
|
||||
indexedAt: true,
|
||||
destChannelId: true,
|
||||
destMessageId: true,
|
||||
},
|
||||
});
|
||||
return packages;
|
||||
}
|
||||
|
||||
export async function getLatestPackages(limit = 5) {
|
||||
return db.package.findMany({
|
||||
orderBy: { indexedAt: "desc" },
|
||||
take: limit,
|
||||
select: {
|
||||
id: true,
|
||||
fileName: true,
|
||||
fileSize: true,
|
||||
archiveType: true,
|
||||
fileCount: true,
|
||||
creator: true,
|
||||
indexedAt: true,
|
||||
destChannelId: true,
|
||||
destMessageId: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getPackageById(id: string) {
|
||||
return db.package.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
files: { take: 20, orderBy: { path: "asc" } },
|
||||
sourceChannel: { select: { title: true } },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// ── Send requests ──
|
||||
|
||||
export async function getPendingSendRequest(requestId: string) {
|
||||
return db.botSendRequest.findUnique({
|
||||
where: { id: requestId },
|
||||
include: {
|
||||
package: {
|
||||
select: {
|
||||
id: true,
|
||||
fileName: true,
|
||||
destChannelId: true,
|
||||
destMessageId: true,
|
||||
previewData: true,
|
||||
},
|
||||
},
|
||||
telegramLink: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateSendRequest(
|
||||
requestId: string,
|
||||
status: "SENDING" | "SENT" | "FAILED",
|
||||
error?: string
|
||||
) {
|
||||
return db.botSendRequest.update({
|
||||
where: { id: requestId },
|
||||
data: {
|
||||
status,
|
||||
error: error ?? undefined,
|
||||
completedAt: status === "SENT" || status === "FAILED" ? new Date() : undefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// ── Subscriptions ──
|
||||
|
||||
export async function getSubscriptions(telegramUserId: bigint) {
|
||||
return db.botSubscription.findMany({
|
||||
where: { telegramUserId },
|
||||
orderBy: { createdAt: "desc" },
|
||||
});
|
||||
}
|
||||
|
||||
export async function addSubscription(telegramUserId: bigint, pattern: string) {
|
||||
return db.botSubscription.upsert({
|
||||
where: {
|
||||
telegramUserId_pattern: { telegramUserId, pattern },
|
||||
},
|
||||
create: { telegramUserId, pattern },
|
||||
update: {},
|
||||
});
|
||||
}
|
||||
|
||||
export async function removeSubscription(telegramUserId: bigint, pattern: string) {
|
||||
return db.botSubscription.deleteMany({
|
||||
where: { telegramUserId, pattern },
|
||||
});
|
||||
}
|
||||
|
||||
export async function findMatchingSubscriptions(fileName: string, creator: string | null) {
|
||||
// Get all subscriptions and filter in-memory (simpler for pattern matching)
|
||||
const subs = await db.botSubscription.findMany();
|
||||
return subs.filter((sub) => {
|
||||
const p = sub.pattern.toLowerCase();
|
||||
if (fileName.toLowerCase().includes(p)) return true;
|
||||
if (creator && creator.toLowerCase().includes(p)) return true;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// ── Destination channel ──
|
||||
|
||||
export async function getGlobalDestinationChannel() {
|
||||
const setting = await db.globalSetting.findUnique({
|
||||
where: { key: "destination_channel_id" },
|
||||
});
|
||||
if (!setting) return null;
|
||||
return db.telegramChannel.findFirst({
|
||||
where: { id: setting.value, type: "DESTINATION", isActive: true },
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user