Skip to content

Commit

Permalink
feat: add collaboration and manage permission (#64)
Browse files Browse the repository at this point in the history
* feat: add basic collaboration

* feat: add id below user
  • Loading branch information
invisal authored Mar 31, 2024
1 parent 1f0a06a commit c26ff17
Show file tree
Hide file tree
Showing 25 changed files with 778 additions and 376 deletions.
112 changes: 112 additions & 0 deletions src/app/api/database/[database_id]/ops/handle-assign-user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { HttpStatus } from "@/constants/http-status";
import { db } from "@/db";
import { database_role, database_user_role } from "@/db/schema-database";
import { user as userTable } from "@/db/schema-user";
import { ApiError } from "@/lib/api-error";
import { RequestDatabaseAssignUser } from "@/lib/api/api-database-request";
import { DatabaseOperationHandler } from "@/lib/with-database-ops";
import { and, eq } from "drizzle-orm";
import { NextResponse } from "next/server";

const handleAssignUser: DatabaseOperationHandler<
RequestDatabaseAssignUser
> = async ({ database: databaseInfo, body, user, permission }) => {
const { roleId, userId } = body;

// Validate if user input all fields
if (!userId || !roleId)
throw new ApiError({
message: "Please provide user and role",
status: HttpStatus.BAD_REQUEST,
});

if (!permission.isOwner) {
throw new ApiError({
message: "Only owner can assign other user access",
status: HttpStatus.FORBIDDEN,
});
}

if (user.id === userId) {
throw new ApiError({
message: "Cannot reassign yourself",
status: HttpStatus.FORBIDDEN,
});
}

// Check if role and user are valid
const assignedRole = await db.query.database_role.findFirst({
where: eq(database_role.id, roleId),
});

const assignedUser = await db.query.user.findFirst({
where: eq(userTable.id, userId),
});

if (!assignedRole || assignedRole.databaseId !== databaseInfo.id) {
throw new ApiError({
message: "Role does not exist in this database",
status: HttpStatus.BAD_REQUEST,
});
}

if (!assignedUser) {
throw new ApiError({
message: "User does not exist",
status: HttpStatus.BAD_REQUEST,
});
}

const isOriginalOwner = user.id === databaseInfo.userId;
if (assignedRole.isOwner && !isOriginalOwner) {
throw new ApiError({
message: "Only original owner can assign other owner",
status: HttpStatus.FORBIDDEN,
});
}

// Check if user already have previous role
const existingRole = (
await db
.select()
.from(database_user_role)
.innerJoin(database_role, eq(database_role.id, database_user_role.roleId))
.where(
and(
eq(database_user_role.databaseId, databaseInfo.id),
eq(database_user_role.userId, userId)
)
)
)[0];

if (!existingRole) {
await db.insert(database_user_role).values({
userId,
databaseId: databaseInfo.id,
roleId,
createdBy: user.id,
createdAt: Date.now(),
});
} else {
if (existingRole.database_role.isOwner && !isOriginalOwner) {
throw new ApiError({
message: "Only original owner can reassign other owner",
status: HttpStatus.FORBIDDEN,
});
}

await db
.update(database_user_role)
.set({ roleId, createdAt: Date.now(), createdBy: user.id })
.where(
and(
eq(database_user_role.databaseId, databaseInfo.id),
eq(database_user_role.userId, userId)
)
);
}

return NextResponse.json({ success: true });
};

export default handleAssignUser;
78 changes: 78 additions & 0 deletions src/app/api/database/[database_id]/ops/handle-delete-user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { HttpStatus } from "@/constants/http-status";
import { db } from "@/db";
import { database_role, database_user_role } from "@/db/schema-database";
import { ApiError } from "@/lib/api-error";
import { RequestDatabaseDeleteUser } from "@/lib/api/api-database-request";
import { DatabaseOperationHandler } from "@/lib/with-database-ops";
import { and, eq } from "drizzle-orm";
import { NextResponse } from "next/server";

const handleDeleteUser: DatabaseOperationHandler<
RequestDatabaseDeleteUser
> = async ({ database: databaseInfo, body, user, permission }) => {
const { userId } = body;

// Validate if user input all fields
if (!userId)
throw new ApiError({
message: "Please provide user and role",
status: HttpStatus.BAD_REQUEST,
});

if (!permission.isOwner) {
throw new ApiError({
message: "Only owner can delete other user access",
status: HttpStatus.FORBIDDEN,
});
}

if (user.id === userId) {
throw new ApiError({
message: "Cannot delete yourself",
status: HttpStatus.FORBIDDEN,
});
}

const isOriginalOwner = user.id === databaseInfo.userId;

// Check if user already have previous role
const existingRole = (
await db
.select()
.from(database_user_role)
.innerJoin(database_role, eq(database_role.id, database_user_role.roleId))
.where(
and(
eq(database_user_role.databaseId, databaseInfo.id),
eq(database_user_role.userId, userId)
)
)
)[0];

if (!existingRole) {
throw new ApiError({
message: "User does not belong to this database",
status: HttpStatus.BAD_REQUEST,
});
}

if (existingRole.database_role.isOwner && !isOriginalOwner) {
throw new ApiError({
message: "Only original owner can delete other owner",
status: HttpStatus.FORBIDDEN,
});
}

await db
.delete(database_user_role)
.where(
and(
eq(database_user_role.databaseId, databaseInfo.id),
eq(database_user_role.userId, userId)
)
);

return NextResponse.json({ success: true });
};

export default handleDeleteUser;
20 changes: 20 additions & 0 deletions src/app/api/database/[database_id]/ops/handle-role-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { db } from "@/db";
import { database_role } from "@/db/schema-database";
import { DatabaseOperationHandler } from "@/lib/with-database-ops";
import { eq } from "drizzle-orm";
import { NextResponse } from "next/server";

const handleRoleList: DatabaseOperationHandler = async ({ database }) => {
const roles = await db.query.database_role.findMany({
where: eq(database_role.databaseId, database.id),
});

return NextResponse.json({
roles: roles.map((role) => ({
id: role.id,
name: role.name,
})),
});
};

export default handleRoleList;
Original file line number Diff line number Diff line change
@@ -1,31 +1,40 @@
import { db } from "@/db";
import { database_role, database_user_role } from "@/db/schema-database";
import { user } from "@/db/schema-user";
import withDatabaseOperation from "@/lib/with-database-ops";
import { eq } from "drizzle-orm";
import { NextResponse } from "next/server";

export const runtime = "edge";

export const GET = withDatabaseOperation(async ({ database: databaseInfo }) => {
const users = await db
.select({
id: user.id,
name: user.name,
role: {
id: database_role.id,
name: database_role.name,
},
createdAt: database_user_role.createdAt,
assignedBy: {
id: user.id,
name: user.name,
},
})
.from(database_user_role)
.innerJoin(user, eq(database_user_role.userId, user.id))
.innerJoin(database_role, eq(database_user_role.roleId, database_role.id))
.where(eq(database_user_role.databaseId, databaseInfo.id));

return NextResponse.json({ users });
});
import { db } from "@/db";
import { user } from "@/db/schema";
import { database_role, database_user_role } from "@/db/schema-database";
import { DatabaseOperationHandler } from "@/lib/with-database-ops";
import { eq } from "drizzle-orm";
import { alias } from "drizzle-orm/sqlite-core";
import { NextResponse } from "next/server";

const handleUserList: DatabaseOperationHandler = async ({
database: databaseInfo,
}) => {
const assigned_user = alias(user, "assigned_user");

const users = await db
.select({
id: user.id,
name: user.name,
role: {
id: database_role.id,
name: database_role.name,
},
createdAt: database_user_role.createdAt,
assignedBy: {
id: assigned_user.id,
name: assigned_user.name,
},
})
.from(database_user_role)
.innerJoin(user, eq(database_user_role.userId, user.id))
.innerJoin(
assigned_user,
eq(database_user_role.createdBy, assigned_user.id)
)
.innerJoin(database_role, eq(database_user_role.roleId, database_role.id))
.where(eq(database_user_role.databaseId, databaseInfo.id));

return NextResponse.json({ users });
};

export default handleUserList;
27 changes: 27 additions & 0 deletions src/app/api/database/[database_id]/ops/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { NextResponse } from "next/server";
import withDatabaseOperation from "@/lib/with-database-ops";
import { RequestDatabaseBody } from "@/lib/api/api-database-request";
import handleRoleList from "./handle-role-list";
import handleUserList from "./handle-users-list";
import handleAssignUser from "./handle-assign-user";
import handleDeleteUser from "./handle-delete-user";

export const runtime = "edge";

export const POST = withDatabaseOperation<RequestDatabaseBody>(async function (
props
) {
const body = props.body;

if (body.type === "roles") {
return await handleRoleList(props);
} else if (body.type === "users") {
return await handleUserList(props);
} else if (body.type === "assign-user") {
return await handleAssignUser({ ...props, body });
} else if (body.type === "delete-user") {
return await handleDeleteUser({ ...props, body });
}

return NextResponse.json({ error: "Unknown command" }, { status: 500 });
});
Loading

0 comments on commit c26ff17

Please sign in to comment.