Skip to content

Commit

Permalink
API environment endpoints (#2529)
Browse files Browse the repository at this point in the history
  • Loading branch information
romain-growthbook committed May 30, 2024
1 parent e490f38 commit 558be1f
Show file tree
Hide file tree
Showing 28 changed files with 1,762 additions and 121 deletions.
169 changes: 169 additions & 0 deletions packages/back-end/generated/spec.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion packages/back-end/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
"generate-api-types": "yarn generate-api-models && swagger-cli bundle -t yaml src/api/openapi/openapi.tmp.yaml -o generated/spec.yaml && node src/scripts/generate-openapi.mjs"
},
"dependencies": {
"@aws-sdk/client-sts": "^3.567.0",
"@aws-sdk/client-athena": "^3.564.0",
"@aws-sdk/client-sts": "^3.567.0",
"@clickhouse/client": "^1.0.1",
"@databricks/sql": "^1.8.1",
"@dqbd/tiktoken": "^1.0.7",
Expand Down Expand Up @@ -141,7 +141,9 @@
"delay-cli": "^2.0.0",
"jest": "^27.1.1",
"minimist": "^1.2.8",
"mongodb-memory-server": "^9.2.0",
"rimraf": "^3.0.2",
"supertest": "^7.0.0",
"typescript": "5.3.3"
},
"optionalDependencies": {
Expand Down
2 changes: 2 additions & 0 deletions packages/back-end/src/api/api.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import experimentsRouter from "./experiments/experiments.router";
import metricsRouter from "./metrics/metrics.router";
import segmentsRouter from "./segments/segments.router";
import projectsRouter from "./projects/projects.router";
import environmentsRouter from "./environments/environments.router";
import savedGroupsRouter from "./saved-groups/saved-groups.router";
import sdkConnectionsRouter from "./sdk-connections/sdk-connections.router";
import sdkPayloadRouter from "./sdk-payload/sdk-payload.router";
Expand Down Expand Up @@ -82,6 +83,7 @@ router.use("/metrics", metricsRouter);
router.use("/segments", segmentsRouter);
router.use("/dimensions", dimensionsRouter);
router.use("/projects", projectsRouter);
router.use("/environments", environmentsRouter);
router.use("/sdk-connections", sdkConnectionsRouter);
router.use("/data-sources", dataSourcesRouter);
router.use("/visual-changesets", visualChangesetsRouter);
Expand Down
46 changes: 46 additions & 0 deletions packages/back-end/src/api/environments/deleteEnvironment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { DeleteEnvironmentResponse } from "../../../types/openapi";
import { createApiRequestHandler } from "../../util/handler";
import { deleteEnvironmentValidator } from "../../validators/openapi";
import { updateOrganization } from "../../models/OrganizationModel";
import { OrganizationInterface } from "../../../types/organization";
import { auditDetailsDelete } from "../../services/audit";

export const deleteEnvironment = createApiRequestHandler(
deleteEnvironmentValidator
)(
async (req): Promise<DeleteEnvironmentResponse> => {
const id = req.params.id;
const org = req.context.org;
const environments = org.settings?.environments || [];

const environment = environments.find((env) => env.id === id);
if (!environment) {
throw Error(`Environment ${id} does not exists!`);
}

if (!req.context.permissions.canDeleteEnvironment(environment))
req.context.permissions.throwPermissionError();

const updates: Partial<OrganizationInterface> = {
settings: {
...org.settings,
environments: [...environments.filter((env) => env.id !== id)],
},
};

await updateOrganization(org.id, updates);

await req.audit({
event: "environment.delete",
entity: {
object: "environment",
id: environment.id,
},
details: auditDetailsDelete(environment),
});

return {
deletedId: id,
};
}
);
14 changes: 14 additions & 0 deletions packages/back-end/src/api/environments/environments.router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Router } from "express";
import { listEnvironments } from "./listEnvironments";
import { putEnvironment } from "./putEnvironment";
import { postEnvironment } from "./postEnvironment";
import { deleteEnvironment } from "./deleteEnvironment";

const router = Router();

router.get("/", listEnvironments);
router.post("/", postEnvironment);
router.put("/:id", putEnvironment);
router.delete("/:id", deleteEnvironment);

export default router;
33 changes: 33 additions & 0 deletions packages/back-end/src/api/environments/listEnvironments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { ListEnvironmentsResponse } from "../../../types/openapi";
import { createApiRequestHandler } from "../../util/handler";
import { listEnvironmentsValidator } from "../../validators/openapi";

export const listEnvironments = createApiRequestHandler(
listEnvironmentsValidator
)(
async (req): Promise<ListEnvironmentsResponse> => {
const environments = (
req.context.org.settings?.environments || []
).filter((environment) =>
req.context.permissions.canReadMultiProjectResource(environment.projects)
);

return {
environments: environments.map(
({
id,
description = "",
toggleOnList = false,
defaultState = false,
projects = [],
}) => ({
id,
projects,
description,
defaultState,
toggleOnList,
})
),
};
}
);
46 changes: 46 additions & 0 deletions packages/back-end/src/api/environments/postEnvironment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { PostEnvironmentResponse } from "../../../types/openapi";
import { createApiRequestHandler } from "../../util/handler";
import { postEnvironmentValidator } from "../../validators/openapi";
import { updateOrganization } from "../../models/OrganizationModel";
import { OrganizationInterface } from "../../../types/organization";
import { auditDetailsCreate } from "../../services/audit";
import { validatePayload } from "./validations";

export const postEnvironment = createApiRequestHandler(
postEnvironmentValidator
)(
async (req): Promise<PostEnvironmentResponse> => {
const environment = await validatePayload(req.context, req.body);

const org = req.context.org;

if (org.settings?.environments?.some((env) => env.id === environment.id)) {
throw Error(`Environment ${environment.id} already exists!`);
}

if (!req.context.permissions.canCreateOrUpdateEnvironment(environment))
req.context.permissions.throwPermissionError();

const updates: Partial<OrganizationInterface> = {
settings: {
...org.settings,
environments: [...(org.settings?.environments || []), environment],
},
};

await updateOrganization(org.id, updates);

await req.audit({
event: "environment.create",
entity: {
object: "environment",
id: environment.id,
},
details: auditDetailsCreate(environment),
});

return {
environment,
};
}
);
Loading

1 comment on commit 558be1f

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy preview for docs ready!

✅ Preview
https://docs-gc0yq0oqc-growthbook.vercel.app

Built with commit 558be1f.
This pull request is being automatically deployed with vercel-action

Please sign in to comment.