diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 49d4e39..d3ea7b8 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -23,7 +23,7 @@ jobs: with: registry: ghcr.io username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} + password: ${{ secrets.CR_PAT }} - name: Build and push uses: docker/build-push-action@v4 diff --git a/Dockerfile b/Dockerfile index cd94c9a..c96d22f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,39 @@ -FROM oven/bun +# use the official Bun image +# see all versions at https://hub.docker.com/r/oven/bun/tags +FROM oven/bun:1 AS base +WORKDIR /usr/src/app -WORKDIR /app +# install dependencies into temp directory +# this will cache them and speed up future builds +FROM base AS install +RUN mkdir -p /temp/dev +COPY package.json bun.lockb /temp/dev/ +RUN cd /temp/dev && bun install --frozen-lockfile -COPY package.json . -COPY bun.lockb . +# install with --production (exclude devDependencies) +RUN mkdir -p /temp/prod +COPY package.json bun.lockb /temp/prod/ +RUN cd /temp/prod && bun install --frozen-lockfile --production -RUN bun install --production +# copy Prisma schema and generate Prisma client +COPY prisma /usr/src/app/prisma +COPY prisma /temp/prod/prisma +RUN cd /temp/prod && bunx prisma generate -COPY src src -COPY prisma prisma -COPY tsconfig.json . +# copy node_modules from temp directory +# then copy all (non-ignored) project files into the image +FROM base AS prerelease +COPY --from=install /temp/dev/node_modules node_modules +COPY . . -ENV NODE_ENV production -CMD ["bun", "src/index.ts"] +# copy production dependencies and source code into final image +FROM base AS release +COPY --from=install /temp/prod/node_modules node_modules +COPY --from=prerelease /usr/src/app/main.ts . +COPY --from=prerelease /usr/src/app/package.json . +COPY --from=prerelease /usr/src/app/prisma ./prisma -EXPOSE 3000 \ No newline at end of file +# run the app +USER bun +EXPOSE 3000/tcp +ENTRYPOINT [ "bun", "run", "src/index.ts" ] \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index a9f30d1..52a8796 100644 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index d284601..54d55ed 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,11 @@ "typescript": "^5.0.0" }, "dependencies": { + "@baselime/pino-transport": "^0.1.5", "@biomejs/biome": "^1.8.2", "@prisma/client": "^5.15.1", "discord.js": "^14.15.3", - "elysia": "^1.1.5" + "elysia": "^1.1.5", + "pino": "^9.5.0" } } diff --git a/src/api/server.ts b/src/api/server.ts index d0e04d6..6cf0103 100644 --- a/src/api/server.ts +++ b/src/api/server.ts @@ -38,7 +38,11 @@ app.post("/accLink", async ({ query, body }) => { }, }); - await dmUser(uid, "MikanDev Accounts", `Your account has been linked!\n\n**MikanDev UID:** ${acc}\n**Discord ID:**${uid}`); + await dmUser( + uid, + "MikanDev Accounts", + `Your account has been linked!\n\n**MikanDev UID:** ${acc}\n**Discord ID:**${uid}`, + ); return new Response("Account linked", { status: 200 }); }); diff --git a/src/commands/rank.ts b/src/commands/rank.ts index 0ef378b..d0380fa 100644 --- a/src/commands/rank.ts +++ b/src/commands/rank.ts @@ -1,6 +1,5 @@ import { type CommandInteraction, AttachmentBuilder } from "discord.js"; import { PrismaClient } from "@prisma/client"; -import { start } from "../api/server"; const prisma = new PrismaClient(); diff --git a/src/handlers/command.ts b/src/handlers/command.ts index d23bc8e..c6ddfd5 100644 --- a/src/handlers/command.ts +++ b/src/handlers/command.ts @@ -1,8 +1,31 @@ -import type { CommandInteraction } from "discord.js"; +import { + WebhookClient, + EmbedBuilder, + type CommandInteraction, +} from "discord.js"; +import crypto from "node:crypto"; import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient(); +const logginWebhook = new WebhookClient({ + url: process.env.LOGGING_WEBHOOK_URL as string, +}); + +const sendLog = async (message: string) => { + const logId = crypto.randomBytes(4).toString("hex"); + const embed = new EmbedBuilder() + .setTitle(`Error Log`) + .setDescription(`\`\`\`ts\n${message}\n\`\`\``) + .setColor("#FF0000") + .setTimestamp(); + + await logginWebhook + .send({ content: `EID ${logId}`, embeds: [embed] }) + .catch(console.error); + return logId; +}; + export async function handleCommand(interaction: CommandInteraction) { try { const userDb = await prisma.user.findUnique({ @@ -33,10 +56,15 @@ export async function handleCommand(interaction: CommandInteraction) { ); if (command.slashCommand.enabled) command.interactionRun(interaction); } catch (error) { - console.error(error); - interaction.reply({ - content: "There was an error while executing this command!", - ephemeral: true, - }); + const logId = await sendLog(error.message); + console.error(`Error while executing command: ${logId}`); + const errorEmbed = new EmbedBuilder() + .setTitle("An error occurred while executing this command!") + .setDescription( + `An error occurred while executing this command.\n\nEID ${logId}\n\nPlease contact support if this persists.`, + ) + .setColor("#FF0000") + .setTimestamp(); + await interaction.reply({ embeds: [errorEmbed], ephemeral: true }); } } diff --git a/src/index.ts b/src/index.ts index 1c7faaa..d75efbd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -39,7 +39,11 @@ client.on("ready", () => { client.on("interactionCreate", async (interaction) => { if (interaction.isCommand()) { console.log(`Received command: ${interaction.commandName}`); - await handleCommand(interaction); + try { + await handleCommand(interaction); + } catch (e) { + console.error(e); + } } if (interaction.isButton()) { console.log(`Received button: ${interaction.customId}`);