Skip to content

Commit

Permalink
feat: add vendor callback url field
Browse files Browse the repository at this point in the history
  • Loading branch information
marcelovicentegc committed Nov 4, 2023
1 parent 2fe8eb2 commit a09c2a4
Show file tree
Hide file tree
Showing 13 changed files with 111 additions and 115 deletions.
8 changes: 7 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,10 @@ EMAIL_SERVER_PORT=
EMAIL_FROM=

# comma separated list of emails allowed to sign in to the app
ALLOWED_EMAILS=
ALLOWED_EMAILS=

# used by the tests.js script. You must create the test credentials via the API Management page
# when spinning this application, and using localhost as the origin
TEST_VENDOR_ID=
TEST_API_KEY=
TEST_API_PORT=
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
```mermaid
sequenceDiagram
Human->>+Chiron: Accesses the service based on the ALLOWED_EMAILS list
Human->>+Chiron: Provides vendor information (via API Management page)
Human->>+Chiron: Provides vendor information via API Management page (name, origin URL, callback URL)
Chiron->>+Human: Outputs vendor-specific API keys
Human->>+App: Configures App to hit Chiron with given API keys
App->>+Chiron: Hits Chiron with any data generated by AI (or not)
App->>+Chiron: Hits Chiron with any data generated by AI (or not) with a POST request to /api/data/completions
Human->>+Chiron: Review generated data (Human-in-the-loop)
Chiron->>+App: Hits App back with reviewed data
Chiron->>+App: Hits App back with reviewed data with a POST request to the callback URL
```

