Skip to content

Commit

Permalink
merged latest changes
Browse files Browse the repository at this point in the history
  • Loading branch information
Dhruwang committed Jan 19, 2024
1 parent 870f575 commit d04cbe2
Show file tree
Hide file tree
Showing 12 changed files with 456 additions and 33 deletions.
17 changes: 17 additions & 0 deletions app/(dashboard)/dashboard/settings/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"use server"

import { getServerSession } from "next-auth"

import { createApiKey } from "@/lib/apikey"
import { authOptions } from "@/lib/auth"
import { TApiKeyCreateInput } from "@/lib/types/apiKey"

export async function createApiKeyAction(
accountId: string,
apiKeyData: TApiKeyCreateInput
) {
const session = await getServerSession(authOptions)
if (!session) return

return await createApiKey(accountId, apiKeyData)
}
40 changes: 40 additions & 0 deletions app/api/me/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { headers } from "next/headers"
import { NextResponse } from "next/server"

import { hashApiKey } from "@/lib/apikey"
import { db } from "@/lib/db"

export async function GET() {
const headersList = headers()
const apiKey = headersList.get("x-api-key")
if (apiKey) {
const apiKeyData = await db.apiKey.findUnique({
where: {
hashedKey: hashApiKey(apiKey),
},
select: {
environment: {
select: {
id: true,
createdAt: true,
updatedAt: true,
type: true,
product: {
select: {
id: true,
name: true,
},
},
widgetSetupCompleted: true,
},
},
},
})
if (!apiKeyData) {
return new Response("Not authenticated", {
status: 401,
})
}
return NextResponse.json(apiKeyData.environment)
}
}
90 changes: 59 additions & 31 deletions components/user-name-form.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,36 @@
"use client";
"use client"

import * as React from "react";
import { useRouter } from "next/navigation";
import { zodResolver } from "@hookform/resolvers/zod";
import { User } from "@prisma/client";
import { useForm } from "react-hook-form";
import * as z from "zod";
import * as React from "react"
import { useRouter } from "next/navigation"
import { zodResolver } from "@hookform/resolvers/zod"
import { User } from "@prisma/client"
import { Loader2Icon } from "lucide-react"
import { useForm } from "react-hook-form"
import * as z from "zod"

import { cn } from "@/lib/utils";
import { userNameSchema } from "@/lib/validations/user";
import { buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils"
import { userNameSchema } from "@/lib/validations/user"
import { buttonVariants } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { toast } from "@/components/ui/use-toast";
import { Loader2Icon } from "lucide-react";
} from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { toast } from "@/components/ui/use-toast"

interface UserNameFormProps extends React.HTMLAttributes<HTMLFormElement> {
user: Pick<User, "id" | "name">;
user: Pick<User, "id" | "name">
}

type FormData = z.infer<typeof userNameSchema>;
type FormData = z.infer<typeof userNameSchema>

export function UserNameForm({ user, className, ...props }: UserNameFormProps) {
const router = useRouter();
export function UserNameForm({ user, ...props }: UserNameFormProps) {
const router = useRouter()
const {
handleSubmit,
register,
Expand All @@ -40,30 +40,26 @@ export function UserNameForm({ user, className, ...props }: UserNameFormProps) {
defaultValues: {
name: user?.name || "",
},
});
const [isSaving, setIsSaving] = React.useState<boolean>(false);
})
const [isSaving, setIsSaving] = React.useState<boolean>(false)

async function onSubmit(data: FormData) {
setIsSaving(true);
setIsSaving(true)

// some server action to save the user's name

setIsSaving(false);
setIsSaving(false)

return toast({
title: "Something went wrong.",
description:
"The server action to save the user name is not yet implemented",
variant: "destructive",
});
})
}

return (
<form
className={cn(className)}
onSubmit={handleSubmit(onSubmit)}
{...props}
>
<form className="space-y-4" onSubmit={handleSubmit(onSubmit)} {...props}>
<Card>
<CardHeader>
<CardTitle>Your Name</CardTitle>
Expand Down Expand Up @@ -91,14 +87,46 @@ export function UserNameForm({ user, className, ...props }: UserNameFormProps) {
<CardFooter>
<button
type="submit"
className={cn(buttonVariants(), className)}
className={cn(buttonVariants())}
disabled={isSaving}
>
{isSaving && <Loader2Icon className="mr-2 h-4 w-4 animate-spin" />}
<span>Save</span>
</button>
</CardFooter>
</Card>
<Card>
<CardHeader>
<CardTitle>API keys</CardTitle>
<CardDescription>Add and remobe API keys</CardDescription>
</CardHeader>
<CardContent>
<div className="grid gap-1">
<Label className="sr-only" htmlFor="name">
Name
</Label>
<Input
id="name"
className="w-[400px]"
size={32}
{...register("name")}
/>
{errors?.name && (
<p className="px-1 text-xs text-red-600">{errors.name.message}</p>
)}
</div>
</CardContent>
<CardFooter>
<button
type="submit"
className={cn(buttonVariants())}
disabled={isSaving}
>
{isSaving && <Loader2Icon className="mr-2 h-4 w-4 animate-spin" />}
<span>Generate API key</span>
</button>
</CardFooter>
</Card>
</form>
);
)
}
4 changes: 2 additions & 2 deletions config/dashboard.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DashboardConfig } from "types";
import { DashboardConfig } from "types"

