Skip to content

Commit

Permalink
feat(#31): implemented ui for multi env and integrated api with backend
Browse files Browse the repository at this point in the history
fix(#31): fixed all v2 release conflict
  • Loading branch information
akhilmhdh committed Jan 11, 2023
1 parent 9116bf3 commit 8470030
Show file tree
Hide file tree
Showing 23 changed files with 1,057 additions and 857 deletions.
2 changes: 1 addition & 1 deletion backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,12 @@ app.use('/api/v1/integration', v1IntegrationRouter);
app.use('/api/v1/integration-auth', v1IntegrationAuthRouter);

// v2 routes
app.use('/api/v2/workspace', v2EnvironmentRouter);
app.use('/api/v2/workspace', v2WorkspaceRouter); // TODO: turn into plural route
app.use('/api/v2/secret', v2SecretRouter); // stop supporting, TODO: revise
app.use('/api/v2/secrets', v2SecretsRouter);
app.use('/api/v2/service-token', v2ServiceTokenDataRouter); // TODO: turn into plural route
app.use('/api/v2/api-key-data', v2APIKeyDataRouter);
app.use('/api/v2/environments', v2EnvironmentRouter);

// api docs
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerFile))
Expand Down
2 changes: 1 addition & 1 deletion backend/src/controllers/v1/integrationAuthController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as Sentry from '@sentry/node';
import axios from 'axios';
import { readFileSync } from 'fs';
import { IntegrationAuth, Integration } from '../../models';
import { INTEGRATION_SET, INTEGRATION_OPTIONS, ENV_DEV } from '../../variables';
import { INTEGRATION_SET, INTEGRATION_OPTIONS } from '../../variables';
import { IntegrationService } from '../../services';
import { getApps, revokeAccess } from '../../integrations';

Expand Down
121 changes: 79 additions & 42 deletions backend/src/controllers/v2/environmentController.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { Secret, ServiceToken, Workspace, Integration } from '../../models';
import {
Secret,
ServiceToken,
Workspace,
Integration,
ServiceTokenData,
} from '../../models';
import { SecretVersion } from '../../ee/models';

