From e069d02f9f4db56c73747cdc0492641dcd902da4 Mon Sep 17 00:00:00 2001 From: Matthias Nannt Date: Fri, 19 Jan 2024 14:58:02 +0100 Subject: [PATCH 1/2] clean up user model --- app/(dashboard)/dashboard/layout.tsx | 24 +-- app/(dashboard)/dashboard/loading.tsx | 16 +- components/post-item.tsx | 42 ----- components/post-operations.tsx | 114 ------------- components/user-account-nav.tsx | 28 ++-- components/user-avatar.tsx | 16 +- lib/auth.ts | 98 ++++++++--- package.json | 1 - .../20240119115326_init/migration.sql | 155 ------------------ prisma/schema.prisma | 53 +++--- types/next-auth.d.ts | 17 +- 11 files changed, 144 insertions(+), 420 deletions(-) delete mode 100644 components/post-item.tsx delete mode 100644 components/post-operations.tsx delete mode 100644 prisma/migrations/20240119115326_init/migration.sql diff --git a/app/(dashboard)/dashboard/layout.tsx b/app/(dashboard)/dashboard/layout.tsx index 79ed9d5..63e75f6 100644 --- a/app/(dashboard)/dashboard/layout.tsx +++ b/app/(dashboard)/dashboard/layout.tsx @@ -1,23 +1,23 @@ -import { notFound } from "next/navigation"; +import { notFound } from "next/navigation" -import { dashboardConfig } from "@/config/dashboard"; -import { getCurrentUser } from "@/lib/session"; -import { MainNav } from "@/components/main-nav"; -import { DashboardNav } from "@/components/nav"; -import { SiteFooter } from "@/components/site-footer"; -import { UserAccountNav } from "@/components/user-account-nav"; +import { dashboardConfig } from "@/config/dashboard" +import { getCurrentUser } from "@/lib/session" +import { MainNav } from "@/components/main-nav" +import { DashboardNav } from "@/components/nav" +import { SiteFooter } from "@/components/site-footer" +import { UserAccountNav } from "@/components/user-account-nav" interface DashboardLayoutProps { - children?: React.ReactNode; + children?: React.ReactNode } export default async function DashboardLayout({ children, }: DashboardLayoutProps) { - const user = await getCurrentUser(); + const user = await getCurrentUser() if (!user) { - return notFound(); + return notFound() } return ( @@ -28,7 +28,7 @@ export default async function DashboardLayout({ @@ -44,5 +44,5 @@ export default async function DashboardLayout({ - ); + ) } diff --git a/app/(dashboard)/dashboard/loading.tsx b/app/(dashboard)/dashboard/loading.tsx index 7f45dff..71d410d 100644 --- a/app/(dashboard)/dashboard/loading.tsx +++ b/app/(dashboard)/dashboard/loading.tsx @@ -1,7 +1,6 @@ -import { DashboardHeader } from "@/components/header"; -import { PostItem } from "@/components/post-item"; -import { DashboardShell } from "@/components/shell"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/components/ui/button" +import { DashboardHeader } from "@/components/header" +import { DashboardShell } from "@/components/shell" export default function DashboardLoading() { return ( @@ -9,13 +8,6 @@ export default function DashboardLoading() { -
- - - - - -
- ); + ) } diff --git a/components/post-item.tsx b/components/post-item.tsx deleted file mode 100644 index 3a6ad28..0000000 --- a/components/post-item.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import Link from "next/link" -import { Post } from "@prisma/client" - -import { formatDate } from "@/lib/utils" -import { Skeleton } from "@/components/ui/skeleton" -import { PostOperations } from "@/components/post-operations" - -interface PostItemProps { - post: Pick -} - -export function PostItem({ post }: PostItemProps) { - return ( -
-
- - {post.title} - -
-

- {formatDate(post.createdAt?.toDateString())} -

-
-
- -
- ) -} - -PostItem.Skeleton = function PostItemSkeleton() { - return ( -
-
- - -
-
- ) -} diff --git a/components/post-operations.tsx b/components/post-operations.tsx deleted file mode 100644 index fff5879..0000000 --- a/components/post-operations.tsx +++ /dev/null @@ -1,114 +0,0 @@ -"use client"; - -import * as React from "react"; -import Link from "next/link"; -import { useRouter } from "next/navigation"; -import { Post } from "@prisma/client"; - -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, -} from "@/components/ui/alert-dialog"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { toast } from "@/components/ui/use-toast"; -import { CircleEllipsis, Loader2, Loader2Icon, TrashIcon } from "lucide-react"; - -async function deletePost(postId: string) { - const response = await fetch(`/api/posts/${postId}`, { - method: "DELETE", - }); - - if (!response?.ok) { - toast({ - title: "Something went wrong.", - description: "Your post was not deleted. Please try again.", - variant: "destructive", - }); - } - - return true; -} - -interface PostOperationsProps { - post: Pick; -} - -export function PostOperations({ post }: PostOperationsProps) { - const router = useRouter(); - const [showDeleteAlert, setShowDeleteAlert] = React.useState(false); - const [isDeleteLoading, setIsDeleteLoading] = React.useState(false); - - return ( - <> - - - - Open - - - - - Edit - - - - setShowDeleteAlert(true)} - > - Delete - - - - - - - - Are you sure you want to delete this post? - - - This action cannot be undone. - - - - Cancel - { - event.preventDefault(); - setIsDeleteLoading(true); - - const deleted = await deletePost(post.id); - - if (deleted) { - setIsDeleteLoading(false); - setShowDeleteAlert(false); - router.refresh(); - } - }} - className="bg-red-600 focus:ring-red-600" - > - {isDeleteLoading ? ( - - ) : ( - - )} - Delete - - - - - - ); -} diff --git a/components/user-account-nav.tsx b/components/user-account-nav.tsx index 475baf2..4270777 100644 --- a/components/user-account-nav.tsx +++ b/components/user-account-nav.tsx @@ -1,8 +1,8 @@ -"use client"; +"use client" -import Link from "next/link"; -import { User } from "next-auth"; -import { signOut } from "next-auth/react"; +import Link from "next/link" +import { User } from "next-auth" +import { signOut } from "next-auth/react" import { DropdownMenu, @@ -10,11 +10,15 @@ import { DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { UserAvatar } from "@/components/user-avatar"; +} from "@/components/ui/dropdown-menu" +import { UserAvatar } from "@/components/user-avatar" -interface UserAccountNavProps extends React.HTMLAttributes { - user: Pick; +interface UserAccountNavProps { + user: { + name: User["name"] + avatarUrl: User["image"] + email?: User["email"] + } } export function UserAccountNav({ user }: UserAccountNavProps) { @@ -22,7 +26,7 @@ export function UserAccountNav({ user }: UserAccountNavProps) { @@ -48,15 +52,15 @@ export function UserAccountNav({ user }: UserAccountNavProps) { { - event.preventDefault(); + event.preventDefault() signOut({ callbackUrl: `${window.location.origin}/login`, - }); + }) }} > Sign out - ); + ) } diff --git a/components/user-avatar.tsx b/components/user-avatar.tsx index 586276d..7da1dd4 100644 --- a/components/user-avatar.tsx +++ b/components/user-avatar.tsx @@ -1,18 +1,18 @@ -import { User } from "@prisma/client"; -import { AvatarProps } from "@radix-ui/react-avatar"; +import { User } from "@prisma/client" +import { AvatarProps } from "@radix-ui/react-avatar" +import { UserIcon } from "lucide-react" -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; -import { UserIcon } from "lucide-react"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" interface UserAvatarProps extends AvatarProps { - user: Pick; + user: Pick } export function UserAvatar({ user, ...props }: UserAvatarProps) { return ( - {user.image ? ( - + {user.avatarUrl ? ( + ) : ( {user.name} @@ -20,5 +20,5 @@ export function UserAvatar({ user, ...props }: UserAvatarProps) { )} - ); + ) } diff --git a/lib/auth.ts b/lib/auth.ts index 11119fc..fbd6766 100644 --- a/lib/auth.ts +++ b/lib/auth.ts @@ -1,18 +1,10 @@ -import { PrismaAdapter } from "@next-auth/prisma-adapter"; -import { NextAuthOptions } from "next-auth"; -import GitHubProvider from "next-auth/providers/github"; +import { NextAuthOptions } from "next-auth" +import GitHubProvider from "next-auth/providers/github" -import { env } from "@/env.mjs"; -import { db } from "@/lib/db"; +import { env } from "@/env.mjs" +import { db } from "@/lib/db" export const authOptions: NextAuthOptions = { - // huh any! I know. - // This is a temporary fix for prisma client. - // @see https://github.com/prisma/prisma/issues/16117 - adapter: PrismaAdapter(db as any), - session: { - strategy: "jwt", - }, pages: { signIn: "/login", }, @@ -23,36 +15,92 @@ export const authOptions: NextAuthOptions = { }), ], callbacks: { - async session({ token, session }) { - if (token) { - session.user.id = token.id; - session.user.name = token.name; - session.user.email = token.email; - session.user.image = token.picture; + async signIn({ user, account }: any) { + if (account.type !== "oauth") { + return false + } + + if (account.provider) { + // check if accounts for this provider / account Id already exists + const existingUserWithAccount = await db.user.findFirst({ + include: { + account: true, + }, + where: { + githubId: account.providerAccountId, + }, + }) + + if (existingUserWithAccount) { + // User with this provider found + // check if email still the same + if (existingUserWithAccount.email === user.email) { + return true + } + + // user seemed to change his email within the provider + // update the email in the database + await db.user.update({ + where: { + id: existingUserWithAccount.id, + }, + data: { + email: user.email, + }, + }) + return true + } + + // create user if it does not exist + await db.user.create({ + data: { + name: user.name, + githubId: account.providerAccountId, + email: user.email, + avatarUrl: user.image, + account: { + create: { + ...account, + }, + }, + }, + }) + + return true } - return session; + return false }, async jwt({ token, user }) { const dbUser = await db.user.findFirst({ where: { email: token.email, }, - }); + }) if (!dbUser) { if (user) { - token.id = user?.id; + token.id = user?.id } - return token; + return token } return { id: dbUser.id, name: dbUser.name, email: dbUser.email, - picture: dbUser.image, - }; + avatarUrl: dbUser.avatarUrl, + } + }, + async session({ token, session }) { + if (token) { + session.user.id = token.id + if (token.name) session.user.name = token.name + if (token.email) session.user.email = token.email + if (token.avatarUrl) session.user.avatarUrl = token.avatarUrl as string + } + + return session }, }, -}; +} diff --git a/package.json b/package.json index 6199e8f..e413698 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,6 @@ }, "dependencies": { "@hookform/resolvers": "^3.3.4", - "@next-auth/prisma-adapter": "^1.0.7", "@prisma/client": "5.8.1", "@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-avatar": "^1.0.4", diff --git a/prisma/migrations/20240119115326_init/migration.sql b/prisma/migrations/20240119115326_init/migration.sql deleted file mode 100644 index 5f30009..0000000 --- a/prisma/migrations/20240119115326_init/migration.sql +++ /dev/null @@ -1,155 +0,0 @@ --- CreateEnum -CREATE TYPE "InstallationType" AS ENUM ('user', 'organisation'); - --- CreateEnum -CREATE TYPE "MembershipRole" AS ENUM ('owner', 'member'); - --- CreateTable -CREATE TABLE "accounts" ( - "id" TEXT NOT NULL, - "userId" TEXT NOT NULL, - "type" TEXT NOT NULL, - "provider" TEXT NOT NULL, - "providerAccountId" TEXT NOT NULL, - "refresh_token" TEXT, - "access_token" TEXT, - "expires_at" INTEGER, - "token_type" TEXT, - "scope" TEXT, - "id_token" TEXT, - "session_state" TEXT, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "accounts_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "installations" ( - "id" TEXT NOT NULL, - "githubId" INTEGER NOT NULL, - "type" "InstallationType" NOT NULL, - - CONSTRAINT "installations_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "repositories" ( - "id" TEXT NOT NULL, - "githubId" INTEGER NOT NULL, - "name" TEXT NOT NULL, - "description" TEXT, - "homepage" TEXT, - "topics" TEXT[], - "default_branch" TEXT NOT NULL, - "installationId" TEXT NOT NULL, - "levels" JSONB NOT NULL DEFAULT '[]', - - CONSTRAINT "repositories_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "memberships" ( - "installationId" TEXT NOT NULL, - "userId" TEXT NOT NULL, - "role" "MembershipRole" NOT NULL, - - CONSTRAINT "memberships_pkey" PRIMARY KEY ("userId","installationId") -); - --- CreateTable -CREATE TABLE "sessions" ( - "id" TEXT NOT NULL, - "sessionToken" TEXT NOT NULL, - "userId" TEXT NOT NULL, - "expires" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "sessions_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "users" ( - "id" TEXT NOT NULL, - "githubId" INTEGER, - "name" TEXT, - "email" TEXT, - "emailVerified" TIMESTAMP(3), - "image" TEXT, - "address" TEXT, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "users_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "verification_tokens" ( - "identifier" TEXT NOT NULL, - "token" TEXT NOT NULL, - "expires" TIMESTAMP(3) NOT NULL -); - --- CreateTable -CREATE TABLE "point_transactions" ( - "id" TEXT NOT NULL, - "points" INTEGER NOT NULL, - "description" TEXT NOT NULL, - "url" TEXT, - "userId" TEXT NOT NULL, - "repositoryId" TEXT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "point_transactions_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE UNIQUE INDEX "accounts_provider_providerAccountId_key" ON "accounts"("provider", "providerAccountId"); - --- CreateIndex -CREATE UNIQUE INDEX "installations_githubId_key" ON "installations"("githubId"); - --- CreateIndex -CREATE UNIQUE INDEX "repositories_githubId_key" ON "repositories"("githubId"); - --- CreateIndex -CREATE INDEX "memberships_userId_idx" ON "memberships"("userId"); - --- CreateIndex -CREATE INDEX "memberships_installationId_idx" ON "memberships"("installationId"); - --- CreateIndex -CREATE UNIQUE INDEX "sessions_sessionToken_key" ON "sessions"("sessionToken"); - --- CreateIndex -CREATE UNIQUE INDEX "users_githubId_key" ON "users"("githubId"); - --- CreateIndex -CREATE UNIQUE INDEX "users_email_key" ON "users"("email"); - --- CreateIndex -CREATE UNIQUE INDEX "verification_tokens_token_key" ON "verification_tokens"("token"); - --- CreateIndex -CREATE UNIQUE INDEX "verification_tokens_identifier_token_key" ON "verification_tokens"("identifier", "token"); - --- AddForeignKey -ALTER TABLE "accounts" ADD CONSTRAINT "accounts_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "repositories" ADD CONSTRAINT "repositories_installationId_fkey" FOREIGN KEY ("installationId") REFERENCES "installations"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "memberships" ADD CONSTRAINT "memberships_installationId_fkey" FOREIGN KEY ("installationId") REFERENCES "installations"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "memberships" ADD CONSTRAINT "memberships_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "sessions" ADD CONSTRAINT "sessions_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "point_transactions" ADD CONSTRAINT "point_transactions_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "point_transactions" ADD CONSTRAINT "point_transactions_repositoryId_fkey" FOREIGN KEY ("repositoryId") REFERENCES "repositories"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 123049a..0478e7e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -10,9 +10,27 @@ datasource db { url = env("DATABASE_URL") } +model User { + id String @id @default(cuid()) + createdAt DateTime @default(now()) @map(name: "created_at") + updatedAt DateTime @default(now()) @map(name: "updated_at") + githubId String? @unique + name String? + email String? @unique + avatarUrl String? + + address String? + + account Account? + pointTransactions PointTransaction[] + memberships Membership[] + + @@map(name: "users") +} + model Account { id String @id @default(cuid()) - userId String + userId String @unique type String provider String providerAccountId String @@ -39,7 +57,7 @@ enum InstallationType { model Installation { id String @id @default(cuid()) - githubId Int @unique + githubId String @unique type InstallationType memberships Membership[] @@ -50,7 +68,7 @@ model Installation { model Repository { id String @id @default(cuid()) - githubId Int @unique + githubId String @unique name String description String? homepage String? @@ -83,35 +101,6 @@ model Membership { @@map(name: "memberships") } -model Session { - id String @id @default(cuid()) - sessionToken String @unique - userId String - expires DateTime - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - - @@map(name: "sessions") -} - -model User { - id String @id @default(cuid()) - githubId Int? @unique - name String? - email String? @unique - emailVerified DateTime? - image String? - address String? - createdAt DateTime @default(now()) @map(name: "created_at") - updatedAt DateTime @default(now()) @map(name: "updated_at") - - accounts Account[] - sessions Session[] - pointTransactions PointTransaction[] - memberships Membership[] - - @@map(name: "users") -} - model VerificationToken { identifier String token String @unique diff --git a/types/next-auth.d.ts b/types/next-auth.d.ts index 343ea77..ab96afd 100644 --- a/types/next-auth.d.ts +++ b/types/next-auth.d.ts @@ -1,18 +1,21 @@ -import { User } from "next-auth"; -import { JWT } from "next-auth/jwt"; +import { User } from "next-auth" +import { JWT } from "next-auth/jwt" -type UserId = string; +type UserId = string declare module "next-auth/jwt" { interface JWT { - id: UserId; + id: UserId } } declare module "next-auth" { interface Session { - user: User & { - id: UserId; - }; + user: { + id: string + name: string + email: string + avatarUrl: string + } } } From c76d0501215fde810d7215a15f3944245dfe3de0 Mon Sep 17 00:00:00 2001 From: Matthias Nannt Date: Fri, 19 Jan 2024 15:04:07 +0100 Subject: [PATCH 2/2] add more address fields --- prisma/schema.prisma | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 0478e7e..01f1ac3 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -19,7 +19,11 @@ model User { email String? @unique avatarUrl String? - address String? + address String? + postalCode String? + city String? + state String? + country String account Account? pointTransactions PointTransaction[]