> SUPABASE 프로젝트 생성 및 DATABASE 생성
connect 버튼을 누르면 아래 url이 생성이 되는데 .env부분에 저장하자. (반드시 transaction pooler로!)

.env파일 생성
DATABASE_PASSWORD="DB비번"
DATABASE_URL="postgresql://postgres:DB비번@db.klxwsgjnhvvgcfrxlrke.supabase.co:5432/postgres"
> DRIZZLE 설치 (공홈 참조)
npm i drizzle-orm postgres --legacy-peer-deps (--legacy부분은 설치 에러 날때만 사용)
npm i -D drizzle-kit --legacy-peer-deps
> db 연동
- app/db.ts 파일
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
const client = postgres(process.env.DATABASE_URL!, { prepare: false });
const db = drizzle(client);
export default db;
- drizzle.config.ts
import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: "./app/features/**/schema.ts",
out: "./app/migrations",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});
- package.json
"scripts": {
"build": "react-router build",
"dev": "react-router dev",
"start": "react-router-serve ./build/server/index.js",
"typecheck": "react-router typegen && tsc --build --noEmit",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:studio": "drizzle-kit studio"
},
db 부분 추가
> schema.ts 생성
모든 스키마를 한 곳에 몰아 넣어도 되고, 각 기능별로 분리해도 된다. 해당 프로젝트는 후자를 선택 할 것이다.
/features/jobs/schema.ts
import { bigint, pgEnum, pgTable, text, timestamp } from "drizzle-orm/pg-core";
import { JOB_TYPES, LOCATION_TYPES, SALARY_RANGE } from "./constants";
export const jobTypes = pgEnum(
"job_type",
JOB_TYPES.map((type) => type.value) as [string, ...string[]]
);
export const locations = pgEnum(
"location",
LOCATION_TYPES.map((type) => type.value) as [string, ...string[]]
);
export const salaryRanges = pgEnum("salary_range", SALARY_RANGE);
export const jobs = pgTable("jobs", {
job_id: bigint({ mode: "number" }).primaryKey().generatedAlwaysAsIdentity(),
position: text().notNull(),
overview: text().notNull(),
responsibilities: text().notNull(),
qualifications: text().notNull(),
benefits: text().notNull(),
skills: text().notNull(),
company_name: text().notNull(),
company_logo: text().notNull(),
company_location: text().notNull(),
apply_url: text().notNull(),
job_type: jobTypes().notNull(),
location: locations().notNull(),
salary_range: salaryRanges().notNull(),
created_at: timestamp().notNull().defaultNow(),
updated_at: timestamp().notNull().defaultNow(),
});
기존에 생성했던 constants파일을 통해 enum 형식으로 정의할 수 있다.
그리고 npm run db:generate 명령어를 입력하면, 드리즐에서 생성한 테이블이 SQL문법에 맞게 생성되고, npm run db:migrate를 하면 supabase에 마이그레이션된다.
> users 스키마 추가
import {
jsonb,
pgEnum,
pgSchema,
pgTable,
text,
timestamp,
uuid,
} from "drizzle-orm/pg-core";
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(() => users.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;
}>(),
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",
}),
following_id: uuid().references(() => profiles.profile_id, {
onDelete: "cascade",
}),
created_at: timestamp().notNull().defaultNow(),
});
supabase에서는 자동적으로 auth스키마에 users테이블이 생성된다. 따라서, 우리가 별도로 생성하는 users 테이블은 profiles테이블로 이름을 바꾸고, 자동으로 생성되는 users 테이블의 id값을 연동시켜준다.
참고로, 아래 코드 부분은 이미 supabase에 users테이블이 등록되어 있지만 추가한 이유는, drizzle의 profile_id를 선언하기 용도다. 따라서, migrate시에 users는 이미 있기 때문에 새로 또 추가하지 않는다.
const users = pgSchema("auth").table("users", {
id: uuid().primaryKey(),
});
> products 스키마 추가
import {
bigint,
check,
integer,
jsonb,
pgTable,
primaryKey,
text,
timestamp,
uuid,
} from "drizzle-orm/pg-core";
import { profiles } from "../users/schema";
import { sql } from "drizzle-orm";
export const products = pgTable("products", {
product_id: bigint({ mode: "number" })
.primaryKey()
.generatedAlwaysAsIdentity(),
name: text().notNull(),
tagline: text().notNull(),
description: text().notNull(),
how_it_works: text().notNull(),
icon: text().notNull(),
url: text().notNull(),
stats: jsonb().notNull().default({ views: 0, reviews: 0 }),
profile_id: uuid()
.references(() => profiles.profile_id, { onDelete: "cascade" })
.notNull(),
category_id: bigint({ mode: "number" }).references(
() => categories.category_id,
{ onDelete: "set null" }
),
created_at: timestamp().notNull().defaultNow(),
updated_at: timestamp().notNull().defaultNow(),
});
export const categories = pgTable("categories", {
category_id: bigint({ mode: "number" })
.primaryKey()
.generatedAlwaysAsIdentity(),
name: text().notNull(),
description: text().notNull(),
created_at: timestamp().notNull().defaultNow(),
updated_at: timestamp().notNull().defaultNow(),
});
export const product_upvotes = pgTable(
"product_upvotes",
{
product_id: bigint({ mode: "number" }).references(
() => products.product_id,
{
onDelete: "cascade",
}
),
profile_id: uuid().references(() => profiles.profile_id, {
onDelete: "cascade",
}),
},
(table) => [primaryKey({ columns: [table.product_id, table.profile_id] })]
);
export const reviews = pgTable(
"reviews",
{
review_id: bigint({ mode: "number" })
.primaryKey()
.generatedAlwaysAsIdentity(),
product_id: bigint({ mode: "number" }).references(
() => products.product_id,
{
onDelete: "cascade",
}
),
profile_id: uuid().references(() => profiles.profile_id, {
onDelete: "cascade",
}),
rating: integer().notNull(),
review: text().notNull(),
created_at: timestamp().notNull().defaultNow(),
updated_at: timestamp().notNull().defaultNow(),
},
(table) => [check("rating_check", sql`${table.rating} BETWEEN 1 AND 5`)]
);
제약 사항은 마지막 부분에 기입한다. 그리고 sql 문법을 사용 할수도 있다. (이 때 보통 대문자 사용 - 마지막 reviews 테이블 참조)
> community 스키마 추가
import {
AnyPgColumn,
bigint,
pgTable,
primaryKey,
text,
timestamp,
uuid,
} from "drizzle-orm/pg-core";
import { profiles } from "../users/schema";
export const topics = pgTable("topics", {
topic_id: bigint({ mode: "number" }).primaryKey().generatedAlwaysAsIdentity(),
name: text().notNull(),
slug: text().notNull(),
created_at: timestamp().notNull().defaultNow(),
});
export const posts = pgTable("posts", {
post_id: bigint({ mode: "number" }).primaryKey().generatedAlwaysAsIdentity(),
title: text().notNull(),
content: text().notNull(),
created_at: timestamp().notNull().defaultNow(),
updated_at: timestamp().notNull().defaultNow(),
topic_id: bigint({ mode: "number" }).references(() => topics.topic_id, {
onDelete: "cascade",
}),
profile_id: uuid().references(() => profiles.profile_id, {
onDelete: "cascade",
}),
});
export const postUpvotes = pgTable(
"post_upvotes",
{
post_id: bigint({ mode: "number" }).references(() => posts.post_id, {
onDelete: "cascade",
}),
profile_id: uuid().references(() => profiles.profile_id, {
onDelete: "cascade",
}),
},
(table) => [primaryKey({ columns: [table.post_id, table.profile_id] })]
);
export const postReplies = pgTable("post_replies", {
post_reply_id: bigint({ mode: "number" })
.primaryKey()
.generatedAlwaysAsIdentity(),
post_id: bigint({ mode: "number" }).references(() => posts.post_id, {
onDelete: "cascade",
}),
parent_id: bigint({ mode: "number" }).references(
(): AnyPgColumn => postReplies.post_reply_id,
{
onDelete: "cascade",
}
),
profile_id: uuid()
.references(() => profiles.profile_id, {
onDelete: "cascade",
})
.notNull(),
reply: text().notNull(),
created_at: timestamp().notNull().defaultNow(),
updated_at: timestamp().notNull().defaultNow(),
});
post_replies 테이블에서 parent_id 부분을 보면 본인 테이블을 참조 해야 하기 때문에 anyPgColumn 타입을 추가한다. (안그러면 타입스크립트 밑줄 에러남)
> teams 스키마 추가
import {
bigint,
check,
integer,
pgEnum,
pgTable,
text,
timestamp,
} from "drizzle-orm/pg-core";
import { PRODUCT_STAGES } from "./constants";
import { sql } from "drizzle-orm";
export const productStage = pgEnum(
"product_stage",
PRODUCT_STAGES.map((stage) => stage.value) as [string, ...string[]]
);
export const team = pgTable(
"team",
{
team_id: bigint({ mode: "number" })
.primaryKey()
.generatedAlwaysAsIdentity(),
product_name: text().notNull(),
team_size: integer().notNull(),
equity_split: integer().notNull(),
product_stage: productStage().notNull(),
roles: text().notNull(),
product_description: text().notNull(),
created_at: timestamp().notNull().defaultNow(),
updated_at: timestamp().notNull().defaultNow(),
},
(table) => [
check("team_size_check", sql`${table.team_size} BETWEEN 1 AND 100`),
check("equity_split_check", sql`${table.equity_split} BETWEEN 1 AND 100`),
check(
"product_description_check",
sql`LENGTH(${table.product_description}) <= 200`
),
]
);
> users스키마 추가
import {
bigint,
boolean,
jsonb,
pgEnum,
pgSchema,
pgTable,
primaryKey,
text,
timestamp,
uuid,
} from "drizzle-orm/pg-core";
import { products } from "../products/schema";
import { posts } from "../community/schema";
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(() => users.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;
}>(),
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",
}),
following_id: uuid().references(() => profiles.profile_id, {
onDelete: "cascade",
}),
created_at: timestamp().notNull().defaultNow(),
});
export const notificationType = pgEnum("notification_type", [
"follow",
"review",
"reply",
"mention",
]);
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(),
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] }),
]
);
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",
}
),
sender_id: uuid().references(() => profiles.profile_id, {
onDelete: "cascade",
}),
content: text().notNull(),
created_at: timestamp().notNull().defaultNow(),
});
> trigger 추가
sql/trigger/user_to_profile_trigger.sql 파일 생성
create function public.handle_new_user()
returns trigger
language plpgsql
security definer
set search_path = ''
as $$
begin
if new.raw_app_meta_data is not null then
if new.raw_app_meta_data ? 'provider' AND new.raw_app_meta_data ->> 'provider' = 'email' then
insert into public.profiles (profile_id, name, username, role)
values (new.id, 'Anonymous', 'mr.' || substr(md5(random()::text), 1, 8), 'developer');
end if;
end if;
return new;
end;
$$;
create trigger user_to_profile_trigger
after insert on auth.users
for each row execute function public.handle_new_user();
위 코드는 sql 구문이며 해당 구문을 아래와 같이 supabase sql editor 구문에다 붙여넣고 run을 하면 된다. 해당 구문은 자동으로 생성된 auth.users에 신규 데이터가 생성 되면 내가 생성한 profiles 테이블에 값이 추가되는 트리거에 대한 명령어이다.

