feat: complete profile page with profile edit, project likes and UI updates
This commit is contained in:
@ -5,7 +5,13 @@ import { z } from "zod"
|
||||
|
||||
import { and, eq, sql } from "drizzle-orm"
|
||||
import * as schema from "./schema"
|
||||
import { sandbox, user, usersToSandboxes } from "./schema"
|
||||
import {
|
||||
Sandbox,
|
||||
sandbox,
|
||||
sandboxLikes,
|
||||
user,
|
||||
usersToSandboxes,
|
||||
} from "./schema"
|
||||
|
||||
export interface Env {
|
||||
DB: D1Database
|
||||
@ -18,6 +24,13 @@ export interface Env {
|
||||
|
||||
// npm run generate
|
||||
// npx wrangler d1 execute d1-sandbox --local --file=./drizzle/<FILE>
|
||||
interface SandboxWithLiked extends Sandbox {
|
||||
liked: boolean
|
||||
}
|
||||
|
||||
interface UserResponse extends Omit<schema.User, "sandbox"> {
|
||||
sandbox: SandboxWithLiked[]
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(
|
||||
@ -258,33 +271,147 @@ export default {
|
||||
.get()
|
||||
|
||||
return success
|
||||
} else if (path === "/api/sandbox/like") {
|
||||
if (method === "POST") {
|
||||
const likeSchema = z.object({
|
||||
sandboxId: z.string(),
|
||||
userId: z.string(),
|
||||
})
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { sandboxId, userId } = likeSchema.parse(body)
|
||||
|
||||
// Check if user has already liked
|
||||
const existingLike = await db.query.sandboxLikes.findFirst({
|
||||
where: (likes, { and, eq }) =>
|
||||
and(eq(likes.sandboxId, sandboxId), eq(likes.userId, userId)),
|
||||
})
|
||||
|
||||
if (existingLike) {
|
||||
// Unlike
|
||||
await db
|
||||
.delete(sandboxLikes)
|
||||
.where(
|
||||
and(
|
||||
eq(sandboxLikes.sandboxId, sandboxId),
|
||||
eq(sandboxLikes.userId, userId)
|
||||
)
|
||||
)
|
||||
|
||||
await db
|
||||
.update(sandbox)
|
||||
.set({
|
||||
likeCount: sql`${sandbox.likeCount} - 1`,
|
||||
})
|
||||
.where(eq(sandbox.id, sandboxId))
|
||||
|
||||
return json({
|
||||
message: "Unlike successful",
|
||||
liked: false,
|
||||
})
|
||||
} else {
|
||||
// Like
|
||||
await db.insert(sandboxLikes).values({
|
||||
sandboxId,
|
||||
userId,
|
||||
createdAt: new Date(),
|
||||
})
|
||||
|
||||
await db
|
||||
.update(sandbox)
|
||||
.set({
|
||||
likeCount: sql`${sandbox.likeCount} + 1`,
|
||||
})
|
||||
.where(eq(sandbox.id, sandboxId))
|
||||
|
||||
return json({
|
||||
message: "Like successful",
|
||||
liked: true,
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
return new Response("Invalid request format", { status: 400 })
|
||||
}
|
||||
} else if (method === "GET") {
|
||||
const params = url.searchParams
|
||||
const sandboxId = params.get("sandboxId")
|
||||
const userId = params.get("userId")
|
||||
|
||||
if (!sandboxId || !userId) {
|
||||
return invalidRequest
|
||||
}
|
||||
|
||||
const like = await db.query.sandboxLikes.findFirst({
|
||||
where: (likes, { and, eq }) =>
|
||||
and(eq(likes.sandboxId, sandboxId), eq(likes.userId, userId)),
|
||||
})
|
||||
|
||||
return json({
|
||||
liked: !!like,
|
||||
})
|
||||
} else {
|
||||
return methodNotAllowed
|
||||
}
|
||||
} else if (path === "/api/user") {
|
||||
if (method === "GET") {
|
||||
const params = url.searchParams
|
||||
|
||||
if (params.has("id")) {
|
||||
const id = params.get("id") as string
|
||||
|
||||
const res = await db.query.user.findFirst({
|
||||
where: (user, { eq }) => eq(user.id, id),
|
||||
with: {
|
||||
sandbox: {
|
||||
orderBy: (sandbox, { desc }) => [desc(sandbox.createdAt)],
|
||||
with: {
|
||||
likes: true,
|
||||
},
|
||||
},
|
||||
usersToSandboxes: true,
|
||||
},
|
||||
})
|
||||
if (res) {
|
||||
const transformedUser: UserResponse = {
|
||||
...res,
|
||||
sandbox: res.sandbox.map(
|
||||
(sb): SandboxWithLiked => ({
|
||||
...sb,
|
||||
liked: sb.likes.some((like) => like.userId === id),
|
||||
})
|
||||
),
|
||||
}
|
||||
return json(transformedUser)
|
||||
}
|
||||
return json(res ?? {})
|
||||
} else if (params.has("username")) {
|
||||
const username = params.get("username") as string
|
||||
const userId = params.get("currentUserId")
|
||||
const res = await db.query.user.findFirst({
|
||||
where: (user, { eq }) => eq(user.username, username),
|
||||
with: {
|
||||
sandbox: {
|
||||
orderBy: (sandbox, { desc }) => [desc(sandbox.createdAt)],
|
||||
with: {
|
||||
likes: true,
|
||||
},
|
||||
},
|
||||
usersToSandboxes: true,
|
||||
},
|
||||
})
|
||||
if (res) {
|
||||
const transformedUser: UserResponse = {
|
||||
...res,
|
||||
sandbox: res.sandbox.map(
|
||||
(sb): SandboxWithLiked => ({
|
||||
...sb,
|
||||
liked: sb.likes.some((like) => like.userId === userId),
|
||||
})
|
||||
),
|
||||
}
|
||||
return json(transformedUser)
|
||||
}
|
||||
return json(res ?? {})
|
||||
} else {
|
||||
const res = await db.select().from(user).all()
|
||||
@ -326,6 +453,57 @@ export default {
|
||||
await db.delete(user).where(eq(user.id, id))
|
||||
return success
|
||||
} else return invalidRequest
|
||||
} else if (method === "PUT") {
|
||||
const updateUserSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string().optional(),
|
||||
email: z.string().email().optional(),
|
||||
username: z.string().optional(),
|
||||
avatarUrl: z.string().optional(),
|
||||
generations: z.number().optional(),
|
||||
})
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const validatedData = updateUserSchema.parse(body)
|
||||
|
||||
const { id, username, ...updateData } = validatedData
|
||||
|
||||
// If username is being updated, check for existing username
|
||||
if (username) {
|
||||
const existingUser = await db
|
||||
.select()
|
||||
.from(user)
|
||||
.where(eq(user.username, username))
|
||||
.get()
|
||||
if (existingUser && existingUser.id !== id) {
|
||||
return json({ error: "Username already exists" }, { status: 409 })
|
||||
}
|
||||
}
|
||||
|
||||
const cleanUpdateData = {
|
||||
...updateData,
|
||||
...(username ? { username } : {}),
|
||||
}
|
||||
|
||||
const res = await db
|
||||
.update(user)
|
||||
.set(cleanUpdateData)
|
||||
.where(eq(user.id, id))
|
||||
.returning()
|
||||
.get()
|
||||
|
||||
if (!res) {
|
||||
return json({ error: "User not found" }, { status: 404 })
|
||||
}
|
||||
|
||||
return json({ res })
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return json({ error: error.errors }, { status: 400 })
|
||||
}
|
||||
return json({ error: "Internal server error" }, { status: 500 })
|
||||
}
|
||||
} else {
|
||||
return methodNotAllowed
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { createId } from "@paralleldrive/cuid2"
|
||||
import { relations } from "drizzle-orm"
|
||||
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"
|
||||
import { sql } from "drizzle-orm"
|
||||
import { relations, sql } from "drizzle-orm"
|
||||
import { integer, primaryKey, sqliteTable, text } from "drizzle-orm/sqlite-core"
|
||||
|
||||
// #region Tables
|
||||
export const user = sqliteTable("user", {
|
||||
id: text("id")
|
||||
.$defaultFn(() => createId())
|
||||
@ -12,18 +12,14 @@ export const user = sqliteTable("user", {
|
||||
email: text("email").notNull(),
|
||||
username: text("username").notNull().unique(),
|
||||
avatarUrl: text("avatarUrl"),
|
||||
createdAt: integer("createdAt", { mode: "timestamp_ms" })
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
createdAt: integer("createdAt", { mode: "timestamp_ms" }).default(
|
||||
sql`CURRENT_TIMESTAMP`
|
||||
),
|
||||
generations: integer("generations").default(0),
|
||||
})
|
||||
|
||||
export type User = typeof user.$inferSelect
|
||||
|
||||
export const userRelations = relations(user, ({ many }) => ({
|
||||
sandbox: many(sandbox),
|
||||
usersToSandboxes: many(usersToSandboxes),
|
||||
}))
|
||||
|
||||
export const sandbox = sqliteTable("sandbox", {
|
||||
id: text("id")
|
||||
.$defaultFn(() => createId())
|
||||
@ -32,8 +28,9 @@ export const sandbox = sqliteTable("sandbox", {
|
||||
name: text("name").notNull(),
|
||||
type: text("type").notNull(),
|
||||
visibility: text("visibility", { enum: ["public", "private"] }),
|
||||
createdAt: integer("createdAt", { mode: "timestamp_ms" })
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
createdAt: integer("createdAt", { mode: "timestamp_ms" }).default(
|
||||
sql`CURRENT_TIMESTAMP`
|
||||
),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => user.id),
|
||||
@ -43,13 +40,23 @@ export const sandbox = sqliteTable("sandbox", {
|
||||
|
||||
export type Sandbox = typeof sandbox.$inferSelect
|
||||
|
||||
export const sandboxRelations = relations(sandbox, ({ one, many }) => ({
|
||||
author: one(user, {
|
||||
fields: [sandbox.userId],
|
||||
references: [user.id],
|
||||
}),
|
||||
usersToSandboxes: many(usersToSandboxes),
|
||||
}))
|
||||
export const sandboxLikes = sqliteTable(
|
||||
"sandbox_likes",
|
||||
{
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => user.id),
|
||||
sandboxId: text("sandbox_id")
|
||||
.notNull()
|
||||
.references(() => sandbox.id),
|
||||
createdAt: integer("createdAt", { mode: "timestamp_ms" }).default(
|
||||
sql`CURRENT_TIMESTAMP`
|
||||
),
|
||||
},
|
||||
(table) => ({
|
||||
pk: primaryKey({ columns: [table.sandboxId, table.userId] }),
|
||||
})
|
||||
)
|
||||
|
||||
export const usersToSandboxes = sqliteTable("users_to_sandboxes", {
|
||||
userId: text("userId")
|
||||
@ -61,6 +68,33 @@ export const usersToSandboxes = sqliteTable("users_to_sandboxes", {
|
||||
sharedOn: integer("sharedOn", { mode: "timestamp_ms" }),
|
||||
})
|
||||
|
||||
// #region Relations
|
||||
export const userRelations = relations(user, ({ many }) => ({
|
||||
sandbox: many(sandbox),
|
||||
usersToSandboxes: many(usersToSandboxes),
|
||||
likes: many(sandboxLikes),
|
||||
}))
|
||||
|
||||
export const sandboxRelations = relations(sandbox, ({ one, many }) => ({
|
||||
author: one(user, {
|
||||
fields: [sandbox.userId],
|
||||
references: [user.id],
|
||||
}),
|
||||
usersToSandboxes: many(usersToSandboxes),
|
||||
likes: many(sandboxLikes),
|
||||
}))
|
||||
|
||||
export const sandboxLikesRelations = relations(sandboxLikes, ({ one }) => ({
|
||||
user: one(user, {
|
||||
fields: [sandboxLikes.userId],
|
||||
references: [user.id],
|
||||
}),
|
||||
sandbox: one(sandbox, {
|
||||
fields: [sandboxLikes.sandboxId],
|
||||
references: [sandbox.id],
|
||||
}),
|
||||
}))
|
||||
|
||||
export const usersToSandboxesRelations = relations(
|
||||
usersToSandboxes,
|
||||
({ one }) => ({
|
||||
@ -74,3 +108,5 @@ export const usersToSandboxesRelations = relations(
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
// #endregion
|
||||
|
Reference in New Issue
Block a user