Skip to content

Commit

Permalink
feat: add Write API token methods
Browse files Browse the repository at this point in the history
  • Loading branch information
angeloashmore committed Jan 23, 2024
1 parent 1dbaa48 commit dd74d5b
Show file tree
Hide file tree
Showing 6 changed files with 450 additions and 6 deletions.
158 changes: 154 additions & 4 deletions packages/manager/src/managers/git/GitManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,23 @@ import { decode } from "../../lib/decode";

import { API_ENDPOINTS } from "../../constants/API_ENDPOINTS";

import { UnauthorizedError, UnexpectedDataError } from "../../errors";
import {
UnauthenticatedError,
UnauthorizedError,
UnexpectedDataError,
} from "../../errors";

import { BaseManager } from "../BaseManager";

import { GitRepo, GitRepoSpcifier, Namespace } from "./types";
import { GitRepo, GitRepoSpecifier, Owner } from "./types";
import { buildGitRepoSpecifier } from "./buildGitRepoSpecifier";

type CreateGitHubAuthStateReturnType = {
key: string;
expiresAt: Date;
};

type GitManagerFetchOwnersReturnType = Namespace[];
type GitManagerFetchOwnersReturnType = Owner[];

type GitManagerFetchReposReturnType = GitRepo[];

Expand All @@ -34,7 +39,7 @@ type GitManagerFetchLinkedReposArgs = {
};
};

type GitManagerFetchLinkedReposReturnType = GitRepoSpcifier[];
type GitManagerFetchLinkedReposReturnType = GitRepoSpecifier[];

type GitManagerLinkRepoArgs = {
prismic: {
Expand All @@ -58,6 +63,40 @@ type GitManagerUnlinkRepoArgs = {
};
};

type CheckHasWriteAPITokenArgs = {
prismic: {
domain: string;
};
git: {
provider: "gitHub";
owner: string;
name: string;
};
};

type UpdateWriteAPITokenArgs = {
prismic: {
domain: string;
};
git: {
provider: "gitHub";
owner: string;
name: string;
};
token: string;
};

type DeleteWriteAPITokenArgs = {
prismic: {
domain: string;
};
git: {
provider: "gitHub";
owner: string;
name: string;
};
};

export class GitManager extends BaseManager {
async createGitHubAuthState(): Promise<CreateGitHubAuthStateReturnType> {
const url = new URL(
Expand Down Expand Up @@ -276,6 +315,117 @@ export class GitManager extends BaseManager {
}
}

async checkHasWriteAPIToken(
args: CheckHasWriteAPITokenArgs,
): Promise<boolean> {
const url = new URL(
"./git/linked-repos/write-api-token",
API_ENDPOINTS.SliceMachineV1,
);
url.searchParams.set("repository", args.prismic.domain);
url.searchParams.set(
"git",
buildGitRepoSpecifier({
provider: args.git.provider,
owner: args.git.owner,
name: args.git.name,
}),
);

const res = await this.#fetch(url);

if (!res.ok) {
switch (res.status) {
case 401:
throw new UnauthenticatedError();
case 403:
throw new UnauthorizedError();
default:
throw new Error("Failed to check Prismic Write API token.");
}
}

const json = await res.json();
const { value, error } = decode(
t.type({
hasWriteAPIToken: t.boolean,
}),
json,
);

if (error) {
throw new UnexpectedDataError(
`Failed to decode: ${error.errors.join(", ")}`,
{ cause: error },
);
}

return value.hasWriteAPIToken;
}

async updateWriteAPIToken(args: UpdateWriteAPITokenArgs): Promise<void> {
const url = new URL(
"./git/linked-repos/write-api-token",
API_ENDPOINTS.SliceMachineV1,
);
const res = await this.#fetch(url, {
method: "PUT",
body: {
prismic: {
domain: args.prismic.domain,
},
git: {
provider: args.git.provider,
owner: args.git.owner,
name: args.git.name,
},
token: args.token,
},
});

if (!res.ok) {
switch (res.status) {
case 401:
throw new UnauthenticatedError();
case 403:
throw new UnauthorizedError();
default:
throw new Error("Failed to update Prismic Write API token.");
}
}
}

async deleteWriteAPIToken(args: DeleteWriteAPITokenArgs): Promise<void> {
const url = new URL(
"./git/linked-repos/write-api-token",
API_ENDPOINTS.SliceMachineV1,
);
const res = await this.#fetch(url, {
method: "DELETE",
body: {
prismic: {
domain: args.prismic.domain,
},
git: {
provider: args.git.provider,
owner: args.git.owner,
name: args.git.name,
},
},
});

