Skip to content

Commit

Permalink
feat(#31): implemented api for environment crud operations
Browse files Browse the repository at this point in the history
  • Loading branch information
akhilmhdh committed Jan 11, 2023
1 parent 861639d commit 3ad3e19
Show file tree
Hide file tree
Showing 15 changed files with 293 additions and 37 deletions.
2 changes: 2 additions & 0 deletions backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import {
workspace as v2WorkspaceRouter,
serviceTokenData as v2ServiceTokenDataRouter,
apiKeyData as v2APIKeyDataRouter,
environment as v2EnvironmentRouter,
} from './routes/v2';

import { healthCheck } from './routes/status';
Expand Down Expand Up @@ -108,6 +109,7 @@ 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
8 changes: 7 additions & 1 deletion backend/src/controllers/v1/integrationAuthController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,17 @@ export const oAuthExchange = async (

if (!INTEGRATION_SET.has(integration))
throw new Error('Failed to validate integration');

const environments = req.membership.workspace?.environments || [];
if(environments.length === 0){
throw new Error("Failed to get environments")
}

await IntegrationService.handleOAuthExchange({
workspaceId,
integration,
code
code,
environment: environments[0].slug,
});
} catch (err) {
Sentry.setUser(null);
Expand Down
10 changes: 6 additions & 4 deletions backend/src/controllers/v1/secretController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
import { pushKeys } from '../../helpers/key';
import { eventPushSecrets } from '../../events';
import { EventService } from '../../services';
import { ENV_SET } from '../../variables';
import { postHogClient } from '../../services';

interface PushSecret {
Expand Down Expand Up @@ -44,7 +43,8 @@ export const pushSecrets = async (req: Request, res: Response) => {
const { workspaceId } = req.params;

// validate environment
if (!ENV_SET.has(environment)) {
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment');
}

Expand Down Expand Up @@ -116,7 +116,8 @@ export const pullSecrets = async (req: Request, res: Response) => {
const { workspaceId } = req.params;

// validate environment
if (!ENV_SET.has(environment)) {
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment');
}

Expand Down Expand Up @@ -183,7 +184,8 @@ export const pullSecretsServiceToken = async (req: Request, res: Response) => {
const { workspaceId } = req.params;

// validate environment
if (!ENV_SET.has(environment)) {
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment');
}

Expand Down
4 changes: 2 additions & 2 deletions backend/src/controllers/v1/serviceTokenController.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Request, Response } from 'express';
import { ServiceToken } from '../../models';
import { createToken } from '../../helpers/auth';
import { ENV_SET } from '../../variables';
import { JWT_SERVICE_SECRET } from '../../config';

/**
Expand Down Expand Up @@ -36,7 +35,8 @@ export const createServiceToken = async (req: Request, res: Response) => {
} = req.body;

// validate environment
if (!ENV_SET.has(environment)) {
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment');
}

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

/**
* Create new workspace environment named [environmentName] under workspace with id
* @param req
* @param res
* @returns
*/
export const createWorkspaceEnvironment = async (
req: Request,
res: Response
) => {
const { workspaceId, 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');
}
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to create new workspace environment',
});
}

return res.status(200).send({
message: 'Successfully created new environment',
workspace: workspaceId,
environment: {
name: environmentName,
slug: environmentSlug,
},
});
};

/**
* Rename workspace environment with new name and slug of a workspace with [workspaceId]
* Old slug [oldEnvironmentSlug] must be provided
* @param req
* @param res
* @returns
*/
export const renameWorkspaceEnvironment = async (
req: Request,
res: Response
) => {
const { workspaceId, 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,
}
);
if (!workspace) {
throw new Error('Failed to update workspace');
}

await Secret.updateMany(
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }
);
await ServiceToken.updateMany(
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }
);
await Integration.updateMany(
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }
);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to update workspace environment',
});
}

return res.status(200).send({
message: 'Successfully update environment',
workspace: workspaceId,
environment: {
name: environmentName,
slug: environmentSlug,
},
});
};

