use multi-region postgres on fly

This commit is contained in:
EGOIST 2022-05-06 10:44:49 +08:00
parent ed991f870f
commit 6ec274517e
10 changed files with 69 additions and 46 deletions

View File

@ -1,4 +1,4 @@
import { prisma } from "~/lib/db.server"
import { prismaRead, prismaWrite } from "~/lib/db.server"
import {
checkPageSlug,
checkSubdomain,
@ -7,7 +7,7 @@ import {
} from "~/models/site.model"
import { PageVisibilityEnum } from "~/lib/types"
import { isUUID } from "~/lib/uuid"
import { MembershipRole, PageType, Prisma } from "@prisma/client"
import { MembershipRole, PageType, type Prisma } from "@prisma/client"
import { nanoid } from "nanoid"
import { z } from "zod"
import { type AuthUser } from "~/lib/auth.server"
@ -84,7 +84,7 @@ export const siteController = {
}
const [pages, total] = await Promise.all([
prisma.page.findMany({
prismaRead.page.findMany({
where,
orderBy: {
createdAt: "desc",
@ -96,7 +96,7 @@ export const siteController = {
}
: undefined,
}),
await prisma.page.count({
await prismaRead.page.count({
where: {
siteId: site.id,
},
@ -129,7 +129,7 @@ export const siteController = {
const gate = createGate({ user })
const page = input.pageId
? await prisma.page.findUnique({
? await prismaRead.page.findUnique({
where: {
id: input.pageId,
},
@ -137,7 +137,7 @@ export const siteController = {
site: true,
},
})
: await prisma.page.create({
: await prismaWrite.page.create({
data: {
title: "Untitled",
slug: `untitled-${nanoid(4)}`,
@ -165,7 +165,7 @@ export const siteController = {
const slug = input.slug || page.slug
await checkPageSlug({ slug, excludePage: page.id, siteId: page.siteId })
const updated = await prisma.page.update({
const updated = await prismaWrite.page.update({
where: {
id: page.id,
},
@ -204,13 +204,13 @@ export const siteController = {
}
const page = isPageUUID
? await prisma.page.findUnique({
? await prismaRead.page.findUnique({
where: {
id: input.page,
},
})
: site
? await prisma.page.findFirst({
? await prismaRead.page.findFirst({
where: { siteId: site.id, slug: input.page },
})
: null
@ -252,7 +252,7 @@ export const siteController = {
})
}
const updated = await prisma.site.update({
const updated = await prismaWrite.site.update({
where: {
id: site.id,
},
@ -278,7 +278,7 @@ export const siteController = {
await checkSubdomain({ subdomain: payload.subdomain })
const site = await prisma.site.create({
const site = await prismaWrite.site.create({
data: {
name: payload.name,
subdomain: payload.subdomain,

View File

@ -1,6 +1,6 @@
import type { User, Membership } from "@prisma/client"
import { AUTH_COOKIE_NAME } from "./config.server"
import { prisma } from "./db.server"
import { prismaRead } from "./db.server"
import dayjs from "dayjs"
import { type CookieSerializeOptions, createCookie } from "@remix-run/node"
import { IS_PROD, OUR_DOMAIN } from "./config.shared"
@ -23,7 +23,7 @@ export const getAuthTokenFromRequest = async (request: Request) => {
}
const findUserFromToken = async (token: string) => {
const accessToken = await prisma.accessToken.findUnique({
const accessToken = await prismaRead.accessToken.findUnique({
where: {
token,
},

View File

@ -8,3 +8,7 @@ export const S3_ENDPOINT = process.env.S3_ENDPOINT
export const MAILGUN_APIKEY = process.env.MAILGUN_APIKEY
export const MAILGUN_DOMAIN = process.env.MAILGUN_DOMAIN
export const MAILGUN_EU = process.env.MAILGUN_EU
// Primary Fly region
export const PRIMARY_REGION = process.env.PRIMARY_REGION
// Curreny Fly region
export const FLY_REGION = process.env.FLY_REGION

View File

@ -1,7 +1,26 @@
import { PrismaClient } from "@prisma/client"
import { FLY_REGION, PRIMARY_REGION } from "./config.server"
import { IS_PROD } from "./config.shared"
import { singleton } from "./singleton.server"
export const prisma = /* @__PURE__ */ singleton(
"prisma",
export const prismaWrite = /* @__PURE__ */ singleton(
"prisma-write",
() => new PrismaClient()
)
// Connect to the read replica of our database on Fly
export const prismaRead = /* @__PURE__ */ singleton("prisma-read", () => {
// 5433 is the read-replica port
let url = process.env.DATABASE_URL
if (FLY_REGION !== PRIMARY_REGION && IS_PROD) {
url = url.replace(":5432", ":5433")
}
console.log("read replica url", url)
return new PrismaClient({
datasources: {
db: {
url,
},
},
})
})

View File

@ -1,4 +1,4 @@
import { prisma } from "~/lib/db.server"
import { prismaRead, prismaWrite } from "~/lib/db.server"
import { isUUID } from "~/lib/uuid"
import { MembershipRole } from "@prisma/client"
@ -9,7 +9,7 @@ export const checkSubdomain = async ({
subdomain: string
updatingSiteId?: string
}) => {
const existingSite = await prisma.site.findUnique({
const existingSite = await prismaRead.site.findUnique({
where: {
subdomain,
},
@ -17,7 +17,7 @@ export const checkSubdomain = async ({
if (existingSite?.deletedAt) {
// Actuall delete the site so that the subdomain can be used again
await prisma.site.delete({
await prismaWrite.site.delete({
where: {
id: existingSite.id,
},
@ -31,7 +31,7 @@ export const checkSubdomain = async ({
}
export const getUserLastActiveSite = async (userId: string) => {
const memberships = await prisma.membership.findMany({
const memberships = await prismaRead.membership.findMany({
where: {
userId,
role: {
@ -55,12 +55,12 @@ export const getUserLastActiveSite = async (userId: string) => {
export const getSite = async (input: string) => {
const site = isUUID(input)
? await prisma.site.findUnique({
? await prismaRead.site.findUnique({
where: {
id: input,
},
})
: await prisma.site.findUnique({
: await prismaRead.site.findUnique({
where: {
subdomain: input,
},
@ -78,7 +78,7 @@ export const getMembership = async (data: {
userId: string
role: MembershipRole
}) => {
const first = await prisma.membership.findFirst({
const first = await prismaRead.membership.findFirst({
where: {
role: data.role,
userId: data.userId,
@ -90,7 +90,7 @@ export const getMembership = async (data: {
}
export const getSitesByUser = async ({ userId }: { userId: string }) => {
const memberships = await prisma.membership.findMany({
const memberships = await prismaRead.membership.findMany({
where: {
userId,
role: {
@ -116,7 +116,7 @@ export const checkPageSlug = async ({
if (!slug) {
throw new Error("Missing page slug")
}
const page = await prisma.page.findFirst({
const page = await prismaRead.page.findFirst({
where: {
siteId,
slug,
@ -128,7 +128,7 @@ export const checkPageSlug = async ({
if (!page) return
if (page.deletedAt) {
await prisma.page.delete({
await prismaWrite.page.delete({
where: {
id: page.id,
},

View File

@ -1,5 +1,5 @@
import { type AuthUser } from "~/lib/auth.server"
import { prisma } from "~/lib/db.server"
import { prismaRead, prismaWrite } from "~/lib/db.server"
import { createGate } from "~/lib/gate.server"
export const userModel = {
@ -15,7 +15,7 @@ export const userModel = {
) {
const gate = createGate({ user, requireAuth: true })
if (payload.email) {
const userByEmail = await prisma.user.findUnique({
const userByEmail = await prismaRead.user.findUnique({
where: {
email: payload.email,
},
@ -26,7 +26,7 @@ export const userModel = {
}
if (payload.username) {
const userByUsername = await prisma.user.findUnique({
const userByUsername = await prismaRead.user.findUnique({
where: {
username: payload.username,
},
@ -36,7 +36,7 @@ export const userModel = {
}
}
const updated = await prisma.user.update({
const updated = await prismaWrite.user.update({
where: {
id: gate.user.id,
},

View File

@ -1,8 +1,8 @@
import { LoaderFunction, redirect } from "@remix-run/node"
import { type LoaderFunction, redirect } from "@remix-run/node"
import { z } from "zod"
import { generateCookie } from "~/lib/auth.server"
import { IS_PROD, OUR_DOMAIN } from "~/lib/config.shared"
import { prisma } from "~/lib/db.server"
import { prismaRead, prismaWrite } from "~/lib/db.server"
export const loader: LoaderFunction = async ({ request }) => {
const url = new URL(request.url)
@ -22,7 +22,7 @@ export const loader: LoaderFunction = async ({ request }) => {
// Set cookie again for custom domain and subdomain.localhost (because *.localhost in cookie domain doesn't work)
if (isCustomDomain || !IS_PROD) {
const accessToken = await prisma.accessToken.findUnique({
const accessToken = await prismaRead.accessToken.findUnique({
where: {
publicId: data.id,
},
@ -36,7 +36,7 @@ export const loader: LoaderFunction = async ({ request }) => {
throw new Error("invalid id or id expired")
}
await prisma.accessToken.update({
await prismaWrite.accessToken.update({
where: {
id: accessToken.id,
},

View File

@ -1,6 +1,6 @@
import { LoaderFunction, redirect } from "@remix-run/node"
import { type LoaderFunction, redirect } from "@remix-run/node"
import { z } from "zod"
import { prisma } from "~/lib/db.server"
import { prismaRead, prismaWrite } from "~/lib/db.server"
import { nanoid } from "nanoid"
import UAParser from "ua-parser-js"
import dayjs from "dayjs"
@ -21,7 +21,7 @@ export const loader: LoaderFunction = async ({ request }) => {
userAgent: request.headers.get("user-agent"),
})
const loginToken = await prisma.loginToken.findUnique({
const loginToken = await prismaRead.loginToken.findUnique({
where: {
id: data.token,
},
@ -35,14 +35,14 @@ export const loader: LoaderFunction = async ({ request }) => {
throw new Error(`token expired`)
}
let user = await prisma.user.findUnique({
let user = await prismaRead.user.findUnique({
where: {
email: loginToken.email,
},
})
if (!user) {
user = await prisma.user.create({
user = await prismaWrite.user.create({
data: {
email: loginToken.email,
name: loginToken.email.split("@")[0],
@ -51,7 +51,7 @@ export const loader: LoaderFunction = async ({ request }) => {
})
}
await prisma.loginToken.delete({
await prismaWrite.loginToken.delete({
where: {
id: loginToken.id,
},
@ -60,7 +60,7 @@ export const loader: LoaderFunction = async ({ request }) => {
const ua = new UAParser(data.userAgent)
const publicId = nanoid(32)
const accessToken = await prisma.accessToken.create({
const accessToken = await prismaWrite.accessToken.create({
data: {
user: {
connect: {
@ -85,7 +85,7 @@ export const loader: LoaderFunction = async ({ request }) => {
!nextUrl.hostname.endsWith(`.${OUR_DOMAIN}`)
) {
// Check if the host belong to a site
const existing = await prisma.domain.findUnique({
const existing = await prismaRead.domain.findUnique({
where: {
domain: nextUrl.hostname,
},

View File

@ -1,7 +1,7 @@
import type { ActionFunction } from "@remix-run/node"
import { z } from "zod"
import { getAuthUser } from "~/lib/auth.server"
import { prisma } from "~/lib/db.server"
import { prismaRead, prismaWrite } from "~/lib/db.server"
import { createGate } from "~/lib/gate.server"
export const action: ActionFunction = async ({ request, params }) => {
@ -17,7 +17,7 @@ export const action: ActionFunction = async ({ request, params }) => {
id: params.id,
})
const page = await prisma.page.findUnique({
const page = await prismaRead.page.findUnique({
where: {
id: data.id,
},
@ -31,7 +31,7 @@ export const action: ActionFunction = async ({ request, params }) => {
throw gate.permissionError()
}
await prisma.page.update({
await prismaWrite.page.update({
where: {
id: page.id,
},

View File

@ -2,7 +2,7 @@ import { ActionFunction, redirect } from "@remix-run/node"
import dayjs from "dayjs"
import { z } from "zod"
import { IS_PROD, OUR_DOMAIN } from "~/lib/config.shared"
import { prisma } from "~/lib/db.server"
import { prismaWrite } from "~/lib/db.server"
import { sendLoginEmail } from "~/lib/mailgun.server"
export const action: ActionFunction = async ({ request }) => {
@ -17,7 +17,7 @@ export const action: ActionFunction = async ({ request }) => {
referer: request.headers.get("referer"),
})
const token = await prisma.loginToken.create({
const token = await prismaWrite.loginToken.create({
data: {
email: input.email,
expiresAt: dayjs().add(10, "minute").toDate(),