/**
* Create new workspace environment named [environmentName] under workspace with id
Expand All @@ -12,25 +19,24 @@ export const createWorkspaceEnvironment = async (
req: Request,
res: Response
) => {
const { workspaceId, environmentName, environmentSlug } = req.body;
const { workspaceId } = req.params;
const { environmentName, environmentSlug } = req.body;
try {
// atomic create the environment
const workspace = await Workspace.findOneAndUpdate(
{
_id: workspaceId,
'environments.slug': { $ne: environmentSlug },
'environments.name': { $ne: environmentName },
},
{
$addToSet: {
environments: { name: environmentName, slug: environmentSlug },
},
}
);

if (!workspace) {
throw new Error('Failed to update workspace environment');
const workspace = await Workspace.findById(workspaceId).exec();
if (
!workspace ||
workspace?.environments.find(
({ name, slug }) => slug === environmentSlug || environmentName === name
)
) {
throw new Error('Failed to create workspace environment');
}

workspace?.environments.push({
name: environmentName.toLowerCase(),
slug: environmentSlug.toLowerCase(),
});
await workspace.save();
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
Expand Down Expand Up @@ -60,34 +66,56 @@ export const renameWorkspaceEnvironment = async (
req: Request,
res: Response
) => {
const { workspaceId, environmentName, environmentSlug, oldEnvironmentSlug } =
req.body;
const { workspaceId } = req.params;
const { environmentName, environmentSlug, oldEnvironmentSlug } = req.body;
try {
// user should pass both new slug and env name
if (!environmentSlug || !environmentName) {
throw new Error('Invalid environment given.');
}

// atomic update the env to avoid conflict
const workspace = await Workspace.findOneAndUpdate(
{ _id: workspaceId, 'environments.slug': oldEnvironmentSlug },
{
'environments.$.name': environmentName,
'environments.$.slug': environmentSlug,
}
);
const workspace = await Workspace.findById(workspaceId).exec();
if (!workspace) {
throw new Error('Failed to update workspace');
throw new Error('Failed to create workspace environment');
}

const isEnvExist = workspace.environments.some(
({ name, slug }) =>
slug !== oldEnvironmentSlug &&
(name === environmentName || slug === environmentSlug)
);
if (isEnvExist) {
throw new Error('Invalid environment given');
}

const envIndex = workspace?.environments.findIndex(
({ slug }) => slug === oldEnvironmentSlug
);
if (envIndex === -1) {
throw new Error('Invalid environment given');
}

workspace.environments[envIndex].name = environmentName.toLowerCase();
workspace.environments[envIndex].slug = environmentSlug.toLowerCase();

await workspace.save();
await Secret.updateMany(
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }
);
await SecretVersion.updateMany(
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }
);
await ServiceToken.updateMany(
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }
);
await ServiceTokenData.updateMany(
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }
);
await Integration.updateMany(
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }
Expand Down Expand Up @@ -120,37 +148,46 @@ export const deleteWorkspaceEnvironment = async (
req: Request,
res: Response
) => {
const { workspaceId, environmentSlug } = req.body;
const { workspaceId } = req.params;
const { environmentSlug } = req.body;
try {
// atomic delete the env in the workspacce
const workspace = await Workspace.findOneAndUpdate(
{ _id: workspaceId },
{
$pull: {
environments: {
slug: environmentSlug,
},
},
}
);
// atomic update the env to avoid conflict
const workspace = await Workspace.findById(workspaceId).exec();
if (!workspace) {
throw new Error('Failed to delete workspace environment');
throw new Error('Failed to create workspace environment');
}

const envIndex = workspace?.environments.findIndex(
({ slug }) => slug === environmentSlug
);
if (envIndex === -1) {
throw new Error('Invalid environment given');
}

workspace.environments.splice(envIndex, 1);
await workspace.save();

// clean up
await Secret.deleteMany({
workspace: workspaceId,
environment: environmentSlug,
});
await SecretVersion.deleteMany({
workspace: workspaceId,
environment: environmentSlug,
});
await ServiceToken.deleteMany({
workspace: workspaceId,
environment: environmentSlug,
});
await ServiceTokenData.deleteMany({
workspace: workspaceId,
environment: environmentSlug,
});
await Integration.deleteMany({
workspace: workspaceId,
environment: environmentSlug,
});

} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
Expand Down
5 changes: 0 additions & 5 deletions backend/src/ee/models/secretVersion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ import { Schema, model, Types } from 'mongoose';
import {
SECRET_SHARED,
SECRET_PERSONAL,
ENV_DEV,
ENV_TESTING,
ENV_STAGING,
ENV_PROD
} from '../../variables';

export interface ISecretVersion {
Expand Down Expand Up @@ -56,7 +52,6 @@ const secretVersionSchema = new Schema<ISecretVersion>(
},
environment: {
type: String,
enum: [ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD],
required: true
},
isDeleted: { // consider removing field
Expand Down
2 changes: 1 addition & 1 deletion backend/src/models/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
export interface IIntegration {
_id: Types.ObjectId;
workspace: Types.ObjectId;
environment: 'dev' | 'test' | 'staging' | 'prod';
environment: string;
isActive: boolean;
app: string;
target: string;
Expand Down
25 changes: 14 additions & 11 deletions backend/src/routes/v2/environment.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import express from 'express';
import express, { Response, Request } from 'express';
const router = express.Router();
import { body, param } from 'express-validator';
import { environmentController } from '../../controllers/v2';
Expand All @@ -7,14 +7,15 @@ import {
requireWorkspaceAuth,
validateRequest,
} from '../../middleware';
import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../../variables';
import { ADMIN, MEMBER } from '../../variables';

router.post(
'/:workspaceId',
requireAuth,
'/:workspaceId/environments',
requireAuth({
acceptedAuthModes: ['jwt'],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
acceptedStatuses: [COMPLETED, GRANTED],
}),
param('workspaceId').exists().trim(),
body('environmentSlug').exists().trim(),
Expand All @@ -24,11 +25,12 @@ router.post(
);

router.put(
'/:workspaceId',
requireAuth,
'/:workspaceId/environments',
requireAuth({
acceptedAuthModes: ['jwt'],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
acceptedStatuses: [COMPLETED, GRANTED],
}),
param('workspaceId').exists().trim(),
body('environmentSlug').exists().trim(),
Expand All @@ -39,11 +41,12 @@ router.put(
);

router.delete(
'/:workspaceId',
requireAuth,
'/:workspaceId/environments',
requireAuth({
acceptedAuthModes: ['jwt'],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN],
acceptedStatuses: [GRANTED],
}),
param('workspaceId').exists().trim(),
body('environmentSlug').exists().trim(),
Expand Down
4 changes: 2 additions & 2 deletions backend/src/routes/v2/secrets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
router.post(
'/',
body('workspaceId').exists().isString().trim(),
body('environment').exists().isString().trim().isIn(['dev', 'staging', 'prod', 'test']),
body('environment').exists().isString().trim(),
body('secrets')
.exists()
.custom((value) => {
Expand Down Expand Up @@ -73,7 +73,7 @@ router.post(
router.get(
'/',
query('workspaceId').exists().trim(),
query('environment').exists().trim().isIn(['dev', 'staging', 'prod', 'test']),
query('environment').exists().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: ['jwt', 'serviceToken']
Expand Down
2 changes: 1 addition & 1 deletion frontend/components/basic/Listbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export default function ListBox({
<Listbox.Option
key={personIdx}
className={({ active, selected }) =>
`my-0.5 relative cursor-default select-none py-2 pl-10 pr-4 rounded-md ${
`my-0.5 relative cursor-default select-none py-2 pl-10 pr-4 rounded-md capitalize ${
selected ? 'bg-white/10 text-gray-400 font-bold' : ''
} ${
active && !selected
Expand Down
Loading

0 comments on commit 8470030

Please sign in to comment.