export const dashboardConfig: DashboardConfig = {
mainNav: [
Expand All @@ -18,4 +18,4 @@ export const dashboardConfig: DashboardConfig = {
href: "/dashboard/settings",
},
],
};
}
75 changes: 75 additions & 0 deletions lib/apikey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import "server-only"

import { createHash, randomBytes } from "crypto"

import { db } from "@/lib/db"

import { TApiKey, TApiKeyCreateInput } from "./types/apiKey"

export const getApiKey = async (apiKeyId: string): Promise<TApiKey | null> => {
try {
const apiKeyData = await db.apiKey.findUnique({
where: {
id: apiKeyId,
},
})

return apiKeyData
} catch (error) {
throw error
}
}
export const getApiKeys = async (
respositoryId: string,
page?: number
): Promise<TApiKey[]> => {
try {
const apiKeys = await db.apiKey.findMany({
where: {
respositoryId,
},
})
return apiKeys
} catch (error) {
throw error
}
}

export const hashApiKey = (key: string): string =>
createHash("sha256").update(key).digest("hex")

export async function createApiKey(
respositoryId: string,
apiKeyData: TApiKeyCreateInput
): Promise<TApiKey> {
try {
const key = randomBytes(16).toString("hex")
const hashedKey = hashApiKey(key)

const result = await db.apiKey.create({
data: {
...apiKeyData,
hashedKey,
respositoryId: { connect: { id: respositoryId } },
},
})
console.log({ ...result, apiKey: key })
return { ...result, apiKey: key }
} catch (error) {
throw error
}
}

export const deleteApiKey = async (id: string): Promise<TApiKey | null> => {
try {
const deletedApiKeyData = await db.apiKey.delete({
where: {
id: id,
},
})

return deletedApiKeyData
} catch (error) {
throw error
}
}
65 changes: 65 additions & 0 deletions lib/repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { db } from "@/lib/db"

import { TRepository, TRepositoryCreateInput } from "./types/repository"

export const getRepository = async (
repositoryId: string
): Promise<TRepository | null> => {
try {
const repositoryData = await db.repository.findUnique({
where: {
id: repositoryId,
},
})

return repositoryData
} catch (error) {
throw error
}
}

export const getRepositories = async (
ownerId: string
): Promise<TRepository[]> => {
try {
const repositories = await db.repository.findMany({
where: {
ownerId,
},
})

return repositories
} catch (error) {
throw error
}
}

export const createRepository = async (
repositoryData: TRepositoryCreateInput
): Promise<TRepository> => {
try {
const newRepository = await db.repository.create({
data: repositoryData,
})

return newRepository
} catch (error) {
throw error
}
}

export const deleteRepository = async (
repositoryId: string
): Promise<TRepository | null> => {
try {
const deletedRepository = await db.repository.delete({
where: {
id: repositoryId,
},
})

return deletedRepository
} catch (error) {
throw error
}
}
37 changes: 37 additions & 0 deletions lib/types/account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { z } from "zod"

import { ZApiKey } from "./apiKey"

export const ZAccountInput = z.object({
userId: z.string(),
type: z.string(),
provider: z.string(),
providerAccountId: z.string(),
access_token: z.string().nullish(),
refresh_token: z.string().nullish(),
expires_at: z.number().nullish(),
scope: z.string().nullish(),
token_type: z.string().nullish(),
id_token: z.string().nullish(),
})

export type TAccountInput = z.infer<typeof ZAccountInput>

export const ZAccount = z.object({
id: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
userId: z.string(),
type: z.string(),
provider: z.string(),
providerAccountId: z.string(),
access_token: z.string().nullable(),
refresh_token: z.string().nullable().optional(),
expires_at: z.number().nullable(),
scope: z.string().nullable(),
token_type: z.string().nullable(),
id_token: z.string().nullable(),
apiKeys: z.array(ZApiKey),
})

export type TAccount = z.infer<typeof ZAccount>
Loading

0 comments on commit d04cbe2

Please sign in to comment.