> seed파일 생성
sql/seed.sql
-- Seed categories
INSERT INTO categories (name, description, created_at, updated_at)
VALUES
('SaaS', 'Software as a Service products', NOW(), NOW()),
('AI/ML', 'Artificial Intelligence and Machine Learning', NOW(), NOW()),
('Developer Tools', 'Tools for developers', NOW(), NOW()),
('Design Tools', 'Tools for designers', NOW(), NOW()),
('Marketing Tools', 'Tools for marketers', NOW(), NOW());
-- Seed products
INSERT INTO products (name, tagline, description, how_it_works, icon, url, stats, profile_id, category_id, created_at, updated_at)
VALUES
('DevTool Pro', 'The ultimate developer toolkit', 'Comprehensive development suite', 'Easy integration with existing workflow', '/icons/devtool.png', 'https://devtool.pro', '{"views": 0, "reviews": 0}', '60ba326b-4f6a-47ac-b3fa-820777689856', 1, NOW(), NOW()),
('DesignMaster', 'Design like a pro', 'Professional design platform', 'Intuitive interface for designers', '/icons/design.png', 'https://designmaster.app', '{"views": 0, "reviews": 0}', '60ba326b-4f6a-47ac-b3fa-820777689856', 2, NOW(), NOW()),
('MarketGenius', 'Smart marketing automation', 'AI-powered marketing platform', 'Automated marketing workflows', '/icons/market.png', 'https://marketgenius.io', '{"views": 0, "reviews": 0}', '60ba326b-4f6a-47ac-b3fa-820777689856', 3, NOW(), NOW()),
('CodeBuddy', 'Your coding companion', 'AI pair programming assistant', 'Real-time code suggestions', '/icons/code.png', 'https://codebuddy.dev', '{"views": 0, "reviews": 0}', '60ba326b-4f6a-47ac-b3fa-820777689856', 4, NOW(), NOW()),
('DataViz', 'Beautiful data visualization', 'Turn data into insights', 'Drag-and-drop visualization builder', '/icons/dataviz.png', 'https://dataviz.app', '{"views": 0, "reviews": 0}', '60ba326b-4f6a-47ac-b3fa-820777689856', 5, NOW(), NOW());
-- Seed product upvotes (bridge table)
INSERT INTO product_upvotes (product_id, profile_id)
VALUES (1, '60ba326b-4f6a-47ac-b3fa-820777689856');
-- Seed reviews
INSERT INTO reviews (product_id, profile_id, rating, review, created_at, updated_at)
VALUES
(1, '60ba326b-4f6a-47ac-b3fa-820777689856', 5, 'Excellent developer tool!', NOW(), NOW()),
(2, '60ba326b-4f6a-47ac-b3fa-820777689856', 4, 'Great design features', NOW(), NOW()),
(3, '60ba326b-4f6a-47ac-b3fa-820777689856', 5, 'Amazing marketing automation', NOW(), NOW()),
(4, '60ba326b-4f6a-47ac-b3fa-820777689856', 4, 'Very helpful coding assistant', NOW(), NOW()),
(5, '60ba326b-4f6a-47ac-b3fa-820777689856', 5, 'Powerful visualization tool', NOW(), NOW());
-- Seed topics
INSERT INTO topics (name, slug, created_at)
VALUES
('Development', 'development', NOW()),
('Design', 'design', NOW()),
('Marketing', 'marketing', NOW()),
('Startups', 'startups', NOW()),
('AI', 'ai', NOW());
-- Seed posts
INSERT INTO posts (title, content, topic_id, profile_id, created_at, updated_at)
VALUES
('Getting Started with DevTool Pro', 'A comprehensive guide to DevTool Pro...', 1, '60ba326b-4f6a-47ac-b3fa-820777689856', NOW(), NOW()),
('Design Tips and Tricks', 'Essential design principles...', 2, '60ba326b-4f6a-47ac-b3fa-820777689856', NOW(), NOW()),
('Marketing Automation Best Practices', 'How to automate your marketing...', 3, '60ba326b-4f6a-47ac-b3fa-820777689856', NOW(), NOW()),
('Launching Your First Product', 'Steps to a successful product launch...', 4, '60ba326b-4f6a-47ac-b3fa-820777689856', NOW(), NOW()),
('AI in Modern Development', 'How AI is changing development...', 5, '60ba326b-4f6a-47ac-b3fa-820777689856', NOW(), NOW());
-- Seed post upvotes (bridge table)
INSERT INTO post_upvotes (post_id, profile_id)
VALUES (1, '60ba326b-4f6a-47ac-b3fa-820777689856');
-- Seed post replies
INSERT INTO post_replies (post_id, profile_id, reply, created_at, updated_at)
VALUES
(1, '60ba326b-4f6a-47ac-b3fa-820777689856', 'Great post about DevTool Pro!', NOW(), NOW()),
(2, '60ba326b-4f6a-47ac-b3fa-820777689856', 'These design tips are very helpful', NOW(), NOW()),
(3, '60ba326b-4f6a-47ac-b3fa-820777689856', 'Marketing automation is crucial', NOW(), NOW()),
(4, '60ba326b-4f6a-47ac-b3fa-820777689856', 'Launch strategy is on point', NOW(), NOW()),
(5, '60ba326b-4f6a-47ac-b3fa-820777689856', 'AI is indeed transforming development', NOW(), NOW());
-- Seed gpt ideas
INSERT INTO gpt_ideas (idea, views, claimed_by, created_at)
VALUES
('AI-powered code review assistant', 0, '60ba326b-4f6a-47ac-b3fa-820777689856', NOW()),
('Design system generator', 0, NULL, NOW()),
('Marketing campaign optimizer', 0, NULL, NOW()),
('Developer productivity tracker', 0, NULL, NOW()),
('Automated documentation tool', 0, NULL, NOW());
-- Seed gpt ideas likes (bridge table)
INSERT INTO gpt_ideas_likes (gpt_idea_id, profile_id)
VALUES (1, '60ba326b-4f6a-47ac-b3fa-820777689856');
-- Seed team
INSERT INTO team (product_name, team_size, equity_split, product_stage, roles, product_description, created_at, updated_at)
VALUES
('DevTool Pro', 3, 30, 'mvp', 'Developer, Designer, Marketing', 'Developer productivity suite', NOW(), NOW()),
('DesignMaster', 2, 50, 'prototype', 'Designer, Developer', 'Design automation platform', NOW(), NOW()),
('MarketGenius', 4, 25, 'product', 'Marketing, Sales, Developer, Designer', 'Marketing analytics platform', NOW(), NOW()),
('CodeBuddy', 2, 50, 'idea', 'Developer, Product Manager', 'AI coding assistant', NOW(), NOW()),
('DataViz', 3, 33, 'mvp', 'Data Scientist, Developer, Designer', 'Data visualization tool', NOW(), NOW());
-- Seed message rooms
INSERT INTO message_rooms (created_at)
VALUES (NOW());
-- Seed message room members (bridge table)
INSERT INTO message_room_members (message_room_id, profile_id, created_at)
VALUES (1, '60ba326b-4f6a-47ac-b3fa-820777689856', NOW());
-- Seed messages
INSERT INTO messages (message_room_id, sender_id, content, created_at)
VALUES
(1, '60ba326b-4f6a-47ac-b3fa-820777689856', 'Hello! Interested in collaboration', NOW()),
(1, '60ba326b-4f6a-47ac-b3fa-820777689856', 'Let''s discuss the project details', NOW()),
(1, '60ba326b-4f6a-47ac-b3fa-820777689856', 'What''s your availability?', NOW()),
(1, '60ba326b-4f6a-47ac-b3fa-820777689856', 'I can start next week', NOW()),
(1, '60ba326b-4f6a-47ac-b3fa-820777689856', 'Great, looking forward to working together', NOW());
-- Seed notifications
INSERT INTO notifications (source_id, product_id, post_id, target_id, type, created_at)
VALUES
('60ba326b-4f6a-47ac-b3fa-820777689856', 1, NULL, '60ba326b-4f6a-47ac-b3fa-820777689856', 'review', NOW()),
('60ba326b-4f6a-47ac-b3fa-820777689856', NULL, 1, '60ba326b-4f6a-47ac-b3fa-820777689856', 'reply', NOW()),
('60ba326b-4f6a-47ac-b3fa-820777689856', NULL, NULL, '60ba326b-4f6a-47ac-b3fa-820777689856', 'follow', NOW()),
('60ba326b-4f6a-47ac-b3fa-820777689856', NULL, 2, '60ba326b-4f6a-47ac-b3fa-820777689856', 'mention', NOW()),
('60ba326b-4f6a-47ac-b3fa-820777689856', 2, NULL, '60ba326b-4f6a-47ac-b3fa-820777689856', 'review', NOW());
-- Seed jobs
INSERT INTO jobs (
position,
overview,
responsibilities,
qualifications,
benefits,
skills,
company_name,
company_logo,
company_location,
apply_url,
job_type,
location,
salary_range,
created_at,
updated_at
)
VALUES
(
'Senior Frontend Developer',
'Join our team to build modern web applications using React and TypeScript',
'Lead frontend development, mentor junior developers, architect solutions',
'Min 5 years experience with React, Strong TypeScript skills',
'Health insurance, 401k, Remote work, Learning budget',
'React, TypeScript, Next.js, TailwindCSS',
'TechCorp Inc',
'/logos/techcorp.png',
'San Francisco, CA',
'https://techcorp.com/careers/senior-frontend',
'full-time',
'remote',
'$150,000 - $250,000',
NOW(),
NOW()
),
(
'UI/UX Designer',
'Create beautiful and intuitive user interfaces for our products',
'Design user flows, create wireframes, conduct user research',
'3+ years of product design experience, Figma expertise',
'Flexible hours, Health coverage, Stock options',
'Figma, User Research, Prototyping, Design Systems',
'DesignLabs',
'/logos/designlabs.png',
'New York, NY',
'https://designlabs.com/jobs/uiux-designer',
'full-time',
'hybrid',
'$100,000 - $120,000',
NOW(),
NOW()
),
(
'DevOps Engineer',
'Help us scale our cloud infrastructure and improve deployment processes',
'Manage AWS infrastructure, implement CI/CD pipelines, monitor systems',
'Strong AWS experience, Kubernetes expertise, Infrastructure as Code',
'Remote work, Competitive salary, Learning opportunities',
'AWS, Kubernetes, Terraform, CI/CD',
'CloudScale',
'/logos/cloudscale.png',
'Austin, TX',
'https://cloudscale.io/careers/devops',
'full-time',
'remote',
'$120,000 - $150,000',
NOW(),
NOW()
),
(
'Marketing Intern',
'Learn and contribute to our digital marketing initiatives',
'Assist with social media, content creation, and campaign analysis',
'Marketing student or recent graduate, Social media savvy',
'Paid internship, Mentorship, Flexible schedule',
'Social Media, Content Creation, Analytics',
'GrowthHub',
'/logos/growthhub.png',
'Chicago, IL',
'https://growthhub.com/internships/marketing',
'internship',
'in-person',
'$0 - $50,000',
NOW(),
NOW()
),
(
'Freelance Content Writer',
'Create engaging technical content for our developer blog',
'Write technical tutorials, documentation, and blog posts',
'Strong writing skills, Technical background, SEO knowledge',
'Flexible hours, Competitive per-article rates',
'Technical Writing, SEO, Developer Documentation',
'DevMedia',
'/logos/devmedia.png',
'Remote',
'https://devmedia.com/writers',
'freelance',
'remote',
'$50,000 - $70,000',
NOW(),
NOW()
);
해당 sql 구문을 각 테이블 별로 sql editor에 run시켜서 데이터가 생성되는지 확인하자. (60ba326b-4f6a-47ac-b3fa-820777689856 <-- 이거는 내 프로필 id를 뜻한다.)
그리고 위 seed파일은 cursor ai한테 요청한 값이다.
'코딩강의 > Maker 마스터클래스(노마드코더)' 카테고리의 다른 글
| #6 Public Pages (0) | 2025.04.17 |
|---|---|
| #5 Data Loading Strategies (0) | 2025.04.17 |
| #3 UI with Cursor & Shadcn (0) | 2025.03.18 |
| #2 Using CursorAI (0) | 2025.03.17 |
| #1 Basics (0) | 2025.03.13 |