if (!res.ok) {
switch (res.status) {
case 401:
throw new UnauthenticatedError();
case 403:
throw new UnauthorizedError();
default:
throw new Error("Failed to delete Prismic Write API token.");
}
}
}

async #fetch(
url: URL,
config?: {
Expand Down
24 changes: 24 additions & 0 deletions packages/manager/src/managers/git/buildGitRepoSpecifier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { GitRepoSpecifier } from "./types";

/**
* Builds a Git repository specifier from its individual parts.
*
* @example
*
* ```typescript
* buildGitRepoSpecifier({
* provider: "gitHub",
* owner: "foo",
* name: "bar",
* });
* ```
*
* @param repoSpecifier - The Git repository specifier.
*
* @returns The specifier in the form of `provider@owner/name`.
*/
export const buildGitRepoSpecifier = (
repoSpecifier: GitRepoSpecifier,
): string => {
return `${repoSpecifier.provider}@${repoSpecifier.owner}/${repoSpecifier.name}`;
};
7 changes: 5 additions & 2 deletions packages/manager/src/managers/git/types.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
export type Namespace = {
export type Owner = {
provider: "gitHub";
id: string;
name: string;
type: "user" | "team" | null;
};

export type GitRepo = {
provider: "gitHub";
id: string;
owner: string;
name: string;
url: string;
pushedAt: Date;
};

export type GitRepoSpcifier = {
export type GitRepoSpecifier = {
provider: "gitHub";
owner: string;
name: string;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { expect, it } from "vitest";

import { UnauthenticatedError, UnauthorizedError } from "../src";

it("returns true if linked repositories have a Prismic Write API token", async ({
manager,
api,
login,
}) => {
const prismic = { domain: "domain" };
const git = { provider: "gitHub", owner: "owner", name: "name" } as const;

api.mockSliceMachineV1(
"./git/linked-repos/write-api-token",
{ hasWriteAPIToken: true },
{
searchParams: {
repository: prismic.domain,
git: `${git.provider}@${git.owner}/${git.name}`,
},
},
);

await login();
const res = await manager.git.checkHasWriteAPIToken({ prismic, git });

expect(res).toStrictEqual(true);
});

it("returns false if linked repositories do not have a Prismic Write API token", async ({
manager,
api,
login,
}) => {
const prismic = { domain: "domain" };
const git = { provider: "gitHub", owner: "owner", name: "name" } as const;

api.mockSliceMachineV1(
"./git/linked-repos/write-api-token",
{ hasWriteAPIToken: false },
{
searchParams: {
repository: prismic.domain,
git: `${git.provider}@${git.owner}/${git.name}`,
},
},
);

await login();
const res = await manager.git.checkHasWriteAPIToken({ prismic, git });

expect(res).toStrictEqual(false);
});

it("throws UnauthenticatedError if the API returns 403", async ({
manager,
api,
login,
}) => {
api.mockSliceMachineV1("./git/linked-repos/write-api-token", undefined, {
statusCode: 401,
});

await login();
await expect(() =>
manager.git.checkHasWriteAPIToken({
prismic: { domain: "domain" },
git: { provider: "gitHub", owner: "owner", name: "name" },
}),
).rejects.toThrow(UnauthenticatedError);
});

it("throws UnauthorizedError if the API returns 403", async ({
manager,
api,
login,
}) => {
api.mockSliceMachineV1("./git/linked-repos/write-api-token", undefined, {
statusCode: 403,
});

await login();
await expect(() =>
manager.git.checkHasWriteAPIToken({
prismic: { domain: "domain" },
git: { provider: "gitHub", owner: "owner", name: "name" },
}),
).rejects.toThrow(UnauthorizedError);
});

it("throws UnauthenticatedError if the user is logged out", async ({
manager,
}) => {
await manager.user.logout();

await expect(() =>
manager.git.checkHasWriteAPIToken({
prismic: { domain: "domain" },
git: { provider: "gitHub", owner: "owner", name: "name" },
}),
).rejects.toThrow(UnauthenticatedError);
});
Loading

0 comments on commit dd74d5b

Please sign in to comment.