/**
* Delete workspace environment by [environmentSlug] of workspace [workspaceId] and do the clean up
* @param req
* @param res
* @returns
*/
export const deleteWorkspaceEnvironment = async (
req: Request,
res: Response
) => {
const { workspaceId, environmentSlug } = req.body;
try {
// atomic delete the env in the workspacce
const workspace = await Workspace.findOneAndUpdate(
{ _id: workspaceId },
{
$pull: {
environments: {
slug: environmentSlug,
},
},
}
);
if (!workspace) {
throw new Error('Failed to delete workspace environment');
}

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

} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to delete workspace environment',
});
}

return res.status(200).send({
message: 'Successfully deleted environment',
workspace: workspaceId,
environment: environmentSlug,
});
};
4 changes: 3 additions & 1 deletion backend/src/controllers/v2/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import * as serviceTokenDataController from './serviceTokenDataController';
import * as apiKeyDataController from './apiKeyDataController';
import * as secretController from './secretController';
import * as secretsController from './secretsController';
import * as environmentController from './environmentController';

export {
workspaceController,
serviceTokenDataController,
apiKeyDataController,
secretController,
secretsController
secretsController,
environmentController
}
9 changes: 7 additions & 2 deletions backend/src/controllers/v2/workspaceController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
import { pushKeys } from '../../helpers/key';
import { postHogClient, EventService } from '../../services';
import { eventPushSecrets } from '../../events';
import { ENV_SET } from '../../variables';

interface V2PushSecret {
type: string; // personal or shared
Expand Down Expand Up @@ -52,7 +51,8 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
const { workspaceId } = req.params;

// validate environment
if (!ENV_SET.has(environment)) {
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment');
}

Expand Down Expand Up @@ -129,6 +129,11 @@ export const pullSecrets = async (req: Request, res: Response) => {
} else if (req.serviceTokenData) {
userId = req.serviceTokenData.user._id
}
// validate environment
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment');
}

secrets = await pull({
userId,
Expand Down
8 changes: 4 additions & 4 deletions backend/src/helpers/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import {
import { exchangeCode, exchangeRefresh, syncSecrets } from '../integrations';
import { BotService } from '../services';
import {
ENV_DEV,
EVENT_PUSH_SECRETS,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY
} from '../variables';
Expand Down Expand Up @@ -36,11 +34,13 @@ interface Update {
const handleOAuthExchangeHelper = async ({
workspaceId,
integration,
code
code,
environment
}: {
workspaceId: string;
integration: string;
code: string;
environment: string;
}) => {
let action;
let integrationAuth;
Expand Down Expand Up @@ -102,9 +102,9 @@ const handleOAuthExchangeHelper = async ({
// initialize new integration after exchange
await new Integration({
workspace: workspaceId,
environment: ENV_DEV,
isActive: false,
app: null,
environment,
integration,
integrationAuth: integrationAuth._id
}).save();
Expand Down
5 changes: 0 additions & 5 deletions backend/src/models/integration.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { Schema, model, Types } from 'mongoose';
import {
ENV_DEV,
ENV_TESTING,
ENV_STAGING,
ENV_PROD,
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
Expand Down Expand Up @@ -32,7 +28,6 @@ const integrationSchema = new Schema<IIntegration>(
},
environment: {
type: String,
enum: [ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD],
required: true
},
isActive: {
Expand Down
5 changes: 0 additions & 5 deletions backend/src/models/secret.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 ISecret {
Expand Down Expand Up @@ -53,7 +49,6 @@ const secretSchema = new Schema<ISecret>(
},
environment: {
type: String,
enum: [ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD],
required: true
},
secretKeyCiphertext: {
Expand Down
4 changes: 0 additions & 4 deletions backend/src/models/serviceToken.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import { Schema, model, Types } from 'mongoose';
import { ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD } from '../variables';

// TODO: deprecate
export interface IServiceToken {
_id: Types.ObjectId;
name: string;
Expand Down Expand Up @@ -33,7 +30,6 @@ const serviceTokenSchema = new Schema<IServiceToken>(
},
environment: {
type: String,
enum: [ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD],
required: true
},
expiresAt: {
Expand Down
Loading

0 comments on commit 3ad3e19

Please sign in to comment.