Skip to content

Commit 304a9d5

Browse files
committed
fix: fix coderabbit reviews
1 parent 79bb9d2 commit 304a9d5

File tree

9 files changed

+109
-30
lines changed

9 files changed

+109
-30
lines changed

apps/api/prisma/migrations/20251031113043_add_payment_status_enum/migration.sql

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,31 @@ CREATE TYPE "PaymentStatus" AS ENUM ('created', 'authorized', 'captured', 'refun
1111
-- CreateEnum
1212
CREATE TYPE "SubscriptionStatus" AS ENUM ('created', 'authenticated', 'active', 'pending', 'halted', 'cancelled', 'completed', 'expired');
1313

14-
-- AlterTable
15-
ALTER TABLE "Payment" DROP COLUMN "status",
16-
ADD COLUMN "status" "PaymentStatus" NOT NULL;
17-
18-
-- AlterTable
19-
ALTER TABLE "Subscription" DROP COLUMN "status",
20-
ADD COLUMN "status" "SubscriptionStatus" NOT NULL,
21-
ALTER COLUMN "endDate" DROP NOT NULL;
14+
-- AlterTable: Migrate Payment.status from TEXT to PaymentStatus enum
15+
-- First, temporarily drop NOT NULL constraint to allow safe conversion
16+
ALTER TABLE "Payment" ALTER COLUMN "status" DROP NOT NULL;
17+
18+
-- Convert the column type preserving existing data
19+
ALTER TABLE "Payment" ALTER COLUMN "status" TYPE "PaymentStatus" USING status::"PaymentStatus";
20+
21+
-- Backfill any NULLs with a default value if needed (shouldn't be necessary but being safe)
22+
-- If there are NULLs, we'd set a default here, but since original was NOT NULL, this shouldn't be needed
23+
24+
-- Re-add NOT NULL constraint after successful conversion
25+
ALTER TABLE "Payment" ALTER COLUMN "status" SET NOT NULL;
26+
27+
-- AlterTable: Migrate Subscription.status from TEXT to SubscriptionStatus enum
28+
-- First, temporarily drop NOT NULL constraint to allow safe conversion
29+
ALTER TABLE "Subscription" ALTER COLUMN "status" DROP NOT NULL;
30+
31+
-- Convert the column type preserving existing data
32+
ALTER TABLE "Subscription" ALTER COLUMN "status" TYPE "SubscriptionStatus" USING status::"SubscriptionStatus";
33+
34+
-- Backfill any NULLs with a default value if needed (shouldn't be necessary but being safe)
35+
-- If there are NULLs, we'd set a default here, but since original was NOT NULL, this shouldn't be needed
36+
37+
-- Re-add NOT NULL constraint after successful conversion
38+
ALTER TABLE "Subscription" ALTER COLUMN "status" SET NOT NULL;
39+
40+
-- AlterTable: Make Subscription.endDate nullable
41+
ALTER TABLE "Subscription" ALTER COLUMN "endDate" DROP NOT NULL;

apps/api/src/prisma.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,5 @@ async function connectDB() {
117117

118118
export { withTimeout };
119119
export default { prisma, connectDB };
120+
121+
export type ExtendedPrismaClient = typeof prisma;

apps/api/src/routers/payment.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import prismaModule from "../prisma.js";
77

88
const { prisma } = prismaModule;
99

10-
const ALLOWED_NOTE_KEYS = ["plan", "user_email"] as const;
10+
const ALLOWED_NOTE_KEYS = ["plan"] as const;
1111
const MAX_NOTE_VALUE_LENGTH = 255;
1212

1313
const createOrderSchema = z.object({

apps/api/src/services/auth.service.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,30 @@ export const authService = {
116116
createData.session_state = session_state;
117117
}
118118

119-
const account = await prisma.account.upsert({
120-
where: {
121-
provider_providerAccountId: {
122-
provider,
123-
providerAccountId,
124-
},
119+
// Prisma rejects upsert with empty update object, so short-circuit to findUnique
120+
const whereClause = {
121+
provider_providerAccountId: {
122+
provider,
123+
providerAccountId,
125124
},
125+
};
126+
127+
if (Object.keys(updateData).length === 0) {
128+
const existingAccount = await prisma.account.findUnique({
129+
where: whereClause,
130+
});
131+
132+
if (existingAccount) {
133+
return existingAccount;
134+
}
135+
136+
return await prisma.account.create({
137+
data: createData,
138+
});
139+
}
140+
141+
const account = await prisma.account.upsert({
142+
where: whereClause,
126143
update: updateData,
127144
create: createData,
128145
});

apps/api/src/services/query.service.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import type { PrismaClient } from "@prisma/client";
2-
import type prismaModule from "../prisma.js";
3-
4-
type ExtendedPrismaClient = typeof prismaModule.prisma;
2+
import type { ExtendedPrismaClient } from "../prisma.js";
53

64
export const queryService = {
75
/**

apps/api/src/services/user.service.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import type { PrismaClient } from "@prisma/client";
2-
import type prismaModule from "../prisma.js";
2+
import type { ExtendedPrismaClient } from "../prisma.js";
33
import { SUBSCRIPTION_STATUS } from "../constants/subscription.js";
44

5-
type ExtendedPrismaClient = typeof prismaModule.prisma;
6-
75
export const userService = {
86
/**
97
* Get total count of users

apps/api/src/utils/encryption.ts

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,16 +94,48 @@ export function decrypt(
9494

9595
/**
9696
* Encrypt OAuth tokens in an Account object
97+
* Only includes token fields that are explicitly present (not undefined) in the input
98+
* This prevents overwriting existing tokens with null on partial updates
9799
*/
98100
export function encryptAccountTokens(data: any): any {
99101
if (!data) return data;
100102

101-
return {
102-
...data,
103-
refresh_token: data.refresh_token ? encrypt(data.refresh_token) : null,
104-
access_token: data.access_token ? encrypt(data.access_token) : null,
105-
id_token: data.id_token ? encrypt(data.id_token) : null,
106-
};
103+
const {
104+
refresh_token: _refresh_token,
105+
access_token: _access_token,
106+
id_token: _id_token,
107+
expires_at: _expires_at,
108+
token_type: _token_type,
109+
scope: _scope,
110+
...result
111+
} = data;
112+
113+
// Only add token fields if they were explicitly provided
114+
if (data.access_token !== undefined) {
115+
result.access_token = encrypt(data.access_token);
116+
}
117+
118+
if (data.refresh_token !== undefined) {
119+
result.refresh_token = encrypt(data.refresh_token);
120+
}
121+
122+
if (data.id_token !== undefined) {
123+
result.id_token = encrypt(data.id_token);
124+
}
125+
126+
if (data.expires_at !== undefined) {
127+
result.expires_at = data.expires_at;
128+
}
129+
130+
if (data.token_type !== undefined) {
131+
result.token_type = data.token_type;
132+
}
133+
134+
if (data.scope !== undefined) {
135+
result.scope = data.scope;
136+
}
137+
138+
return result;
107139
}
108140

109141
/**

apps/web/src/components/checkout/checkout-confirmation.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import React, { useState } from "react";
44
import { Check } from "lucide-react";
55
import { cn } from "@/lib/utils";
66
import { useSession } from "next-auth/react";
7+
import type { Session } from "next-auth";
78

89
interface CheckoutConfirmationProps {
910
className?: string;
@@ -14,17 +15,24 @@ const CheckoutConfirmation: React.FC<CheckoutConfirmationProps> = ({
1415
}) => {
1516
const { data: session } = useSession();
1617
const [error, setError] = useState<string | null>(null);
18+
const [isJoining, setIsJoining] = useState(false);
1719

1820
const handleJoinCommunity = async () => {
21+
if (isJoining) return;
22+
setIsJoining(true);
23+
setError(null);
24+
1925
if (!session?.user) {
2026
setError("Please sign in to join the community");
27+
setIsJoining(false);
2128
return;
2229
}
2330

24-
const accessToken = (session as any)?.accessToken;
31+
const accessToken = (session as Session)?.accessToken;
2532

2633
if (!accessToken) {
2734
setError("Authentication token not found");
35+
setIsJoining(false);
2836
return;
2937
}
3038

@@ -40,6 +48,7 @@ const CheckoutConfirmation: React.FC<CheckoutConfirmationProps> = ({
4048
if (!response.ok) {
4149
const errorData = await response.json();
4250
setError(errorData.error || "Failed to join community");
51+
setIsJoining(false);
4352
return;
4453
}
4554

@@ -48,6 +57,7 @@ const CheckoutConfirmation: React.FC<CheckoutConfirmationProps> = ({
4857
} catch (err) {
4958
console.error("Failed to join community:", err);
5059
setError("Failed to connect to server");
60+
setIsJoining(false);
5161
}
5262
};
5363

@@ -84,9 +94,10 @@ const CheckoutConfirmation: React.FC<CheckoutConfirmationProps> = ({
8494
<div className="pt-4">
8595
<button
8696
onClick={handleJoinCommunity}
97+
disabled={isJoining}
8798
className="px-8 py-3 bg-[#A970FF] hover:bg-[#9255E8] text-white font-semibold rounded-lg transition-colors duration-200"
8899
>
89-
Join
100+
{isJoining ? "Joining..." : "Join"}
90101
</button>
91102
{error && <p className="text-red-400 text-sm mt-2">{error}</p>}
92103
</div>

apps/web/src/providers/trpc-provider.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
44
import { httpBatchLink } from "@trpc/client";
55
import { useState, useMemo } from "react";
66
import { useSession } from "next-auth/react";
7+
import type { Session } from "next-auth";
78
import superjson from "superjson";
89
import { trpc } from "@/lib/trpc";
910

@@ -31,7 +32,7 @@ export function TRPCProvider({ children }: { children: React.ReactNode }) {
3132
transformer: superjson,
3233
url: trpcUrl,
3334
async headers() {
34-
const token = (session as any)?.accessToken;
35+
const token = (session as Session)?.accessToken;
3536
if (token) {
3637
return {
3738
authorization: `Bearer ${token}`,

0 commit comments

Comments
 (0)