Skip to content

Commit e178a00

Browse files
committed
fix db type issue
1 parent e79b6f3 commit e178a00

7 files changed

Lines changed: 50 additions & 29 deletions

File tree

src/app/dashboard/admin/@tools/export.tsx

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,35 @@ export default function ExportButton({ data, label }: ExportButtonProps) {
2525
return
2626
}
2727

28-
const headers = Object.keys(data[0] as Record<string, unknown>).join(",")
29-
const escapeCSV = (value: unknown) => {
28+
const headers = Object.keys(data[0] as Record<string, unknown>)
29+
const escapeCSV = (value: unknown): string => {
3030
if (value == null) return ""
31-
if (value instanceof Date) return value.toISOString()
32-
if (typeof value === "string" && !isNaN(Date.parse(value))) {
33-
return new Date(value).toISOString()
31+
32+
let str: string
33+
if (typeof value === "object") {
34+
if (value instanceof Date) {
35+
str = value.toISOString()
36+
} else {
37+
// Stringify JSON objects/arrays
38+
str = JSON.stringify(value)
39+
}
40+
} else {
41+
str = String(value)
3442
}
35-
const str = String(value)
36-
if (/[",\n]/.test(str)) {
43+
44+
// Escape CSV: wrap in quotes if contains comma, quote, or newline
45+
// and escape internal quotes by doubling them
46+
if (/[",\n\r]/.test(str)) {
3747
return `"${str.replace(/"/g, '""')}"`
3848
}
3949
return str
4050
}
41-
const rows = data.map((row) => Object.values(row).map(escapeCSV).join(",")).join("\n")
42-
const csv = headers + "\n" + rows
51+
52+
const rows = data
53+
.map((row) => headers.map((key) => escapeCSV((row as Record<string, unknown>)[key])).join(","))
54+
.join("\n")
55+
56+
const csv = headers.join(",") + "\n" + rows
4357
const blob = new Blob([csv], { type: "text/csv" })
4458
const url = window.URL.createObjectURL(blob)
4559

src/app/dashboard/admin/@tools/page.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ export default async function AdminUserTable() {
1212
<div className="flex h-[50px] items-center p-1">
1313
<h2 className="text-2xl font-semibold">Tools</h2>
1414
</div>
15-
<div className="grid grid-cols-1 md:grid-cols-5 gap-4">
16-
<UpdateEmail />
17-
</div>
1815
<div className=" flex flex-col space-y-4 my-4">
1916
<ExportButton data={projects} label="projects" />
2017
<ExportButton data={payments} label="payments" />

src/app/dashboard/admin/@tools/update-email.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@ const defaultValues: FormSchema = {
2727
newEmail: "",
2828
}
2929

30+
// deprecated, only for admin to update user email
31+
// users should use the updateEmail procedure in users router to update their own email
3032
const UpdateEmail = () => {
3133
return (
32-
<ToolCard title="Update Email">
34+
<ToolCard title="Update user email">
3335
<UpdateEmailForm />
3436
</ToolCard>
3537
)

src/server/api/routers/admin/users/update-email.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,37 @@
11
import { clerkClient } from "@clerk/nextjs/server"
22
import { TRPCError } from "@trpc/server"
3-
import { eq } from "drizzle-orm"
3+
import { eq, sql } from "drizzle-orm"
44
import { z } from "zod"
55

66
import { adminProcedure } from "~/server/api/trpc"
7-
import { User } from "~/server/db/schema"
7+
import { Project, User } from "~/server/db/schema"
88

9+
// deprecated, only for admin to update user email
10+
// users should use the updateEmail procedure in users router to update their own email
911
export const updateEmail = adminProcedure
1012
.input(z.object({ userId: z.string(), oldEmail: z.string().email(), newEmail: z.string().email() }))
1113
.mutation(async ({ ctx, input }) => {
1214
const clerk = await clerkClient()
1315
const user_data = await ctx.db.query.User.findFirst({
1416
where: eq(User.email, input.oldEmail),
1517
})
18+
1619
if (!user_data) {
1720
throw new TRPCError({ code: "NOT_FOUND", message: `User with email: ${input.oldEmail} does not exist` })
1821
}
22+
1923
const user_email_data = await ctx.db.query.User.findFirst({
2024
where: eq(User.email, input.newEmail),
2125
})
26+
2227
if (user_email_data) {
2328
throw new TRPCError({ code: "FORBIDDEN", message: `User with email: ${input.newEmail} already exist` })
2429
}
30+
2531
const user = await ctx.db.query.User.findFirst({
2632
where: eq(User.id, input.userId),
2733
})
34+
2835
if (!user) {
2936
throw new TRPCError({ code: "NOT_FOUND", message: `User with id: ${input.userId} does not exist` })
3037
}
@@ -46,6 +53,12 @@ export const updateEmail = adminProcedure
4653
console.error(err)
4754
throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Failed to update email" })
4855
}
56+
await ctx.db
57+
.update(Project)
58+
.set({
59+
members: sql`array_replace(${Project.members}, ${input.oldEmail}, ${input.newEmail})`,
60+
})
61+
.where(sql`${input.oldEmail} = ANY(${Project.members})`)
4962

5063
return user
5164
})

src/server/api/routers/projects/get-projects.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,14 @@ export const getProjectByName = publicRatedProcedure(Ratelimit.fixedWindow(60, "
4141
export const getProjectByUser = protectedRatedProcedure(Ratelimit.fixedWindow(60, "30s"))
4242
.input(z.object({ user: z.string(), isPublic: z.boolean().optional() }))
4343
.query(async ({ input, ctx }) => {
44-
let whereClause = sql`TRUE`
44+
const conditions = []
4545

4646
if (input.user) {
47-
const searchPattern = `%${input.user}%`
48-
whereClause = sql`${whereClause} AND ${Project.members} ILIKE ${searchPattern}`
47+
conditions.push(arrayContains(Project.members, [input.user]))
4948
}
5049

5150
if (typeof input.isPublic === "boolean") {
52-
whereClause = sql`${whereClause} AND ${Project.is_public} = ${input.isPublic}`
51+
conditions.push(eq(Project.is_public, input.isPublic))
5352
}
5453

5554
const projectData = await ctx.db.query.Project.findMany({
@@ -71,7 +70,7 @@ export const getProjectByUser = protectedRatedProcedure(Ratelimit.fixedWindow(60
7170
application_url: true,
7271
is_public: true,
7372
},
74-
where: whereClause,
73+
where: conditions.length > 0 ? and(...conditions) : undefined,
7574
})
7675

7776
return projectData

src/server/api/routers/users/update-email.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,23 @@ export const updateEmail = protectedRatedProcedure(Ratelimit.fixedWindow(4, "30s
2020
const user_data = await ctx.db.query.User.findFirst({
2121
where: eq(User.email, input.oldEmail),
2222
})
23+
2324
if (!user_data) {
2425
throw new TRPCError({ code: "NOT_FOUND", message: `User with email: ${input.oldEmail} does not exist` })
2526
}
27+
2628
const user_email_data = await ctx.db.query.User.findFirst({
2729
where: eq(User.email, input.newEmail),
2830
})
31+
2932
if (user_email_data) {
3033
throw new TRPCError({ code: "FORBIDDEN", message: `User with email: ${input.newEmail} already exist` })
3134
}
35+
3236
const user = await ctx.db.query.User.findFirst({
3337
where: eq(User.id, input.userId),
3438
})
39+
3540
if (!user) {
3641
throw new TRPCError({ code: "NOT_FOUND", message: `User with id: ${input.userId} does not exist` })
3742
}

src/server/db/drizzle.ts

Lines changed: 0 additions & 9 deletions
This file was deleted.

0 commit comments

Comments
 (0)