## Development
Expand Down
4 changes: 4 additions & 0 deletions app/api/data/completions/review/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ export async function POST(req) {

const result = await reviewCompletion(data, direction);

if (result?.acknowledged || result?.insertedId) {
// TODO: Call the vendor's webhook based on the result property
}

return new NextResponse(JSON.stringify(result), {
status: 200,
});
Expand Down
14 changes: 7 additions & 7 deletions app/api/data/completions/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ export async function POST(req) {
];

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

if (!apiKey) {
return new NextResponse("Bad Request", {
return new NextResponse(JSON.stringify("Bad Request"), {
status: 400,
});
}
Expand All @@ -31,23 +31,23 @@ export async function POST(req) {
const { host: vendorHost } = new URL(result.vendorUrl);

if (vendorHost !== originHost) {
return new NextResponse("Forbidden", {
return new NextResponse(JSON.stringify("Forbidden"), {
status: 403,
});
}

const decryptedApiKey = decrypt(result.apiKey);

if (decryptedApiKey !== apiKey) {
return new NextResponse("Unauthorized", {
return new NextResponse(JSON.stringify("Unauthorized"), {
status: 401,
});
}

const body = await req.json();

if (!body || !body?._id) {
return new NextResponse("Bad Request", {
return new NextResponse(JSON.stringify("Bad Request"), {
status: 400,
});
}
Expand All @@ -63,13 +63,13 @@ export async function POST(req) {
try {
await saveCompletion(data);

return new NextResponse("Created", {
return new NextResponse(JSON.stringify("Created"), {
status: 201,
});
} catch (error) {
console.error(error);

return new NextResponse("Error", {
return new NextResponse(JSON.stringify("Error"), {
status: 500,
});
}
Expand Down
2 changes: 1 addition & 1 deletion app/completions/approved/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default async function Home() {

if (!approvedCompletions || approvedCompletions.length === 0) {
return (
<Empty empty={{ description: "No completions were approved yet" }} />
<Empty empty={{ description: "No completions were approved yet 🤗" }} />
);
}

Expand Down
2 changes: 1 addition & 1 deletion app/completions/pending/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default async function PendingCompletionsReviewPage() {
const pendingReviews = await res.json();

if (!pendingReviews || pendingReviews.length === 0) {
return <Empty empty={{ description: "No pending reviews available" }} />;
return <Empty empty={{ description: "No pending reviews available 🤗" }} />;
}

const completions = pendingReviews.map(
Expand Down
2 changes: 1 addition & 1 deletion app/completions/rejected/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default async function Home() {

if (!rejectedCompletions || rejectedCompletions.length === 0) {
return (
<Empty empty={{ description: "No completions were rejected yet" }} />
<Empty empty={{ description: "No completions were rejected yet 🤗" }} />
);
}

Expand Down
Binary file modified bun.lockb
Binary file not shown.
12 changes: 12 additions & 0 deletions containers/api-management.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Heading,
List,
Menu,
Text,
TextInput,
} from "grommet";
import { useState } from "react";
Expand Down Expand Up @@ -89,6 +90,17 @@ export default function ApiManagementContainer(props) {
<br />
<List
data={vendors}
primaryKey={(item) => (
<Text key={item.name} size="large" weight="bold">
{item.name}
</Text>
)}
secondaryKey={(item) => (
<Text key={item.callbackUrl} size="small" color="dark-4">
Callback URL: {item.callbackUrl}
</Text>
)}
itemKey={(item) => item.name}
pad={{ left: "small", right: "none" }}
action={(item, index) => (
<Menu
Expand Down
5 changes: 4 additions & 1 deletion lib/db/reads.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ export async function getApiKeys() {
const result = await cursor.toArray();
await cursor.close();

const vendors = result.map((apiKey) => apiKey.vendorName);
const vendors = result.map((apiKey) => ({
name: apiKey.vendorName,
callbackUrl: apiKey.vendorCallbackUrl,
}));

return vendors;
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"start": "next start",
"lint": "next lint",
"semantic-release": "semantic-release",
"test": "sh test.sh"
"test": "node -r dotenv/config tests.js"
},
"dependencies": {
"@auth/mongodb-adapter": "^2.0.1",
Expand All @@ -25,6 +25,7 @@
"styled-components": "5"
},
"devDependencies": {
"dotenv": "^16.3.1",
"eslint": "^8",
"eslint-config-next": "13.5.4",
"eslint-config-prettier": "^9.0.0",
Expand Down
99 changes: 0 additions & 99 deletions test.sh

This file was deleted.

69 changes: 69 additions & 0 deletions tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
const http = require("http");
const crypto = require("crypto");

const vendorId = process.env.TEST_VENDOR_ID;
const apiKey = process.env.TEST_API_KEY;
const testApiPort = process.env.TEST_API_PORT;

function generateUUID() {
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) =>
(c ^ (crypto.randomBytes(1)[0] & (15 >> (c / 4)))).toString(16),
);
}

function generateRandomString() {
return crypto.randomBytes(4).toString("hex");
}

// Create a server that listens to requests on port 3001
const server = http.createServer((req, res) => {
// Set CORS headers
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Request-Method", "*");
res.setHeader("Access-Control-Allow-Methods", "OPTIONS, POST");
res.setHeader("Access-Control-Allow-Headers", "*");

if (req.method === "OPTIONS") {
res.writeHead(200);
res.end();
return;
}

// Handle POST requests
if (req.method === "POST") {
let data = "";

req.on("data", (chunk) => {
data += chunk;
});

req.on("end", () => {
console.log(JSON.parse(data));
res.writeHead(200);
res.end();
});
}
});

server.listen(testApiPort, () => {
console.log(`Test API server listening on port ${testApiPort}`);

fetch("http://localhost:3000/api/data/completions", {
method: "POST",
headers: {
host: `localhost:${testApiPort}`, // this is hidden in real world scenarios
vendorId,
apiKey,
"Content-Type": "application/json",
},
body: JSON.stringify({
_id: generateUUID(),
property1: generateRandomString(),
property2: generateRandomString(),
}),
})
.then((res) => res.json())
.then((json) => {
console.log(json);
});
});

0 comments on commit a09c2a4

Please sign in to comment.