Skip to content
This repository was archived by the owner on Nov 26, 2025. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
.svelte-kit
Dockerfile*
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ node_modules
dist
*.old
*.tsbuildinfo
logs
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ Before you begin, ensure you have the following installed:
> This command will output the local Supabase URLs and anon key. Make note of these, especially the DATABASE_URL and SUPABASE_URL (usually http://localhost:54321) and SUPABASE_ANON_KEY (which you'll need for client-side configuration if you were using the Supabase client directly, though your setup abstracts this).
- **Supabase Cloud:** You'll configure your `.env` variables (see next step) with your Supabase cloud project details.
4. **Set Environment Variables:** You'll need to set several environment variables. Create a `.env` file in the project root and add the following:

- **Server-side variables (/apps/server/.env):**
```
CORS_ORIGIN=http://localhost:5173 # Or wherever your frontend is located
Expand Down
33 changes: 17 additions & 16 deletions apps/server/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,36 +1,37 @@
FROM oven/bun:alpine AS base
FROM oven/bun:1.1.18-alpine AS base
Comment thread
coderabbitai[bot] marked this conversation as resolved.

# --- Building ---
FROM base AS builder

RUN apk update
# Set working directory

WORKDIR /app
RUN bun install -g turbo

# This assumes its being run in the base spookcord directory
COPY . .
RUN turbo prune server --docker

# Add lockfile and package.json's of isolated subworkspace
RUN bunx turbo prune server --docker

# --- Installer ---
FROM base AS installer
RUN apk update

WORKDIR /app

# First install dependencies (as they change less often)
COPY --from=builder /app/out/json/ .
RUN bun install

# Build the project and its dependencies
COPY --from=builder /app/out/full/ .

RUN bun turbo compile
# Install deps & Compile
RUN bun install --production --ignore-scripts --frozen-lockfile
RUN bunx turbo compile

Comment on lines +22 to 24
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Dev-deps required during turbo compile will be missing

Inside the installer stage you first run:

RUN bun install --production --ignore-scripts --frozen-lockfile
RUN bunx turbo compile

Because --production drops devDependencies, any compile pipeline step that calls tsc, drizzle-kit generate, or similar tools from devDeps will fail. This risk increased after moving @spookcord/db-schema & friends to devDependencies.

Two safer options:

- RUN bun install --production --ignore-scripts --frozen-lockfile
- RUN bunx turbo compile
+ # Install *all* deps for the build, then prune afterwards
+ RUN bun install --ignore-scripts --frozen-lockfile
+ RUN bunx turbo compile
+ RUN bun prune --production   # remove devDeps before hand-off

or split compile into the earlier builder stage where full deps already exist.

🤖 Prompt for AI Agents
In apps/server/Dockerfile around lines 22 to 24, the use of `bun install
--production` removes devDependencies needed for the `turbo compile` step,
causing compile tools to fail. To fix this, move the `bunx turbo compile`
command to the earlier builder stage where all dependencies, including
devDependencies, are installed, or run `bun install` without the `--production`
flag before compiling to ensure devDependencies are present during compilation.

# --- Runner ---
FROM base AS runner

WORKDIR /app

# Don't run production as root
USER bun
COPY --from=installer /app/apps/server/server ./server

COPY --from=installer --chown=bun:bun /app/apps/server/server ./server
COPY --from=installer --chown=bun:bun /app/apps/server/src/db/migrations ./src/db/migrations

EXPOSE 3000/tcp
CMD ./server

CMD ["./server"]
3 changes: 1 addition & 2 deletions apps/server/drizzle.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,5 @@ export default defineConfig({
table: 'journal',
schema: 'drizzle'
},
verbose: true,
strict: true
verbose: true
});
6 changes: 3 additions & 3 deletions apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@
"dependencies": {
"@orpc/client": "^1.2.0",
"@orpc/server": "^1.2.0",
"@spookcord/db-schema": "workspace:*",
"@spookcord/types": "workspace:*",
"@types/jsonwebtoken": "^9.0.9",
"better-auth": "^1.2.8",
"dotenv": "^16.5.0",
"drizzle-orm": "^latest",
Expand All @@ -28,7 +25,10 @@
"zod": "^3.25.56"
},
"devDependencies": {
"@spookcord/db-schema": "workspace:*",
"@spookcord/types": "workspace:*",
"@types/bun": "^1.2.13",
"@types/jsonwebtoken": "^9.0.9",
Comment on lines +28 to +31
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Potential runtime & build-time breakage by moving @spookcord/* to devDependencies

@spookcord/db-schema almost certainly ships runtime code (SQL helpers / Drizzle table objects). With this change it will no longer be installed inside the installer stage (bun install --production …), yet it will also not be present while Turbo runs build/compile inside that same stage because dev-deps are skipped.
Unless the whole server is statically bundled (and all references in compiled output tree-shaken), the binary or JS fallback will require() this package and crash at runtime.

Same concern for @spookcord/types if any value (not only types) is imported.

Please double-check that:

  1. The server binary produced by bun build --compile really embeds these packages.
  2. No dynamic import() / require() of the packages happens at runtime.

If either statement is false, move the packages back to dependencies.


🏁 Script executed:

#!/bin/bash
# Quick check: look for runtime imports of the schema package
rg --no-heading --trim -N $'import .*["@]spookcord/db-schema' apps/server/src

Length of output: 599


🏁 Script executed:

#!/bin/bash
# Locate Dockerfiles
fd Dockerfile
# Search for bun install commands
rg -n "bun install" -C2 .
# Search for bun build commands
rg -n "bun build" -C2 .

Length of output: 1586


Restore @spookcord/db-schema (and @spookcord/types, if used at runtime) to dependencies

The server’s Dockerfile does:
RUN bun install --production …
RUN bunx turbo compile

Since devDependencies aren’t installed in that stage, the bundler and runtime will be missing @spookcord/db-schema (imported throughout apps/server/src/*) and any non-pure-type exports from @spookcord/types. Please move them back to dependencies in apps/server/package.json to ensure they’re available both at compile time and runtime.

• apps/server/Dockerfile: line 22 uses bun install --production, skipping devDeps
• apps/server/src/…: multiple runtime import statements of @spookcord/db-schema
• apps/server/package.json: revert these from devDependenciesdependencies

--- a/apps/server/package.json
+++ b/apps/server/package.json
@@ -25,7 +25,7 @@
   "dependencies": {
-    "@spookcord/db-schema": "workspace:*",      ← restore here
-    "@spookcord/types": "workspace:*",          ← if any runtime imports
+    "@spookcord/db-schema": "workspace:*",
+    "@spookcord/types": "workspace:*",
     "@types/bun": "^1.2.13",
     "@types/jsonwebtoken": "^9.0.9",
   },
   "devDependencies": {
-    "@spookcord/db-schema": "workspace:*",
-    "@spookcord/types": "workspace:*",
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"@spookcord/db-schema": "workspace:*",
"@spookcord/types": "workspace:*",
"@types/bun": "^1.2.13",
"@types/jsonwebtoken": "^9.0.9",
--- a/apps/server/package.json
+++ b/apps/server/package.json
@@ -25,7 +25,9 @@
"dependencies": {
+ "@spookcord/db-schema": "workspace:*",
+ "@spookcord/types": "workspace:*",
"@types/bun": "^1.2.13",
"@types/jsonwebtoken": "^9.0.9",
},
"devDependencies": {
- "@spookcord/db-schema": "workspace:*",
- "@spookcord/types": "workspace:*",
}
🤖 Prompt for AI Agents
In apps/server/package.json around lines 28 to 31, move the packages
@spookcord/db-schema and @spookcord/types from devDependencies back to
dependencies. This ensures they are installed during the production bun install
step in the Dockerfile and available at both compile time and runtime. Update
the dependencies section accordingly and remove them from devDependencies.

"@types/pg": "^8.15.2",
"drizzle-kit": "^latest",
"tsc-alias": "^1.8.16",
Expand Down
97 changes: 97 additions & 0 deletions apps/server/src/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,102 @@
import { drizzle } from 'drizzle-orm/node-postgres';

import * as schema from '@spookcord/db-schema';
import { migrate } from 'drizzle-orm/postgres-js/migrator';
import { sql } from 'drizzle-orm';

export const db = drizzle(process.env.DATABASE_URL || '', { schema });

export async function runMigrations() {
console.info('[database] Running migrations');
try {
await migrate(db, { migrationsFolder: './src/db/migrations' });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider using an absolute path for the migrations folder.

The relative path './src/db/migrations' could fail if the working directory changes. This might cause issues in different deployment environments.

Consider using an absolute path:

+import { fileURLToPath } from 'url';
+import { dirname, join } from 'path';
+
+const __dirname = dirname(fileURLToPath(import.meta.url));

 export async function runMigrations() {
 	console.info('[database] Running migrations');
 	try {
-		await migrate(db, { migrationsFolder: './src/db/migrations' });
+		await migrate(db, { migrationsFolder: join(__dirname, 'migrations') });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await migrate(db, { migrationsFolder: './src/db/migrations' });
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __dirname = dirname(fileURLToPath(import.meta.url));
export async function runMigrations() {
console.info('[database] Running migrations');
try {
await migrate(db, { migrationsFolder: join(__dirname, 'migrations') });
await checkRealtime(db);
console.info('[database] Migrations completed');
} catch (error) {
console.error('[database] Migration failed', error);
process.exit(1);
}
}
🤖 Prompt for AI Agents
In apps/server/src/db/index.ts at line 14, the migrations folder path is
currently relative, which can cause failures if the working directory changes.
Update the path to use an absolute path by resolving it with a method like
path.resolve or equivalent, ensuring it correctly points to the migrations
directory regardless of the working directory or deployment environment.

console.log('[database] OK');
await checkRealtime();
} catch (error) {
console.error(
'[database] FAIL | Migrations failed \n',
error,
'\n - This error should be reported. (This is an unrecoverable error)'
);
process.exit(1);
}
}

async function checkRealtime() {
console.log('[database] Checking realtime');

try {
// Check if we already have the function
const functionExistsResult = await db.execute(sql`
SELECT EXISTS (
SELECT 1
FROM pg_proc
WHERE proname = 'add_table_to_realtime'
)
`);

// Do we really need .exists?
const functionExists = functionExistsResult.rows[0]?.exists || false;

if (!functionExists) {
// Create the function
console.warn("[database] WARN function doesn't exist, created");
await db.execute(sql`
CREATE OR REPLACE FUNCTION add_table_to_realtime(_table_name text)
RETURNS text AS
$$
BEGIN
EXECUTE format('ALTER PUBLICATION supabase_realtime ADD TABLE %I;', _table_name);
RETURN 'success';
EXCEPTION
WHEN duplicate_object THEN
RETURN 'Table already exists';
WHEN OTHERS THEN
RAISE;
END
$$ LANGUAGE 'plpgsql';
`);
console.info('[database] OK, created.');
} else {
console.debug('[database] DBG Function exists');
}

const tableExistsResult = await db.execute(sql`
SELECT EXISTS (
SELECT 1
FROM pg_tables
WHERE schemaname = 'public' AND tablename = 'message'
)
`);

const tableExists = tableExistsResult.rows[0].exists || false;

if (tableExists) {
const result = await db.execute(sql`
SELECT add_table_to_realtime('message');
`);

const status = result.rows[0].add_table_to_realtime || 'unknown';

switch (status) {
case 'success':
console.log('[database] OK table successfully added.');
break;
case 'Table already exists':
console.debug('[database] OK table aready exists');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix typo in log message.

-					console.debug('[database] OK table aready exists');
+					console.debug('[database] OK table already exists');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.debug('[database] OK table aready exists');
console.debug('[database] OK table already exists');
🤖 Prompt for AI Agents
In apps/server/src/db/index.ts at line 88, there is a typo in the console.debug
log message: "aready" should be corrected to "already". Update the log message
to fix this spelling mistake.

break;
default:
console.error('[database] WARN an unexpected error occurred ', status);
}
return;
}

console.error("[database] FAIL no table named 'message' found!");
} catch (error) {
console.error(
'[database] FAIL | Realtime table failed\n',
error,
"\n |-> Continuning anyway, your messages won't appear in realtime."
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
CREATE TABLE "account" (
"id" text PRIMARY KEY NOT NULL,
"account_id" text NOT NULL,
"provider_id" text NOT NULL,
"user_id" text NOT NULL,
"access_token" text,
"refresh_token" text,
"id_token" text,
"access_token_expires_at" timestamp,
"refresh_token_expires_at" timestamp,
"scope" text,
"password" text,
"created_at" timestamp NOT NULL,
"updated_at" timestamp NOT NULL
);
--> statement-breakpoint
CREATE TABLE "category" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" text NOT NULL,
"server_id" uuid NOT NULL,
"position" serial NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "channel" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" text NOT NULL,
"category_id" uuid NOT NULL,
"type" text DEFAULT 'text' NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "jwks" (
"id" text PRIMARY KEY NOT NULL,
"public_key" text NOT NULL,
"private_key" text NOT NULL,
"created_at" timestamp NOT NULL
);
--> statement-breakpoint
CREATE TABLE "message" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"body" text NOT NULL,
"sender_id" text NOT NULL,
"channel_id" uuid NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "message" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE "server" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" text NOT NULL,
"owner_id" text NOT NULL,
"image_url" text DEFAULT '',
"invite_code" text NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "server_invite_code_unique" UNIQUE("invite_code")
);
--> statement-breakpoint
CREATE TABLE "server_members" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"server_id" uuid NOT NULL,
"user_id" text NOT NULL,
"joined_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "session" (
"id" text PRIMARY KEY NOT NULL,
"expires_at" timestamp NOT NULL,
"token" text NOT NULL,
"created_at" timestamp NOT NULL,
"updated_at" timestamp NOT NULL,
"ip_address" text,
"user_agent" text,
"user_id" text NOT NULL,
CONSTRAINT "session_token_unique" UNIQUE("token")
);
--> statement-breakpoint
CREATE TABLE "user" (
"id" text PRIMARY KEY NOT NULL,
"name" text NOT NULL,
"email" text NOT NULL,
"email_verified" boolean NOT NULL,
"image" text,
"created_at" timestamp NOT NULL,
"updated_at" timestamp NOT NULL,
CONSTRAINT "user_email_unique" UNIQUE("email")
);
--> statement-breakpoint
CREATE TABLE "verification" (
"id" text PRIMARY KEY NOT NULL,
"identifier" text NOT NULL,
"value" text NOT NULL,
"expires_at" timestamp NOT NULL,
"created_at" timestamp,
"updated_at" timestamp
);
--> statement-breakpoint
ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "category" ADD CONSTRAINT "category_server_id_server_id_fk" FOREIGN KEY ("server_id") REFERENCES "public"."server"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "channel" ADD CONSTRAINT "channel_category_id_category_id_fk" FOREIGN KEY ("category_id") REFERENCES "public"."category"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "message" ADD CONSTRAINT "message_sender_id_user_id_fk" FOREIGN KEY ("sender_id") REFERENCES "public"."user"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "message" ADD CONSTRAINT "message_channel_id_channel_id_fk" FOREIGN KEY ("channel_id") REFERENCES "public"."channel"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "server" ADD CONSTRAINT "server_owner_id_user_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "server_members" ADD CONSTRAINT "server_members_server_id_server_id_fk" FOREIGN KEY ("server_id") REFERENCES "public"."server"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "server_members" ADD CONSTRAINT "server_members_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE UNIQUE INDEX "server_members_unique_idx" ON "server_members" USING btree ("server_id","user_id");
Loading