Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/projects & envs #92

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 27 additions & 4 deletions src/api-gateway/api-gateway.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
# -----------------------------------------------

type ApiTokens {
_id: String!
launchRoomToken: String!
_id: String
launchRoomToken: String
}

type Clause {
Expand All @@ -27,10 +27,21 @@ type ClientSideAvailability {
usingMobileKey: Boolean!
}

# The javascript `Date` as string. Type represents date and time as the ISO Date string.
scalar DateTime

type Empty {
ok: Boolean!
}

type Environment {
_id: ID!
apiToken: ID!
deletedAt: DateTime
name: String!
project: ID!
}

type Fallthrough {
rollout: VariationsObject
variation: Int
Expand All @@ -47,6 +58,7 @@ type FlagDetails {
clientSideAvailability: ClientSideAvailability!
deleted: Boolean!
description: String!
environment: ID!
fallthrough: Fallthrough!
key: String!
name: String!
Expand Down Expand Up @@ -77,16 +89,27 @@ scalar JSON @specifiedBy(url: "http://www.ecma-international.org/publications/fi
type Mutation {
subscribeToNewsletter(email: String!, firstName: String, lastName: String): Empty!
upsertApiTokens(_id: String!, launchRoomToken: String!): ApiTokens
upsertFlag(archived: Boolean, description: String, fallthrough: FallthroughInput, key: ID!, name: String, offVariation: Int, on: Boolean, rules: [RuleInput!], variationsBoolean: [Boolean!], variationsJson: [String!], variationsNumber: [Int!], variationsString: [String!], workspaceId: ID!): Boolean!
upsertEnvironment(_id: ID, deletedAt: DateTime, launchRoomToken: String, name: String!, project: ID): Environment
upsertFlag(archived: Boolean, description: String, environment: ID!, fallthrough: FallthroughInput, key: ID!, name: String, offVariation: Int, on: Boolean, rules: [RuleInput!], variationsBoolean: [Boolean!], variationsJson: [String!], variationsNumber: [Int!], variationsString: [String!], workspaceId: ID!): Boolean!
upsertProject(_id: ID, deletedAt: DateTime, name: String!, workspace: ID!): Project
}

type Prerequisite {
key: String!
variation: Int!
}

type Project {
_id: ID!
deletedAt: DateTime
name: String!
workspace: ID!
}

type Query {
apiTokens: ApiTokens!
fetchApiTokens(_id: ID!): ApiTokens!
fetchEnvironments(workspace: ID!): [Environment!]!
fetchProjects(workspace: ID!): [Project!]!
flagDetails(key: ID!, workspaceId: ID!): FlagDetails
flagsStatus(archived: Boolean!, limit: Int!, skip: Int!, workspaceId: ID!): FlagsStatus!

Expand Down
4 changes: 4 additions & 0 deletions src/api-gateway/api-gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { OnefxAuth } from "onefx-auth";
import { NonEmptyArray } from "type-graphql/dist/interfaces/NonEmptyArray";
import { NewsletterResolver } from "@/shared/newsletter/newsletter-resolver";
import { FlagResolver } from "@/api-gateway/resolvers/flag-resolver";
import { ProjectsResolver } from "@/api-gateway/resolvers/project-resolver";
import { EnvironmentsResolver } from "@/api-gateway/resolvers/environment-resolver";
import { MyServer } from "@/server/start-server";
import { customAuthChecker } from "@/api-gateway/auth-checker";
import { ApiTokensResolver } from "@/shared/api-tokens/api-tokens-resolver";
Expand All @@ -29,6 +31,8 @@ export async function setApiGateway(server: MyServer): Promise<void> {
NewsletterResolver,
FlagResolver,
ApiTokensResolver,
ProjectsResolver,
EnvironmentsResolver,
];
server.resolvers = resolvers;

Expand Down
125 changes: 125 additions & 0 deletions src/api-gateway/resolvers/environment-resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import {
Args,
ID,
ArgsType,
Authorized,
Ctx,
Field,
Mutation,
ObjectType,
Query,
Resolver,
} from "type-graphql";
import { IContext } from "@/api-gateway/api-gateway";
import { Environment as EnvironmentDoc } from "@/model/environment-model";

@ObjectType()
class Environment {
@Field(() => ID)
_id: string;

@Field(() => String)
name: string;

@Field(() => ID)
project: string;

@Field(() => ID)
apiToken: string;

@Field(() => Date, { nullable: true })
deletedAt: Date;
}

@ArgsType()
class UpsertEnvironmentRequest {
@Field(() => ID, { nullable: true })
_id: string;

@Field(() => String)
name: string;

@Field(() => ID, { nullable: true })
project?: string;

@Field(() => String, { nullable: true })
launchRoomToken?: string;

@Field(() => Date, { nullable: true })
deletedAt?: Date;
}

@ArgsType()
class RequestByWorkspaceId {
@Field(() => ID)
workspace: string;
}

@Resolver()
export class EnvironmentsResolver {
@Authorized()
@Query(() => [Environment])
public async fetchEnvironments(
@Args() { workspace }: RequestByWorkspaceId,
@Ctx() { model: { environmentModel, projectModel } }: IContext
): Promise<EnvironmentDoc[]> {
const projects = await projectModel.find({ workspace, deletedAt: null });
const environments = await Promise.all(
projects.map(
async (project): Promise<EnvironmentDoc[]> =>
environmentModel.find({
project: project._id,
deletedAt: null,
})
)
);
return environments.flat();
}

@Authorized()
@Mutation(() => Environment, { nullable: true })
async upsertEnvironment(
@Args() { _id, name, project, deletedAt }: UpsertEnvironmentRequest,
@Ctx()
{
model: { environmentModel, projectModel, apiTokens, flagModel },
}: IContext
): Promise<EnvironmentDoc | null> {
if (_id) {
return environmentModel.findOneAndUpdate(
{ _id },
{ name, project, deletedAt }
);
}
const currentProject = await projectModel.findOne({ _id: project });
const apiToken = await apiTokens.create({
workspace: currentProject?.workspace,
});
const environment = await environmentModel.create({
name,
project,
apiToken,
});

const prevEnvironment = await environmentModel.findOne({
project,
deletedAt: null,
});
if (prevEnvironment) {
const flags = await flagModel
.find({
workspace: currentProject?.workspace,
environment: prevEnvironment._id,
})
.lean();

await Promise.all(
flags.map(async (flag) => {
delete flag._id;
await flagModel.create({ ...flag, environment: environment._id });
})
);
}
return environment;
}
}
54 changes: 38 additions & 16 deletions src/api-gateway/resolvers/flag-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ class FlagDetails {
@Field(() => ClientSideAvailability)
clientSideAvailability: ClientSideAvailability;

@Field(() => ID)
environment: boolean;

@Field(() => Boolean)
archived: boolean;
}
Expand Down Expand Up @@ -281,6 +284,9 @@ class UpFlagDetailsArgs {
@Field(() => FallthroughInput, { nullable: true })
fallthrough: FallthroughInput;

@Field(() => ID)
environment: string;

@Field(() => Boolean, { nullable: true })
archived: boolean;
}
Expand Down Expand Up @@ -341,7 +347,8 @@ export class FlagResolver {
@Mutation(() => Boolean)
async upsertFlag(
@Args() detail: UpFlagDetailsArgs,
@Ctx() { model: { flagModel, userWorkspace }, userId }: IContext
@Ctx()
{ model: { flagModel, userWorkspace, environmentModel }, userId }: IContext
): Promise<boolean> {
const {
key,
Expand All @@ -351,11 +358,15 @@ export class FlagResolver {
offVariation,
fallthrough,
archived,
environment,
} = detail;

await assertWorkspace(userWorkspace, userId, workspaceId);

const flag = await flagModel.findOne({ key, workspace: workspaceId });
const flag = await flagModel.findOne({
key,
workspace: workspaceId,
environment,
});
if (flag) {
const updated = {} as Record<string, unknown>;

Expand All @@ -381,11 +392,13 @@ export class FlagResolver {
if (archived !== undefined) {
updated.archived = archived;
}
updated.environment = environment;

await flagModel.findOneAndUpdate(
{
key,
workspace: workspaceId,
environment,
},
updated
);
Expand All @@ -401,20 +414,29 @@ export class FlagResolver {
variationsJson?.map((value) => JSON.parse(value)) ||
variationsNumber ||
variationsString;

await flagModel.create({
workspace: workspaceId,
clientSideAvailability: {
usingMobileKey: true,
usingEnvironmentId: true,
},
isOn: true,
salt: "",
sel: "",
targets: [],
variations,
...detail,
const currentEnvironment = await environmentModel.findById(environment);
const environments = await environmentModel.find({
project: currentEnvironment?.project,
deletedAt: null,
});
await Promise.all(
environments.map(async (value) =>
flagModel.create({
workspace: workspaceId,
clientSideAvailability: {
usingMobileKey: true,
usingEnvironmentId: true,
},
isOn: true,
salt: "",
sel: "",
targets: [],
variations,
...detail,
environment: value._id,
})
)
);
}

return true;
Expand Down
77 changes: 77 additions & 0 deletions src/api-gateway/resolvers/project-resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {
Args,
ID,
ArgsType,
Authorized,
Ctx,
Field,
Mutation,
ObjectType,
Query,
Resolver,
} from "type-graphql";
import { IContext } from "@/api-gateway/api-gateway";
import { Project as ProjectDoc } from "@/model/project-model";

@ObjectType()
class Project {
@Field(() => ID)
_id: string;

@Field(() => String)
name: string;

@Field(() => ID)
workspace: string;

@Field(() => Date, { nullable: true })
deletedAt: Date;
}

@ArgsType()
class UpsertProjectRequest {
@Field(() => ID, { nullable: true })
_id: string;

@Field(() => String)
name: string;

@Field(() => ID)
workspace: string;

@Field(() => Date, { nullable: true })
deletedAt?: Date;
}

@ArgsType()
class RequestByWorkspaceId {
@Field(() => ID)
workspace: string;
}

@Resolver()
export class ProjectsResolver {
@Authorized()
@Query(() => [Project])
public async fetchProjects(
@Args() { workspace }: RequestByWorkspaceId,
@Ctx() { model: { projectModel } }: IContext
): Promise<ProjectDoc[]> {
return projectModel.find({ workspace, deletedAt: null });
}

@Authorized()
@Mutation(() => Project, { nullable: true })
async upsertProject(
@Args() { _id, name, workspace, deletedAt }: UpsertProjectRequest,
@Ctx() { model: { projectModel } }: IContext
): Promise<ProjectDoc | null> {
if (_id) {
return projectModel.findOneAndUpdate(
{ _id },
{ name, workspace, deletedAt }
);
}
return projectModel.create({ name, workspace });
}
}
Loading