> Drizzle RLS
드리즐을 이용해서 RLS세팅하는 방법을 배워보자. (이전에는 SQL 형식으로 직접 supabase에서 세팅했는데, 그거보다 이게 좋다고 한다. 타입스크립트 자동완성 기능활용 할 수 있고, 깃헙에 올릴수있으니)
- users/schema.ts
import {
bigint,
boolean,
jsonb,
pgEnum,
pgPolicy,
pgSchema,
pgTable,
primaryKey,
text,
timestamp,
uuid,
} from "drizzle-orm/pg-core";
import { authenticatedRole, authUid, authUsers } from "drizzle-orm/supabase";
import { products } from "../products/schema";
import { posts } from "../community/schema";
import { sql } from "drizzle-orm";
// export const users = pgSchema("auth").table("users", {
// id: uuid().primaryKey(),
// });
export const roles = pgEnum("role", [
"developer",
"designer",
"marketer",
"founder",
"product-manager",
]);
export const profiles = pgTable("profiles", {
profile_id: uuid()
.primaryKey()
.references(() => authUsers.id, { onDelete: "cascade" }),
avatar: text(),
name: text().notNull(),
username: text().notNull(),
headline: text(),
bio: text(),
role: roles().default("developer").notNull(),
stats: jsonb()
.$type<{
followers: number;
following: number;
}>()
.default({ followers: 0, following: 0 }),
views: jsonb(),
created_at: timestamp().notNull().defaultNow(),
updated_at: timestamp().notNull().defaultNow(),
});
export const follows = pgTable(
"follows",
{
follower_id: uuid()
.references(() => profiles.profile_id, {
onDelete: "cascade",
})
.notNull(),
following_id: uuid()
.references(() => profiles.profile_id, {
onDelete: "cascade",
})
.notNull(),
created_at: timestamp().notNull().defaultNow(),
},
(table) => [primaryKey({ columns: [table.follower_id, table.following_id] })]
);
export const notificationType = pgEnum("notification_type", [
"follow",
"review",
"reply",
]);
export const notifications = pgTable("notifications", {
notification_id: bigint({ mode: "number" })
.primaryKey()
.generatedAlwaysAsIdentity(),
source_id: uuid().references(() => profiles.profile_id, {
onDelete: "cascade",
}),
product_id: bigint({ mode: "number" }).references(() => products.product_id, {
onDelete: "cascade",
}),
post_id: bigint({ mode: "number" }).references(() => posts.post_id, {
onDelete: "cascade",
}),
target_id: uuid()
.references(() => profiles.profile_id, {
onDelete: "cascade",
})
.notNull(),
seen: boolean().default(false).notNull(),
type: notificationType().notNull(),
created_at: timestamp().notNull().defaultNow(),
});
export const messageRooms = pgTable("message_rooms", {
message_room_id: bigint({ mode: "number" })
.primaryKey()
.generatedAlwaysAsIdentity(),
created_at: timestamp().notNull().defaultNow(),
});
export const messageRoomMembers = pgTable(
"message_room_members",
{
message_room_id: bigint({ mode: "number" }).references(
() => messageRooms.message_room_id,
{
onDelete: "cascade",
}
),
profile_id: uuid().references(() => profiles.profile_id, {
onDelete: "cascade",
}),
created_at: timestamp().notNull().defaultNow(),
},
(table) => [
primaryKey({ columns: [table.message_room_id, table.profile_id] }),
pgPolicy("message_room_members_policy", {
for: "select",
to: authenticatedRole,
as: "permissive",
using: sql`public.is_user_member(${table.message_room_id}, auth.uid())`,
}),
]
);
export const messages = pgTable("messages", {
message_id: bigint({ mode: "number" })
.primaryKey()
.generatedAlwaysAsIdentity(),
message_room_id: bigint({ mode: "number" })
.references(() => messageRooms.message_room_id, {
onDelete: "cascade",
})
.notNull(),
sender_id: uuid()
.references(() => profiles.profile_id, {
onDelete: "cascade",
})
.notNull(),
content: text().notNull(),
created_at: timestamp().notNull().defaultNow(),
});
export const todos = pgTable(
"todos",
{
todo_id: bigint({ mode: "number" })
.primaryKey()
.generatedAlwaysAsIdentity(),
title: text().notNull(),
completed: boolean().notNull().default(false),
created_at: timestamp().notNull().defaultNow(),
profile_id: uuid()
.references(() => profiles.profile_id, {
onDelete: "cascade",
})
.notNull(),
},
(table) => [
pgPolicy("todos-insert-policy", {
for: "insert",
to: authenticatedRole,
as: "permissive",
withCheck: sql`${authUid} = ${table.profile_id}`,
}),
pgPolicy("todos-select-policy", {
for: "select",
to: authenticatedRole,
as: "permissive",
using: sql`${authUid} = ${table.profile_id}`,
}),
]
);
1) RLS를 바로 테이블 생성시에 지정해줄 수 있다. (이게 좋을듯 이렇게 하자)
2) 기존에 users를 쓰려면 auth스키마에서 가지고 온 후 써야했는데, 이렇게 하면 아래 드리즐 orm에서 바로 authUsers를 가져다 쓸 수 있기 때문에 필요가 없음
이렇게 하고 npm run db:generate를 하게 되면 아래 부분이 생성 되는데, 이거는 삭제하고 마이그레이션을 진행해주자. (auth스키마 부분을 해당 파일에서 삭제했기 때문에 나오는 것임/ 실수로 나는 삭제해버렸다..)
> vercel 배포
npm i @vercel/react-router 설치
- react-router-config.ts
import type { Config } from "@react-router/dev/config";
import { vercelPreset } from "@vercel/react-router/vite";
export default {
// Config options...
// Server-side render by default, to enable SPA mode set this to `false`
ssr: true,
presets: [vercelPreset()],
} satisfies Config;
위와 같이 설정 후, git push
그 후 vercel에서 해당 git 주소를 연결해주면 된다.
이 후 Cloudflare에 도메인 연결해주는 것과, cloudeflare의 방화벽 설정 등은 강의를 보자!
> 방화벽 관련
특정 나라에서 접속 못하게 막거나, request를 몇초안에 몇번하면 막게 하는등의 방화벽을 만들 수 있음
> sentry관련
코드 특정 부분에 에러가 나면, 그 부분을 잡아주는 서비스이다. (에러 녹화 기능 / 사용자가 여러번 클릭한 경우 등 등, 일부는 유료)
'코딩강의 > Maker 마스터클래스(노마드코더)' 카테고리의 다른 글
#13 Toss Payments (0) | 2025.05.29 |
---|---|
#12 Transactional Emails (0) | 2025.05.27 |
#11 GPT & CRON Jobs (0) | 2025.05.23 |
#10 DMs (0) | 2025.05.21 |
#9 Fetchers (0) | 2025.05.14 |