diff --git a/package.json b/package.json
index 8b09b26..6b3c518 100644
--- a/package.json
+++ b/package.json
@@ -41,6 +41,7 @@
"next-auth": "^4.24.6",
"nextjs-cors": "^2.2.0",
"octokit": "^4.0.2",
+ "openai": "^4.47.3",
"parse-git-config": "^3.0.0",
"parse-github-url": "^1.0.2",
"react": "18.2.0",
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index a87c091..b118875 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -2,111 +2,126 @@
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
- provider = "prisma-client-js"
- previewFeatures = ["fullTextSearch"]
+ provider = "prisma-client-js"
+ previewFeatures = ["fullTextSearch"]
}
datasource db {
- provider = "postgresql"
- // NOTE: When using mysql or sqlserver, uncomment the @db.Text annotations in model Account below
- // Further reading:
- // https://next-auth.js.org/adapters/prisma#create-the-prisma-schema
- // https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#string
- url = env("DATABASE_URL")
+ provider = "postgresql"
+ // NOTE: When using mysql or sqlserver, uncomment the @db.Text annotations in model Account below
+ // Further reading:
+ // https://next-auth.js.org/adapters/prisma#create-the-prisma-schema
+ // https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#string
+ url = env("DATABASE_URL")
+}
+
+model Post {
+ id String @id @default(cuid()) // do not generate id
+ name String
+ title String
+ description String //make this 40 characters long and about a dog
+ dateUploaded DateTime? // make this
+ likes Like[]
+}
+
+model Like {
+ id String @id @default(cuid()) // do not generate id
+ postId String
+ post Post @relation(fields: [postId], references: [id])
}
// Necessary for Next auth
model Account {
- id String @id @default(cuid())
- userId String
- type String
- provider String
- providerAccountId String
- refresh_token String? // @db.Text
- refresh_token_expires_in Int?
- access_token String? // @db.Text
- expires_at Int?
- token_type String?
- scope String?
- id_token String? // @db.Text
- session_state String?
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)
-
- @@unique([provider, providerAccountId])
+ id String @id @default(cuid())
+ userId String
+ type String
+ provider String
+ providerAccountId String
+ refresh_token String? // @db.Text
+ refresh_token_expires_in Int?
+ access_token String? // @db.Text
+ expires_at Int?
+ token_type String?
+ scope String?
+ id_token String? // @db.Text
+ session_state String?
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+
+ @@unique([provider, providerAccountId])
}
model VerifiedEmails {
- email String @id
+ email String @id
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
- @@unique([email])
+ @@unique([email])
}
model Session {
- id String @id @default(cuid())
- sessionToken String @unique
- userId String
- expires DateTime
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+ id String @id @default(cuid())
+ sessionToken String @unique
+ userId String
+ expires DateTime
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model User {
- id String @id @default(cuid())
- name String?
- email String? @unique
- emailVerified DateTime?
- image String?
- accounts Account[]
- sessions Session[]
- branches Branch[]
- projects Project[]
+ id String @id @default(cuid())
+ name String?
+ email String? @unique
+ emailVerified DateTime?
+ image String?
+ accounts Account[]
+ sessions Session[]
+ branches Branch[]
+ projects Project[]
}
model VerificationToken {
- identifier String
- token String @unique
- expires DateTime
+ identifier String
+ token String @unique
+ expires DateTime
- @@unique([identifier, token])
+ @@unique([identifier, token])
}
model Project {
- owner String
- ownerName String
- repository String
- repositoryName String
- branches Branch[]
-
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
-
- createdBy User @relation(fields: [createdById], references: [id], onDelete: Cascade, onUpdate: Cascade)
- createdById String
- rdsInstance RDSInstance? @relation(fields: [rdsInstanceId], references: [id])
- rdsInstanceId String?
-
- @@unique([repository])
- @@index([ownerName, repositoryName, owner, repository])
+ owner String
+ ownerName String
+ repository String
+ repositoryName String
+ branches Branch[]
+
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ createdBy User @relation(fields: [createdById], references: [id], onDelete: Cascade, onUpdate: Cascade)
+ createdById String
+ rdsInstance RDSInstance? @relation(fields: [rdsInstanceId], references: [id])
+ rdsInstanceId String?
+
+ @@unique([repository])
+ @@index([ownerName, repositoryName, owner, repository])
}
model RDSInstance {
- id String @id @default(cuid())
- baseConnection String?
- project Project[]
+ id String @id @default(cuid())
+ baseConnection String?
+ project Project[]
}
model Branch {
- id String @id @default(cuid())
- name String
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
+ id String @id @default(cuid())
+ name String
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
- createdBy User @relation(fields: [createdById], references: [id], onDelete: Cascade, onUpdate: Cascade)
- createdById String
- project Project @relation(fields: [projectRepository], references: [repository], onDelete: Cascade, onUpdate: Cascade)
- projectRepository String
+ createdBy User @relation(fields: [createdById], references: [id], onDelete: Cascade, onUpdate: Cascade)
+ createdById String
+ project Project @relation(fields: [projectRepository], references: [repository], onDelete: Cascade, onUpdate: Cascade)
+ projectRepository String
- @@unique([projectRepository, name])
+ @@unique([projectRepository, name])
}
diff --git a/src/app/_components/DashboardContents.tsx b/src/app/_components/DashboardContents.tsx
index 1443c07..ab20e02 100644
--- a/src/app/_components/DashboardContents.tsx
+++ b/src/app/_components/DashboardContents.tsx
@@ -4,7 +4,6 @@ import { api } from "~/trpc/react";
import { useState } from "react";
import ProjectList from "./ProjectList";
import SearchInput from "./SearchInput";
-import Link from "next/link";
export default function DashboardItems() {
const [searchTerm, setSearchTerm] = useState("");
@@ -22,17 +21,13 @@ export default function DashboardItems() {
searchTerms: searchTerm,
});
- const nukeMutation = api.database.nuke.useMutation();
-
const mappedProjects = projectsData?.map((project) => {
- console.log(project.rdsInstanceId);
-
return {
projectName: project.repository,
route: project.repository,
branchesCount: project.branches.length,
databasesCount: project.branches.length,
- instanceStatus: project.status,
+ instanceStatus: "Unknown",
branches: project.branches.map((branch) => {
return {
creator: branch.createdBy.name ?? "Unknown",
@@ -49,22 +44,14 @@ export default function DashboardItems() {
return (
-
-
-
-
+
{isLoading &&
Loading...
}
{error &&
Error: {error.message}
}
{!isLoading &&
!error &&
(!mappedProjects || mappedProjects.length === 0) && (
- No available projects.{" "}
-
- Create a new project?
-
+ No available projects
)}
{mappedProjects && (
diff --git a/src/app/dummy/page.tsx b/src/app/dummy/page.tsx
new file mode 100644
index 0000000..e445f1d
--- /dev/null
+++ b/src/app/dummy/page.tsx
@@ -0,0 +1,24 @@
+"use client";
+
+import { useState } from "react";
+import { api } from "~/trpc/react";
+
+export default function Dummy() {
+ const [results, setResults] = useState("");
+ const testDummy = api.dummy.testDummy.useMutation();
+
+ const handleCreateDummyData = () => {
+ testDummy
+ .mutateAsync({ name: "test" })
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+ .then((data) => setResults(`success: ${data.message}`))
+ .catch((error) => console.error(error));
+ };
+
+ return (
+
+
+ {results}
+
+ );
+}
diff --git a/src/env.js b/src/env.js
index 0e27d60..ea01269 100644
--- a/src/env.js
+++ b/src/env.js
@@ -29,6 +29,8 @@ export const env = createEnv({
AWS_ACCESS_KEY_ID: z.string(),
AWS_SECRET_ACCESS_KEY: z.string(),
AWS_SESSION_TOKEN: z.string().optional(),
+ OPENAI_API_KEY: z.string(),
+ OPENAI_ORG_ID: z.string(),
},
/**
@@ -56,6 +58,8 @@ export const env = createEnv({
AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID,
AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY,
AWS_SESSION_TOKEN: process.env.AWS_SESSION_TOKEN,
+ OPENAI_API_KEY: process.env.OPENAI_API_KEY,
+ OPENAI_ORG_ID: process.env.OPENAI_ORG_ID,
},
/**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
diff --git a/src/server/api/root.ts b/src/server/api/root.ts
index 1f39164..e183347 100644
--- a/src/server/api/root.ts
+++ b/src/server/api/root.ts
@@ -3,6 +3,7 @@ import { createCallerFactory, createTRPCRouter } from "~/server/api/trpc";
import { greeting } from "./routers/greeting";
import { project } from "./routers/project";
import { githubWebhookRouter } from "./routers/prisma";
+import { dummy } from "./routers/dummy";
/**
* This is the primary router for your server.
@@ -14,6 +15,7 @@ export const appRouter = createTRPCRouter({
health: greeting,
database: project,
webhook: githubWebhookRouter,
+ dummy: dummy,
});
// export type definition of API
diff --git a/src/server/api/routers/dummy.ts b/src/server/api/routers/dummy.ts
new file mode 100644
index 0000000..e1d288d
--- /dev/null
+++ b/src/server/api/routers/dummy.ts
@@ -0,0 +1,14 @@
+import { z } from "zod";
+
+import dummyCreate from "~/server/dummyData";
+import { createTRPCRouter, publicProcedure } from "~/server/api/trpc";
+
+export const dummy = createTRPCRouter({
+ testDummy: publicProcedure
+ .input(z.object({ name: z.string() }))
+ .mutation(async () => {
+ const results = await dummyCreate();
+
+ return results;
+ }),
+});
diff --git a/src/server/dummyData.ts b/src/server/dummyData.ts
new file mode 100644
index 0000000..9c71842
--- /dev/null
+++ b/src/server/dummyData.ts
@@ -0,0 +1,144 @@
+import { PrismaClient } from "@prisma/client";
+import OpenAI from "openai";
+import { readFileSync } from "fs";
+
+const prisma = new PrismaClient();
+
+// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
+const openai = new OpenAI({
+ apiKey: process.env.OPENAI_API_SECRET_KEY ?? "empty",
+ organization: process.env.OPENAI_ORG_ID ?? "empty",
+});
+
+interface ChatResponse {
+ choices: Choice[];
+}
+
+interface Choice {
+ message: {
+ content: string;
+ };
+}
+
+async function generateDummyData(
+ model: string,
+ fields: string[],
+ schema: string,
+): Promise
{
+ console.log(
+ `Generating dummy data for model ${model} with fields: ${fields.join(", ")}`,
+ );
+ try {
+ const response: ChatResponse = (await openai.chat.completions.create({
+ model: "gpt-4o",
+ messages: [
+ {
+ role: "system",
+ content: `You are an expert data generator. Create a singular JSON object with realistic dummy data for a ${model} model with the following fields: ${fields.join(
+ ", ",
+ )}.
+ Guidelines:
+ - Please use this ${schema} to reference any relationships between models.
+ - Return strictly plain JSON that's not embedded, and without extraneous marks.
+ - Do not generate fields that are unique IDs.
+ - Each field should have a maximum of 20 characters per entry unless specified otherwise.
+ - DateTime fields should be in the format "YYYY-MM-DDTHH:MM:SSZ" (e.g., 2022-03-15T10:00:00Z).
+ - For fields with comments, follow the instructions in the comments.
+ - For nested fields, use the Prisma nested create syntax. If using createMany for a nested field,
+ ignore any of the parent model's fields that are not required.
+ - Ensure that related records are created with valid references or data.
+ Example syntax for nested fields:
+ {
+ "email": "saanvi@prisma.io",
+ "posts": {
+ "createMany": {
+ "data": [
+ { "title": "My first post" },
+ { "title": "My second post" }
+ ]
+ }
+ }
+ }
+ Return only the JSON object.`,
+ },
+ ],
+ max_tokens: 4096,
+ })) as ChatResponse;
+ console.log("Response", response.choices[0]?.message.content);
+ return response.choices[0]?.message.content;
+ } catch (error) {
+ console.error("Failed to generate dummy data:", error);
+ throw error;
+ }
+}
+
+async function tryCreateDataWithRetry(
+ modelName: string,
+ fields: string[],
+ schema: string,
+ retries: number,
+): Promise {
+ for (let attempt = 1; attempt <= retries; attempt++) {
+ try {
+ const dummyData: unknown = await generateDummyData(
+ modelName,
+ fields,
+ schema,
+ );
+ if (dummyData) {
+ // Use type assertion to safely access the model
+ const model = prisma[modelName as keyof typeof prisma];
+ if (model && prisma) {
+ const createCommand = prisma[
+ modelName.toLocaleLowerCase() as keyof typeof prisma
+ ] as { create: (data: unknown) => unknown };
+ await createCommand.create({
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+ data: JSON.parse(dummyData as string),
+ });
+ console.log(`Data inserted for model ${modelName}`);
+ return; // Exit function if successful
+ } else {
+ console.error(`Model ${modelName} does not exist in Prisma Client.`);
+ return; // Exit function if model does not exist
+ }
+ }
+ } catch (error) {
+ console.error(`Attempt ${attempt} failed:`, error);
+ if (attempt < retries) {
+ console.log("Retrying...");
+ } else {
+ console.error("All attempts failed.");
+ throw error;
+ }
+ }
+ }
+}
+
+async function dummyCreate(): Promise<{ message: string }> {
+ console.log("Creating dummy data...");
+ const schema = readFileSync("./prisma/schema.prisma", "utf8");
+ const models = schema.match(/model \w+ {[^}]+}/g);
+ console.log(models);
+
+ if (models) {
+ for (const modelDef of models) {
+ console.log(modelDef);
+ const modelName = modelDef.match(/model (\w+) {/)?.[1];
+ console.log(modelName);
+ const fields = modelDef
+ .match(" {([^}]+)}")
+ ?.map((field) => field.slice(0, -1));
+
+ console.log(fields);
+
+ if (modelName && fields) {
+ await tryCreateDataWithRetry(modelName, fields, schema, 5);
+ }
+ }
+ }
+ await prisma.$disconnect();
+ return { message: "Dummy data created successfully." };
+}
+
+export default dummyCreate;