Skip to content

Commit

Permalink
feat: ability to create api keys
Browse files Browse the repository at this point in the history
  • Loading branch information
marcelovicentegc committed Oct 15, 2023
1 parent ae83b89 commit eb221e3
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 18 deletions.
143 changes: 138 additions & 5 deletions app/api-management/page.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,148 @@
"use client";

import Empty from "@/components/empty";
import { FormPopUp } from "@/components/form";
import { PageLayout } from "@/components/page-layout";
import { Box, FormField, TextInput } from "grommet";
import { useState } from "react";
import { StatusGood } from "grommet-icons";

export default async function ApiManagementPage() {
export default function ApiManagementPage() {
const integrations = [];
const [open, setOpen] = useState(false);
const [submitting, setSubmitting] = useState(false);

const onOpen = () => setOpen(true);
const onClose = () => setOpen(false);

const onSubmit = async (event) => {
setSubmitting(true);
const {
value: { name: vendorName, url: vendorUrl },
} = event;

const formattedUrl = new URL(vendorUrl);

const data = {
vendorName,
vendorUrl: formattedUrl.origin,
};

const response = await fetch("/api/api-keys", {
method: "POST",
body: JSON.stringify(data),
});

if (response.ok) {
onClose();

const data = await response.json();

prompt(
`
This is the only time you will see this API key,
so make sure you copy it and store it somewhere safe.`,
JSON.stringify(data),
);
}

setSubmitting(false);
};

if (!integrations || integrations.length === 0) {
return (
<Empty
empty={{ description: "No integrations available to manage yet" }}
/>
<>
<Empty
empty={{
description: "No integrations available to manage yet",
label: "Create one 🆕",
callback: onOpen,
}}
/>
<Form
open={open}
onClose={onClose}
onSubmit={onSubmit}
submitting={submitting}
/>
</>
);
}

return integrations.map((integration) => integration._id);
return (
<PageLayout>
{integrations.map((integration) => integration._id)}
<Form
open={open}
onClose={onClose}
onSubmit={onSubmit}
submitting={submitting}
/>
</PageLayout>
);
}

function Form(props) {
const { onClose, open, onSubmit, submitting } = props;

return (
<FormPopUp
open={open}
onClose={onClose}
heading={"Add"}
onSubmit={onSubmit}
submitting={submitting}
>
<FormField
label="Vendor Name"
aria-label="name"
name="name"
required
validate={[
{ regexp: /^[a-z]/i },
(name) => {
if (name && name.length === 1) return "must be >1 character";
return undefined;
},
() => {
return {
message: (
<Box align="end">
<StatusGood />
</Box>
),
status: "info",
};
},
]}
/>
<FormField
label="Vendor URL"
name="url"
required
validate={[
(url) => {
try {
new URL(url);
} catch (error) {
console.error(error);
return "must be a valid url";
}
},
() => {
return {
message: (
<Box align="end">
<StatusGood />
</Box>
),
status: "info",
};
},
]}
>
<TextInput name="url" aria-label="url" type="url" />
</FormField>
</FormPopUp>
);
}
19 changes: 14 additions & 5 deletions app/api/api-keys/route.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import crypto from "crypto";
import { getServerSession } from "next-auth";
import { NextResponse } from "next/server";
import { authOptions } from "../auth/[...nextauth]/route";
import { saveApiKeys } from "@/lib/db/writes";
import { encrypt } from "@/lib/encryption";

export async function POST(req, res) {
const session = await getServerSession(req, res, authOptions);
export async function POST(req) {
const session = await getServerSession(authOptions);

if (!session) {
return new NextResponse("Unauthorized", { status: 401 });
}

try {
const { vendorName, vendorUrl } = await req.body;
const { vendorName, vendorUrl } = await req.json();

if (!vendorUrl) {
throw new Error("You must enter a vendor URL.");
Expand Down Expand Up @@ -43,9 +44,17 @@ export async function POST(req, res) {
createdBy: session.user.email,
};

await saveApiKeys(data);
const result = await saveApiKeys(data);

return new NextResponse("Created", {
if (!result?.insertedId) {
console.error("Failed to insert API key into database");

return new NextResponse("Error", {
status: 500,
});
}

return new NextResponse(JSON.stringify({ vendorId, apiKey }), {
status: 201,
});
} catch (error) {
Expand Down
6 changes: 4 additions & 2 deletions app/api/data/completions/review/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ export async function POST(req) {
return new NextResponse(JSON.stringify("Unauthorized"), { status: 401 });
}

if (!req.body._id && !req.body.direction) {
const body = await req.json();

if (!body._id && !body.direction) {
return new NextResponse(JSON.stringify("Bad Request"), { status: 400 });
}

try {
const { _id, direction } = req.body;
const { _id, direction } = body;
validateDirection(direction);

const result = await reviewCompletion(_id, direction);
Expand Down
6 changes: 4 additions & 2 deletions app/api/data/completions/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,16 @@ export async function POST(req) {
});
}

if (!req.body) {
const body = await req.json();

if (!body) {
return new NextResponse("Bad Request", {
status: 400,
});
}

try {
await saveCompletion(req.body);
await saveCompletion(body);

return new NextResponse("Created", {
status: 201,
Expand Down
6 changes: 2 additions & 4 deletions components/empty.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
"use client";

import { Box, Button, Text } from "grommet";
import { useRouter } from "next/navigation";

export default function Empty(props) {
const { empty } = props;
const { push } = useRouter();

return (
<Box gap="small" pad="xlarge" align="center">
Expand All @@ -16,12 +14,12 @@ export default function Empty(props) {
>
{empty.description}
</Text>
{empty?.label && empty?.actionRoute ? (
{empty?.label && empty?.callback ? (
<Box>
<Button
label={empty.label}
primary
onClick={() => push(empty.actionRoute)}
onClick={() => empty.callback()}
/>
</Box>
) : null}
Expand Down
32 changes: 32 additions & 0 deletions components/form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"use client";

import { Box, Button, Form, Heading, Layer } from "grommet";

import { Close } from "grommet-icons";

export function FormPopUp(props) {
const { onClose, open, heading, onSubmit, children, submitting } = props;

return open ? (
<Layer position="right" full="vertical" modal>
<Box fill="vertical" overflow="auto" width="medium" pad="medium">
<Form validate="blur" onSubmit={onSubmit}>
<Box flex={false} direction="row" justify="between">
<Heading level={2} margin="none">
{heading}
</Heading>
<Button icon={<Close />} onClick={onClose} />
</Box>
{children}
<Box flex={false} as="footer" align="start">
<Button
type="submit"
label={submitting ? "Submitting" : "Submit"}
primary
/>
</Box>
</Form>
</Box>
</Layer>
) : null;
}
13 changes: 13 additions & 0 deletions components/page-layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"use client";

import { Box } from "grommet";

export function PageLayout(props) {
const { children } = props;

return (
<Box align="center" justify="center" pad="large">
<Box width="medium">{children}</Box>
</Box>
);
}
1 change: 1 addition & 0 deletions lib/db/writes.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ObjectId } from "mongodb";
import {
getApiKeysCollection,
getApprovedCompletionsCollection,
getRejectedCompletionsCollection,
} from "../mongodb";
Expand Down

0 comments on commit eb221e3

Please sign in to comment.