diff --git a/.docker/config/config.yml b/.docker/config/config.yml
deleted file mode 100644
index 35e2962..0000000
--- a/.docker/config/config.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-mediaManagers:
- - id: "sonarr"
- enabled: true
- type: "sonarr"
- apiUrl: "http://sonarr:8989"
- apiKey: "secretsecretsecret99"
-discord:
- channelId: "1285714063535378522"
diff --git a/.docker/sonarr/config.xml b/.docker/sonarr/config.xml
deleted file mode 100644
index fa5f6b0..0000000
--- a/.docker/sonarr/config.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
- *
- 8989
- 9898
- False
- True
- secret
- main
- debug
-
-
-
- Sonarr
- Docker
- DisabledForLocalAddresses
- Basic
-
\ No newline at end of file
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..b0682f2
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,3 @@
+node_modules
+./config.yml
+.docker
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index b5f70c8..79ba49c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,6 @@
node_modules
dist
-.docker/postgres
+.docker
/config.yml
.env
.env.test
diff --git a/docker-compose.test.yml b/docker-compose.test.yml
index 2e1f8c5..5883b71 100644
--- a/docker-compose.test.yml
+++ b/docker-compose.test.yml
@@ -10,14 +10,13 @@ services:
- NODE_ENV=production
- DATABASE_URL=postgresql://postgres:postgres@db:5432/postgres
- CONFIG_PATH=/config/config.yml
- - DEBUG=true
- - SKIP_MIGRATIONS=true
+ # - DEBUG=true
volumes:
- - .docker/config:/config
+ - ./config.yml:/config/config.yml
restart: unless-stopped
db:
- image: postgres:13-alpine
+ image: postgres:16-alpine
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
@@ -26,18 +25,5 @@ services:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
- sonarr:
- image: lscr.io/linuxserver/sonarr:latest
- container_name: sonarr
- environment:
- - PUID=1000
- - PGID=1000
- - TZ=Etc/UTC
- volumes:
- - .docker/sonarr/config.xml:/config/config.xml:rw
- ports:
- - 8989:8989
- restart: unless-stopped
-
volumes:
postgres_data:
diff --git a/src/db/index.ts b/src/db/index.ts
index 781c25a..cd38029 100644
--- a/src/db/index.ts
+++ b/src/db/index.ts
@@ -17,19 +17,21 @@ export const db = drizzle(client, {
},
});
-async function main() {
+export async function runMigrations() {
if (env.NODE_ENV === "production") {
- // Run migrations when deployed
if (env.SKIP_MIGRATIONS) {
+ console.log("🙈 Skipping migrations");
return;
}
const migrationClient = postgres(env.DATABASE_URL);
const db = drizzle(migrationClient);
- console.log("Running migrations...");
- await migrate(db, { migrationsFolder: "./src/server/db/migrations" });
+ console.log("🔨 Running migrations...");
+ await migrate(db, { migrationsFolder: "./src/db/migrations" });
await migrationClient.end();
- console.log("Migrations complete!");
+ console.log("✅ Migrations complete!");
+ } else {
+ console.log(
+ "🏃♂️ Skipping migrations in dev mode - run manually with 'pnpm db:push'",
+ );
}
}
-
-void main();
diff --git a/src/db/migrations/0000_dry_sage.sql b/src/db/migrations/0000_dry_sage.sql
new file mode 100644
index 0000000..7c39d60
--- /dev/null
+++ b/src/db/migrations/0000_dry_sage.sql
@@ -0,0 +1,83 @@
+DO $$ BEGIN
+ CREATE TYPE "public"."manager_type" AS ENUM('radarr', 'sonarr');
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ CREATE TYPE "public"."vote_outcome" AS ENUM('keep', 'delete');
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "media_items" (
+ "id" varchar PRIMARY KEY NOT NULL,
+ "manager_id" integer NOT NULL,
+ "manager_type" "manager_type" NOT NULL,
+ "manager_config_id" varchar NOT NULL,
+ "title" varchar NOT NULL,
+ "size_on_disk" numeric DEFAULT '0' NOT NULL,
+ "release_date" timestamp NOT NULL,
+ "year" integer,
+ "has_file" boolean DEFAULT false,
+ "imdb_id" varchar,
+ "tmdb_id" integer,
+ "rating" real,
+ "added_to_manager" timestamp NOT NULL,
+ "path_on_disk" varchar,
+ "image" varchar,
+ "created_at" timestamp DEFAULT now() NOT NULL,
+ "updated_at" timestamp DEFAULT now(),
+ CONSTRAINT "media_items_manager_id_manager_config_id_unique" UNIQUE("manager_id","manager_config_id")
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "subscribers" (
+ "discord_user_id" varchar PRIMARY KEY NOT NULL,
+ "created_at" timestamp DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "items_to_delete" (
+ "id" varchar PRIMARY KEY NOT NULL,
+ "delete_after" timestamp NOT NULL,
+ "deleted_at" timestamp,
+ "created_at" timestamp DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "voting_sessions" (
+ "id" varchar PRIMARY KEY NOT NULL,
+ "discord_message_id" varchar NOT NULL,
+ "created_at" timestamp DEFAULT now() NOT NULL,
+ "ends_at" timestamp NOT NULL,
+ "handled" boolean DEFAULT false NOT NULL,
+ "vote_outcome" "vote_outcome"
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "whitelist" (
+ "id" varchar PRIMARY KEY NOT NULL,
+ "created_at" timestamp DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "items_to_delete" ADD CONSTRAINT "items_to_delete_id_media_items_id_fk" FOREIGN KEY ("id") REFERENCES "public"."media_items"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "voting_sessions" ADD CONSTRAINT "voting_sessions_id_media_items_id_fk" FOREIGN KEY ("id") REFERENCES "public"."media_items"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "whitelist" ADD CONSTRAINT "whitelist_id_media_items_id_fk" FOREIGN KEY ("id") REFERENCES "public"."media_items"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+CREATE INDEX IF NOT EXISTS "manager_id_idx" ON "media_items" USING btree ("manager_id");--> statement-breakpoint
+CREATE INDEX IF NOT EXISTS "manager_type_idx" ON "media_items" USING btree ("manager_type");--> statement-breakpoint
+CREATE INDEX IF NOT EXISTS "manager_config_id_idx" ON "media_items" USING btree ("manager_config_id");--> statement-breakpoint
+CREATE INDEX IF NOT EXISTS "added_to_manager_idx" ON "media_items" USING btree ("added_to_manager");--> statement-breakpoint
+CREATE INDEX IF NOT EXISTS "discord_message_id_idx" ON "voting_sessions" USING btree ("discord_message_id");--> statement-breakpoint
+CREATE INDEX IF NOT EXISTS "handled_idx" ON "voting_sessions" USING btree ("handled");
\ No newline at end of file
diff --git a/src/db/migrations/meta/0000_snapshot.json b/src/db/migrations/meta/0000_snapshot.json
new file mode 100644
index 0000000..d4f780a
--- /dev/null
+++ b/src/db/migrations/meta/0000_snapshot.json
@@ -0,0 +1,423 @@
+{
+ "id": "8c9ea751-7eea-48ac-8563-8d43a1e5725f",
+ "prevId": "00000000-0000-0000-0000-000000000000",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.media_items": {
+ "name": "media_items",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "manager_id": {
+ "name": "manager_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "manager_type": {
+ "name": "manager_type",
+ "type": "manager_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "manager_config_id": {
+ "name": "manager_config_id",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "size_on_disk": {
+ "name": "size_on_disk",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "release_date": {
+ "name": "release_date",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "year": {
+ "name": "year",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "has_file": {
+ "name": "has_file",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "imdb_id": {
+ "name": "imdb_id",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tmdb_id": {
+ "name": "tmdb_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rating": {
+ "name": "rating",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "added_to_manager": {
+ "name": "added_to_manager",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "path_on_disk": {
+ "name": "path_on_disk",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image": {
+ "name": "image",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "manager_id_idx": {
+ "name": "manager_id_idx",
+ "columns": [
+ {
+ "expression": "manager_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "manager_type_idx": {
+ "name": "manager_type_idx",
+ "columns": [
+ {
+ "expression": "manager_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "manager_config_id_idx": {
+ "name": "manager_config_id_idx",
+ "columns": [
+ {
+ "expression": "manager_config_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "added_to_manager_idx": {
+ "name": "added_to_manager_idx",
+ "columns": [
+ {
+ "expression": "added_to_manager",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "media_items_manager_id_manager_config_id_unique": {
+ "name": "media_items_manager_id_manager_config_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "manager_id",
+ "manager_config_id"
+ ]
+ }
+ }
+ },
+ "public.subscribers": {
+ "name": "subscribers",
+ "schema": "",
+ "columns": {
+ "discord_user_id": {
+ "name": "discord_user_id",
+ "type": "varchar",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.items_to_delete": {
+ "name": "items_to_delete",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "delete_after": {
+ "name": "delete_after",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "items_to_delete_id_media_items_id_fk": {
+ "name": "items_to_delete_id_media_items_id_fk",
+ "tableFrom": "items_to_delete",
+ "tableTo": "media_items",
+ "columnsFrom": [
+ "id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.voting_sessions": {
+ "name": "voting_sessions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "discord_message_id": {
+ "name": "discord_message_id",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "ends_at": {
+ "name": "ends_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "handled": {
+ "name": "handled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "vote_outcome": {
+ "name": "vote_outcome",
+ "type": "vote_outcome",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "discord_message_id_idx": {
+ "name": "discord_message_id_idx",
+ "columns": [
+ {
+ "expression": "discord_message_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "handled_idx": {
+ "name": "handled_idx",
+ "columns": [
+ {
+ "expression": "handled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "voting_sessions_id_media_items_id_fk": {
+ "name": "voting_sessions_id_media_items_id_fk",
+ "tableFrom": "voting_sessions",
+ "tableTo": "media_items",
+ "columnsFrom": [
+ "id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.whitelist": {
+ "name": "whitelist",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "whitelist_id_media_items_id_fk": {
+ "name": "whitelist_id_media_items_id_fk",
+ "tableFrom": "whitelist",
+ "tableTo": "media_items",
+ "columnsFrom": [
+ "id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ }
+ },
+ "enums": {
+ "public.manager_type": {
+ "name": "manager_type",
+ "schema": "public",
+ "values": [
+ "radarr",
+ "sonarr"
+ ]
+ },
+ "public.vote_outcome": {
+ "name": "vote_outcome",
+ "schema": "public",
+ "values": [
+ "keep",
+ "delete"
+ ]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/src/db/migrations/meta/_journal.json b/src/db/migrations/meta/_journal.json
new file mode 100644
index 0000000..6ec19b4
--- /dev/null
+++ b/src/db/migrations/meta/_journal.json
@@ -0,0 +1,13 @@
+{
+ "version": "7",
+ "dialect": "postgresql",
+ "entries": [
+ {
+ "idx": 0,
+ "version": "7",
+ "when": 1726927500269,
+ "tag": "0000_dry_sage",
+ "breakpoints": true
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/discord/client.ts b/src/discord/client.ts
index cd34d57..26301fe 100644
--- a/src/discord/client.ts
+++ b/src/discord/client.ts
@@ -28,8 +28,6 @@ discordClient.login(env.DISCORD_BOT_TOKEN);
export const discordReadyPromise = new Promise((resolve) => {
discordClient.once("ready", () => {
- console.log("Discord bot is ready! 🤖");
- console.log("Discord config:", config.discord);
resolve();
});
});
diff --git a/src/discord/commands/config.ts b/src/discord/commands/config.ts
index 5cd2b4e..725ab1e 100644
--- a/src/discord/commands/config.ts
+++ b/src/discord/commands/config.ts
@@ -4,7 +4,6 @@ import { config } from "../../config";
import { discordClient } from "../client";
discordClient.once("ready", () => {
- console.log("Creating config commands");
discordClient.application?.commands.create({
name: "config",
description: "Show the current config",
diff --git a/src/discord/commands/deleted.ts b/src/discord/commands/deleted.ts
index 00ee1c9..e80e0fc 100644
--- a/src/discord/commands/deleted.ts
+++ b/src/discord/commands/deleted.ts
@@ -8,7 +8,6 @@ import { itemsToDelete } from "../../db/schema/voting";
import { discordClient } from "../client";
discordClient.once("ready", () => {
- console.log("Creating whitelist commands");
discordClient.application?.commands.create({
name: "listdeleted",
description: "List the latest deleted items",
diff --git a/src/discord/commands/help.ts b/src/discord/commands/help.ts
new file mode 100644
index 0000000..c53c0e1
--- /dev/null
+++ b/src/discord/commands/help.ts
@@ -0,0 +1,34 @@
+import { ApplicationCommandType, CommandInteraction } from "discord.js";
+
+import { discordClient } from "../client";
+
+discordClient.once("ready", () => {
+ discordClient.application?.commands.create({
+ name: "help",
+ description: "Get help with the bot",
+ type: ApplicationCommandType.ChatInput,
+ });
+});
+
+discordClient.on("interactionCreate", async (interaction) => {
+ if (!interaction.isCommand()) return;
+
+ const { commandName } = interaction;
+
+ if (commandName === "help") {
+ await handleHelp(interaction);
+ }
+});
+
+const helpMessage = `
+Use \`/subscribe\` to subscribe to notifications about new votes.
+
+Read more at https://github.com/benjick/byedarr
+`.trim();
+
+async function handleHelp(interaction: CommandInteraction) {
+ await interaction.reply({
+ content: helpMessage,
+ ephemeral: true,
+ });
+}
diff --git a/src/discord/commands/index.ts b/src/discord/commands/index.ts
index af032bf..9681250 100644
--- a/src/discord/commands/index.ts
+++ b/src/discord/commands/index.ts
@@ -1,4 +1,5 @@
import "./config";
import "./deleted";
+import "./help";
import "./subscribe";
import "./whitelist";
diff --git a/src/discord/commands/subscribe.ts b/src/discord/commands/subscribe.ts
index d8f47e3..28ac843 100644
--- a/src/discord/commands/subscribe.ts
+++ b/src/discord/commands/subscribe.ts
@@ -6,7 +6,6 @@ import { subscribers } from "../../db/schema/subscribers";
import { discordClient } from "../client";
discordClient.once("ready", () => {
- console.log("Creating subscribe commands");
discordClient.application?.commands.create({
name: "subscribe",
description: "Subscribe to voting notifications for vote candidates",
diff --git a/src/discord/commands/whitelist.ts b/src/discord/commands/whitelist.ts
index 4b8664b..d9d0664 100644
--- a/src/discord/commands/whitelist.ts
+++ b/src/discord/commands/whitelist.ts
@@ -6,7 +6,6 @@ import { whitelist } from "../../db/schema/voting";
import { discordClient } from "../client";
discordClient.once("ready", () => {
- console.log("Creating whitelist commands");
discordClient.application?.commands.create({
name: "listwhitelist",
description: "List the latest whitelisted items",
diff --git a/src/env.ts b/src/env.ts
index 2da8aef..c516ee6 100644
--- a/src/env.ts
+++ b/src/env.ts
@@ -16,6 +16,14 @@ const envSchema = z.object({
.default("development"),
});
-console.log(process.env);
+const envMap = {
+ DISCORD_BOT_TOKEN: process.env.DISCORD_BOT_TOKEN,
+ CONFIG_PATH: process.env.CONFIG_PATH,
+ DATABASE_URL: process.env.DATABASE_URL,
+ SKIP_MIGRATIONS: process.env.SKIP_MIGRATIONS,
+ DRY_RUN: process.env.DRY_RUN,
+ DEBUG: process.env.DEBUG,
+ NODE_ENV: process.env.NODE_ENV,
+};
-export const env = envSchema.parse(process.env);
+export const env = envSchema.parse(envMap);
diff --git a/src/index.ts b/src/index.ts
index e870845..858babd 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,6 +1,7 @@
import { schedule } from "node-cron";
import { config } from "./config";
+import { runMigrations } from "./db";
import { discordReadyPromise } from "./discord";
import { env } from "./env";
import { bot } from "./lib/bot";
@@ -9,9 +10,13 @@ import {
processDeletions,
} from "./services/deletionService";
import { updateDatabase } from "./services/updateService";
-import { processVotingSessions } from "./services/votingService";
+import {
+ hasExistingVotingSessions,
+ processVotingSessions,
+} from "./services/votingService";
async function main() {
+ await runMigrations();
console.log(bot);
if (env.DEBUG) {
console.log("env:", env);
@@ -19,6 +24,14 @@ async function main() {
}
await discordReadyPromise;
+ const hasVotingSessions = await hasExistingVotingSessions();
+
+ if (!hasVotingSessions) {
+ console.log("🤖 First run detected, running initial checks");
+ await updateDatabase();
+ await determineDeletions();
+ }
+
schedule(config.cron.findMedia, async () => {
console.log("⏰ Running cron");
await updateDatabase();
@@ -30,7 +43,6 @@ async function main() {
await Promise.all([processVotingSessions(), processDeletions()]);
});
- await updateDatabase();
if (env.NODE_ENV === "development") {
void dev();
}
diff --git a/src/services/votingService.ts b/src/services/votingService.ts
index bb1dd11..02847a0 100644
--- a/src/services/votingService.ts
+++ b/src/services/votingService.ts
@@ -69,6 +69,7 @@ export async function processVotingSessions() {
}
});
}
+
function decideOnVotingSession(
results: DiscordMessageVotes,
): "keep" | "delete" {
@@ -81,3 +82,8 @@ function decideOnVotingSession(
}
return "delete" as const;
}
+
+export async function hasExistingVotingSessions() {
+ const res = await db.query.votingSessions.findFirst({});
+ return !!res;
+}