Skip to content

Commit

Permalink
Whoops nope (#66)
Browse files Browse the repository at this point in the history
  • Loading branch information
vcarl authored Aug 22, 2024
1 parent e702271 commit b6eb6b0
Show file tree
Hide file tree
Showing 28 changed files with 534 additions and 1,959 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,7 @@ jobs:
--from-literal=DISCORD_APP_ID=${{ secrets.DISCORD_APP_ID }} \
--from-literal=DISCORD_SECRET=${{ secrets.DISCORD_SECRET }} \
--from-literal=DISCORD_HASH=${{ secrets.DISCORD_HASH }} \
--from-literal=DISCORD_TEST_GUILD=${{ secrets.DISCORD_TEST_GUILD }} \
--from-literal=DATABASE_URL=${{ secrets.DATABASE_URL }}
--from-literal=DISCORD_TEST_GUILD=${{ secrets.DISCORD_TEST_GUILD }}
kubectl apply -k .
- name: Set Sentry release
Expand Down
5 changes: 0 additions & 5 deletions .lintstagedrc.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
module.exports = {
"**/*.[tj]s?(x)": ["eslint --fix --max-warnings=0", "prettier --check"],
"migrations/*.[tj]s": [
"npm run start:migrate",
"npm run generate:db-types",
"git add app/db.d.ts",
],
};
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install

COPY remix.config.js tailwind.config.js kysely.config.ts tsconfig.json .eslint* .prettierignore ./
COPY remix.config.js tailwind.config.js knexfile.ts tsconfig.json .eslint* .prettierignore ./
COPY app ./app

RUN npm run build
Expand All @@ -22,7 +22,7 @@ RUN npm prune --production
COPY --from=build /app/build ./build
COPY --from=build /app/public ./public

COPY kysely.config.ts ./
COPY knexfile.ts ./
COPY migrations ./migrations

CMD ["npm", "run", "start"]
11 changes: 4 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
# Mod bot

This code powers the Euno bot on Discord.
# Jobs bot

Initial setup

Expand All @@ -13,7 +11,7 @@ yarn dev
Uses:

- [Remix](https://remix.run/docs/en/v1)
- [Kysely](https://kysely.dev/)
- [Knex](https://knexjs.org/)
- SQLite3 (with [better-sqlite3](http://npmjs.com/package/better-sqlite3))

Deployed with:
Expand All @@ -24,9 +22,8 @@ Deployed with:

Details:

migrations with `npm run start:migrate`. latest installed version is tracked in 2 tables of the sqlite data. schema changes must be done cautiously, should have a set up/tear down function tested before merging. Start a new migration with `npx kysely migrate:make <name>`
migrations with `yarn migrate:latest`. latest installed version is tracked in 2 tables of the sqlite data. schema changes must be done cautiously, should have a set up/tear down function tested before merging.

Migrations are stored in `migrations/`
Generated DB types are stored in `app/db.d.ts` and generated automatically in a precommit hook.
seed data is stored in seeds/

auth system is simple delegated auth to discord. accounts are created if not found locally, no passwords or secondary confirmation atm
2 changes: 1 addition & 1 deletion app/components/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function Login({ errors }: { errors?: { [k: string]: string } }) {
<input type="hidden" name="redirectTo" value={redirectTo} />
<button
type="submit"
className="w-full rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600 focus:bg-blue-400"
className="w-full rounded bg-blue-500 py-2 px-4 text-white hover:bg-blue-600 focus:bg-blue-400"
>
Log in with Discord
</button>
Expand Down
29 changes: 0 additions & 29 deletions app/db.d.ts

This file was deleted.

28 changes: 13 additions & 15 deletions app/db.server.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import SQLite from "better-sqlite3";
import { Kysely, SqliteDialect } from "kysely";
import type { DB } from "./db";
import { databaseUrl } from "./helpers/env";

import knex from "knex";
import knexfile from "~/../knexfile";
export { SqliteError } from "better-sqlite3";

export const dialect = new SqliteDialect({
database: new SQLite(databaseUrl),
});

const db = new Kysely<DB>({
dialect,
});

export default db;
export type { DB };
const environment = process.env.NODE_ENV || ("development" as const);
// @ts-nocheck
const config: {
client: string;
connection: {
filename: string;
};
useNullAsDefault: boolean;
} = (knexfile as any)[environment];

export default knex(config);
3 changes: 1 addition & 2 deletions app/discord/client.server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { GatewayIntentBits, Client, Partials, ActivityType } from "discord.js";
import { ReacordDiscordJs } from "reacord";
import { discordToken } from "~/helpers/env";

export const client = new Client({
intents: [
Expand All @@ -20,7 +19,7 @@ export const reacord = new ReacordDiscordJs(client);
export const login = () => {
console.log("INI", "Bootstrap starting…");
client
.login(discordToken)
.login(process.env.DISCORD_HASH || "")
.then(async () => {
console.log("INI", "Bootstrap complete");

Expand Down
19 changes: 3 additions & 16 deletions app/helpers/env.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
const enum ENVIRONMENTS {
const enum ENVIONMENTS {
production = "production",
test = "test",
local = "",
}

let ok = true;
const getEnv = (key: string, optional = false) => {
const value = process.env[key];
if (process.env.NODE_ENV === "test") {
return "";
}
if (!value && !optional) {
console.log(`Add a ${key} value to .env`);
ok = false;
Expand All @@ -18,18 +13,10 @@ const getEnv = (key: string, optional = false) => {
return value ?? "";
};

export const isProd = () => process.env.NODE_ENV === ENVIRONMENTS.production;
console.log(
"Running as",
isProd() ? "PRODUCTION" : "TEST",
`environment: '${process.env.NODE_ENV}'`,
);

export const databaseUrl = getEnv("DATABASE_URL");
export const sessionSecret = getEnv("SESSION_SECRET");
export const isProd = () => process.env.ENVIRONMENT === ENVIONMENTS.production;
console.log("Running as", isProd() ? "PRODUCTION" : "TEST", "environment");

export const applicationKey = getEnv("DISCORD_PUBLIC_KEY");
export const discordSecret = getEnv("DISCORD_SECRET");
export const applicationId = getEnv("DISCORD_APP_ID");
export const discordToken = getEnv("DISCORD_HASH");
export const testGuild = getEnv("DISCORD_TEST_GUILD");
Expand Down
16 changes: 2 additions & 14 deletions app/helpers/modLog.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type {
Message,
TextChannel,
MessageCreateOptions,
User,
ClientUser,
} from "discord.js";
import { ChannelType } from "discord.js";
import TTLCache from "@isaacs/ttlcache";

import { fetchSettings, SETTINGS } from "~/models/guilds.server";
Expand Down Expand Up @@ -113,15 +113,7 @@ export const reportUser = async ({
} else {
// If this is new, send a new message
const { modLog: modLogId } = await fetchSettings(guild, [SETTINGS.modLog]);
const modLog = await guild.channels.fetch(modLogId);
if (!modLog) {
throw new Error("Channel configured for use as mod log not found");
}
if (modLog.type !== ChannelType.GuildText) {
throw new Error(
"Invalid channel configured for use as mod log, must be guild text",
);
}
const modLog = (await guild.channels.fetch(modLogId)) as TextChannel;
const newLogs: Report[] = [{ message, reason, staff }];

const logBody = await constructLog({
Expand Down Expand Up @@ -161,10 +153,6 @@ const constructLog = async ({
SETTINGS.moderator,
]);

if (!moderator) {
throw new Error("No role configured to be used as moderator");
}

const preface = `<@${lastReport.message.author.id}> (${
lastReport.message.author.username
}) warned ${previousWarnings.size + 1} times recently, posted in ${
Expand Down
2 changes: 1 addition & 1 deletion app/helpers/modResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const humanReadableResolutions = {
[resolutions.kick]: "Kick",
[resolutions.ban]: "Ban",
} as const;
export type Resolution = (typeof resolutions)[keyof typeof resolutions];
export type Resolution = typeof resolutions[keyof typeof resolutions];

export const useVotes = () => {
const [votes, setVotes] = useState({} as Record<Resolution, string[]>);
Expand Down
59 changes: 23 additions & 36 deletions app/models/guilds.server.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import type { Guild as DiscordGuild } from "discord.js";
import db, { SqliteError } from "~/db.server";
import type { DB } from "~/db.server";
import knex, { SqliteError } from "~/db.server";

export type Guild = DB["guilds"];
type jsonString = string;
export interface Guild {
id: string;
settings: jsonString;
}

export const SETTINGS = {
modLog: "modLog",
Expand All @@ -19,21 +22,14 @@ interface SettingsRecord {
}

export const fetchGuild = async (guild: DiscordGuild) => {
return await db
.selectFrom("guilds")
.where("id", "=", guild.id)
.executeTakeFirst();
return await knex<Guild>("guilds").where({ id: guild.id }).first();
};

export const registerGuild = async (guild: DiscordGuild) => {
try {
await db
.insertInto("guilds")
.values({
id: guild.id,
settings: JSON.stringify({}),
})
.execute();
await knex("guilds").insert({
id: guild.id,
settings: {},
});
} catch (e) {
if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
// do nothing
Expand All @@ -47,30 +43,21 @@ export const setSettings = async (
guild: DiscordGuild,
settings: SettingsRecord,
) => {
await db
.updateTable("guilds")
.set("settings", (eb) =>
eb.fn("json_patch", ["settings", eb.val(JSON.stringify(settings))]),
)
.where("id", "=", guild.id)
.execute();
await Promise.all(
Object.entries(settings).map(([key, value]) =>
knex("guilds")
.update({ settings: knex.jsonSet("settings", `$.${key}`, value) })
.where({ id: guild.id }),
),
);
};

export const fetchSettings = async <T extends keyof typeof SETTINGS>(
guild: DiscordGuild,
keys: T[],
) => {
return (
(await db
.selectFrom("guilds")
// @ts-expect-error This is broken because of a migration from knex and
// old/bad use of jsonb for storing settings. The type is guaranteed here
// not by the codegen
.select<DB, "guilds", SettingsRecord>((eb) =>
keys.map((k) => eb.ref("settings", "->").key(k).as(k)),
)
.where("id", "=", guild.id)
// This cast is also evidence of the pattern being broken
.executeTakeFirstOrThrow()) as Pick<SettingsRecord, T>
);
): Promise<Pick<SettingsRecord, typeof keys[number]>> => {
return await knex("guilds")
.where({ id: guild.id })
.select(knex.jsonExtract(keys.map((k) => ["settings", `$.${k}`, k])))
.first();
};
Loading

0 comments on commit b6eb6b0

Please sign in to comment.