[copy airbnb] 10. 预订功能(路线、逻辑) Reservation functionality (routes, logic)

1、src/app/actions

此目录的作用是放置与数据库交互的逻辑,一般这里的action会被服务端渲染的页面调用比如:src/app/listings/[listingId]/page.tsx

2、src/app/types 用于定义额外的类型,比如:

import { User ,Listing ,Reservation} from "@prisma/client";
export type SafeListing = Omit<Listing,"createdAt"> & {
    createdAt:string
}

export type SafeReservation = Omit<
    Reservation,
    "createdAt"|"startDate"|"endDate"|"listing"
>
& {
    createdAt:string;
    startDate:string;
    endDate:string;
    listing:SafeListing;
}

export type SafeUser = Omit<
    User,
    "createdAt"|"updatedAt"|"emailVerified"
>
& {
    createdAt:string;
    updatedAt:string;
    emailVerified:string | null;
}

3、使用prisma进行update操作

import { Listing } from '@prisma/client';
import { NextRequest, NextResponse } from "next/server";
import prisma from "@/app/libs/prismadb";
import getCurrentUser from "@/app/actions/getCurrentUser";

export async function POST( request:Request)
{
    const currentUser = await getCurrentUser();
    if(!currentUser){
        return NextResponse.error();
    }

    const body = await request.json();

    const {
        listingId,
        startDate,
        endDate,
        totalPrice
    } = body;

    if(!listingId || !startDate ||!endDate||!totalPrice){
        return NextResponse.error();
    }

    const listingAndReservations = await prisma.listing.update({
        where:{
            id:listingId
        },
        data:{
            reservations:{
                create:{
                    userId:currentUser.id,
                    startDate,
                    endDate,
                    totalPrice
                }
            }
        }
    })

    return NextResponse.json(listingAndReservations);
}

这是一个更新操作,它会先找到消ID等于listingId的清单,然后在reservations中创建一个新的预约,这个预约包含了userIdstartDateendDate以及totalPrice

首先,await prisma.listing.update表示它正等待(因为是异步)Prisma的update方法来更新一个listing(清单)。

然后,where: { id:listingId }是查找条件,即找到数据库数据中id属性等于listingId的清单。

data部分,表示要在找到的清单上进行的更新操作。这里的操作是在reservations中创建一个新的预约。

reservations: {create: {userId:currentUser.id, startDate, endDate, totalPrice}}中:

  • create表示创建新的预约。
  • userId:currentUser.id表示这个预约的用户ID就是当前用户的ID。
  • startDateendDate分别表示预约的开始和结束日期。
  • totalPrice表示预约的总价格。

最后,这个函数会返回一个Promise,等待更新操作完成,然后返回更新后的清单对象。

4、数据库设计

通过上述update操作,引起了我对数据库设计的兴趣,prisma如何组织复杂的表间关系呢?看一下下面的实际例子:

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mongodb"
  url      = env("DATABASE_URL")
}

model User {
  id String @id @default(auto()) @map("_id") @db.ObjectId
  name String?
  email String? @unique
  emailVerified DateTime?
  image String?
  hashedPassword String?
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  favoriteIds String[] @db.ObjectId

  accounts Account[] 
  listings Listing[] 
  reservations Reservation[]
}

model Account{
  id String @id @default(auto()) @map("_id") @db.ObjectId
  userId String @db.ObjectId
  type String
  provider String
  providerAccountId String
  refresh_token String? @db.String
  access_token String? @db.String
  expires_at Int?
  token_type String?
  scope String?
  id_token String? @db.String
  session_state String?
  
  user User @relation(fields: [userId], references: [id],onDelete:Cascade)

  @@unique([provider,providerAccountId])
}

model Listing{
  id String @id @default(auto()) @map("_id") @db.ObjectId
  title String 
  description String
  imageSrc String
  createdAt DateTime @default(now())
  category String
  roomCount Int
  bathroomCount Int
  guestCount Int
  locationValue String
  userId String @db.ObjectId
  price Int

  user User @relation(fields: [userId], references: [id],onDelete:Cascade) 
  reservations Reservation[]
}

model Reservation {
  id         String   @id @default(auto()) @map("_id") @db.ObjectId
  userId     String  @db.ObjectId
  listingId  String  @db.ObjectId
  startDate  DateTime
  endDate    DateTime
  totalPrice Int
  createdAt  DateTime @default(now())
  updatedAt  DateTime @updatedAt

  user  User @relation(fields: [userId], references: [id], onDelete: Cascade)
  listing    Listing @relation(fields: [listingId], references: [id], onDelete: Cascade)
}

让我们关注表的部分:

在这个 Prisma 模型中,我们有 UserAccountListing, 和 Reservation 四个模型。这些模型直接的关系如下:

  1. User 和 Account:这是一对多的关系。每个用户可以有多个账号,但每个账号只能属于一个用户。这个关系通过 User 中的 accounts Account[] 和 Account 中的 user User @relation(fields: [userId], references: [id],onDelete:Cascade) 建立。
  2. User 和 Listing:这也是一对多的关系。每个用户可以发布多个清单,但每个清单只能由一个用户发布。这个关系通过 User 中的 listings Listing[] 和 Listing 中的 user User @relation(fields: [userId], references: [id],onDelete:Cascade) 建立。
  3. User 和 Reservation:这是一对多的关系。每个用户可以创建多个预约,但每个预约只能归属于一个用户。关系通过 User 中的 reservations Reservation[] 和 Reservation 中的 user User @relation(fields: [userId], references: [id], onDelete: Cascade) 建立。
  4. Listing 和 Reservation:这是一对多的关系。每个清单可以拥有多个预约,但每个预约只能关联一个清单。这个关系通过 Listing 中的 reservations Reservation[] 和 Reservation 中的 listing Listing @relation(fields: [listingId], references: [id], onDelete: Cascade) 建立。

确定表关系的方式通常要根据实际情况来分析。一般来说,你需要根据你的业务需求以及数据的特性来确定。例如,如果一个用户可以拥有多个账号,那么这就是一对多的关系。如果一个预约只能关联一个清单,那么这就是一对一的关系。总的来说,确定哪种关系需要用在哪个表之间,需要对你的业务逻辑有深入的理解。

5、prisma 实现左联查询

    //“我想查看我的所有房源的预定信息”
    if(authorId){
        query.listing  = {userId:authorId};
    }

    const reservations = await prisma.reservation.findMany({
        where:query,
        include:{
            listing:true
        },
        orderBy:{
            createdAt:"desc"
        }
    })

在这个例子中,如果我希望查询的是“预约(reservation)”,但是条件来自于关联表“房源”( listing) 下的字段 userId时,我们将会如上表示。

在这个查询中,listingId=authorId 表示我们正在寻找那些 listing 的所有者(即 userId)是 authorId 的预约记录。换句话说,我们正在查找指定用户(authorId)作为房东的所有预约。

这确实是一种深层次的查询,因为我们不只是查看 reservation 表的记录,还要进一步查看与这些记录相关联的 listing 表的记录。通过这种方式,我们能够将两张表中的数据关联起来,提供全面的信息,如房东的所有预约记录等。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部