feat(infisical-pg): connected project api changes with frontend

This commit is contained in:
Akhil Mohan
2024-01-27 12:38:22 +05:30
parent 4f3582a98b
commit 8a6ab7f2f6
39 changed files with 343 additions and 339 deletions
@@ -7,13 +7,13 @@ export async function up(knex: Knex): Promise<void> {
if (!doesTableExist) { if (!doesTableExist) {
await knex.schema.createTable(TableName.BackupPrivateKey, (t) => { await knex.schema.createTable(TableName.BackupPrivateKey, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("encryptedPrivateKey").notNullable(); t.text("encryptedPrivateKey").notNullable();
t.string("iv").notNullable(); t.text("iv").notNullable();
t.string("tag").notNullable(); t.text("tag").notNullable();
t.string("algorithm").notNullable(); t.string("algorithm").notNullable();
t.string("keyEncoding").notNullable(); t.string("keyEncoding").notNullable();
t.string("salt").notNullable(); t.text("salt").notNullable();
t.string("verifier").notNullable(); t.text("verifier").notNullable();
t.timestamps(true, true, true); t.timestamps(true, true, true);
t.uuid("userId").notNullable().unique(); t.uuid("userId").notNullable().unique();
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE"); t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
@@ -14,8 +14,8 @@ export async function up(knex: Knex): Promise<void> {
t.timestamps(true, true, true); t.timestamps(true, true, true);
}); });
} }
// environments
await createOnUpdateTrigger(knex, TableName.Project); await createOnUpdateTrigger(knex, TableName.Project);
// environments
if (!(await knex.schema.hasTable(TableName.Environment))) { if (!(await knex.schema.hasTable(TableName.Environment))) {
await knex.schema.createTable(TableName.Environment, (t) => { await knex.schema.createTable(TableName.Environment, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
@@ -47,7 +47,8 @@ export async function up(knex: Knex): Promise<void> {
export async function down(knex: Knex): Promise<void> { export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.Environment); await knex.schema.dropTableIfExists(TableName.Environment);
await knex.schema.dropTableIfExists(TableName.ProjectKeys);
await knex.schema.dropTableIfExists(TableName.Project); await knex.schema.dropTableIfExists(TableName.Project);
await dropOnUpdateTrigger(knex, TableName.Project);
await dropOnUpdateTrigger(knex, TableName.ProjectKeys); await dropOnUpdateTrigger(knex, TableName.ProjectKeys);
await dropOnUpdateTrigger(knex, TableName.Project);
} }
@@ -14,7 +14,7 @@ export async function up(knex: Knex): Promise<void> {
// does not need update trigger we will do it manually // does not need update trigger we will do it manually
t.timestamps(true, true, true); t.timestamps(true, true, true);
t.uuid("projectId").notNullable(); t.uuid("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.ProjectRoles).onDelete("CASCADE"); t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
}); });
} }
@@ -132,8 +132,10 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
}), }),
response: { response: {
200: z.object({ 200: z.object({
membership: ProjectMembershipsSchema, data: z.object({
permissions: z.any().array() membership: ProjectMembershipsSchema,
permissions: z.any().array()
})
}) })
} }
}, },
@@ -143,7 +145,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
req.auth.userId, req.auth.userId,
req.params.projectId req.params.projectId
); );
return { permissions, membership }; return { data: { permissions, membership } };
} }
}); });
}; };
+8 -4
View File
@@ -11,18 +11,22 @@ export const mergeOneToManyRelation = <
childMapper: (arg: T) => C, childMapper: (arg: T) => C,
childKey: Ck childKey: Ck
) => { ) => {
const recordFirstVisitIndex: Record<string, number> = {}; let prevPkIndex = -1;
let prevPkId: null | string = null;
const groupedRecord: (P & Record<Ck, C[]>)[] = []; const groupedRecord: (P & Record<Ck, C[]>)[] = [];
for (let i = 0; i < data.length; i += 1) { for (let i = 0; i < data.length; i += 1) {
const pk = data[i][key]; const pk = data[i][key];
const row = data[i]; const row = data[i];
if (typeof recordFirstVisitIndex[pk] === "undefined") { if (pk !== prevPkId) {
const parent = parentMapper(row) as any; const parent = parentMapper(row) as any;
parent[childKey] = []; parent[childKey] = [];
groupedRecord.push(parent); groupedRecord.push(parent);
recordFirstVisitIndex[pk] = i; prevPkId = pk;
prevPkIndex += 1;
} }
groupedRecord[recordFirstVisitIndex[pk]][childKey].push(childMapper(row)); console.log(prevPkIndex, prevPkId);
groupedRecord[prevPkIndex][childKey].push(childMapper(row));
} }
return groupedRecord; return groupedRecord;
}; };
@@ -6,6 +6,8 @@ import { ActorType } from "@app/services/auth/auth-type";
export const injectPermission = fp(async (server) => { export const injectPermission = fp(async (server) => {
server.decorateRequest("permission", null); server.decorateRequest("permission", null);
server.addHook("onRequest", async (req) => { server.addHook("onRequest", async (req) => {
if (!req.auth) return;
if (req.auth.actor === ActorType.USER) { if (req.auth.actor === ActorType.USER) {
req.permission = { type: ActorType.USER, id: req.auth.userId }; req.permission = { type: ActorType.USER, id: req.auth.userId };
} }
+2
View File
@@ -38,6 +38,7 @@ import { injectIdentity } from "../plugins/auth/inject-identity";
import { registerV1Routes } from "./v1"; import { registerV1Routes } from "./v1";
import { registerV2Routes } from "./v2"; import { registerV2Routes } from "./v2";
import { registerV3Routes } from "./v3"; import { registerV3Routes } from "./v3";
import { injectPermission } from "../plugins/auth/inject-permission";
export const registerRoutes = async ( export const registerRoutes = async (
server: FastifyZodProvider, server: FastifyZodProvider,
@@ -149,6 +150,7 @@ export const registerRoutes = async (
}); });
await server.register(injectIdentity); await server.register(injectIdentity);
await server.register(injectPermission);
server.route({ server.route({
url: "/status", url: "/status",
@@ -82,7 +82,7 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
response: { response: {
200: z.object({ 200: z.object({
message: z.string(), message: z.string(),
backupPrivateKey: BackupPrivateKeySchema backupPrivateKey: BackupPrivateKeySchema.omit({ verifier: true })
}) })
} }
}, },
@@ -105,7 +105,7 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
response: { response: {
200: z.object({ 200: z.object({
message: z.string(), message: z.string(),
backupPrivateKey: BackupPrivateKeySchema backupPrivateKey: BackupPrivateKeySchema.omit({ verifier: true })
}) })
} }
}, },
@@ -10,7 +10,13 @@ import {
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
export const registerProjectRouter = (server: FastifyZodProvider) => { const projectWithEnv = ProjectsSchema.merge(
z.object({
environments: z.object({ name: z.string(), slug: z.string(), id: z.string() }).array()
})
);
export const registerProjectRouter = async (server: FastifyZodProvider) => {
server.route({ server.route({
url: "/:workspaceId/keys", url: "/:workspaceId/keys",
method: "GET", method: "GET",
@@ -81,9 +87,7 @@ export const registerProjectRouter = (server: FastifyZodProvider) => {
schema: { schema: {
response: { response: {
200: z.object({ 200: z.object({
workspaces: ProjectsSchema.merge( workspaces: projectWithEnv.array()
z.object({ environments: z.object({ name: z.string(), slug: z.string() }).array() })
).array()
}) })
} }
}, },
@@ -103,9 +107,7 @@ export const registerProjectRouter = (server: FastifyZodProvider) => {
}), }),
response: { response: {
200: z.object({ 200: z.object({
workspace: ProjectsSchema.merge( workspace: projectWithEnv.optional()
z.object({ environments: z.object({ name: z.string(), slug: z.string() }).array() })
).optional()
}) })
} }
}, },
@@ -130,7 +132,7 @@ export const registerProjectRouter = (server: FastifyZodProvider) => {
}), }),
response: { response: {
200: z.object({ 200: z.object({
workspace: ProjectsSchema workspace: projectWithEnv
}) })
} }
}, },
@@ -1,6 +1,6 @@
import { z } from "zod"; import { z } from "zod";
import { UsersSchema } from "@app/db/schemas"; import { UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
@@ -11,7 +11,7 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
schema: { schema: {
response: { response: {
200: z.object({ 200: z.object({
user: UsersSchema user: UsersSchema.merge(UserEncryptionKeysSchema.omit({ verifier: true }))
}) })
} }
}, },
@@ -1,6 +1,11 @@
import { z } from "zod"; import { z } from "zod";
import { AuthTokenSessionsSchema, OrganizationsSchema, UsersSchema } from "@app/db/schemas"; import {
AuthTokenSessionsSchema,
OrganizationsSchema,
UserEncryptionKeysSchema,
UsersSchema
} from "@app/db/schemas";
import { ApiKeysSchema } from "@app/db/schemas/api-keys"; import { ApiKeysSchema } from "@app/db/schemas/api-keys";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMethod, AuthMode } from "@app/services/auth/auth-type"; import { AuthMethod, AuthMode } from "@app/services/auth/auth-type";
@@ -195,7 +200,7 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
schema: { schema: {
response: { response: {
200: z.object({ 200: z.object({
user: UsersSchema user: UsersSchema.merge(UserEncryptionKeysSchema.omit({ verifier: true }))
}) })
} }
}, },
@@ -63,14 +63,17 @@ export const tokenServiceFactory = ({ tokenDal }: TAuthTokenServiceFactoryDep) =
const tokenHash = await bcrypt.hash(token, appCfg.SALT_ROUNDS); const tokenHash = await bcrypt.hash(token, appCfg.SALT_ROUNDS);
await tokenDal.transaction(async (tx) => { await tokenDal.transaction(async (tx) => {
await tokenDal.delete({ userId, type, orgId: orgId || null }, tx); await tokenDal.delete({ userId, type, orgId: orgId || null }, tx);
const newToken = await tokenDal.create({ const newToken = await tokenDal.create(
tokenHash, {
expiresAt: tkCfg.expiresAt.toUTCString(), tokenHash,
type, expiresAt: tkCfg.expiresAt.toUTCString(),
userId, type,
orgId, userId,
triesLeft: tkCfg?.triesLeft orgId,
}); triesLeft: tkCfg?.triesLeft
},
tx
);
return newToken; return newToken;
}); });
@@ -14,6 +14,7 @@ import {
TResetPasswordViaBackupKeyDTO TResetPasswordViaBackupKeyDTO
} from "./auth-password-type"; } from "./auth-password-type";
import { AuthTokenType } from "./auth-type"; import { AuthTokenType } from "./auth-type";
import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas";
type TAuthPasswordServiceFactoryDep = { type TAuthPasswordServiceFactoryDep = {
authDal: TAuthDalFactory; authDal: TAuthDalFactory;
@@ -212,13 +213,19 @@ export const authPaswordServiceFactory = ({
); );
if (!isValidClientProff) throw new Error("failed to create backup key"); if (!isValidClientProff) throw new Error("failed to create backup key");
const backup = await authDal.transaction(async (tx) => { const backup = await authDal.transaction(async (tx) => {
const backupKey = await authDal.upsertBackupKey(userEnc.userId, { const backupKey = await authDal.upsertBackupKey(
encryptedPrivateKey, userEnc.userId,
iv, {
tag, encryptedPrivateKey,
salt, iv,
verifier tag,
}); salt,
verifier,
algorithm: SecretEncryptionAlgo.AES_256_GCM,
keyEncoding: SecretKeyEncoding.UTF8
},
tx
);
await userDal.updateUserEncryptionByUserId( await userDal.updateUserEncryptionByUserId(
userEnc.userId, userEnc.userId,
@@ -68,8 +68,11 @@ export const projectRoleServiceFactory = ({
if (existingRole && existingRole.id !== roleId) if (existingRole && existingRole.id !== roleId)
throw new BadRequestError({ name: "Update Role", message: "Duplicate role" }); throw new BadRequestError({ name: "Update Role", message: "Duplicate role" });
} }
const [updatedRole] = await projectRoleDal.update({ id: roleId, projectId }, { ...data }); const [updatedRole] = await projectRoleDal.update(
if (!updateRole) throw new BadRequestError({ message: "Role not found", name: "Update role" }); { id: roleId, projectId },
{ ...data, permissions: data.permissions ? JSON.stringify(data.permissions) : undefined }
);
if (!updatedRole) throw new BadRequestError({ message: "Role not found", name: "Update role" });
return updatedRole; return updatedRole;
}; };
@@ -15,13 +15,13 @@ export const projectDalFactory = (db: TDbClient) => {
const workspaces = await db(TableName.ProjectMembership) const workspaces = await db(TableName.ProjectMembership)
.where({ userId }) .where({ userId })
.join( .join(
TableName.Environment, TableName.Project,
`${TableName.Environment}.projectId`, `${TableName.ProjectMembership}.projectId`,
`${TableName.Project}.id` `${TableName.Project}.id`
) )
.join( .join(
TableName.Project, TableName.Environment,
`${TableName.ProjectMembership}.projectId`, `${TableName.Environment}.projectId`,
`${TableName.Project}.id` `${TableName.Project}.id`
) )
.select( .select(
@@ -57,13 +57,13 @@ export const projectDalFactory = (db: TDbClient) => {
const workspaces = await db(TableName.ProjectMembership) const workspaces = await db(TableName.ProjectMembership)
.where(`${TableName.Project}.id`, id) .where(`${TableName.Project}.id`, id)
.join( .join(
TableName.Environment, TableName.Project,
`${TableName.Environment}.projectId`, `${TableName.ProjectMembership}.projectId`,
`${TableName.Project}.id` `${TableName.Project}.id`
) )
.join( .join(
TableName.Project, TableName.Environment,
`${TableName.ProjectMembership}.projectId`, `${TableName.Environment}.projectId`,
`${TableName.Project}.id` `${TableName.Project}.id`
) )
.select( .select(
@@ -50,11 +50,14 @@ export const projectServiceFactory = ({
// TODO(backend-pg): licence server // TODO(backend-pg): licence server
const newProject = projectDal.transaction(async (tx) => { const newProject = projectDal.transaction(async (tx) => {
const project = await projectDal.create({ name: workspaceName, orgId }, tx); const project = await projectDal.create({ name: workspaceName, orgId }, tx);
await projectMembershipDal.create({ await projectMembershipDal.create(
userId: actorId, {
role: ProjectMembershipRole.Admin, userId: actorId,
projectId: project.id role: ProjectMembershipRole.Admin,
}); projectId: project.id
},
tx
);
const envs = await projectEnvDal.insertMany( const envs = await projectEnvDal.insertMany(
DEFAULT_PROJECT_ENVS.map((el) => ({ ...el, projectId: project.id })), DEFAULT_PROJECT_ENVS.map((el) => ({ ...el, projectId: project.id })),
tx tx
+6 -1
View File
@@ -37,7 +37,7 @@ export const userDalFactory = (db: TDbClient) => {
const findUserEncKeyByUserId = async (userId: string) => { const findUserEncKeyByUserId = async (userId: string) => {
try { try {
return await db(TableName.Users) const user = await db(TableName.Users)
.where(`${TableName.Users}.id`, userId) .where(`${TableName.Users}.id`, userId)
.join( .join(
TableName.UserEncryptionKey, TableName.UserEncryptionKey,
@@ -45,6 +45,11 @@ export const userDalFactory = (db: TDbClient) => {
`${TableName.UserEncryptionKey}.userId` `${TableName.UserEncryptionKey}.userId`
) )
.first(); .first();
if (user?.id) {
// change to user id
user.id = user.userId;
}
return user;
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "Find user enc by user id" }); throw new DatabaseError({ error, name: "Find user enc by user id" });
} }
+93 -93
View File
@@ -1,123 +1,123 @@
import { TRole } from "../roles/types"; import { TOrgRole, TProjectRole } from "../roles/types";
import { IdentityAuthMethod } from "./enums"; import { IdentityAuthMethod } from "./enums";
export type IdentityTrustedIp = { export type IdentityTrustedIp = {
id: string; id: string;
ipAddress: string; ipAddress: string;
type: "ipv4" | "ipv6"; type: "ipv4" | "ipv6";
prefix?: number; prefix?: number;
} };
export type Identity = { export type Identity = {
id: string; id: string;
name: string; name: string;
authMethod?: IdentityAuthMethod; authMethod?: IdentityAuthMethod;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
}; };
export type IdentityMembershipOrg = { export type IdentityMembershipOrg = {
id: string; id: string;
identity: Identity; identity: Identity;
organization: string; organization: string;
role: "admin" | "member" | "viewer" | "no-access" | "custom"; role: "admin" | "member" | "viewer" | "no-access" | "custom";
customRole?: TRole<string>; customRole?: TOrgRole;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
} };
export type IdentityMembership = { export type IdentityMembership = {
id: string; id: string;
identity: Identity; identity: Identity;
organization: string; organization: string;
role: "admin" | "member" | "viewer" | "no-access" | "custom"; role: "admin" | "member" | "viewer" | "no-access" | "custom";
customRole?: TRole<string>; customRole?: TProjectRole;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
} };
export type CreateIdentityDTO = { export type CreateIdentityDTO = {
name: string; name: string;
organizationId: string; organizationId: string;
role?: string; role?: string;
} };
export type UpdateIdentityDTO = { export type UpdateIdentityDTO = {
identityId: string; identityId: string;
name?: string; name?: string;
role?: string; role?: string;
organizationId: string; organizationId: string;
} };
export type DeleteIdentityDTO = { export type DeleteIdentityDTO = {
identityId: string; identityId: string;
organizationId: string; organizationId: string;
} };
export type IdentityUniversalAuth = { export type IdentityUniversalAuth = {
identityId: string; identityId: string;
clientId: string; clientId: string;
clientSecretTrustedIps: IdentityTrustedIp[]; clientSecretTrustedIps: IdentityTrustedIp[];
accessTokenTTL: number; accessTokenTTL: number;
accessTokenMaxTTL: number; accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number; accessTokenNumUsesLimit: number;
accessTokenTrustedIps: IdentityTrustedIp[]; accessTokenTrustedIps: IdentityTrustedIp[];
} };
export type AddIdentityUniversalAuthDTO = { export type AddIdentityUniversalAuthDTO = {
organizationId: string; organizationId: string;
identityId: string; identityId: string;
clientSecretTrustedIps: { clientSecretTrustedIps: {
ipAddress: string; ipAddress: string;
}[]; }[];
accessTokenTTL: number; accessTokenTTL: number;
accessTokenMaxTTL: number; accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number; accessTokenNumUsesLimit: number;
accessTokenTrustedIps: { accessTokenTrustedIps: {
ipAddress: string; ipAddress: string;
}[]; }[];
} };
export type UpdateIdentityUniversalAuthDTO = { export type UpdateIdentityUniversalAuthDTO = {
organizationId: string; organizationId: string;
identityId: string; identityId: string;
clientSecretTrustedIps?: { clientSecretTrustedIps?: {
ipAddress: string; ipAddress: string;
}[]; }[];
accessTokenTTL?: number; accessTokenTTL?: number;
accessTokenMaxTTL?: number; accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number; accessTokenNumUsesLimit?: number;
accessTokenTrustedIps?: { accessTokenTrustedIps?: {
ipAddress: string; ipAddress: string;
}[]; }[];
} };
export type CreateIdentityUniversalAuthClientSecretDTO = { export type CreateIdentityUniversalAuthClientSecretDTO = {
identityId: string; identityId: string;
description?: string; description?: string;
ttl?: number; ttl?: number;
numUsesLimit?: number; numUsesLimit?: number;
} };
export type ClientSecretData = { export type ClientSecretData = {
id: string; id: string;
identityUniversalAuth: string; identityUniversalAuth: string;
isClientSecretRevoked: boolean; isClientSecretRevoked: boolean;
description: string; description: string;
clientSecretPrefix: string; clientSecretPrefix: string;
clientSecretNumUses: number; clientSecretNumUses: number;
clientSecretNumUsesLimit: number; clientSecretNumUsesLimit: number;
clientSecretTTL: number; clientSecretTTL: number;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
} };
export type CreateIdentityUniversalAuthClientSecretRes = { export type CreateIdentityUniversalAuthClientSecretRes = {
clientSecret: string; clientSecret: string;
clientSecretData: ClientSecretData; clientSecretData: ClientSecretData;
} };
export type DeleteIdentityUniversalAuthClientSecretDTO = { export type DeleteIdentityUniversalAuthClientSecretDTO = {
identityId: string; identityId: string;
clientSecretId: string; clientSecretId: string;
} };
+4 -4
View File
@@ -1,14 +1,14 @@
export { export {
useCreateOrgRole, useCreateOrgRole,
useCreateRole, useCreateProjectRole,
useDeleteOrgRole, useDeleteOrgRole,
useDeleteRole, useDeleteProjectRole,
useUpdateOrgRole, useUpdateOrgRole,
useUpdateRole useUpdateProjectRole
} from "./mutation"; } from "./mutation";
export { export {
useGetOrgRoles, useGetOrgRoles,
useGetRoles, useGetProjectRoles,
useGetUserOrgPermissions, useGetUserOrgPermissions,
useGetUserProjectPermissions useGetUserProjectPermissions
} from "./queries"; } from "./queries";
+40 -32
View File
@@ -6,45 +6,53 @@ import { apiRequest } from "@app/config/request";
import { roleQueryKeys } from "./queries"; import { roleQueryKeys } from "./queries";
import { import {
TCreateOrgRoleDTO, TCreateOrgRoleDTO,
TCreateRoleDTO, TCreateProjectRoleDTO,
TDeleteOrgRoleDTO, TDeleteOrgRoleDTO,
TDeleteRoleDTO, TDeleteProjectRoleDTO,
TUpdateOrgRoleDTO, TUpdateOrgRoleDTO,
TUpdateRoleDTO TUpdateProjectRoleDTO
} from "./types"; } from "./types";
export const useCreateRole = <T extends string | undefined>() => { export const useCreateProjectRole = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: (dto: TCreateRoleDTO<T>) => apiRequest.post("/api/v1/roles", dto), mutationFn: ({ projectId, permissions, ...dto }: TCreateProjectRoleDTO) =>
onSuccess: (_, { orgId, workspaceId }) => { apiRequest.post(`/api/ee/v1/workspace/${projectId}/roles`, {
queryClient.invalidateQueries(roleQueryKeys.getRoles({ orgId, workspaceId })); ...dto,
} permissions: permissions.length ? packRules(permissions) : []
});
};
export const useUpdateRole = <T extends string | undefined>() => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, ...dto }: TUpdateRoleDTO<T>) => apiRequest.patch(`/api/v1/roles/${id}`, dto),
onSuccess: (_, { orgId, workspaceId }) => {
queryClient.invalidateQueries(roleQueryKeys.getRoles({ orgId, workspaceId }));
}
});
};
export const useDeleteRole = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ orgId, id }: TDeleteRoleDTO) =>
apiRequest.delete(`/api/v1/roles/${id}`, {
data: { orgId }
}), }),
onSuccess: (_, { orgId, workspaceId }) => { onSuccess: (_, { projectId }) => {
queryClient.invalidateQueries(roleQueryKeys.getRoles({ orgId, workspaceId })); queryClient.invalidateQueries(roleQueryKeys.getProjectRoles(projectId));
}
});
};
export const useUpdateProjectRole = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, projectId, permissions, ...dto }: TUpdateProjectRoleDTO) =>
apiRequest.patch(`/api/ee/v1/workspace/${projectId}/roles/${id}`, {
...dto,
permissions: permissions?.length ? packRules(permissions) : []
}),
onSuccess: (_, { projectId }) => {
queryClient.invalidateQueries(roleQueryKeys.getProjectRoles(projectId));
}
});
};
export const useDeleteProjectRole = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ projectId, id }: TDeleteProjectRoleDTO) =>
apiRequest.delete(`/api/ee/v1/workspace/${projectId}/roles/${id}`, {
data: { projectId }
}),
onSuccess: (_, { projectId }) => {
queryClient.invalidateQueries(roleQueryKeys.getProjectRoles(projectId));
} }
}); });
}; };
@@ -71,7 +79,7 @@ export const useUpdateOrgRole = () => {
mutationFn: ({ id, orgId, permissions, ...dto }: TUpdateOrgRoleDTO) => mutationFn: ({ id, orgId, permissions, ...dto }: TUpdateOrgRoleDTO) =>
apiRequest.patch(`/api/ee/v1/organization/${orgId}/roles/${id}`, { apiRequest.patch(`/api/ee/v1/organization/${orgId}/roles/${id}`, {
...dto, ...dto,
permissions: permissions?.length ? packRules(permissions) : undefined permissions: permissions?.length ? packRules(permissions) : []
}), }),
onSuccess: (_, { orgId }) => { onSuccess: (_, { orgId }) => {
queryClient.invalidateQueries(roleQueryKeys.getOrgRoles(orgId)); queryClient.invalidateQueries(roleQueryKeys.getOrgRoles(orgId));
+15 -20
View File
@@ -10,12 +10,11 @@ import { ProjectPermissionSet } from "@app/context/ProjectPermissionContext/type
import { OrgUser } from "../users/types"; import { OrgUser } from "../users/types";
import { import {
TGetRolesDTO,
TGetUserOrgPermissionsDTO, TGetUserOrgPermissionsDTO,
TGetUserProjectPermissionDTO, TGetUserProjectPermissionDTO,
TOrgRole, TOrgRole,
TPermission, TPermission,
TRole TProjectRole
} from "./types"; } from "./types";
const $glob: FieldInstruction<string> = { const $glob: FieldInstruction<string> = {
@@ -37,7 +36,7 @@ const glob: JsInterpreter<FieldCondition<string>> = (node, object, context) => {
const conditionsMatcher = buildMongoQueryMatcher({ $glob }, { glob }); const conditionsMatcher = buildMongoQueryMatcher({ $glob }, { glob });
export const roleQueryKeys = { export const roleQueryKeys = {
getRoles: ({ orgId, workspaceId }: TGetRolesDTO) => ["roles", { orgId, workspaceId }] as const, getProjectRoles: (projectId: string) => ["roles", { projectId }] as const,
getOrgRoles: (orgId: string) => ["org-roles", { orgId }] as const, getOrgRoles: (orgId: string) => ["org-roles", { orgId }] as const,
getUserOrgPermissions: ({ orgId }: TGetUserOrgPermissionsDTO) => getUserOrgPermissions: ({ orgId }: TGetUserOrgPermissionsDTO) =>
["user-permissions", { orgId }] as const, ["user-permissions", { orgId }] as const,
@@ -45,25 +44,21 @@ export const roleQueryKeys = {
["user-project-permissions", { workspaceId }] as const ["user-project-permissions", { workspaceId }] as const
}; };
const getRoles = async ({ orgId, workspaceId }: TGetRolesDTO) => { const getProjectRoles = async (projectId: string) => {
const { data } = await apiRequest.get<{ data: { roles: TRole<typeof workspaceId>[] } }>( const { data } = await apiRequest.get<{
"/api/v1/roles", data: { roles: Array<Omit<TProjectRole, "permissions"> & { permissions: unknown }> };
{ }>(`/api/ee/v1/workspace/${projectId}/roles`);
params: { return data.data.roles.map(({ permissions, ...el }) => ({
workspaceId, ...el,
orgId permissions: unpackRules(permissions as PackRule<TPermission>[])
} }));
}
);
return data.data.roles;
}; };
export const useGetRoles = ({ orgId, workspaceId }: TGetRolesDTO) => export const useGetProjectRoles = (projectId: string) =>
useQuery({ useQuery({
queryKey: roleQueryKeys.getRoles({ orgId, workspaceId }), queryKey: roleQueryKeys.getProjectRoles(projectId),
queryFn: () => getRoles({ orgId, workspaceId }), queryFn: () => getProjectRoles(projectId),
enabled: Boolean(orgId) enabled: Boolean(projectId)
}); });
const getOrgRoles = async (orgId: string) => { const getOrgRoles = async (orgId: string) => {
@@ -109,7 +104,7 @@ export const useGetUserOrgPermissions = ({ orgId }: TGetUserOrgPermissionsDTO) =
const getUserProjectPermissions = async ({ workspaceId }: TGetUserProjectPermissionDTO) => { const getUserProjectPermissions = async ({ workspaceId }: TGetUserProjectPermissionDTO) => {
const { data } = await apiRequest.get<{ const { data } = await apiRequest.get<{
data: { permissions: PackRule<RawRuleOf<MongoAbility<OrgPermissionSet>>>[] }; data: { permissions: PackRule<RawRuleOf<MongoAbility<OrgPermissionSet>>>[] };
}>(`/api/v1/roles/workspace/${workspaceId}/permissions`, {}); }>(`/api/ee/v1/workspace/${workspaceId}/permissions`, {});
return data.data.permissions; return data.data.permissions;
}; };
+26 -32
View File
@@ -1,19 +1,16 @@
export type TGetRolesDTO = { export type TGetProjectRolesDTO = {
orgId: string;
workspaceId?: string; workspaceId?: string;
}; };
// @depreciated export type TProjectRole = {
export type TRole<T extends string | undefined> = {
id: string;
organization: string;
workspace: T;
name: string;
description: string;
slug: string; slug: string;
permissions: T extends string ? TProjectPermission[] : TPermission[]; name: string;
projectId: string;
id: string;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
description?: string;
permissions: TPermission[];
}; };
export type TOrgRole = { export type TOrgRole = {
@@ -36,28 +33,7 @@ export type TPermission = {
export type TProjectPermission = { export type TProjectPermission = {
conditions?: Record<string, any>; conditions?: Record<string, any>;
action: string; action: string;
subject: string; subject: [string];
};
export type TCreateRoleDTO<T extends string | undefined> = {
orgId: string;
workspaceId?: T;
name: string;
description?: string;
slug: string;
permissions: T extends string ? TProjectPermission[] : TPermission[];
};
export type TUpdateRoleDTO<T extends string | undefined> = {
orgId: string;
id: string;
workspaceId?: T;
} & Partial<Omit<TCreateRoleDTO<T>, "orgId" | "workspaceId">>;
export type TDeleteRoleDTO = {
orgId: string;
id: string;
workspaceId?: string;
}; };
export type TGetUserOrgPermissionsDTO = { export type TGetUserOrgPermissionsDTO = {
@@ -85,3 +61,21 @@ export type TDeleteOrgRoleDTO = {
orgId: string; orgId: string;
id: string; id: string;
}; };
export type TCreateProjectRoleDTO = {
projectId: string;
name: string;
description?: string;
slug: string;
permissions: TPermission[];
};
export type TUpdateProjectRoleDTO = {
projectId: string;
id: string;
} & Partial<Omit<TCreateOrgRoleDTO, "orgId">>;
export type TDeleteProjectRoleDTO = {
projectId: string;
id: string;
};
+3 -3
View File
@@ -196,7 +196,7 @@ export const useToggleAutoCapitalization = () => {
return useMutation<{}, {}, ToggleAutoCapitalizationDTO>({ return useMutation<{}, {}, ToggleAutoCapitalizationDTO>({
mutationFn: ({ workspaceID, state }) => mutationFn: ({ workspaceID, state }) =>
apiRequest.patch(`/api/v2/workspace/${workspaceID}/auto-capitalization`, { apiRequest.post(`/api/v1/workspace/${workspaceID}/auto-capitalization`, {
autoCapitalization: state autoCapitalization: state
}), }),
onSuccess: () => { onSuccess: () => {
@@ -245,7 +245,7 @@ export const useReorderWsEnvironment = () => {
otherEnvironmentSlug, otherEnvironmentSlug,
otherEnvironmentName otherEnvironmentName
}) => { }) => {
return apiRequest.patch(`/api/v2/workspace/${workspaceId}/environments`, { return apiRequest.patch(`/api/v1/workspace/${workspaceId}/environments`, {
environmentSlug, environmentSlug,
environmentName, environmentName,
otherEnvironmentSlug, otherEnvironmentSlug,
@@ -263,7 +263,7 @@ export const useUpdateWsEnvironment = () => {
return useMutation<{}, {}, UpdateEnvironmentDTO>({ return useMutation<{}, {}, UpdateEnvironmentDTO>({
mutationFn: ({ workspaceId, id, name, slug }) => { mutationFn: ({ workspaceId, id, name, slug }) => {
return apiRequest.put(`/api/v2/workspace/${workspaceId}/environments/${id}`, { return apiRequest.patch(`/api/v1/workspace/${workspaceId}/environments/${id}`, {
name, name,
slug slug
}); });
+1 -1
View File
@@ -2,7 +2,7 @@ export type Workspace = {
__v: number; __v: number;
id: string; id: string;
name: string; name: string;
organization: string; orgId: string;
autoCapitalization: boolean; autoCapitalization: boolean;
environments: WorkspaceEnv[]; environments: WorkspaceEnv[];
}; };
+7 -8
View File
@@ -4,7 +4,6 @@
/* eslint-disable vars-on-top */ /* eslint-disable vars-on-top */
/* eslint-disable no-var */ /* eslint-disable no-var */
/* eslint-disable func-names */ /* eslint-disable func-names */
// @ts-nocheck
import crypto from "crypto"; import crypto from "crypto";
import { useEffect } from "react"; import { useEffect } from "react";
@@ -167,7 +166,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
} }
}; };
const changeOrg = async (orgId) => { const changeOrg = async (orgId: string) => {
localStorage.setItem("orgData.id", orgId); localStorage.setItem("orgData.id", orgId);
router.push(`/org/${orgId}/overview`); router.push(`/org/${orgId}/overview`);
}; };
@@ -178,15 +177,15 @@ export const AppLayout = ({ children }: LayoutProps) => {
useEffect(() => { useEffect(() => {
// Put a user in an org if they're not in one yet // Put a user in an org if they're not in one yet
const putUserInOrg = async () => { const putUserInOrg = async () => {
if (tempLocalStorage("orgData.id") === "") { if (tempLocalStorage("orgData.id") === "" && orgs?.[0]?.id) {
localStorage.setItem("orgData.id", orgs[0]?.id); localStorage.setItem("orgData.id", orgs?.[0]?.id);
} }
if ( if (
currentOrg && currentOrg &&
((workspaces?.length === 0 && router.asPath.includes("project")) || ((workspaces?.length === 0 && router.asPath.includes("project")) ||
router.asPath.includes("/project/undefined") || router.asPath.includes("/project/undefined") ||
(!orgs?.map((org) => org.id)?.includes(router.query.id) && (!orgs?.map((org) => org.id)?.includes(router.query.id as string) &&
!router.asPath.includes("project") && !router.asPath.includes("project") &&
!router.asPath.includes("personal") && !router.asPath.includes("personal") &&
!router.asPath.includes("integration"))) !router.asPath.includes("integration")))
@@ -320,7 +319,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
size="xs" size="xs"
className="flex w-full items-center justify-start p-0 font-normal" className="flex w-full items-center justify-start p-0 font-normal"
leftIcon={ leftIcon={
currentOrg.id === org.id && ( currentOrg?.id === org.id && (
<FontAwesomeIcon icon={faCheck} className="mr-3 text-primary" /> <FontAwesomeIcon icon={faCheck} className="mr-3 text-primary" />
) )
} }
@@ -433,7 +432,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
> >
<div className="no-scrollbar::-webkit-scrollbar h-full no-scrollbar"> <div className="no-scrollbar::-webkit-scrollbar h-full no-scrollbar">
{workspaces {workspaces
.filter((ws) => ws.organization === currentOrg?.id) .filter((ws) => ws.orgId === currentOrg?.id)
.map(({ id, name }) => ( .map(({ id, name }) => (
<SelectItem <SelectItem
key={`ws-layout-list-${id}`} key={`ws-layout-list-${id}`}
@@ -702,7 +701,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1"> <DropdownMenuContent align="start" className="p-1">
{supportOptions.map(([icon, text, url]) => ( {supportOptions.map(([icon, text, url]) => (
<DropdownMenuItem key={url}> <DropdownMenuItem key={url as string}>
<a <a
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
@@ -476,9 +476,8 @@ const OrganizationPage = withPermission(
const { workspaces, isLoading: isWorkspaceLoading } = useWorkspace(); const { workspaces, isLoading: isWorkspaceLoading } = useWorkspace();
const orgWorkspaces = const orgWorkspaces =
workspaces?.filter( workspaces?.filter((workspace) => workspace.orgId === localStorage.getItem("orgData.id")) ||
(workspace) => workspace.organization === localStorage.getItem("orgData.id") [];
) || [];
const currentOrg = String(router.query.id); const currentOrg = String(router.query.id);
const { createNotification } = useNotificationContext(); const { createNotification } = useNotificationContext();
const addWsUser = useAddUserToWs(); const addWsUser = useAddUserToWs();
@@ -14,7 +14,7 @@ import {
SelectItem SelectItem
} from "@app/components/v2"; } from "@app/components/v2";
import { useOrganization } from "@app/context"; import { useOrganization } from "@app/context";
import { useCreateIdentity, useGetRoles, useUpdateIdentity } from "@app/hooks/api"; import { useCreateIdentity, useGetOrgRoles, useUpdateIdentity } from "@app/hooks/api";
import { IdentityAuthMethod } from "@app/hooks/api/identities"; import { IdentityAuthMethod } from "@app/hooks/api/identities";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
@@ -46,9 +46,7 @@ export const IdentityModal = ({ popUp, handlePopUpOpen, handlePopUpToggle }: Pro
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || ""; const orgId = currentOrg?.id || "";
const { data: roles } = useGetRoles({ const { data: roles } = useGetOrgRoles(orgId);
orgId
});
const { mutateAsync: createMutateAsync } = useCreateIdentity(); const { mutateAsync: createMutateAsync } = useCreateIdentity();
const { mutateAsync: updateMutateAsync } = useUpdateIdentity(); const { mutateAsync: updateMutateAsync } = useUpdateIdentity();
@@ -19,7 +19,7 @@ import {
Tr Tr
} from "@app/components/v2"; } from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import { useGetIdentityMembershipOrgs, useGetRoles, useUpdateIdentity } from "@app/hooks/api"; import { useGetIdentityMembershipOrgs, useGetOrgRoles, useUpdateIdentity } from "@app/hooks/api";
import { IdentityAuthMethod, identityAuthToNameMap } from "@app/hooks/api/identities"; import { IdentityAuthMethod, identityAuthToNameMap } from "@app/hooks/api/identities";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
@@ -51,9 +51,7 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
const { mutateAsync: updateMutateAsync } = useUpdateIdentity(); const { mutateAsync: updateMutateAsync } = useUpdateIdentity();
const { data, isLoading } = useGetIdentityMembershipOrgs(orgId); const { data, isLoading } = useGetIdentityMembershipOrgs(orgId);
const { data: roles } = useGetRoles({ const { data: roles } = useGetOrgRoles(orgId);
orgId
});
const handleChangeRole = async ({ identityId, role }: { identityId: string; role: string }) => { const handleChangeRole = async ({ identityId, role }: { identityId: string; role: string }) => {
try { try {
@@ -32,7 +32,6 @@ import {
useFetchServerStatus, useFetchServerStatus,
useGetOrgRoles, useGetOrgRoles,
useGetOrgUsers, useGetOrgUsers,
useGetRoles,
useUpdateOrgUserRole useUpdateOrgUserRole
} from "@app/hooks/api"; } from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
@@ -58,6 +57,7 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
const orgId = currentOrg?.id || ""; const orgId = currentOrg?.id || "";
const { data: roles, isLoading: isRolesLoading } = useGetOrgRoles(orgId); const { data: roles, isLoading: isRolesLoading } = useGetOrgRoles(orgId);
console.log(roles);
const [searchMemberFilter, setSearchMemberFilter] = useState(""); const [searchMemberFilter, setSearchMemberFilter] = useState("");
@@ -61,11 +61,11 @@ import {
useUpdateOrgUserRole, useUpdateOrgUserRole,
useUploadWsKey useUploadWsKey
} from "@app/hooks/api"; } from "@app/hooks/api";
import { TRole } from "@app/hooks/api/roles/types"; import { TProjectRole } from "@app/hooks/api/roles/types";
import { useFetchServerStatus } from "@app/hooks/api/serverDetails"; import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
type Props = { type Props = {
roles?: TRole<undefined>[]; roles?: TProjectRole[];
isRolesLoading?: boolean; isRolesLoading?: boolean;
}; };
@@ -10,7 +10,7 @@ import { useOrganization, useWorkspace } from "@app/context";
import { import {
useAddIdentityToWorkspace, useAddIdentityToWorkspace,
useGetIdentityMembershipOrgs, useGetIdentityMembershipOrgs,
useGetRoles, useGetProjectRoles,
useGetWorkspaceIdentityMemberships useGetWorkspaceIdentityMemberships
} from "@app/hooks/api"; } from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
@@ -40,10 +40,7 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
const { data: identityMembershipOrgs } = useGetIdentityMembershipOrgs(orgId); const { data: identityMembershipOrgs } = useGetIdentityMembershipOrgs(orgId);
const { data: identityMemberships } = useGetWorkspaceIdentityMemberships(workspaceId); const { data: identityMemberships } = useGetWorkspaceIdentityMemberships(workspaceId);
const { data: roles } = useGetRoles({ const { data: roles } = useGetProjectRoles(workspaceId);
orgId,
workspaceId
});
const { mutateAsync: addIdentityToWorkspaceMutateAsync } = useAddIdentityToWorkspace(); const { mutateAsync: addIdentityToWorkspaceMutateAsync } = useAddIdentityToWorkspace();
@@ -171,7 +168,7 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
<div className="text-sm"> <div className="text-sm">
All identities in your organization have already been added to this project. All identities in your organization have already been added to this project.
</div> </div>
<Link href={`/org/${currentWorkspace?.organization}/members`}> <Link href={`/org/${currentWorkspace?.orgId}/members`}>
<Button variant="outline_bg">Create a new identity</Button> <Button variant="outline_bg">Create a new identity</Button>
</Link> </Link>
</div> </div>
@@ -18,14 +18,9 @@ import {
THead, THead,
Tr Tr
} from "@app/components/v2"; } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { import {
ProjectPermissionActions, useGetProjectRoles,
ProjectPermissionSub,
useOrganization,
useWorkspace
} from "@app/context";
import {
useGetRoles,
useGetWorkspaceIdentityMemberships, useGetWorkspaceIdentityMemberships,
useUpdateIdentityWorkspaceRole useUpdateIdentityWorkspaceRole
} from "@app/hooks/api"; } from "@app/hooks/api";
@@ -43,16 +38,11 @@ type Props = {
export const IdentityTable = ({ handlePopUpOpen }: Props) => { export const IdentityTable = ({ handlePopUpOpen }: Props) => {
const { createNotification } = useNotificationContext(); const { createNotification } = useNotificationContext();
const { currentOrg } = useOrganization();
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const orgId = currentOrg?.id || "";
const workspaceId = currentWorkspace?.id || ""; const workspaceId = currentWorkspace?.id || "";
const { data, isLoading } = useGetWorkspaceIdentityMemberships(currentWorkspace?.id || ""); const { data, isLoading } = useGetWorkspaceIdentityMemberships(currentWorkspace?.id || "");
const { data: roles } = useGetRoles({ const { data: roles } = useGetProjectRoles(workspaceId);
orgId,
workspaceId
});
const { mutateAsync: updateMutateAsync } = useUpdateIdentityWorkspaceRole(); const { mutateAsync: updateMutateAsync } = useUpdateIdentityWorkspaceRole();
@@ -47,7 +47,7 @@ import {
useAddUserToWs, useAddUserToWs,
useDeleteUserFromWorkspace, useDeleteUserFromWorkspace,
useGetOrgUsers, useGetOrgUsers,
useGetRoles, useGetProjectRoles,
useGetUserWsKey, useGetUserWsKey,
useGetWorkspaceUsers, useGetWorkspaceUsers,
useUpdateUserWorkspaceRole, useUpdateUserWorkspaceRole,
@@ -73,10 +73,7 @@ export const MemberListTab = () => {
const orgId = currentOrg?.id || ""; const orgId = currentOrg?.id || "";
const workspaceId = currentWorkspace?.id || ""; const workspaceId = currentWorkspace?.id || "";
const { data: roles, isLoading: isRolesLoading } = useGetRoles({ const { data: roles, isLoading: isRolesLoading } = useGetProjectRoles(workspaceId);
orgId,
workspaceId
});
const { data: wsKey } = useGetUserWsKey(workspaceId); const { data: wsKey } = useGetUserWsKey(workspaceId);
const { data: members, isLoading: isMembersLoading } = useGetWorkspaceUsers(workspaceId); const { data: members, isLoading: isMembersLoading } = useGetWorkspaceUsers(workspaceId);
@@ -433,7 +430,7 @@ export const MemberListTab = () => {
) : ( ) : (
<div className="flex flex-col space-y-4"> <div className="flex flex-col space-y-4">
<div>All the users in your organization are already invited.</div> <div>All the users in your organization are already invited.</div>
<Link href={`/org/${currentWorkspace?.organization}/members`}> <Link href={`/org/${currentWorkspace?.orgId}/members`}>
<Button variant="outline_bg">Add users to organization</Button> <Button variant="outline_bg">Add users to organization</Button>
</Link> </Link>
</div> </div>
@@ -3,7 +3,7 @@ import { motion } from "framer-motion";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { withProjectPermission } from "@app/hoc"; import { withProjectPermission } from "@app/hoc";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { TRole } from "@app/hooks/api/roles/types"; import { TProjectRole } from "@app/hooks/api/roles/types";
import { ProjectRoleList } from "./components/ProjectRoleList"; import { ProjectRoleList } from "./components/ProjectRoleList";
import { ProjectRoleModifySection } from "./components/ProjectRoleModifySection"; import { ProjectRoleModifySection } from "./components/ProjectRoleModifySection";
@@ -21,7 +21,7 @@ export const ProjectRoleListTab = withProjectPermission(
exit={{ opacity: 0, translateX: 30 }} exit={{ opacity: 0, translateX: 30 }}
> >
<ProjectRoleModifySection <ProjectRoleModifySection
role={popUp.editRole.data as TRole<string>} role={popUp.editRole.data as TProjectRole}
onGoBack={() => handlePopUpClose("editRole")} onGoBack={() => handlePopUpClose("editRole")}
/> />
</motion.div> </motion.div>
@@ -18,42 +18,32 @@ import {
THead, THead,
Tr Tr
} from "@app/components/v2"; } from "@app/components/v2";
import { import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
ProjectPermissionActions,
ProjectPermissionSub,
useOrganization,
useWorkspace
} from "@app/context";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { useDeleteRole, useGetRoles } from "@app/hooks/api"; import { useDeleteProjectRole, useGetProjectRoles } from "@app/hooks/api";
import { TRole } from "@app/hooks/api/roles/types"; import { TProjectRole } from "@app/hooks/api/roles/types";
type Props = { type Props = {
onSelectRole: (role?: TRole<string>) => void; onSelectRole: (role?: TProjectRole) => void;
}; };
export const ProjectRoleList = ({ onSelectRole }: Props) => { export const ProjectRoleList = ({ onSelectRole }: Props) => {
const [searchRoles, setSearchRoles] = useState(""); const [searchRoles, setSearchRoles] = useState("");
const { createNotification } = useNotificationContext(); const { createNotification } = useNotificationContext();
const { popUp, handlePopUpOpen, handlePopUpClose } = usePopUp(["deleteRole"] as const); const { popUp, handlePopUpOpen, handlePopUpClose } = usePopUp(["deleteRole"] as const);
const { currentOrg } = useOrganization();
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const orgId = currentOrg?.id || "";
const workspaceId = currentWorkspace?.id || ""; const workspaceId = currentWorkspace?.id || "";
const { data: roles, isLoading: isRolesLoading } = useGetRoles({ const { data: roles, isLoading: isRolesLoading } = useGetProjectRoles(workspaceId);
orgId, console.log(roles);
workspaceId
});
const { mutateAsync: deleteRole } = useDeleteRole(); const { mutateAsync: deleteRole } = useDeleteProjectRole();
const handleRoleDelete = async () => { const handleRoleDelete = async () => {
const { id } = popUp?.deleteRole?.data as TRole<string>; const { id } = popUp?.deleteRole?.data as TProjectRole;
try { try {
await deleteRole({ await deleteRole({
orgId, projectId: workspaceId,
workspaceId,
id id
}); });
createNotification({ type: "success", text: "Successfully removed the role" }); createNotification({ type: "success", text: "Successfully removed the role" });
@@ -99,7 +89,7 @@ export const ProjectRoleList = ({ onSelectRole }: Props) => {
</THead> </THead>
<TBody> <TBody>
{isRolesLoading && <TableSkeleton columns={4} innerKey="org-roles" />} {isRolesLoading && <TableSkeleton columns={4} innerKey="org-roles" />}
{(roles as TRole<string>[])?.map((role) => { {(roles as TProjectRole[])?.map((role) => {
const { id, name, slug } = role; const { id, name, slug } = role;
const isNonMutatable = ["admin", "member", "viewer", "no-access"].includes(slug); const isNonMutatable = ["admin", "member", "viewer", "no-access"].includes(slug);
@@ -157,9 +147,9 @@ export const ProjectRoleList = ({ onSelectRole }: Props) => {
<DeleteActionModal <DeleteActionModal
isOpen={popUp.deleteRole.isOpen} isOpen={popUp.deleteRole.isOpen}
title={`Are you sure want to delete ${ title={`Are you sure want to delete ${
(popUp?.deleteRole?.data as TRole<string>)?.name || " " (popUp?.deleteRole?.data as TProjectRole)?.name || " "
} role?`} } role?`}
deleteKey={(popUp?.deleteRole?.data as TRole<string>)?.slug || ""} deleteKey={(popUp?.deleteRole?.data as TProjectRole)?.slug || ""}
onClose={() => handlePopUpClose("deleteRole")} onClose={() => handlePopUpClose("deleteRole")}
onDeleteApproved={handleRoleDelete} onDeleteApproved={handleRoleDelete}
/> />
@@ -20,9 +20,9 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { Button, FormControl, Input } from "@app/components/v2"; import { Button, FormControl, Input } from "@app/components/v2";
import { ProjectPermissionSub, useOrganization, useWorkspace } from "@app/context"; import { ProjectPermissionSub, useWorkspace } from "@app/context";
import { useCreateRole, useUpdateRole } from "@app/hooks/api"; import { useCreateProjectRole, useUpdateProjectRole } from "@app/hooks/api";
import { TRole } from "@app/hooks/api/roles/types"; import { TProjectRole } from "@app/hooks/api/roles/types";
import { MultiEnvProjectPermission } from "./MultiEnvProjectPermission"; import { MultiEnvProjectPermission } from "./MultiEnvProjectPermission";
import { import {
@@ -111,7 +111,7 @@ const SINGLE_PERMISSION_LIST = [
] as const; ] as const;
type Props = { type Props = {
role?: TRole<string>; role?: TProjectRole;
onGoBack: VoidFunction; onGoBack: VoidFunction;
}; };
@@ -120,8 +120,6 @@ export const ProjectRoleModifySection = ({ role, onGoBack }: Props) => {
const isNewRole = !role?.slug; const isNewRole = !role?.slug;
const { createNotification } = useNotificationContext(); const { createNotification } = useNotificationContext();
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const workspaceId = currentWorkspace?.id || ""; const workspaceId = currentWorkspace?.id || "";
@@ -135,17 +133,16 @@ export const ProjectRoleModifySection = ({ role, onGoBack }: Props) => {
defaultValues: role ? { ...role, permissions: rolePermission2Form(role.permissions) } : {}, defaultValues: role ? { ...role, permissions: rolePermission2Form(role.permissions) } : {},
resolver: zodResolver(formSchema) resolver: zodResolver(formSchema)
}); });
const { mutateAsync: createRole } = useCreateRole(); const { mutateAsync: createRole } = useCreateProjectRole();
const { mutateAsync: updateRole } = useUpdateRole(); const { mutateAsync: updateRole } = useUpdateProjectRole();
const handleRoleUpdate = async (el: TFormSchema) => { const handleRoleUpdate = async (el: TFormSchema) => {
if (!role?.id) return; if (!role?.id) return;
try { try {
await updateRole({ await updateRole({
orgId,
id: role?.id, id: role?.id,
workspaceId, projectId: workspaceId,
...el, ...el,
permissions: formRolePermission2API(el.permissions) permissions: formRolePermission2API(el.permissions)
}); });
@@ -165,8 +162,7 @@ export const ProjectRoleModifySection = ({ role, onGoBack }: Props) => {
try { try {
await createRole({ await createRole({
orgId, projectId: workspaceId,
workspaceId,
...el, ...el,
permissions: formRolePermission2API(el.permissions) permissions: formRolePermission2API(el.permissions)
}); });
@@ -33,7 +33,7 @@ export const formSchema = z.object({
.object({ .object({
secrets: z.record(multiEnvPermissionSchema).optional(), secrets: z.record(multiEnvPermissionSchema).optional(),
member: generalPermissionSchema, member: generalPermissionSchema,
"identity": generalPermissionSchema, identity: generalPermissionSchema,
role: generalPermissionSchema, role: generalPermissionSchema,
integrations: generalPermissionSchema, integrations: generalPermissionSchema,
webhooks: generalPermissionSchema, webhooks: generalPermissionSchema,
@@ -90,7 +90,10 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
const formVal: Record<string, any> = {}; const formVal: Record<string, any> = {};
permissions.forEach((permission) => { permissions.forEach((permission) => {
const { subject, action } = permission; const {
subject: [subject],
action
} = permission;
if (!formVal?.[subject]) formVal[subject] = {}; if (!formVal?.[subject]) formVal[subject] = {};
if (subject === "secrets") { if (subject === "secrets") {
@@ -93,7 +93,7 @@ export const EnvironmentTable = ({ handlePopUpOpen }: Props) => {
{!isLoading && {!isLoading &&
currentWorkspace && currentWorkspace &&
currentWorkspace.environments.map(({ name, slug, id }, pos) => ( currentWorkspace.environments.map(({ name, slug, id }, pos) => (
<Tr key={name}> <Tr key={id}>
<Td>{name}</Td> <Td>{name}</Td>
<Td>{slug}</Td> <Td>{slug}</Td>
<Td className="flex items-center justify-end"> <Td className="flex items-center justify-end">
@@ -15,8 +15,8 @@ type Props = {
}; };
const schema = yup.object({ const schema = yup.object({
environmentName: yup.string().label("Environment Name").required(), name: yup.string().label("Environment Name").required(),
environmentSlug: yup.string().label("Environment Slug").required() slug: yup.string().label("Environment Slug").required()
}); });
export type FormData = yup.InferType<typeof schema>; export type FormData = yup.InferType<typeof schema>;
@@ -26,19 +26,20 @@ export const UpdateEnvironmentModal = ({ popUp, handlePopUpClose, handlePopUpTog
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const { mutateAsync, isLoading } = useUpdateWsEnvironment(); const { mutateAsync, isLoading } = useUpdateWsEnvironment();
const { control, handleSubmit, reset } = useForm<FormData>({ const { control, handleSubmit, reset } = useForm<FormData>({
resolver: yupResolver(schema) resolver: yupResolver(schema),
values: popUp.updateEnv.data as FormData
}); });
const oldEnvId = (popUp?.updateEnv?.data as { id: string })?.id; const oldEnvId = (popUp?.updateEnv?.data as { id: string })?.id;
const onFormSubmit = async ({ environmentName, environmentSlug }: FormData) => { const onFormSubmit = async ({ name, slug }: FormData) => {
try { try {
if (!currentWorkspace?.id) return; if (!currentWorkspace?.id) return;
await mutateAsync({ await mutateAsync({
workspaceId: currentWorkspace.id, workspaceId: currentWorkspace.id,
name: environmentName, name,
slug: environmentSlug, slug,
id: oldEnvId id: oldEnvId
}); });
@@ -70,7 +71,7 @@ export const UpdateEnvironmentModal = ({ popUp, handlePopUpClose, handlePopUpTog
<Controller <Controller
control={control} control={control}
defaultValue="" defaultValue=""
name="environmentName" name="name"
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => (
<FormControl <FormControl
label="Environment Name" label="Environment Name"
@@ -84,7 +85,7 @@ export const UpdateEnvironmentModal = ({ popUp, handlePopUpClose, handlePopUpTog
<Controller <Controller
control={control} control={control}
defaultValue="" defaultValue=""
name="environmentSlug" name="slug"
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => (
<FormControl <FormControl
label="Environment Slug" label="Environment Slug"