Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
8 changes: 2 additions & 6 deletions apps/web/actions/analytics/track-user-signed-up.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export async function checkAndMarkUserSignedUpTracked(): Promise<{
return { shouldTrack: false };
}

const result = await db()
const [result] = await db()
.update(users)
.set({
preferences: sql`JSON_SET(COALESCE(${users.preferences}, JSON_OBJECT()), '$.trackedEvents.user_signed_up', true)`,
Expand All @@ -42,11 +42,7 @@ export async function checkAndMarkUserSignedUpTracked(): Promise<{
sql`(${users.id} = ${currentUser.id}) AND (${users.created_at} >= CURRENT_DATE()) AND JSON_CONTAINS(COALESCE(${users.preferences}, JSON_OBJECT()), CAST(true AS JSON), '$.trackedEvents.user_signed_up') = 0`,
);

if (result.rowsAffected && result.rowsAffected > 0) {
return { shouldTrack: true };
}

return { shouldTrack: false };
return { shouldTrack: result.affectedRows > 0 };
} catch {
return { shouldTrack: false };
}
Expand Down
6 changes: 2 additions & 4 deletions apps/web/actions/organization/remove-invite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export async function removeOrganizationInvite(
throw new Error("Only the owner can remove organization invites");
}

const result = await db()
const [result] = await db()
.delete(organizationInvites)
.where(
and(
Expand All @@ -40,9 +40,7 @@ export async function removeOrganizationInvite(
),
);

if (result.rowsAffected === 0) {
throw new Error("Invite not found");
}
if (result.affectedRows === 0) throw new Error("Invite not found");

revalidatePath("/dashboard/settings/organization");

Expand Down
6 changes: 2 additions & 4 deletions apps/web/actions/organization/remove-member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export async function removeOrganizationMember(
throw new Error("Owner cannot remove themselves");
}

const result = await db()
const [result] = await db()
.delete(organizationMembers)
.where(
and(
Expand All @@ -60,9 +60,7 @@ export async function removeOrganizationMember(
),
);

if (result.rowsAffected === 0) {
throw new Error("Member not found");
}
if (result.affectedRows === 0) throw new Error("Member not found");

revalidatePath("/dashboard/settings/organization");
return { success: true };
Expand Down
4 changes: 2 additions & 2 deletions apps/web/app/api/desktop/[...route]/video.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ app.post(
{ status: 404 },
);

const result = await db()
const [result] = await db()
.update(videoUploads)
.set({
uploaded,
Expand All @@ -362,7 +362,7 @@ app.post(
),
);

if (result.rowsAffected === 0)
if (result.affectedRows === 0)
await db().insert(videoUploads).values({
videoId,
uploaded,
Expand Down
4 changes: 2 additions & 2 deletions apps/web/app/api/upload/[...route]/multipart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ app.post(

const videoId = "videoId" in body ? body.videoId : videoIdFromFileKey;
if (videoId) {
const result = await db()
const [result] = await db()
.update(videos)
.set({
duration: updateIfDefined(body.durationInSecs, videos.duration),
Expand All @@ -316,7 +316,7 @@ app.post(
);

// This proves authentication
if (result.rowsAffected > 0)
if (result.affectedRows > 0)
await db()
.delete(videoUploads)
.where(eq(videoUploads.videoId, Video.VideoId.make(videoId)));
Expand Down
1 change: 1 addition & 0 deletions apps/web/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export async function middleware(request: NextRequest) {
}

export const config = {
runtime: "nodejs",
matcher: [
/*
* Match all request paths except for the ones starting with:
Expand Down
3 changes: 1 addition & 2 deletions infra/sst.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export default $config({
{ key: "NEXT_PUBLIC_AXIOM_DATASET", value: AXIOM_DATASET },
{ key: "CAP_AWS_BUCKET", value: recordingsBucket.bucket },
{ key: "NEXT_PUBLIC_CAP_AWS_BUCKET", value: recordingsBucket.bucket },
{ key: "DATABASE_URL", value: secrets.DATABASE_URL_HTTP.value },
{ key: "DATABASE_URL_MYSQL", value: secrets.DATABASE_URL_MYSQL.value },
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 | 🟠 Major

Verify environment variable name expected by the application.

The Vercel environment variable is being set with key DATABASE_URL_MYSQL, but application code typically expects DATABASE_URL. On line 291, the workflow cluster is configured with DATABASE_URL: secrets.DATABASE_URL_MYSQL.value, suggesting the application expects DATABASE_URL as the key.

Apply this diff if the application expects DATABASE_URL:

-			{ key: "DATABASE_URL_MYSQL", value: secrets.DATABASE_URL_MYSQL.value },
+			{ key: "DATABASE_URL", value: secrets.DATABASE_URL_MYSQL.value },

Alternatively, if the application has been updated to use DATABASE_URL_MYSQL, verify with:

#!/bin/bash
# Description: Check which environment variable name is used in the application code

echo "=== Searching for DATABASE_URL usage in application code ==="
rg -n "process\.env\.DATABASE_URL[^_]|DATABASE_URL[^_]" apps/web/ packages/database/ --type ts

echo "=== Searching for DATABASE_URL_MYSQL usage ==="
rg -n "process\.env\.DATABASE_URL_MYSQL|DATABASE_URL_MYSQL" apps/web/ packages/database/ --type ts
🤖 Prompt for AI Agents
In infra/sst.config.ts around line 52, the environment variable key is set to
DATABASE_URL_MYSQL but the application (and another workflow entry at line 291)
expects DATABASE_URL; change the key to DATABASE_URL while keeping the secret
value (e.g., { key: "DATABASE_URL", value: secrets.DATABASE_URL_MYSQL.value })
and ensure any other env entries or workflow configs use the same DATABASE_URL
name so they match the application; if instead the app truly uses
DATABASE_URL_MYSQL, update the other workflow/config at line 291 to use
DATABASE_URL_MYSQL consistently.

];

// new aws.s3.BucketAccelerateConfigurationV2("RecordingsBucketAcceleration", {
Expand Down Expand Up @@ -174,7 +174,6 @@ export default $config({

function Secrets() {
return {
DATABASE_URL_HTTP: new sst.Secret("DATABASE_URL_HTTP"),
DATABASE_URL_MYSQL: new sst.Secret("DATABASE_URL_MYSQL"),
CAP_AWS_ACCESS_KEY: new sst.Secret("CAP_AWS_ACCESS_KEY"),
CAP_AWS_SECRET_KEY: new sst.Secret("CAP_AWS_SECRET_KEY"),
Expand Down
4 changes: 2 additions & 2 deletions packages/database/auth/drizzle-adapter.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { STRIPE_AVAILABLE, stripe } from "@cap/utils";
import { Organisation, User } from "@cap/web-domain";
import { and, eq } from "drizzle-orm";
import type { PlanetScaleDatabase } from "drizzle-orm/planetscale-serverless";
import type { MySql2Database } from "drizzle-orm/mysql2";
import type { Adapter } from "next-auth/adapters";
import type Stripe from "stripe";
import { nanoId } from "../helpers.ts";
import { accounts, sessions, users, verificationTokens } from "../schema.ts";

export function DrizzleAdapter(db: PlanetScaleDatabase): Adapter {
export function DrizzleAdapter(db: MySql2Database): Adapter {
return {
async createUser(userData: any) {
await db.insert(users).values({
Expand Down
21 changes: 2 additions & 19 deletions packages/database/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,10 @@
import { Client, type Config } from "@planetscale/database";
import { sql } from "drizzle-orm";
import type { AnyMySqlColumn } from "drizzle-orm/mysql-core";
import { drizzle } from "drizzle-orm/planetscale-serverless";
import { drizzle } from "drizzle-orm/mysql2";

function createDrizzle() {
const URL = process.env.DATABASE_URL!;

let fetchHandler: Promise<Config["fetch"]> | undefined;

if (URL.startsWith("mysql://")) {
fetchHandler = import("@mattrax/mysql-planetscale").then((m) =>
m.createFetchHandler(URL),
);
}

const connection = new Client({
url: URL,
fetch: async (input, init) => {
return await ((await fetchHandler) || fetch)(input, init);
},
});

return drizzle(connection);
return drizzle(process.env.DATABASE_URL_MYSQL);

Check failure on line 8 in packages/database/index.ts

View workflow job for this annotation

GitHub Actions / Typecheck

Argument of type '[string | undefined]' is not assignable to parameter of type '[string | AnyMySql2Connection] | [string | AnyMySql2Connection, MySql2DrizzleConfig<Record<string, never>>] | [...]'.

Check failure on line 8 in packages/database/index.ts

View workflow job for this annotation

GitHub Actions / Typecheck

Argument of type '[string | undefined]' is not assignable to parameter of type '[string | AnyMySql2Connection] | [string | AnyMySql2Connection, MySql2DrizzleConfig<Record<string, never>>] | [...]'.
}
Comment on lines 6 to 8
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 | 🔴 Critical

Fix: DATABASE_URL_MYSQL must be validated as non-undefined.

Line 8 passes process.env.DATABASE_URL_MYSQL directly to drizzle(), but this value could be undefined. TypeScript correctly flags this as a type error. If the environment variable is missing, the application will fail at runtime with cryptic errors.

Apply this diff to ensure the environment variable exists:

 function createDrizzle() {
-	return drizzle(process.env.DATABASE_URL_MYSQL);
+	const url = process.env.DATABASE_URL_MYSQL;
+	if (!url) {
+		throw new Error("DATABASE_URL_MYSQL environment variable is not set");
+	}
+	return drizzle(url);
 }

Alternatively, if you're using a validated environment helper (like @cap/env), use that instead:

import { serverEnv } from "@cap/env";

function createDrizzle() {
	return drizzle(serverEnv().DATABASE_URL_MYSQL);
}
🧰 Tools
🪛 GitHub Check: Typecheck

[failure] 8-8:
Argument of type '[string | undefined]' is not assignable to parameter of type '[string | AnyMySql2Connection] | [string | AnyMySql2Connection, MySql2DrizzleConfig<Record<string, never>>] | [...]'.


[failure] 8-8:
Argument of type '[string | undefined]' is not assignable to parameter of type '[string | AnyMySql2Connection] | [string | AnyMySql2Connection, MySql2DrizzleConfig<Record<string, never>>] | [...]'.

🤖 Prompt for AI Agents
In packages/database/index.ts around lines 7 to 9, the call
drizzle(process.env.DATABASE_URL_MYSQL) may receive undefined; validate the
environment variable before passing it. Update the function to read the variable
into a local const, throw a clear error if it's undefined or empty, and then
pass that validated string to drizzle (or replace with your validated env helper
like serverEnv().DATABASE_URL_MYSQL if available).


let _cached: ReturnType<typeof createDrizzle> | undefined;
Expand Down
1 change: 1 addition & 0 deletions packages/database/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"drizzle-orm": "0.44.6",
"dub": "^0.64.0",
"effect": "^3.18.4",
"mysql2": "^3.15.2",
"nanoid": "^5.0.4",
"next": "15.5.4",
"next-auth": "^4.24.5",
Expand Down
Loading
Loading