aboutsummaryrefslogtreecommitdiffstats
path: root/web/app
diff options
context:
space:
mode:
Diffstat (limited to 'web/app')
-rw-r--r--web/app/api/auth/[...nextauth]/route.tsx3
-rw-r--r--web/app/api/v1/links/route.ts60
-rw-r--r--web/app/favicon.icobin0 -> 25931 bytes
-rw-r--r--web/app/globals.css76
-rw-r--r--web/app/layout.tsx22
-rw-r--r--web/app/page.tsx15
6 files changed, 176 insertions, 0 deletions
diff --git a/web/app/api/auth/[...nextauth]/route.tsx b/web/app/api/auth/[...nextauth]/route.tsx
new file mode 100644
index 00000000..bfcda516
--- /dev/null
+++ b/web/app/api/auth/[...nextauth]/route.tsx
@@ -0,0 +1,3 @@
+import { authHandler } from "@/lib/auth";
+
+export { authHandler as GET, authHandler as POST }
diff --git a/web/app/api/v1/links/route.ts b/web/app/api/v1/links/route.ts
new file mode 100644
index 00000000..5be1018e
--- /dev/null
+++ b/web/app/api/v1/links/route.ts
@@ -0,0 +1,60 @@
+import { authOptions } from "@/lib/auth";
+import prisma from "@/lib/prisma";
+import { ZNewBookmarkedLinkRequest, ZGetLinksResponse, ZBookmarkedLink } from "@/lib/types/api/links";
+import { getServerSession } from "next-auth";
+import { NextRequest, NextResponse } from "next/server";
+
+export async function POST(request: NextRequest) {
+ // TODO: We probably should be using an API key here instead of the session;
+ const session = await getServerSession(authOptions);
+ if (!session) {
+ return new Response(null, { status: 401 });
+ }
+
+ const linkRequest = ZNewBookmarkedLinkRequest.safeParse(await request.json());
+
+ if (!linkRequest.success) {
+ return NextResponse.json({
+ error: linkRequest.error.toString(),
+ }, { status: 400 });
+ }
+
+ const link = await prisma.bookmarkedLink.create({
+ data: {
+ url: linkRequest.data.url,
+ userId: session.user.id,
+ }
+ });
+
+ let response: ZBookmarkedLink = { ...link };
+
+ return NextResponse.json(response, { status: 201 });
+}
+
+export async function GET() {
+ // TODO: We probably should be using an API key here instead of the session;
+ const session = await getServerSession(authOptions);
+ if (!session) {
+ return new Response(null, { status: 401 });
+ }
+ const links = await prisma.bookmarkedLink.findMany({
+ where: {
+ userId: session.user.id,
+ },
+ select: {
+ id: true,
+ url: true,
+ createdAt: true,
+ details: {
+ select: {
+ title: true,
+ description: true,
+ imageUrl: true,
+ }
+ },
+ }
+ });
+
+ let response: ZGetLinksResponse = { links };
+ return NextResponse.json(response);
+}
diff --git a/web/app/favicon.ico b/web/app/favicon.ico
new file mode 100644
index 00000000..718d6fea
--- /dev/null
+++ b/web/app/favicon.ico
Binary files differ
diff --git a/web/app/globals.css b/web/app/globals.css
new file mode 100644
index 00000000..6a757250
--- /dev/null
+++ b/web/app/globals.css
@@ -0,0 +1,76 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer base {
+ :root {
+ --background: 0 0% 100%;
+ --foreground: 222.2 84% 4.9%;
+
+ --card: 0 0% 100%;
+ --card-foreground: 222.2 84% 4.9%;
+
+ --popover: 0 0% 100%;
+ --popover-foreground: 222.2 84% 4.9%;
+
+ --primary: 222.2 47.4% 11.2%;
+ --primary-foreground: 210 40% 98%;
+
+ --secondary: 210 40% 96.1%;
+ --secondary-foreground: 222.2 47.4% 11.2%;
+
+ --muted: 210 40% 96.1%;
+ --muted-foreground: 215.4 16.3% 46.9%;
+
+ --accent: 210 40% 96.1%;
+ --accent-foreground: 222.2 47.4% 11.2%;
+
+ --destructive: 0 84.2% 60.2%;
+ --destructive-foreground: 210 40% 98%;
+
+ --border: 214.3 31.8% 91.4%;
+ --input: 214.3 31.8% 91.4%;
+ --ring: 222.2 84% 4.9%;
+
+ --radius: 0.5rem;
+ }
+
+ .dark {
+ --background: 222.2 84% 4.9%;
+ --foreground: 210 40% 98%;
+
+ --card: 222.2 84% 4.9%;
+ --card-foreground: 210 40% 98%;
+
+ --popover: 222.2 84% 4.9%;
+ --popover-foreground: 210 40% 98%;
+
+ --primary: 210 40% 98%;
+ --primary-foreground: 222.2 47.4% 11.2%;
+
+ --secondary: 217.2 32.6% 17.5%;
+ --secondary-foreground: 210 40% 98%;
+
+ --muted: 217.2 32.6% 17.5%;
+ --muted-foreground: 215 20.2% 65.1%;
+
+ --accent: 217.2 32.6% 17.5%;
+ --accent-foreground: 210 40% 98%;
+
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 210 40% 98%;
+
+ --border: 217.2 32.6% 17.5%;
+ --input: 217.2 32.6% 17.5%;
+ --ring: 212.7 26.8% 83.9%;
+ }
+}
+
+@layer base {
+ * {
+ @apply border-border;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+} \ No newline at end of file
diff --git a/web/app/layout.tsx b/web/app/layout.tsx
new file mode 100644
index 00000000..3314e478
--- /dev/null
+++ b/web/app/layout.tsx
@@ -0,0 +1,22 @@
+import type { Metadata } from "next";
+import { Inter } from "next/font/google";
+import "./globals.css";
+
+const inter = Inter({ subsets: ["latin"] });
+
+export const metadata: Metadata = {
+ title: "Create Next App",
+ description: "Generated by create next app",
+};
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+ <html lang="en">
+ <body className={inter.className}>{children}</body>
+ </html>
+ );
+}
diff --git a/web/app/page.tsx b/web/app/page.tsx
new file mode 100644
index 00000000..2df40508
--- /dev/null
+++ b/web/app/page.tsx
@@ -0,0 +1,15 @@
+import { LoginButton } from "../components/auth/login";
+import { LogoutButton } from "../components/auth/logout";
+
+export default function Home() {
+ return (
+ <main className="flex min-h-screen flex-col items-center justify-between p-24">
+ <div>
+ <LoginButton />
+ <br />
+ <br />
+ <LogoutButton />
+ </div>
+ </main>
+ );
+}