mirror of
https://github.com/khairul169/code-share.git
synced 2026-10-24 11:53:58 +07:00
feat: add project fork id, add project permission check
This commit is contained in:
+40
-7
@@ -4,6 +4,7 @@ import { procedure, router } from "../api/trpc";
|
||||
import { file, insertFileSchema, selectFileSchema } from "../db/schema/file";
|
||||
import { z } from "zod";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { getProjectById, hasPermission } from "./project";
|
||||
|
||||
const fileRouter = router({
|
||||
getAll: procedure
|
||||
@@ -14,7 +15,13 @@ const fileRouter = router({
|
||||
isPinned: z.boolean().optional(),
|
||||
})
|
||||
)
|
||||
.query(async ({ input: opt }) => {
|
||||
.query(async ({ ctx, input: opt }) => {
|
||||
const project = await getProjectById(opt.projectId);
|
||||
|
||||
if (!hasPermission(ctx, project, "r")) {
|
||||
throw new TRPCError({ code: "FORBIDDEN" });
|
||||
}
|
||||
|
||||
const files = await db.query.file.findMany({
|
||||
where: and(
|
||||
eq(file.projectId, opt.projectId),
|
||||
@@ -29,10 +36,21 @@ const fileRouter = router({
|
||||
return files;
|
||||
}),
|
||||
|
||||
getById: procedure.input(z.number()).query(async ({ input }) => {
|
||||
return db.query.file.findFirst({
|
||||
getById: procedure.input(z.number()).query(async ({ ctx, input }) => {
|
||||
const result = await db.query.file.findFirst({
|
||||
where: and(eq(file.id, input), isNull(file.deletedAt)),
|
||||
with: { project: { columns: { visibility: true, userId: true } } },
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!hasPermission(ctx, result?.project, "r")) {
|
||||
throw new TRPCError({ code: "FORBIDDEN" });
|
||||
}
|
||||
|
||||
return result;
|
||||
}),
|
||||
|
||||
create: procedure
|
||||
@@ -44,7 +62,13 @@ const fileRouter = router({
|
||||
isDirectory: true,
|
||||
})
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const project = await getProjectById(input.projectId);
|
||||
|
||||
if (!hasPermission(ctx, project, "w")) {
|
||||
throw new TRPCError({ code: "FORBIDDEN" });
|
||||
}
|
||||
|
||||
let basePath = "";
|
||||
if (input.parentId) {
|
||||
const parent = await db.query.file.findFirst({
|
||||
@@ -58,7 +82,7 @@ const fileRouter = router({
|
||||
}
|
||||
|
||||
const data: z.infer<typeof insertFileSchema> = {
|
||||
projectId: input.projectId,
|
||||
projectId: project.id,
|
||||
parentId: input.parentId,
|
||||
path: basePath + input.filename,
|
||||
filename: input.filename,
|
||||
@@ -71,17 +95,22 @@ const fileRouter = router({
|
||||
|
||||
update: procedure
|
||||
.input(selectFileSchema.partial().required({ id: true, projectId: true }))
|
||||
.mutation(async ({ input }) => {
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const fileData = await db.query.file.findFirst({
|
||||
where: and(
|
||||
eq(file.projectId, input.projectId),
|
||||
eq(file.id, input.id),
|
||||
isNull(file.deletedAt)
|
||||
),
|
||||
with: { project: { columns: { visibility: true, userId: true } } },
|
||||
});
|
||||
|
||||
if (!fileData) {
|
||||
throw new TRPCError({ code: "NOT_FOUND" });
|
||||
}
|
||||
if (!hasPermission(ctx, fileData.project, "w")) {
|
||||
throw new TRPCError({ code: "FORBIDDEN" });
|
||||
}
|
||||
|
||||
return db.transaction(async (tx) => {
|
||||
const data = { ...input };
|
||||
@@ -109,13 +138,17 @@ const fileRouter = router({
|
||||
});
|
||||
}),
|
||||
|
||||
delete: procedure.input(z.number()).mutation(async ({ input }) => {
|
||||
delete: procedure.input(z.number()).mutation(async ({ ctx, input }) => {
|
||||
const fileData = await db.query.file.findFirst({
|
||||
where: and(eq(file.id, input), isNull(file.deletedAt)),
|
||||
with: { project: { columns: { visibility: true, userId: true } } },
|
||||
});
|
||||
if (!fileData) {
|
||||
throw new TRPCError({ code: "NOT_FOUND" });
|
||||
}
|
||||
if (!hasPermission(ctx, fileData.project, "w")) {
|
||||
throw new TRPCError({ code: "FORBIDDEN" });
|
||||
}
|
||||
|
||||
return db.transaction(async (tx) => {
|
||||
const [result] = await tx
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
project,
|
||||
insertProjectSchema,
|
||||
selectProjectSchema,
|
||||
ProjectSchema,
|
||||
} from "../db/schema/project";
|
||||
import { z } from "zod";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
@@ -15,6 +16,7 @@ import { ucwords } from "~/lib/utils";
|
||||
import { hashPassword } from "../lib/crypto";
|
||||
import { createToken } from "../lib/jwt";
|
||||
import { file } from "../db/schema/file";
|
||||
import { Context } from "../api/trpc/context";
|
||||
|
||||
const projectRouter = router({
|
||||
getAll: procedure
|
||||
@@ -45,7 +47,7 @@ const projectRouter = router({
|
||||
|
||||
getById: procedure
|
||||
.input(z.number().or(z.string()))
|
||||
.query(async ({ input }) => {
|
||||
.query(async ({ ctx, input }) => {
|
||||
const where = and(
|
||||
typeof input === "string"
|
||||
? eq(project.slug, input)
|
||||
@@ -53,12 +55,20 @@ const projectRouter = router({
|
||||
isNull(project.deletedAt)
|
||||
);
|
||||
|
||||
return db.query.project.findFirst({
|
||||
const result = await db.query.project.findFirst({
|
||||
where,
|
||||
with: {
|
||||
user: { columns: { password: false } },
|
||||
},
|
||||
});
|
||||
|
||||
if (!hasPermission(ctx, result, "r")) {
|
||||
throw new TRPCError({ code: "FORBIDDEN" });
|
||||
}
|
||||
|
||||
const isMutable = hasPermission(ctx, result, "w");
|
||||
|
||||
return { ...result, isMutable };
|
||||
}),
|
||||
|
||||
create: procedure
|
||||
@@ -127,6 +137,10 @@ const projectRouter = router({
|
||||
throw new Error("Fork Project not found!");
|
||||
}
|
||||
|
||||
if (!hasPermission(ctx, forkProject, "r")) {
|
||||
throw new TRPCError({ code: "FORBIDDEN" });
|
||||
}
|
||||
|
||||
const forkFiles = await tx.query.file.findMany({
|
||||
where: and(
|
||||
eq(file.projectId, input.forkFromId),
|
||||
@@ -146,7 +160,7 @@ const projectRouter = router({
|
||||
|
||||
await tx
|
||||
.update(project)
|
||||
.set({ settings: forkProject.settings })
|
||||
.set({ settings: forkProject.settings, forkId: forkProject.id })
|
||||
.where(eq(project.id, projectData.id));
|
||||
} else {
|
||||
await tx.insert(file).values([
|
||||
@@ -171,7 +185,7 @@ const projectRouter = router({
|
||||
.omit({ slug: true, userId: true })
|
||||
.required({ id: true })
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const data = { ...input };
|
||||
|
||||
const projectData = await db.query.project.findFirst({
|
||||
@@ -181,6 +195,10 @@ const projectRouter = router({
|
||||
throw new TRPCError({ code: "NOT_FOUND" });
|
||||
}
|
||||
|
||||
if (!hasPermission(ctx, projectData, "w")) {
|
||||
throw new TRPCError({ code: "FORBIDDEN" });
|
||||
}
|
||||
|
||||
if (data.settings) {
|
||||
data.settings = Object.assign(
|
||||
projectData.settings || {},
|
||||
@@ -197,7 +215,7 @@ const projectRouter = router({
|
||||
return result;
|
||||
}),
|
||||
|
||||
delete: procedure.input(z.number()).mutation(async ({ input }) => {
|
||||
delete: procedure.input(z.number()).mutation(async ({ ctx, input }) => {
|
||||
const projectData = await db.query.project.findFirst({
|
||||
where: and(eq(project.id, input), isNull(project.deletedAt)),
|
||||
});
|
||||
@@ -205,6 +223,10 @@ const projectRouter = router({
|
||||
throw new TRPCError({ code: "NOT_FOUND" });
|
||||
}
|
||||
|
||||
if (!hasPermission(ctx, projectData, "r")) {
|
||||
throw new TRPCError({ code: "FORBIDDEN" });
|
||||
}
|
||||
|
||||
const [result] = await db
|
||||
.update(project)
|
||||
.set({ deletedAt: sql`CURRENT_TIMESTAMP` })
|
||||
@@ -232,4 +254,38 @@ const projectRouter = router({
|
||||
}),
|
||||
});
|
||||
|
||||
export function hasPermission(
|
||||
ctx: Context,
|
||||
project: Pick<ProjectSchema, "userId" | "visibility"> | undefined,
|
||||
permission: "r" | "w"
|
||||
) {
|
||||
if (!project) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let read = false,
|
||||
write = false;
|
||||
|
||||
if (ctx.user?.id === project.userId) {
|
||||
read = true;
|
||||
write = true;
|
||||
}
|
||||
|
||||
if (["public", "unlisted"].includes(project.visibility as never)) {
|
||||
read = true;
|
||||
}
|
||||
|
||||
return permission === "r" ? read : write;
|
||||
}
|
||||
|
||||
export async function getProjectById(id: number) {
|
||||
const projectData = await db.query.project.findFirst({
|
||||
where: and(eq(project.id, id), isNull(project.deletedAt)),
|
||||
});
|
||||
if (!projectData) {
|
||||
throw new TRPCError({ code: "NOT_FOUND" });
|
||||
}
|
||||
return projectData;
|
||||
}
|
||||
|
||||
export default projectRouter;
|
||||
|
||||
Reference in New Issue
Block a user