From bb89d66d92e2a1cbb604057fb47f7e86af8e29f0 Mon Sep 17 00:00:00 2001 From: Matvey Ryabchikov <35634442+ronanru@users.noreply.github.com> Date: Wed, 25 Oct 2023 02:16:36 +0300 Subject: [PATCH 01/30] it works but needs more testing --- cli/src/cli/index.ts | 31 +++++- cli/src/helpers/createProject.ts | 9 +- cli/src/index.ts | 2 + cli/src/installers/dependencyVersionMap.ts | 5 + cli/src/installers/drizzle.ts | 97 ++++++++++++++++++- cli/src/installers/envVars.ts | 41 +++++--- cli/src/installers/index.ts | 10 ++ cli/src/installers/prisma.ts | 26 ++++- .../src/server/auth-app/with-drizzle.ts | 4 +- .../src/server/auth-pages/with-drizzle.ts | 4 +- .../src/server/db/drizzle-schema-auth.ts | 12 +-- .../src/server/db/drizzle-schema-base.ts | 4 +- .../src/server/db/index-drizzle/with-mysql.ts | 12 +++ .../src/server/db/index-drizzle/with-neon.ts | 12 +++ .../with-planetscale.ts} | 0 .../server/db/index-drizzle/with-postgres.ts | 12 +++ .../server/db/index-drizzle/with-sqlite.ts | 7 ++ tsconfig.json | 2 +- 18 files changed, 257 insertions(+), 33 deletions(-) create mode 100644 cli/template/extras/src/server/db/index-drizzle/with-mysql.ts create mode 100644 cli/template/extras/src/server/db/index-drizzle/with-neon.ts rename cli/template/extras/src/server/db/{index-drizzle.ts => index-drizzle/with-planetscale.ts} (100%) create mode 100644 cli/template/extras/src/server/db/index-drizzle/with-postgres.ts create mode 100644 cli/template/extras/src/server/db/index-drizzle/with-sqlite.ts diff --git a/cli/src/cli/index.ts b/cli/src/cli/index.ts index 22125f30a4..b8cbd12061 100644 --- a/cli/src/cli/index.ts +++ b/cli/src/cli/index.ts @@ -3,7 +3,11 @@ import chalk from "chalk"; import { Command } from "commander"; import { CREATE_T3_APP, DEFAULT_APP_NAME } from "~/consts.js"; -import { type AvailablePackages } from "~/installers/index.js"; +import { + databaseProviders, + type AvailablePackages, + type DatabaseProvider, +} from "~/installers/index.js"; import { getVersion } from "~/utils/getT3Version.js"; import { getUserPkgManager } from "~/utils/getUserPkgManager.js"; import { IsTTYError } from "~/utils/isTTYError.js"; @@ -37,6 +41,7 @@ interface CliResults { appName: string; packages: AvailablePackages[]; flags: CliFlags; + databaseProvider: DatabaseProvider; } const defaultOptions: CliResults = { @@ -55,6 +60,7 @@ const defaultOptions: CliResults = { importAlias: "~/", appRouter: false, }, + databaseProvider: "sqlite", }; export const runCli = async (): Promise => { @@ -124,6 +130,13 @@ export const runCli = async (): Promise => { "Explicitly tell the CLI to use a custom import alias", defaultOptions.flags.importAlias ) + .option( + "--db-provider", + `Choose a database provider to use. Possible values: ${databaseProviders.join( + ", " + )}`, + defaultOptions.flags.importAlias + ) .option( "--appRouter [boolean]", "Explicitly tell the CLI to use the new Next.js app router", @@ -262,6 +275,20 @@ export const runCli = async (): Promise => { initialValue: false, }); }, + databaseProvider: ({ results }) => { + if (results.database === "none") return; + return p.select({ + message: "What database provider would you like to use?", + options: [ + { value: "mysql", label: "MySQL" }, + { value: "postgres", label: "PostgreSQL" }, + { value: "sqlite", label: "SQLite" }, + { value: "planetscale", label: "Planetscale Serverless" }, + { value: "neon", label: "Neon Serverless" }, + ], + initialValue: "mysql", + }); + }, ...(!cliResults.flags.noGit && { git: () => { return p.confirm({ @@ -307,6 +334,8 @@ export const runCli = async (): Promise => { return { appName: project.name ?? cliResults.appName, packages, + databaseProvider: + (project.databaseProvider as DatabaseProvider) || "sqlite", flags: { ...cliResults.flags, appRouter: project.appRouter ?? cliResults.flags.appRouter, diff --git a/cli/src/helpers/createProject.ts b/cli/src/helpers/createProject.ts index df50b78015..1be94b7ff6 100644 --- a/cli/src/helpers/createProject.ts +++ b/cli/src/helpers/createProject.ts @@ -10,7 +10,10 @@ import { selectLayoutFile, selectPageFile, } from "~/helpers/selectBoilerplate.js"; -import { type PkgInstallerMap } from "~/installers/index.js"; +import { + type DatabaseProvider, + type PkgInstallerMap, +} from "~/installers/index.js"; import { getUserPkgManager } from "~/utils/getUserPkgManager.js"; interface CreateProjectOptions { @@ -20,6 +23,7 @@ interface CreateProjectOptions { noInstall: boolean; importAlias: string; appRouter: boolean; + databaseProvider: DatabaseProvider; } export const createProject = async ({ @@ -28,6 +32,7 @@ export const createProject = async ({ packages, noInstall, appRouter, + databaseProvider, }: CreateProjectOptions) => { const pkgManager = getUserPkgManager(); const projectDir = path.resolve(process.cwd(), projectName); @@ -40,6 +45,7 @@ export const createProject = async ({ scopedAppName, noInstall, appRouter, + databaseProvider, }); // Install the selected packages @@ -51,6 +57,7 @@ export const createProject = async ({ packages, noInstall, appRouter, + databaseProvider, }); // Select necessary _app,index / layout,page files diff --git a/cli/src/index.ts b/cli/src/index.ts index 5657401ea2..18d249100e 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -37,6 +37,7 @@ const main = async () => { appName, packages, flags: { noGit, noInstall, importAlias, appRouter }, + databaseProvider, } = await runCli(); const usePackages = buildPkgInstallerMap(packages); @@ -48,6 +49,7 @@ const main = async () => { projectName: appDir, scopedAppName, packages: usePackages, + databaseProvider, importAlias, noInstall, appRouter, diff --git a/cli/src/installers/dependencyVersionMap.ts b/cli/src/installers/dependencyVersionMap.ts index e12459cd2d..f02127d9aa 100644 --- a/cli/src/installers/dependencyVersionMap.ts +++ b/cli/src/installers/dependencyVersionMap.ts @@ -18,6 +18,11 @@ export const dependencyVersionMap = { "dotenv-cli": "^7.3.0", mysql2: "^3.6.1", "@planetscale/database": "^1.11.0", + pg: "^8.11.3", + "@types/pg": "^8.10.7", + "@types/better-sqlite3": "^7.6.6", + "better-sqlite3": "^9.0.0", + "@neondatabase/serverless": "^0.6.0", // TailwindCSS tailwindcss: "^3.3.3", diff --git a/cli/src/installers/drizzle.ts b/cli/src/installers/drizzle.ts index 1b88e16ff6..8780d97b3e 100644 --- a/cli/src/installers/drizzle.ts +++ b/cli/src/installers/drizzle.ts @@ -5,20 +5,39 @@ import { type PackageJson } from "type-fest"; import { PKG_ROOT } from "~/consts.js"; import { type Installer } from "~/installers/index.js"; import { addPackageDependency } from "~/utils/addPackageDependency.js"; +import { type AvailableDependencies } from "./dependencyVersionMap.js"; export const drizzleInstaller: Installer = ({ projectDir, packages, scopedAppName, + databaseProvider, }) => { + const devPackages: AvailableDependencies[] = ["drizzle-kit", "dotenv-cli"]; + if (databaseProvider === "planetscale") devPackages.push("mysql2"); + if (databaseProvider === "postgres") devPackages.push("@types/pg"); + if (databaseProvider === "sqlite") devPackages.push("@types/better-sqlite3"); + if (databaseProvider === "neon") devPackages.push("pg"); + addPackageDependency({ projectDir, - dependencies: ["drizzle-kit", "dotenv-cli", "mysql2"], + dependencies: devPackages, devMode: true, }); addPackageDependency({ projectDir, - dependencies: ["drizzle-orm", "@planetscale/database"], + dependencies: [ + "drizzle-orm", + ( + { + planetscale: "@planetscale/database", + mysql: "mysql2", + postgres: "pg", + sqlite: "better-sqlite3", + neon: "@neondatabase/serverless", + } as const + )[databaseProvider], + ], devMode: false, }); @@ -42,10 +61,80 @@ export const drizzleInstaller: Installer = ({ "project1_${name}", `${scopedAppName}_\${name}` ); + let configContent = fs.readFileSync(configFile, "utf-8"); + const dbType = ( + { + postgres: "pg", + neon: "pg", + sqlite: "sqlite", + mysql: "mysql", + planetscale: "mysql", + } as const + )[databaseProvider]; configContent = configContent.replace("project1_*", `${scopedAppName}_*`); + if (databaseProvider !== "mysql" && databaseProvider !== "planetscale") { + configContent = configContent.replace( + "mysql2", + { + postgres: "pg", + neon: "pg", + sqlite: "better-sqlite", + }[databaseProvider] + ); + schemaContent = schemaContent.replace( + "drizzle-orm/mysql-core", + `drizzle-orm/${dbType}-core` + ); + schemaContent = schemaContent.replaceAll( + "mysqlTableCreator", + `${dbType}TableCreator` + ); + schemaContent = schemaContent.replaceAll(".onUpdateNow()", ""); + if (dbType === "sqlite") { + schemaContent = schemaContent.replace(" varchar,\n", ""); + schemaContent = schemaContent.replace(" bigint,\n", ""); + schemaContent = schemaContent.replace(" timestamp,\n", ""); + schemaContent = schemaContent.replaceAll("varchar", "text"); + schemaContent = schemaContent.replaceAll("bigint", "int"); + schemaContent = schemaContent.replace( + /timestamp\("([a-zA-Z\-_]+)", { mode: "date" }\)/g, + 'int("$1", { mode: "timestamp_ms" })' + ); + schemaContent = schemaContent.replace( + /timestamp\("([a-zA-Z\-_]+)"\)/g, + 'int("$1", { mode: "timestamp" })' + ); + schemaContent = schemaContent.replace( + `timestamp("emailVerified", { + mode: "date", + fsp: 3, + })`, + 'int("emailVerified", { mode: "timestamp" })' + ); + schemaContent = schemaContent.replaceAll( + ".primaryKey().autoincrement()", + ".primaryKey({ autoIncrement: true })" + ); + } + if (dbType === "pg") { + schemaContent = schemaContent.replace(" bigint,\n", ""); + schemaContent = schemaContent.replace( + " int,\n", + " integer as int,\n serial,\n" + ); + schemaContent = schemaContent.replace( + 'id: bigint("id", { mode: "number" }).primaryKey().autoincrement(),', + 'id: serial("post_id").primaryKey(),' + ); + schemaContent = schemaContent.replace("fsp: ", "precision: "); + } + } - const clientSrc = path.join(extrasDir, "src/server/db/index-drizzle.ts"); + const clientSrc = path.join( + extrasDir, + `src/server/db/index-drizzle/with-${databaseProvider}.ts` + ); const clientDest = path.join(projectDir, "src/server/db/index.ts"); // add db:push script to package.json @@ -54,7 +143,7 @@ export const drizzleInstaller: Installer = ({ const packageJsonContent = fs.readJSONSync(packageJsonPath) as PackageJson; packageJsonContent.scripts = { ...packageJsonContent.scripts, - "db:push": "dotenv drizzle-kit push:mysql", + "db:push": `dotenv drizzle-kit push:${dbType}`, "db:studio": "dotenv drizzle-kit studio", }; diff --git a/cli/src/installers/envVars.ts b/cli/src/installers/envVars.ts index 9b4e3a4233..ec2433833e 100644 --- a/cli/src/installers/envVars.ts +++ b/cli/src/installers/envVars.ts @@ -2,16 +2,25 @@ import path from "path"; import fs from "fs-extra"; import { PKG_ROOT } from "~/consts.js"; -import { type Installer } from "~/installers/index.js"; +import { type DatabaseProvider, type Installer } from "~/installers/index.js"; -export const envVariablesInstaller: Installer = ({ projectDir, packages }) => { +export const envVariablesInstaller: Installer = ({ + projectDir, + packages, + databaseProvider, +}) => { const usingAuth = packages?.nextAuth.inUse; const usingPrisma = packages?.prisma.inUse; const usingDrizzle = packages?.drizzle.inUse; const usingDb = usingPrisma || usingDrizzle; - const envContent = getEnvContent(!!usingAuth, !!usingPrisma, !!usingDrizzle); + const envContent = getEnvContent( + !!usingAuth, + !!usingPrisma, + !!usingDrizzle, + databaseProvider + ); const envFile = usingAuth && usingDb @@ -42,7 +51,8 @@ export const envVariablesInstaller: Installer = ({ projectDir, packages }) => { const getEnvContent = ( usingAuth: boolean, usingPrisma: boolean, - usingDrizzle: boolean + usingDrizzle: boolean, + databaseProvider: DatabaseProvider ) => { let content = ` # When adding additional environment variables, the schema in "/src/env.mjs" @@ -55,16 +65,23 @@ const getEnvContent = ( content += ` # Prisma # https://www.prisma.io/docs/reference/database-reference/connection-urls#env -DATABASE_URL="file:./db.sqlite" `; - if (usingDrizzle) { - content += ` -# Drizzle -# Get the Database URL from the "prisma" dropdown selector in PlanetScale. -# Change the query params at the end of the URL to "?ssl={"rejectUnauthorized":true}" -DATABASE_URL='mysql://YOUR_MYSQL_URL_HERE?ssl={"rejectUnauthorized":true}' -`; + if (usingDrizzle) content += "\n# Drizzle\n"; + + if (usingPrisma || usingDrizzle) { + if (databaseProvider === "planetscale") { + content += `Get the Database URL from the "prisma" dropdown selector in PlanetScale. + # Change the query params at the end of the URL to "?ssl={"rejectUnauthorized":true}" + DATABASE_URL='mysql://YOUR_MYSQL_URL_HERE?ssl={"rejectUnauthorized":true}'`; + } else if (databaseProvider === "mysql") { + content += `DATABASE_URL='mysql://username:password@localhost:3306/db_name?schema=public'`; + } else if (databaseProvider === "postgres" || databaseProvider === "neon") { + content += `DATABASE_URL='postgresql://username:password@localhost:5432/db_name?schema=public'`; + } else if (databaseProvider === "sqlite") { + content += `DATABASE_URL='file:./db.sqlite'`; + } + content += "\n"; } if (usingAuth) diff --git a/cli/src/installers/index.ts b/cli/src/installers/index.ts index 8ab83c2c1b..39b8c5606f 100644 --- a/cli/src/installers/index.ts +++ b/cli/src/installers/index.ts @@ -18,6 +18,15 @@ export const availablePackages = [ ] as const; export type AvailablePackages = (typeof availablePackages)[number]; +export const databaseProviders = [ + "mysql", + "postgres", + "sqlite", + "planetscale", + "neon", +] as const; +export type DatabaseProvider = (typeof databaseProviders)[number]; + export interface InstallerOptions { projectDir: string; pkgManager: PackageManager; @@ -26,6 +35,7 @@ export interface InstallerOptions { appRouter?: boolean; projectName: string; scopedAppName: string; + databaseProvider: DatabaseProvider; } export type Installer = (opts: InstallerOptions) => void; diff --git a/cli/src/installers/prisma.ts b/cli/src/installers/prisma.ts index 6001b3067d..0be77b6553 100644 --- a/cli/src/installers/prisma.ts +++ b/cli/src/installers/prisma.ts @@ -6,7 +6,11 @@ import { PKG_ROOT } from "~/consts.js"; import { type Installer } from "~/installers/index.js"; import { addPackageDependency } from "~/utils/addPackageDependency.js"; -export const prismaInstaller: Installer = ({ projectDir, packages }) => { +export const prismaInstaller: Installer = ({ + projectDir, + packages, + databaseProvider, +}) => { addPackageDependency({ projectDir, dependencies: ["prisma"], @@ -25,7 +29,26 @@ export const prismaInstaller: Installer = ({ projectDir, packages }) => { "prisma/schema", packages?.nextAuth.inUse ? "with-auth.prisma" : "base.prisma" ); + let schemaText = fs.readFileSync(schemaSrc, "utf-8"); + if (databaseProvider !== "sqlite") { + schemaText = schemaText.replace( + 'provider = "sqlite"', + `provider = "${ + { + mysql: "mysql", + postgres: "postgresql", + neon: "postgresql", + planetscale: "mysql", + }[databaseProvider] + }"` + ); + if (["mysql", "planetscale"].includes(databaseProvider)) { + schemaText = schemaText.replace("// @db.Text", "@db.Text"); + } + } const schemaDest = path.join(projectDir, "prisma/schema.prisma"); + fs.mkdirSync(path.dirname(schemaDest), { recursive: true }); + fs.writeFileSync(schemaDest, schemaText); const clientSrc = path.join(extrasDir, "src/server/db/db-prisma.ts"); const clientDest = path.join(projectDir, "src/server/db.ts"); @@ -41,7 +64,6 @@ export const prismaInstaller: Installer = ({ projectDir, packages }) => { "db:studio": "prisma studio", }; - fs.copySync(schemaSrc, schemaDest); fs.copySync(clientSrc, clientDest); fs.writeJSONSync(packageJsonPath, packageJsonContent, { spaces: 2, diff --git a/cli/template/extras/src/server/auth-app/with-drizzle.ts b/cli/template/extras/src/server/auth-app/with-drizzle.ts index 6809dbd1c0..3c72469eb4 100644 --- a/cli/template/extras/src/server/auth-app/with-drizzle.ts +++ b/cli/template/extras/src/server/auth-app/with-drizzle.ts @@ -8,7 +8,7 @@ import DiscordProvider from "next-auth/providers/discord"; import { env } from "~/env.mjs"; import { db } from "~/server/db"; -import { mysqlTable } from "~/server/db/schema"; +import { createTable } from "~/server/db/schema"; /** * Module augmentation for `next-auth` types. Allows us to add custom properties to the `session` @@ -46,7 +46,7 @@ export const authOptions: NextAuthOptions = { }, }), }, - adapter: DrizzleAdapter(db, mysqlTable), + adapter: DrizzleAdapter(db, createTable), providers: [ DiscordProvider({ clientId: env.DISCORD_CLIENT_ID, diff --git a/cli/template/extras/src/server/auth-pages/with-drizzle.ts b/cli/template/extras/src/server/auth-pages/with-drizzle.ts index 89391fd84a..daaa82d2ca 100644 --- a/cli/template/extras/src/server/auth-pages/with-drizzle.ts +++ b/cli/template/extras/src/server/auth-pages/with-drizzle.ts @@ -9,7 +9,7 @@ import DiscordProvider from "next-auth/providers/discord"; import { env } from "~/env.mjs"; import { db } from "~/server/db"; -import { mysqlTable } from "~/server/db/schema"; +import { createTable } from "~/server/db/schema"; /** * Module augmentation for `next-auth` types. Allows us to add custom properties to the `session` @@ -47,7 +47,7 @@ export const authOptions: NextAuthOptions = { }, }), }, - adapter: DrizzleAdapter(db, mysqlTable), + adapter: DrizzleAdapter(db, createTable), providers: [ DiscordProvider({ clientId: env.DISCORD_CLIENT_ID, diff --git a/cli/template/extras/src/server/db/drizzle-schema-auth.ts b/cli/template/extras/src/server/db/drizzle-schema-auth.ts index b6bb842fc3..e53a950fbc 100644 --- a/cli/template/extras/src/server/db/drizzle-schema-auth.ts +++ b/cli/template/extras/src/server/db/drizzle-schema-auth.ts @@ -17,9 +17,9 @@ import { type AdapterAccount } from "next-auth/adapters"; * * @see https://orm.drizzle.team/docs/goodies#multi-project-schema */ -export const mysqlTable = mysqlTableCreator((name) => `project1_${name}`); +export const createTable = mysqlTableCreator((name) => `project1_${name}`); -export const posts = mysqlTable( +export const posts = createTable( "post", { id: bigint("id", { mode: "number" }).primaryKey().autoincrement(), @@ -36,7 +36,7 @@ export const posts = mysqlTable( }) ); -export const users = mysqlTable("user", { +export const users = createTable("user", { id: varchar("id", { length: 255 }).notNull().primaryKey(), name: varchar("name", { length: 255 }), email: varchar("email", { length: 255 }).notNull(), @@ -52,7 +52,7 @@ export const usersRelations = relations(users, ({ many }) => ({ sessions: many(sessions), })); -export const accounts = mysqlTable( +export const accounts = createTable( "account", { userId: varchar("userId", { length: 255 }).notNull(), @@ -79,7 +79,7 @@ export const accountsRelations = relations(accounts, ({ one }) => ({ user: one(users, { fields: [accounts.userId], references: [users.id] }), })); -export const sessions = mysqlTable( +export const sessions = createTable( "session", { sessionToken: varchar("sessionToken", { length: 255 }) @@ -97,7 +97,7 @@ export const sessionsRelations = relations(sessions, ({ one }) => ({ user: one(users, { fields: [sessions.userId], references: [users.id] }), })); -export const verificationTokens = mysqlTable( +export const verificationTokens = createTable( "verificationToken", { identifier: varchar("identifier", { length: 255 }).notNull(), diff --git a/cli/template/extras/src/server/db/drizzle-schema-base.ts b/cli/template/extras/src/server/db/drizzle-schema-base.ts index 7e50b8ee7d..bda3089138 100644 --- a/cli/template/extras/src/server/db/drizzle-schema-base.ts +++ b/cli/template/extras/src/server/db/drizzle-schema-base.ts @@ -16,9 +16,9 @@ import { * * @see https://orm.drizzle.team/docs/goodies#multi-project-schema */ -export const mysqlTable = mysqlTableCreator((name) => `project1_${name}`); +export const createTable = mysqlTableCreator((name) => `project1_${name}`); -export const posts = mysqlTable( +export const posts = createTable( "post", { id: bigint("id", { mode: "number" }).primaryKey().autoincrement(), diff --git a/cli/template/extras/src/server/db/index-drizzle/with-mysql.ts b/cli/template/extras/src/server/db/index-drizzle/with-mysql.ts new file mode 100644 index 0000000000..763ad29fd6 --- /dev/null +++ b/cli/template/extras/src/server/db/index-drizzle/with-mysql.ts @@ -0,0 +1,12 @@ +import { drizzle } from "drizzle-orm/mysql2"; +import mysql from "mysql2/promise"; + +import { env } from "~/env.mjs"; +import * as schema from "./schema"; + +export const db = drizzle( + mysql.createPool({ + uri: env.DATABASE_URL, + }), + { schema } +); diff --git a/cli/template/extras/src/server/db/index-drizzle/with-neon.ts b/cli/template/extras/src/server/db/index-drizzle/with-neon.ts new file mode 100644 index 0000000000..6a5502fc71 --- /dev/null +++ b/cli/template/extras/src/server/db/index-drizzle/with-neon.ts @@ -0,0 +1,12 @@ +import { Client } from "@neondatabase/serverless"; +import { drizzle } from "drizzle-orm/neon-serverless"; + +import { env } from "~/env.mjs"; +import * as schema from "./schema"; + +export const db = drizzle( + new Client({ + connectionString: env.DATABASE_URL, + }), + { schema } +); diff --git a/cli/template/extras/src/server/db/index-drizzle.ts b/cli/template/extras/src/server/db/index-drizzle/with-planetscale.ts similarity index 100% rename from cli/template/extras/src/server/db/index-drizzle.ts rename to cli/template/extras/src/server/db/index-drizzle/with-planetscale.ts diff --git a/cli/template/extras/src/server/db/index-drizzle/with-postgres.ts b/cli/template/extras/src/server/db/index-drizzle/with-postgres.ts new file mode 100644 index 0000000000..ad1fe6c641 --- /dev/null +++ b/cli/template/extras/src/server/db/index-drizzle/with-postgres.ts @@ -0,0 +1,12 @@ +import { drizzle } from "drizzle-orm/node-postgres"; +import { Client } from "pg"; + +import { env } from "~/env.mjs"; +import * as schema from "./schema"; + +export const db = drizzle( + new Client({ + connectionString: env.DATABASE_URL, + }), + { schema } +); diff --git a/cli/template/extras/src/server/db/index-drizzle/with-sqlite.ts b/cli/template/extras/src/server/db/index-drizzle/with-sqlite.ts new file mode 100644 index 0000000000..2a2e73f4d0 --- /dev/null +++ b/cli/template/extras/src/server/db/index-drizzle/with-sqlite.ts @@ -0,0 +1,7 @@ +import Database from "better-sqlite3"; +import { drizzle } from "drizzle-orm/better-sqlite3"; + +import { env } from "~/env.mjs"; +import * as schema from "./schema"; + +export const db = drizzle(new Database(env.DATABASE_URL), { schema }); diff --git a/tsconfig.json b/tsconfig.json index 3ff225328e..6239c83b1c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { /* LANGUAGE COMPILATION OPTIONS */ "target": "ES2020", - "lib": ["DOM", "DOM.Iterable", "ES2020"], + "lib": ["DOM", "DOM.Iterable", "ES2021"], "module": "Node16", "moduleResolution": "nodenext", "resolveJsonModule": true, From b89463ba4490000b27ec62b3993b459dba35835b Mon Sep 17 00:00:00 2001 From: Matvey Ryabchikov <35634442+ronanru@users.noreply.github.com> Date: Wed, 25 Oct 2023 02:25:00 +0300 Subject: [PATCH 02/30] fix drizzle config --- cli/src/installers/drizzle.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cli/src/installers/drizzle.ts b/cli/src/installers/drizzle.ts index 8780d97b3e..443b28a08e 100644 --- a/cli/src/installers/drizzle.ts +++ b/cli/src/installers/drizzle.ts @@ -82,6 +82,9 @@ export const drizzleInstaller: Installer = ({ sqlite: "better-sqlite", }[databaseProvider] ); + if (databaseProvider === "sqlite") + configContent = configContent.replace("connectionString:", "url:"); + schemaContent = schemaContent.replace( "drizzle-orm/mysql-core", `drizzle-orm/${dbType}-core` From 80eed450a8f2d5781e9b1df109fdfc3d59c22d3b Mon Sep 17 00:00:00 2001 From: Matvey Ryabchikov <35634442+ronanru@users.noreply.github.com> Date: Wed, 25 Oct 2023 02:29:58 +0300 Subject: [PATCH 03/30] fix ci error --- cli/src/installers/drizzle.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cli/src/installers/drizzle.ts b/cli/src/installers/drizzle.ts index 443b28a08e..a94e9a71ef 100644 --- a/cli/src/installers/drizzle.ts +++ b/cli/src/installers/drizzle.ts @@ -95,8 +95,10 @@ export const drizzleInstaller: Installer = ({ ); schemaContent = schemaContent.replaceAll(".onUpdateNow()", ""); if (dbType === "sqlite") { - schemaContent = schemaContent.replace(" varchar,\n", ""); - schemaContent = schemaContent.replace(" bigint,\n", ""); + if (packages?.nextAuth.inUse) { + schemaContent = schemaContent.replace(" varchar,\n", ""); + schemaContent = schemaContent.replace(" bigint,\n", ""); + } schemaContent = schemaContent.replace(" timestamp,\n", ""); schemaContent = schemaContent.replaceAll("varchar", "text"); schemaContent = schemaContent.replaceAll("bigint", "int"); From 03634e6025370bc1cf9bd9ad3c7e5eb4adc708cc Mon Sep 17 00:00:00 2001 From: Matvey Ryabchikov <35634442+ronanru@users.noreply.github.com> Date: Wed, 25 Oct 2023 02:37:44 +0300 Subject: [PATCH 04/30] use planetscale as default for drizzle --- cli/src/cli/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cli/src/cli/index.ts b/cli/src/cli/index.ts index b8cbd12061..a85890e908 100644 --- a/cli/src/cli/index.ts +++ b/cli/src/cli/index.ts @@ -189,6 +189,10 @@ export const runCli = async (): Promise => { process.exit(0); } + cliResults.databaseProvider = cliResults.packages.includes("drizzle") + ? "planetscale" + : "sqlite"; + return cliResults; } @@ -335,7 +339,8 @@ export const runCli = async (): Promise => { appName: project.name ?? cliResults.appName, packages, databaseProvider: - (project.databaseProvider as DatabaseProvider) || "sqlite", + (project.databaseProvider as DatabaseProvider) || + (packages.includes("drizzle") ? "planetscale" : "sqlite"), flags: { ...cliResults.flags, appRouter: project.appRouter ?? cliResults.flags.appRouter, From f5d5a2df214ec94fd16ddd851e19250ebaeb656d Mon Sep 17 00:00:00 2001 From: Matvey Ryabchikov <35634442+ronanru@users.noreply.github.com> Date: Mon, 30 Oct 2023 22:39:55 +0300 Subject: [PATCH 05/30] add ci tests --- .github/workflows/e2e.yml | 7 ++++--- cli/src/cli/index.ts | 2 +- .../extras/src/server/db/index-drizzle/with-sqlite.ts | 7 ++++++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index cb12c5c5e3..fedf586e34 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -29,6 +29,7 @@ jobs: prisma: ["true", "false"] appRouter: ["true", "false"] drizzle: ["true", "false"] + dbType: ["planetscale", "sqlite", "mysql", "postgresql", "neon"] name: "Build and Start T3 App ${{ matrix.trpc }}-${{ matrix.tailwind }}-${{ matrix.nextAuth }}-${{ matrix.prisma }}-${{ matrix.drizzle}}-${{ matrix.appRouter }}" steps: @@ -39,7 +40,7 @@ jobs: - name: Check valid matrix id: matrix-valid run: | - echo "continue=${{ matrix.prisma == 'false' || matrix.drizzle == 'false' }}" >> $GITHUB_OUTPUT + echo "continue=${{ (matrix.prisma == 'false' || matrix.drizzle == 'false') && (matrix.drizzle == 'true' || matrix.prisma == 'true' || matrix.dbType == 'sqlite') }}" >> $GITHUB_OUTPUT - name: Install Node.js if: ${{ steps.matrix-valid.outputs.continue == 'true' }} @@ -79,9 +80,9 @@ jobs: # has to be scaffolded outside the CLI project so that no lint/tsconfig are leaking # through. this way it ensures that it is the app's configs that are being used # FIXME: this is a bit hacky, would rather have --packages=trpc,tailwind,... but not sure how to setup the matrix for that - - run: cd cli && pnpm start ../../ci-${{ matrix.trpc }}-${{ matrix.tailwind }}-${{ matrix.nextAuth }}-${{ matrix.prisma }}-${{ matrix.drizzle}}-${{ matrix.appRouter }} --noGit --CI --trpc=${{ matrix.trpc }} --tailwind=${{ matrix.tailwind }} --nextAuth=${{ matrix.nextAuth }} --prisma=${{ matrix.prisma }} --drizzle=${{ matrix.drizzle }} --appRouter=${{ matrix.appRouter }} + - run: cd cli && pnpm start ../../ci-${{ matrix.trpc }}-${{ matrix.tailwind }}-${{ matrix.nextAuth }}-${{ matrix.prisma }}-${{ matrix.drizzle}}-${{ matrix.appRouter }}-${{ matrix.dbType }} --noGit --CI --trpc=${{ matrix.trpc }} --tailwind=${{ matrix.tailwind }} --nextAuth=${{ matrix.nextAuth }} --prisma=${{ matrix.prisma }} --drizzle=${{ matrix.drizzle }} --appRouter=${{ matrix.appRouter }} --dbProvider=${{ matrix.dbType }} if: ${{ steps.matrix-valid.outputs.continue == 'true' }} - - run: cd ../ci-${{ matrix.trpc }}-${{ matrix.tailwind }}-${{ matrix.nextAuth }}-${{ matrix.prisma }}-${{ matrix.drizzle}}-${{ matrix.appRouter }} && pnpm build + - run: cd ../ci-${{ matrix.trpc }}-${{ matrix.tailwind }}-${{ matrix.nextAuth }}-${{ matrix.prisma }}-${{ matrix.drizzle}}-${{ matrix.appRouter }}-${{ matrix.dbType }} && pnpm build if: ${{ steps.matrix-valid.outputs.continue == 'true' }} env: NEXTAUTH_SECRET: foo diff --git a/cli/src/cli/index.ts b/cli/src/cli/index.ts index a85890e908..64175332d8 100644 --- a/cli/src/cli/index.ts +++ b/cli/src/cli/index.ts @@ -131,7 +131,7 @@ export const runCli = async (): Promise => { defaultOptions.flags.importAlias ) .option( - "--db-provider", + "--dbProvider [provider]", `Choose a database provider to use. Possible values: ${databaseProviders.join( ", " )}`, diff --git a/cli/template/extras/src/server/db/index-drizzle/with-sqlite.ts b/cli/template/extras/src/server/db/index-drizzle/with-sqlite.ts index 2a2e73f4d0..0026a0a578 100644 --- a/cli/template/extras/src/server/db/index-drizzle/with-sqlite.ts +++ b/cli/template/extras/src/server/db/index-drizzle/with-sqlite.ts @@ -4,4 +4,9 @@ import { drizzle } from "drizzle-orm/better-sqlite3"; import { env } from "~/env.mjs"; import * as schema from "./schema"; -export const db = drizzle(new Database(env.DATABASE_URL), { schema }); +export const db = drizzle( + new Database(env.DATABASE_URL, { + fileMustExist: false, + }), + { schema } +); From 40405de83025b3b1eda77ffe65b52e7e066887a0 Mon Sep 17 00:00:00 2001 From: Matvey Ryabchikov <35634442+ronanru@users.noreply.github.com> Date: Fri, 3 Nov 2023 02:42:52 +0300 Subject: [PATCH 06/30] remove neon --- .github/workflows/e2e.yml | 2 +- cli/src/cli/index.ts | 1 - cli/src/installers/dependencyVersionMap.ts | 1 - cli/src/installers/drizzle.ts | 6 +----- cli/src/installers/envVars.ts | 2 +- cli/src/installers/index.ts | 1 - cli/src/installers/prisma.ts | 1 - .../extras/src/server/db/index-drizzle/with-neon.ts | 12 ------------ 8 files changed, 3 insertions(+), 23 deletions(-) delete mode 100644 cli/template/extras/src/server/db/index-drizzle/with-neon.ts diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index fedf586e34..79550ef96f 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -29,7 +29,7 @@ jobs: prisma: ["true", "false"] appRouter: ["true", "false"] drizzle: ["true", "false"] - dbType: ["planetscale", "sqlite", "mysql", "postgresql", "neon"] + dbType: ["planetscale", "sqlite", "mysql", "postgresql"] name: "Build and Start T3 App ${{ matrix.trpc }}-${{ matrix.tailwind }}-${{ matrix.nextAuth }}-${{ matrix.prisma }}-${{ matrix.drizzle}}-${{ matrix.appRouter }}" steps: diff --git a/cli/src/cli/index.ts b/cli/src/cli/index.ts index 64175332d8..eeaf657eed 100644 --- a/cli/src/cli/index.ts +++ b/cli/src/cli/index.ts @@ -288,7 +288,6 @@ export const runCli = async (): Promise => { { value: "postgres", label: "PostgreSQL" }, { value: "sqlite", label: "SQLite" }, { value: "planetscale", label: "Planetscale Serverless" }, - { value: "neon", label: "Neon Serverless" }, ], initialValue: "mysql", }); diff --git a/cli/src/installers/dependencyVersionMap.ts b/cli/src/installers/dependencyVersionMap.ts index f02127d9aa..5922279e5f 100644 --- a/cli/src/installers/dependencyVersionMap.ts +++ b/cli/src/installers/dependencyVersionMap.ts @@ -22,7 +22,6 @@ export const dependencyVersionMap = { "@types/pg": "^8.10.7", "@types/better-sqlite3": "^7.6.6", "better-sqlite3": "^9.0.0", - "@neondatabase/serverless": "^0.6.0", // TailwindCSS tailwindcss: "^3.3.3", diff --git a/cli/src/installers/drizzle.ts b/cli/src/installers/drizzle.ts index a94e9a71ef..841649f75a 100644 --- a/cli/src/installers/drizzle.ts +++ b/cli/src/installers/drizzle.ts @@ -17,7 +17,6 @@ export const drizzleInstaller: Installer = ({ if (databaseProvider === "planetscale") devPackages.push("mysql2"); if (databaseProvider === "postgres") devPackages.push("@types/pg"); if (databaseProvider === "sqlite") devPackages.push("@types/better-sqlite3"); - if (databaseProvider === "neon") devPackages.push("pg"); addPackageDependency({ projectDir, @@ -34,7 +33,6 @@ export const drizzleInstaller: Installer = ({ mysql: "mysql2", postgres: "pg", sqlite: "better-sqlite3", - neon: "@neondatabase/serverless", } as const )[databaseProvider], ], @@ -66,7 +64,6 @@ export const drizzleInstaller: Installer = ({ const dbType = ( { postgres: "pg", - neon: "pg", sqlite: "sqlite", mysql: "mysql", planetscale: "mysql", @@ -77,9 +74,8 @@ export const drizzleInstaller: Installer = ({ configContent = configContent.replace( "mysql2", { - postgres: "pg", - neon: "pg", sqlite: "better-sqlite", + postgres: "pg", }[databaseProvider] ); if (databaseProvider === "sqlite") diff --git a/cli/src/installers/envVars.ts b/cli/src/installers/envVars.ts index ec2433833e..ee5a36aed5 100644 --- a/cli/src/installers/envVars.ts +++ b/cli/src/installers/envVars.ts @@ -76,7 +76,7 @@ const getEnvContent = ( DATABASE_URL='mysql://YOUR_MYSQL_URL_HERE?ssl={"rejectUnauthorized":true}'`; } else if (databaseProvider === "mysql") { content += `DATABASE_URL='mysql://username:password@localhost:3306/db_name?schema=public'`; - } else if (databaseProvider === "postgres" || databaseProvider === "neon") { + } else if (databaseProvider === "postgres") { content += `DATABASE_URL='postgresql://username:password@localhost:5432/db_name?schema=public'`; } else if (databaseProvider === "sqlite") { content += `DATABASE_URL='file:./db.sqlite'`; diff --git a/cli/src/installers/index.ts b/cli/src/installers/index.ts index 39b8c5606f..e04331a427 100644 --- a/cli/src/installers/index.ts +++ b/cli/src/installers/index.ts @@ -23,7 +23,6 @@ export const databaseProviders = [ "postgres", "sqlite", "planetscale", - "neon", ] as const; export type DatabaseProvider = (typeof databaseProviders)[number]; diff --git a/cli/src/installers/prisma.ts b/cli/src/installers/prisma.ts index 0be77b6553..73ff8eae77 100644 --- a/cli/src/installers/prisma.ts +++ b/cli/src/installers/prisma.ts @@ -37,7 +37,6 @@ export const prismaInstaller: Installer = ({ { mysql: "mysql", postgres: "postgresql", - neon: "postgresql", planetscale: "mysql", }[databaseProvider] }"` diff --git a/cli/template/extras/src/server/db/index-drizzle/with-neon.ts b/cli/template/extras/src/server/db/index-drizzle/with-neon.ts deleted file mode 100644 index 6a5502fc71..0000000000 --- a/cli/template/extras/src/server/db/index-drizzle/with-neon.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Client } from "@neondatabase/serverless"; -import { drizzle } from "drizzle-orm/neon-serverless"; - -import { env } from "~/env.mjs"; -import * as schema from "./schema"; - -export const db = drizzle( - new Client({ - connectionString: env.DATABASE_URL, - }), - { schema } -); From 071bfd97a02d7ac4012d2ad85cd52dc53399e95f Mon Sep 17 00:00:00 2001 From: Matvey Ryabchikov <35634442+ronanru@users.noreply.github.com> Date: Fri, 3 Nov 2023 13:17:25 +0300 Subject: [PATCH 07/30] move drizzle schemas to different files --- .github/workflows/e2e.yml | 2 +- cli/src/installers/drizzle.ts | 89 +++------------ .../base-mysql.ts} | 0 .../server/db/schema-drizzle/base-postgres.ts | 34 ++++++ .../server/db/schema-drizzle/base-sqlite.ts | 28 +++++ .../with-auth-mysql.ts} | 0 .../db/schema-drizzle/with-auth-postgres.ts | 108 ++++++++++++++++++ .../db/schema-drizzle/with-auth-sqlite.ts | 103 +++++++++++++++++ 8 files changed, 289 insertions(+), 75 deletions(-) rename cli/template/extras/src/server/db/{drizzle-schema-base.ts => schema-drizzle/base-mysql.ts} (100%) create mode 100644 cli/template/extras/src/server/db/schema-drizzle/base-postgres.ts create mode 100644 cli/template/extras/src/server/db/schema-drizzle/base-sqlite.ts rename cli/template/extras/src/server/db/{drizzle-schema-auth.ts => schema-drizzle/with-auth-mysql.ts} (100%) create mode 100644 cli/template/extras/src/server/db/schema-drizzle/with-auth-postgres.ts create mode 100644 cli/template/extras/src/server/db/schema-drizzle/with-auth-sqlite.ts diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 79550ef96f..fe2f1767f7 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -31,7 +31,7 @@ jobs: drizzle: ["true", "false"] dbType: ["planetscale", "sqlite", "mysql", "postgresql"] - name: "Build and Start T3 App ${{ matrix.trpc }}-${{ matrix.tailwind }}-${{ matrix.nextAuth }}-${{ matrix.prisma }}-${{ matrix.drizzle}}-${{ matrix.appRouter }}" + name: "Build and Start T3 App ${{ matrix.trpc }}-${{ matrix.tailwind }}-${{ matrix.nextAuth }}-${{ matrix.prisma }}-${{ matrix.drizzle}}-${{ matrix.appRouter }}-${{ matrix.dbType }}" steps: - uses: actions/checkout@v3 with: diff --git a/cli/src/installers/drizzle.ts b/cli/src/installers/drizzle.ts index 841649f75a..80365b5041 100644 --- a/cli/src/installers/drizzle.ts +++ b/cli/src/installers/drizzle.ts @@ -39,6 +39,15 @@ export const drizzleInstaller: Installer = ({ devMode: false, }); + const dbType = ( + { + postgres: "postgres", + sqlite: "sqlite", + mysql: "mysql", + planetscale: "mysql", + } as const + )[databaseProvider]; + const extrasDir = path.join(PKG_ROOT, "template/extras"); const configFile = path.join(extrasDir, "config/drizzle.config.ts"); @@ -46,10 +55,8 @@ export const drizzleInstaller: Installer = ({ const schemaSrc = path.join( extrasDir, - "src/server/db", - packages?.nextAuth.inUse - ? "drizzle-schema-auth.ts" - : "drizzle-schema-base.ts" + "src/server/db/schema-drizzle", + packages?.nextAuth.inUse ? `with-auth-${dbType}.ts` : `base-${dbType}.ts` ); const schemaDest = path.join(projectDir, "src/server/db/schema.ts"); @@ -61,76 +68,8 @@ export const drizzleInstaller: Installer = ({ ); let configContent = fs.readFileSync(configFile, "utf-8"); - const dbType = ( - { - postgres: "pg", - sqlite: "sqlite", - mysql: "mysql", - planetscale: "mysql", - } as const - )[databaseProvider]; - configContent = configContent.replace("project1_*", `${scopedAppName}_*`); - if (databaseProvider !== "mysql" && databaseProvider !== "planetscale") { - configContent = configContent.replace( - "mysql2", - { - sqlite: "better-sqlite", - postgres: "pg", - }[databaseProvider] - ); - if (databaseProvider === "sqlite") - configContent = configContent.replace("connectionString:", "url:"); - schemaContent = schemaContent.replace( - "drizzle-orm/mysql-core", - `drizzle-orm/${dbType}-core` - ); - schemaContent = schemaContent.replaceAll( - "mysqlTableCreator", - `${dbType}TableCreator` - ); - schemaContent = schemaContent.replaceAll(".onUpdateNow()", ""); - if (dbType === "sqlite") { - if (packages?.nextAuth.inUse) { - schemaContent = schemaContent.replace(" varchar,\n", ""); - schemaContent = schemaContent.replace(" bigint,\n", ""); - } - schemaContent = schemaContent.replace(" timestamp,\n", ""); - schemaContent = schemaContent.replaceAll("varchar", "text"); - schemaContent = schemaContent.replaceAll("bigint", "int"); - schemaContent = schemaContent.replace( - /timestamp\("([a-zA-Z\-_]+)", { mode: "date" }\)/g, - 'int("$1", { mode: "timestamp_ms" })' - ); - schemaContent = schemaContent.replace( - /timestamp\("([a-zA-Z\-_]+)"\)/g, - 'int("$1", { mode: "timestamp" })' - ); - schemaContent = schemaContent.replace( - `timestamp("emailVerified", { - mode: "date", - fsp: 3, - })`, - 'int("emailVerified", { mode: "timestamp" })' - ); - schemaContent = schemaContent.replaceAll( - ".primaryKey().autoincrement()", - ".primaryKey({ autoIncrement: true })" - ); - } - if (dbType === "pg") { - schemaContent = schemaContent.replace(" bigint,\n", ""); - schemaContent = schemaContent.replace( - " int,\n", - " integer as int,\n serial,\n" - ); - schemaContent = schemaContent.replace( - 'id: bigint("id", { mode: "number" }).primaryKey().autoincrement(),', - 'id: serial("post_id").primaryKey(),' - ); - schemaContent = schemaContent.replace("fsp: ", "precision: "); - } - } + configContent = configContent.replace("project1_*", `${scopedAppName}_*`); const clientSrc = path.join( extrasDir, @@ -144,7 +83,9 @@ export const drizzleInstaller: Installer = ({ const packageJsonContent = fs.readJSONSync(packageJsonPath) as PackageJson; packageJsonContent.scripts = { ...packageJsonContent.scripts, - "db:push": `dotenv drizzle-kit push:${dbType}`, + "db:push": `dotenv drizzle-kit push:${ + dbType === "postgres" ? "pg" : dbType + }`, "db:studio": "dotenv drizzle-kit studio", }; diff --git a/cli/template/extras/src/server/db/drizzle-schema-base.ts b/cli/template/extras/src/server/db/schema-drizzle/base-mysql.ts similarity index 100% rename from cli/template/extras/src/server/db/drizzle-schema-base.ts rename to cli/template/extras/src/server/db/schema-drizzle/base-mysql.ts diff --git a/cli/template/extras/src/server/db/schema-drizzle/base-postgres.ts b/cli/template/extras/src/server/db/schema-drizzle/base-postgres.ts new file mode 100644 index 0000000000..7e7d62a3cd --- /dev/null +++ b/cli/template/extras/src/server/db/schema-drizzle/base-postgres.ts @@ -0,0 +1,34 @@ +// Example model schema from the Drizzle docs +// https://orm.drizzle.team/docs/sql-schema-declaration + +import { sql } from "drizzle-orm"; +import { + index, + pgTableCreator, + serial, + timestamp, + varchar, +} from "drizzle-orm/pg-core"; + +/** + * This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same + * database instance for multiple projects. + * + * @see https://orm.drizzle.team/docs/goodies#multi-project-schema + */ +export const createTable = pgTableCreator((name) => `project1_${name}`); + +export const posts = createTable( + "post", + { + id: serial("id").primaryKey(), + name: varchar("name", { length: 256 }), + createdAt: timestamp("created_at") + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp("updatedAt"), + }, + (example) => ({ + nameIndex: index("name_idx").on(example.name), + }) +); diff --git a/cli/template/extras/src/server/db/schema-drizzle/base-sqlite.ts b/cli/template/extras/src/server/db/schema-drizzle/base-sqlite.ts new file mode 100644 index 0000000000..1615b4e07e --- /dev/null +++ b/cli/template/extras/src/server/db/schema-drizzle/base-sqlite.ts @@ -0,0 +1,28 @@ +// Example model schema from the Drizzle docs +// https://orm.drizzle.team/docs/sql-schema-declaration + +import { sql } from "drizzle-orm"; +import { index, int, sqliteTableCreator, text } from "drizzle-orm/sqlite-core"; + +/** + * This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same + * database instance for multiple projects. + * + * @see https://orm.drizzle.team/docs/goodies#multi-project-schema + */ +export const createTable = sqliteTableCreator((name) => `project1_${name}`); + +export const posts = createTable( + "post", + { + id: int("id", { mode: "number" }).primaryKey({ autoIncrement: true }), + name: text("name", { length: 256 }), + createdAt: int("created_at", { mode: "timestamp" }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: int("updatedAt", { mode: "timestamp" }), + }, + (example) => ({ + nameIndex: index("name_idx").on(example.name), + }) +); diff --git a/cli/template/extras/src/server/db/drizzle-schema-auth.ts b/cli/template/extras/src/server/db/schema-drizzle/with-auth-mysql.ts similarity index 100% rename from cli/template/extras/src/server/db/drizzle-schema-auth.ts rename to cli/template/extras/src/server/db/schema-drizzle/with-auth-mysql.ts diff --git a/cli/template/extras/src/server/db/schema-drizzle/with-auth-postgres.ts b/cli/template/extras/src/server/db/schema-drizzle/with-auth-postgres.ts new file mode 100644 index 0000000000..9a482e2257 --- /dev/null +++ b/cli/template/extras/src/server/db/schema-drizzle/with-auth-postgres.ts @@ -0,0 +1,108 @@ +import { relations, sql } from "drizzle-orm"; +import { + index, + integer, + pgTableCreator, + primaryKey, + serial, + text, + timestamp, + varchar, +} from "drizzle-orm/pg-core"; +import { type AdapterAccount } from "next-auth/adapters"; + +/** + * This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same + * database instance for multiple projects. + * + * @see https://orm.drizzle.team/docs/goodies#multi-project-schema + */ +export const createTable = pgTableCreator((name) => `project1_${name}`); + +export const posts = createTable( + "post", + { + id: serial("id").primaryKey(), + name: varchar("name", { length: 256 }), + createdById: varchar("createdById", { length: 255 }).notNull(), + createdAt: timestamp("created_at") + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp("updatedAt"), + }, + (example) => ({ + createdByIdIdx: index("createdById_idx").on(example.createdById), + nameIndex: index("name_idx").on(example.name), + }) +); + +export const users = createTable("user", { + id: varchar("id", { length: 255 }).notNull().primaryKey(), + name: varchar("name", { length: 255 }), + email: varchar("email", { length: 255 }).notNull(), + emailVerified: timestamp("emailVerified", { + mode: "date", + }).default(sql`CURRENT_TIMESTAMP`), + image: varchar("image", { length: 255 }), +}); + +export const usersRelations = relations(users, ({ many }) => ({ + accounts: many(accounts), +})); + +export const accounts = createTable( + "account", + { + userId: varchar("userId", { length: 255 }).notNull(), + type: varchar("type", { length: 255 }) + .$type() + .notNull(), + provider: varchar("provider", { length: 255 }).notNull(), + providerAccountId: varchar("providerAccountId", { length: 255 }).notNull(), + refresh_token: text("refresh_token"), + access_token: text("access_token"), + expires_at: integer("expires_at"), + token_type: varchar("token_type", { length: 255 }), + scope: varchar("scope", { length: 255 }), + id_token: text("id_token"), + session_state: varchar("session_state", { length: 255 }), + }, + (account) => ({ + compoundKey: primaryKey(account.provider, account.providerAccountId), + userIdIdx: index("userId_idx").on(account.userId), + }) +); + +export const accountsRelations = relations(accounts, ({ one }) => ({ + user: one(users, { fields: [accounts.userId], references: [users.id] }), +})); + +export const sessions = createTable( + "session", + { + sessionToken: varchar("sessionToken", { length: 255 }) + .notNull() + .primaryKey(), + userId: varchar("userId", { length: 255 }).notNull(), + expires: timestamp("expires", { mode: "date" }).notNull(), + }, + (session) => ({ + userIdIdx: index("userId_idx").on(session.userId), + }) +); + +export const sessionsRelations = relations(sessions, ({ one }) => ({ + user: one(users, { fields: [sessions.userId], references: [users.id] }), +})); + +export const verificationTokens = createTable( + "verificationToken", + { + identifier: varchar("identifier", { length: 255 }).notNull(), + token: varchar("token", { length: 255 }).notNull(), + expires: timestamp("expires", { mode: "date" }).notNull(), + }, + (vt) => ({ + compoundKey: primaryKey(vt.identifier, vt.token), + }) +); diff --git a/cli/template/extras/src/server/db/schema-drizzle/with-auth-sqlite.ts b/cli/template/extras/src/server/db/schema-drizzle/with-auth-sqlite.ts new file mode 100644 index 0000000000..346195041f --- /dev/null +++ b/cli/template/extras/src/server/db/schema-drizzle/with-auth-sqlite.ts @@ -0,0 +1,103 @@ +import { relations, sql } from "drizzle-orm"; +import { + index, + int, + primaryKey, + sqliteTableCreator, + text, +} from "drizzle-orm/sqlite-core"; +import { type AdapterAccount } from "next-auth/adapters"; + +/** + * This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same + * database instance for multiple projects. + * + * @see https://orm.drizzle.team/docs/goodies#multi-project-schema + */ +export const createTable = sqliteTableCreator((name) => `project1_${name}`); + +export const posts = createTable( + "post", + { + id: int("id", { mode: "number" }).primaryKey({ autoIncrement: true }), + name: text("name", { length: 256 }), + createdById: text("createdById", { length: 255 }).notNull(), + createdAt: int("created_at", { mode: "timestamp" }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: int("updatedAt", { mode: "timestamp" }), + }, + (example) => ({ + createdByIdIdx: index("createdById_idx").on(example.createdById), + nameIndex: index("name_idx").on(example.name), + }) +); + +export const users = createTable("user", { + id: text("id", { length: 255 }).notNull().primaryKey(), + name: text("name", { length: 255 }), + email: text("email", { length: 255 }).notNull(), + emailVerified: int("emailVerified", { + mode: "timestamp", + }).default(sql`CURRENT_TIMESTAMP`), + image: text("image", { length: 255 }), +}); + +export const usersRelations = relations(users, ({ many }) => ({ + accounts: many(accounts), +})); + +export const accounts = createTable( + "account", + { + userId: text("userId", { length: 255 }).notNull(), + type: text("type", { length: 255 }) + .$type() + .notNull(), + provider: text("provider", { length: 255 }).notNull(), + providerAccountId: text("providerAccountId", { length: 255 }).notNull(), + refresh_token: text("refresh_token"), + access_token: text("access_token"), + expires_at: int("expires_at"), + token_type: text("token_type", { length: 255 }), + scope: text("scope", { length: 255 }), + id_token: text("id_token"), + session_state: text("session_state", { length: 255 }), + }, + (account) => ({ + compoundKey: primaryKey(account.provider, account.providerAccountId), + userIdIdx: index("userId_idx").on(account.userId), + }) +); + +export const accountsRelations = relations(accounts, ({ one }) => ({ + user: one(users, { fields: [accounts.userId], references: [users.id] }), +})); + +export const sessions = createTable( + "session", + { + sessionToken: text("sessionToken", { length: 255 }).notNull().primaryKey(), + userId: text("userId", { length: 255 }).notNull(), + expires: int("expires", { mode: "timestamp" }).notNull(), + }, + (session) => ({ + userIdIdx: index("userId_idx").on(session.userId), + }) +); + +export const sessionsRelations = relations(sessions, ({ one }) => ({ + user: one(users, { fields: [sessions.userId], references: [users.id] }), +})); + +export const verificationTokens = createTable( + "verificationToken", + { + identifier: text("identifier", { length: 255 }).notNull(), + token: text("token", { length: 255 }).notNull(), + expires: int("expires", { mode: "timestamp" }).notNull(), + }, + (vt) => ({ + compoundKey: primaryKey(vt.identifier, vt.token), + }) +); From 19b0d66cb5c10d643712ff71f21e714a0f6d9080 Mon Sep 17 00:00:00 2001 From: Matvey Ryabchikov <35634442+ronanru@users.noreply.github.com> Date: Fri, 3 Nov 2023 15:15:11 +0300 Subject: [PATCH 08/30] add db container --- cli/src/helpers/logNextSteps.ts | 12 +++++++++- cli/src/index.ts | 3 ++- cli/src/installers/dbContainer.ts | 19 ++++++++++++++++ cli/src/installers/envVars.ts | 20 +++++++++++------ cli/src/installers/index.ts | 9 +++++++- cli/src/utils/randomString.ts | 15 +++++++++++++ cli/template/extras/start-database/mysql.sh | 22 +++++++++++++++++++ .../extras/start-database/postgres.sh | 22 +++++++++++++++++++ 8 files changed, 112 insertions(+), 10 deletions(-) create mode 100644 cli/src/installers/dbContainer.ts create mode 100644 cli/src/utils/randomString.ts create mode 100755 cli/template/extras/start-database/mysql.sh create mode 100755 cli/template/extras/start-database/postgres.sh diff --git a/cli/src/helpers/logNextSteps.ts b/cli/src/helpers/logNextSteps.ts index a51d31ffc0..c67a4c5216 100644 --- a/cli/src/helpers/logNextSteps.ts +++ b/cli/src/helpers/logNextSteps.ts @@ -11,9 +11,15 @@ export const logNextSteps = async ({ appRouter, noInstall, projectDir, + databaseProvider, }: Pick< InstallerOptions, - "projectName" | "packages" | "noInstall" | "projectDir" | "appRouter" + | "projectName" + | "packages" + | "noInstall" + | "projectDir" + | "appRouter" + | "databaseProvider" >) => { const pkgManager = getUserPkgManager(); @@ -28,6 +34,10 @@ export const logNextSteps = async ({ } } + if (["postgres", "mysql"].includes(databaseProvider)) { + logger.info(" ./start-database.sh"); + } + if (packages?.prisma.inUse || packages?.drizzle.inUse) { if (["npm", "bun"].includes(pkgManager)) { logger.info(` ${pkgManager} run db:push`); diff --git a/cli/src/index.ts b/cli/src/index.ts index 18d249100e..c4774954cc 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -40,7 +40,7 @@ const main = async () => { databaseProvider, } = await runCli(); - const usePackages = buildPkgInstallerMap(packages); + const usePackages = buildPkgInstallerMap(packages, databaseProvider); // e.g. dir/@mono/app returns ["@mono/app", "dir/app"] const [scopedAppName, appDir] = parseNameAndPath(appName); @@ -99,6 +99,7 @@ const main = async () => { appRouter, noInstall, projectDir, + databaseProvider, }); process.exit(0); diff --git a/cli/src/installers/dbContainer.ts b/cli/src/installers/dbContainer.ts new file mode 100644 index 0000000000..7001e80240 --- /dev/null +++ b/cli/src/installers/dbContainer.ts @@ -0,0 +1,19 @@ +import path from "path"; +import fs from "fs-extra"; + +import { PKG_ROOT } from "~/consts.js"; +import { type Installer } from "~/installers/index.js"; + +export const dbContainerInstaller: Installer = ({ + projectDir, + databaseProvider, + projectName, +}) => { + const scriptSrc = path.join( + PKG_ROOT, + `template/extras/start-database/${databaseProvider}.sh` + ); + const scriptText = fs.readFileSync(scriptSrc, "utf-8"); + const scriptDest = path.join(projectDir, "start-database.sh"); + fs.writeFileSync(scriptDest, scriptText.replaceAll("project1", projectName)); +}; diff --git a/cli/src/installers/envVars.ts b/cli/src/installers/envVars.ts index ee5a36aed5..82004129ac 100644 --- a/cli/src/installers/envVars.ts +++ b/cli/src/installers/envVars.ts @@ -3,11 +3,13 @@ import fs from "fs-extra"; import { PKG_ROOT } from "~/consts.js"; import { type DatabaseProvider, type Installer } from "~/installers/index.js"; +import { generateRandomString } from "~/utils/randomString.js"; export const envVariablesInstaller: Installer = ({ projectDir, packages, databaseProvider, + projectName, }) => { const usingAuth = packages?.nextAuth.inUse; const usingPrisma = packages?.prisma.inUse; @@ -19,7 +21,8 @@ export const envVariablesInstaller: Installer = ({ !!usingAuth, !!usingPrisma, !!usingDrizzle, - databaseProvider + databaseProvider, + projectName ); const envFile = @@ -52,7 +55,8 @@ const getEnvContent = ( usingAuth: boolean, usingPrisma: boolean, usingDrizzle: boolean, - databaseProvider: DatabaseProvider + databaseProvider: DatabaseProvider, + projectName: string ) => { let content = ` # When adding additional environment variables, the schema in "/src/env.mjs" @@ -72,14 +76,16 @@ const getEnvContent = ( if (usingPrisma || usingDrizzle) { if (databaseProvider === "planetscale") { content += `Get the Database URL from the "prisma" dropdown selector in PlanetScale. - # Change the query params at the end of the URL to "?ssl={"rejectUnauthorized":true}" - DATABASE_URL='mysql://YOUR_MYSQL_URL_HERE?ssl={"rejectUnauthorized":true}'`; +# Change the query params at the end of the URL to "?ssl={"rejectUnauthorized":true}" +DATABASE_URL='mysql://YOUR_MYSQL_URL_HERE?ssl={"rejectUnauthorized":true}'`; } else if (databaseProvider === "mysql") { - content += `DATABASE_URL='mysql://username:password@localhost:3306/db_name?schema=public'`; + content += `DATABASE_PASSWORD=${generateRandomString(12)} +DATABASE_URL="mysql://root:$DATABASE_PASSWORD@localhost:3306/${projectName}"`; } else if (databaseProvider === "postgres") { - content += `DATABASE_URL='postgresql://username:password@localhost:5432/db_name?schema=public'`; + content += `DATABASE_PASSWORD=${generateRandomString(12)} +DATABASE_URL="postgresql://postgres:$DATABASE_PASSWORD@localhost:5432/${projectName}?schema=public"`; } else if (databaseProvider === "sqlite") { - content += `DATABASE_URL='file:./db.sqlite'`; + content += `DATABASE_URL="file:./db.sqlite"`; } content += "\n"; } diff --git a/cli/src/installers/index.ts b/cli/src/installers/index.ts index e04331a427..b99676e0a8 100644 --- a/cli/src/installers/index.ts +++ b/cli/src/installers/index.ts @@ -4,6 +4,7 @@ import { prismaInstaller } from "~/installers/prisma.js"; import { tailwindInstaller } from "~/installers/tailwind.js"; import { trpcInstaller } from "~/installers/trpc.js"; import { type PackageManager } from "~/utils/getUserPkgManager.js"; +import { dbContainerInstaller } from "./dbContainer.js"; import { drizzleInstaller } from "./drizzle.js"; // Turning this into a const allows the list to be iterated over for programatically creating prompt options @@ -15,6 +16,7 @@ export const availablePackages = [ "tailwind", "trpc", "envVariables", + "dbContainer", ] as const; export type AvailablePackages = (typeof availablePackages)[number]; @@ -47,7 +49,8 @@ export type PkgInstallerMap = { }; export const buildPkgInstallerMap = ( - packages: AvailablePackages[] + packages: AvailablePackages[], + databaseProvider: DatabaseProvider ): PkgInstallerMap => ({ nextAuth: { inUse: packages.includes("nextAuth"), @@ -69,6 +72,10 @@ export const buildPkgInstallerMap = ( inUse: packages.includes("trpc"), installer: trpcInstaller, }, + dbContainer: { + inUse: ["mysql", "postgres"].includes(databaseProvider), + installer: () => dbContainerInstaller, + }, envVariables: { inUse: true, installer: envVariablesInstaller, diff --git a/cli/src/utils/randomString.ts b/cli/src/utils/randomString.ts new file mode 100644 index 0000000000..925d6f7a96 --- /dev/null +++ b/cli/src/utils/randomString.ts @@ -0,0 +1,15 @@ +import { webcrypto as crypto } from "node:crypto"; + +export const generateRandomString = (length: number) => { + const randomUint32Values = new Uint32Array(length); + crypto.getRandomValues(randomUint32Values); + const u32Max = 0xffffffff; + const alphabet = + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let result = ""; + for (const randomUnit32 of randomUint32Values) { + const rand = randomUnit32 / (u32Max + 1); + result += alphabet[Math.floor(alphabet.length * rand)]; + } + return result; +}; diff --git a/cli/template/extras/start-database/mysql.sh b/cli/template/extras/start-database/mysql.sh new file mode 100755 index 0000000000..3407bc503e --- /dev/null +++ b/cli/template/extras/start-database/mysql.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +DB_CONTAINER_NAME="project1-mysql" + +if ! [ -x "$(command -v docker)" ]; then + echo "Docker is not installed. Please install docker and try again.\nDocker install guide: https://docs.docker.com/engine/install/" + exit 1 +fi + +# import env variables from .env +set -a +source .env + +if [ "$(docker ps -q -f name=$DB_CONTAINER_NAME)" ]; then + docker start $DB_CONTAINER_NAME + echo "Database container started" +else + echo "Did not find database container. Creating a new container..." + docker run --name $DB_CONTAINER_NAME -e MYSQL_ROOT_PASSWORD=$DATABASE_PASSWORD -e MYSQL_DATABASE=project1 -d -p 3306:3306 docker.io/mysql + echo "Database container created and started" +fi + diff --git a/cli/template/extras/start-database/postgres.sh b/cli/template/extras/start-database/postgres.sh new file mode 100755 index 0000000000..73cf7f0208 --- /dev/null +++ b/cli/template/extras/start-database/postgres.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +DB_CONTAINER_NAME="project1-postgres" + +if ! [ -x "$(command -v docker)" ]; then + echo "Docker is not installed. Please install docker and try again.\nDocker install guide: https://docs.docker.com/engine/install/" + exit 1 +fi + +# import env variables from .env +set -a +source .env + +if [ "$(docker ps -q -f name=$DB_CONTAINER_NAME)" ]; then + docker start $DB_CONTAINER_NAME + echo "Database container started" +else + echo "Did not find database container. Creating a new container..." + docker run --name $DB_CONTAINER_NAME -e POSTGRES_PASSWORD=$DATABASE_PASSWORD -e POSTGRES_DB=project1 -d -p 5432:5432 docker.io/postgres + echo "Database container created and started" +fi + From 7e36c882fe8066732e5d95a8edd71d25883b42c8 Mon Sep 17 00:00:00 2001 From: Matvey Ryabchikov <35634442+ronanru@users.noreply.github.com> Date: Fri, 3 Nov 2023 15:26:44 +0300 Subject: [PATCH 09/30] fix drizzle config, switch to postgres.js --- cli/src/installers/dependencyVersionMap.ts | 3 +-- cli/src/installers/drizzle.ts | 10 +++++++--- .../{drizzle.config.ts => drizzle-config-mysql.ts} | 0 .../extras/config/drizzle-config-postgres.ts | 12 ++++++++++++ cli/template/extras/config/drizzle-config-sqlite.ts | 12 ++++++++++++ .../src/server/db/index-drizzle/with-postgres.ts | 11 +++-------- 6 files changed, 35 insertions(+), 13 deletions(-) rename cli/template/extras/config/{drizzle.config.ts => drizzle-config-mysql.ts} (100%) create mode 100644 cli/template/extras/config/drizzle-config-postgres.ts create mode 100644 cli/template/extras/config/drizzle-config-sqlite.ts diff --git a/cli/src/installers/dependencyVersionMap.ts b/cli/src/installers/dependencyVersionMap.ts index 5922279e5f..8b132f2ad6 100644 --- a/cli/src/installers/dependencyVersionMap.ts +++ b/cli/src/installers/dependencyVersionMap.ts @@ -18,8 +18,7 @@ export const dependencyVersionMap = { "dotenv-cli": "^7.3.0", mysql2: "^3.6.1", "@planetscale/database": "^1.11.0", - pg: "^8.11.3", - "@types/pg": "^8.10.7", + postgres: "^3.4.3", "@types/better-sqlite3": "^7.6.6", "better-sqlite3": "^9.0.0", diff --git a/cli/src/installers/drizzle.ts b/cli/src/installers/drizzle.ts index 80365b5041..13825371ae 100644 --- a/cli/src/installers/drizzle.ts +++ b/cli/src/installers/drizzle.ts @@ -15,7 +15,6 @@ export const drizzleInstaller: Installer = ({ }) => { const devPackages: AvailableDependencies[] = ["drizzle-kit", "dotenv-cli"]; if (databaseProvider === "planetscale") devPackages.push("mysql2"); - if (databaseProvider === "postgres") devPackages.push("@types/pg"); if (databaseProvider === "sqlite") devPackages.push("@types/better-sqlite3"); addPackageDependency({ @@ -31,7 +30,7 @@ export const drizzleInstaller: Installer = ({ { planetscale: "@planetscale/database", mysql: "mysql2", - postgres: "pg", + postgres: "postgres", sqlite: "better-sqlite3", } as const )[databaseProvider], @@ -50,7 +49,12 @@ export const drizzleInstaller: Installer = ({ const extrasDir = path.join(PKG_ROOT, "template/extras"); - const configFile = path.join(extrasDir, "config/drizzle.config.ts"); + const configFile = path.join( + extrasDir, + `config/drizzle-config-${ + databaseProvider === "planetscale" ? "mysql" : databaseProvider + }.ts` + ); const configDest = path.join(projectDir, "drizzle.config.ts"); const schemaSrc = path.join( diff --git a/cli/template/extras/config/drizzle.config.ts b/cli/template/extras/config/drizzle-config-mysql.ts similarity index 100% rename from cli/template/extras/config/drizzle.config.ts rename to cli/template/extras/config/drizzle-config-mysql.ts diff --git a/cli/template/extras/config/drizzle-config-postgres.ts b/cli/template/extras/config/drizzle-config-postgres.ts new file mode 100644 index 0000000000..98f449c655 --- /dev/null +++ b/cli/template/extras/config/drizzle-config-postgres.ts @@ -0,0 +1,12 @@ +import { type Config } from "drizzle-kit"; + +import { env } from "~/env.mjs"; + +export default { + schema: "./src/server/db/schema.ts", + driver: "pg", + dbCredentials: { + connectionString: env.DATABASE_URL, + }, + tablesFilter: ["project1_*"], +} satisfies Config; diff --git a/cli/template/extras/config/drizzle-config-sqlite.ts b/cli/template/extras/config/drizzle-config-sqlite.ts new file mode 100644 index 0000000000..33e2856246 --- /dev/null +++ b/cli/template/extras/config/drizzle-config-sqlite.ts @@ -0,0 +1,12 @@ +import { type Config } from "drizzle-kit"; + +import { env } from "~/env.mjs"; + +export default { + schema: "./src/server/db/schema.ts", + driver: "better-sqlite", + dbCredentials: { + url: env.DATABASE_URL, + }, + tablesFilter: ["project1_*"], +} satisfies Config; diff --git a/cli/template/extras/src/server/db/index-drizzle/with-postgres.ts b/cli/template/extras/src/server/db/index-drizzle/with-postgres.ts index ad1fe6c641..384bf9a4e0 100644 --- a/cli/template/extras/src/server/db/index-drizzle/with-postgres.ts +++ b/cli/template/extras/src/server/db/index-drizzle/with-postgres.ts @@ -1,12 +1,7 @@ -import { drizzle } from "drizzle-orm/node-postgres"; -import { Client } from "pg"; +import { drizzle } from "drizzle-orm/postgres-js"; +import postgres from "postgres"; import { env } from "~/env.mjs"; import * as schema from "./schema"; -export const db = drizzle( - new Client({ - connectionString: env.DATABASE_URL, - }), - { schema } -); +export const db = drizzle(postgres(env.DATABASE_URL), { schema }); From 80643cb57db55b1e102d6cae109f92b9fc17ee49 Mon Sep 17 00:00:00 2001 From: Matvey Ryabchikov <35634442+ronanru@users.noreply.github.com> Date: Fri, 3 Nov 2023 15:34:45 +0300 Subject: [PATCH 10/30] chore: add changeset --- .changeset/lemon-deers-grin.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/lemon-deers-grin.md diff --git a/.changeset/lemon-deers-grin.md b/.changeset/lemon-deers-grin.md new file mode 100644 index 0000000000..7712ca969c --- /dev/null +++ b/.changeset/lemon-deers-grin.md @@ -0,0 +1,5 @@ +--- +"create-t3-app": minor +--- + +add more db options From 1aae6faa5ac3a1bcafe520f996f32806424cafb2 Mon Sep 17 00:00:00 2001 From: Matvey Ryabchikov <35634442+ronanru@users.noreply.github.com> Date: Fri, 3 Nov 2023 16:58:43 +0300 Subject: [PATCH 11/30] fixes for sqlite --- cli/src/installers/envVars.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cli/src/installers/envVars.ts b/cli/src/installers/envVars.ts index 82004129ac..5e74d1a285 100644 --- a/cli/src/installers/envVars.ts +++ b/cli/src/installers/envVars.ts @@ -40,8 +40,15 @@ export const envVariablesInstaller: Installer = ({ "template/extras/src/env", envFile ); + const envFileText = fs.readFileSync(envSchemaSrc, "utf-8"); const envSchemaDest = path.join(projectDir, "src/env.mjs"); - fs.copySync(envSchemaSrc, envSchemaDest); + fs.writeFileSync( + envSchemaDest, + databaseProvider === "sqlite" + ? envFileText.replace("\n .url()", "") + : envFileText, + "utf-8" + ); } const envDest = path.join(projectDir, ".env"); @@ -85,7 +92,7 @@ DATABASE_URL="mysql://root:$DATABASE_PASSWORD@localhost:3306/${projectName}"`; content += `DATABASE_PASSWORD=${generateRandomString(12)} DATABASE_URL="postgresql://postgres:$DATABASE_PASSWORD@localhost:5432/${projectName}?schema=public"`; } else if (databaseProvider === "sqlite") { - content += `DATABASE_URL="file:./db.sqlite"`; + content += `DATABASE_URL="db.sqlite"`; } content += "\n"; } From d35ad8ed5e9fbbcf084693e322061a6ed496db28 Mon Sep 17 00:00:00 2001 From: Matvey Ryabchikov <35634442+ronanru@users.noreply.github.com> Date: Fri, 3 Nov 2023 19:12:02 +0300 Subject: [PATCH 12/30] bugfixes --- cli/src/installers/dbContainer.ts | 3 ++- cli/src/installers/envVars.ts | 14 +++++++++----- cli/src/installers/index.ts | 2 +- cli/template/extras/start-database/mysql.sh | 6 ++++-- cli/template/extras/start-database/postgres.sh | 6 ++++-- 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/cli/src/installers/dbContainer.ts b/cli/src/installers/dbContainer.ts index 7001e80240..8a72ea0a40 100644 --- a/cli/src/installers/dbContainer.ts +++ b/cli/src/installers/dbContainer.ts @@ -1,5 +1,5 @@ +import fs from "fs"; import path from "path"; -import fs from "fs-extra"; import { PKG_ROOT } from "~/consts.js"; import { type Installer } from "~/installers/index.js"; @@ -16,4 +16,5 @@ export const dbContainerInstaller: Installer = ({ const scriptText = fs.readFileSync(scriptSrc, "utf-8"); const scriptDest = path.join(projectDir, "start-database.sh"); fs.writeFileSync(scriptDest, scriptText.replaceAll("project1", projectName)); + fs.chmodSync(scriptDest, "755"); }; diff --git a/cli/src/installers/envVars.ts b/cli/src/installers/envVars.ts index 5e74d1a285..63a385b6b5 100644 --- a/cli/src/installers/envVars.ts +++ b/cli/src/installers/envVars.ts @@ -86,13 +86,17 @@ const getEnvContent = ( # Change the query params at the end of the URL to "?ssl={"rejectUnauthorized":true}" DATABASE_URL='mysql://YOUR_MYSQL_URL_HERE?ssl={"rejectUnauthorized":true}'`; } else if (databaseProvider === "mysql") { - content += `DATABASE_PASSWORD=${generateRandomString(12)} -DATABASE_URL="mysql://root:$DATABASE_PASSWORD@localhost:3306/${projectName}"`; + content += `DATABASE_URL="mysql://root:${generateRandomString( + 12 + )}@localhost:3306/${projectName}"`; } else if (databaseProvider === "postgres") { - content += `DATABASE_PASSWORD=${generateRandomString(12)} -DATABASE_URL="postgresql://postgres:$DATABASE_PASSWORD@localhost:5432/${projectName}?schema=public"`; + content += `DATABASE_URL="postgresql://postgres:${generateRandomString( + 12 + )}@localhost:5432/${projectName}"`; } else if (databaseProvider === "sqlite") { - content += `DATABASE_URL="db.sqlite"`; + content += usingPrisma + ? 'DATABASE_URL="file:./db.sqlite"' + : 'DATABASE_URL="db.sqlite"'; } content += "\n"; } diff --git a/cli/src/installers/index.ts b/cli/src/installers/index.ts index b99676e0a8..83a4f0d880 100644 --- a/cli/src/installers/index.ts +++ b/cli/src/installers/index.ts @@ -74,7 +74,7 @@ export const buildPkgInstallerMap = ( }, dbContainer: { inUse: ["mysql", "postgres"].includes(databaseProvider), - installer: () => dbContainerInstaller, + installer: dbContainerInstaller, }, envVariables: { inUse: true, diff --git a/cli/template/extras/start-database/mysql.sh b/cli/template/extras/start-database/mysql.sh index 3407bc503e..fd22d78a58 100755 --- a/cli/template/extras/start-database/mysql.sh +++ b/cli/template/extras/start-database/mysql.sh @@ -11,12 +11,14 @@ fi set -a source .env +DB_PASSWORD=$(echo $DATABASE_URL | awk -F':' '{print $3}' | awk -F'@' '{print $1}') + if [ "$(docker ps -q -f name=$DB_CONTAINER_NAME)" ]; then docker start $DB_CONTAINER_NAME echo "Database container started" else - echo "Did not find database container. Creating a new container..." - docker run --name $DB_CONTAINER_NAME -e MYSQL_ROOT_PASSWORD=$DATABASE_PASSWORD -e MYSQL_DATABASE=project1 -d -p 3306:3306 docker.io/mysql + echo "Did not find a database container. Creating a new container..." + docker run --name $DB_CONTAINER_NAME -e MYSQL_ROOT_PASSWORD=$DB_PASSWORD -e MYSQL_DATABASE=project1 -d -p 3306:3306 docker.io/mysql echo "Database container created and started" fi diff --git a/cli/template/extras/start-database/postgres.sh b/cli/template/extras/start-database/postgres.sh index 73cf7f0208..39283269ee 100755 --- a/cli/template/extras/start-database/postgres.sh +++ b/cli/template/extras/start-database/postgres.sh @@ -11,12 +11,14 @@ fi set -a source .env +DB_PASSWORD=$(echo $DATABASE_URL | awk -F':' '{print $3}' | awk -F'@' '{print $1}') + if [ "$(docker ps -q -f name=$DB_CONTAINER_NAME)" ]; then docker start $DB_CONTAINER_NAME echo "Database container started" else - echo "Did not find database container. Creating a new container..." - docker run --name $DB_CONTAINER_NAME -e POSTGRES_PASSWORD=$DATABASE_PASSWORD -e POSTGRES_DB=project1 -d -p 5432:5432 docker.io/postgres + echo "Did not find a database container. Creating a new container..." + docker run --name $DB_CONTAINER_NAME -e POSTGRES_PASSWORD=$DB_PASSWORD -e POSTGRES_DB=project1 -d -p 5432:5432 docker.io/postgres echo "Database container created and started" fi From 83a5639d6b2654587fcd1170edeefd44c1d3af8e Mon Sep 17 00:00:00 2001 From: Matvey Ryabchikov <35634442+ronanru@users.noreply.github.com> Date: Fri, 3 Nov 2023 22:20:21 +0300 Subject: [PATCH 13/30] fix ci --- .github/workflows/e2e.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index fe2f1767f7..86eca04c05 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -29,7 +29,7 @@ jobs: prisma: ["true", "false"] appRouter: ["true", "false"] drizzle: ["true", "false"] - dbType: ["planetscale", "sqlite", "mysql", "postgresql"] + dbType: ["planetscale", "sqlite", "mysql", "postgres"] name: "Build and Start T3 App ${{ matrix.trpc }}-${{ matrix.tailwind }}-${{ matrix.nextAuth }}-${{ matrix.prisma }}-${{ matrix.drizzle}}-${{ matrix.appRouter }}-${{ matrix.dbType }}" steps: From e2e475058d19fc0b9fedb33de3c1d828087104c5 Mon Sep 17 00:00:00 2001 From: Matvey Ryabchikov <35634442+ronanru@users.noreply.github.com> Date: Sun, 19 Nov 2023 15:33:10 +0300 Subject: [PATCH 14/30] add @prisma/adapter-planetscale --- cli/package.json | 1 + cli/src/installers/dependencyVersionMap.ts | 1 + cli/src/installers/prisma.ts | 17 ++++- .../prisma/schema/base-planetscale.prisma | 21 ++++++ .../schema/with-auth-planetscale.prisma | 74 +++++++++++++++++++ .../src/server/db/db-prisma-planetscale.ts | 22 ++++++ pnpm-lock.yaml | 22 ++++++ 7 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 cli/template/extras/prisma/schema/base-planetscale.prisma create mode 100644 cli/template/extras/prisma/schema/with-auth-planetscale.prisma create mode 100644 cli/template/extras/src/server/db/db-prisma-planetscale.ts diff --git a/cli/package.json b/cli/package.json index fedc8b1e2e..53aee4da40 100644 --- a/cli/package.json +++ b/cli/package.json @@ -65,6 +65,7 @@ "@auth/drizzle-adapter": "^0.3.5", "@next-auth/prisma-adapter": "^1.0.7", "@planetscale/database": "^1.11.0", + "@prisma/adapter-planetscale": "^5.6.0", "@prisma/client": "^5.1.1", "@t3-oss/env-nextjs": "^0.7.0", "@tanstack/react-query": "^4.32.6", diff --git a/cli/src/installers/dependencyVersionMap.ts b/cli/src/installers/dependencyVersionMap.ts index 8b132f2ad6..be12553431 100644 --- a/cli/src/installers/dependencyVersionMap.ts +++ b/cli/src/installers/dependencyVersionMap.ts @@ -11,6 +11,7 @@ export const dependencyVersionMap = { // Prisma prisma: "^5.1.1", "@prisma/client": "^5.1.1", + "@prisma/adapter-planetscale": "^5.6.0", // Drizzle "drizzle-orm": "^0.28.5", diff --git a/cli/src/installers/prisma.ts b/cli/src/installers/prisma.ts index 73ff8eae77..db2245e857 100644 --- a/cli/src/installers/prisma.ts +++ b/cli/src/installers/prisma.ts @@ -21,13 +21,21 @@ export const prismaInstaller: Installer = ({ dependencies: ["@prisma/client"], devMode: false, }); + if (databaseProvider === "planetscale") + addPackageDependency({ + projectDir, + dependencies: ["@prisma/adapter-planetscale", "@planetscale/database"], + devMode: false, + }); const extrasDir = path.join(PKG_ROOT, "template/extras"); const schemaSrc = path.join( extrasDir, "prisma/schema", - packages?.nextAuth.inUse ? "with-auth.prisma" : "base.prisma" + `${packages?.nextAuth.inUse ? "with-auth" : "base"}${ + databaseProvider === "planetscale" ? "-planetscale" : "" + }.prisma` ); let schemaText = fs.readFileSync(schemaSrc, "utf-8"); if (databaseProvider !== "sqlite") { @@ -49,7 +57,12 @@ export const prismaInstaller: Installer = ({ fs.mkdirSync(path.dirname(schemaDest), { recursive: true }); fs.writeFileSync(schemaDest, schemaText); - const clientSrc = path.join(extrasDir, "src/server/db/db-prisma.ts"); + const clientSrc = path.join( + extrasDir, + databaseProvider === "planetscale" + ? "src/server/db/db-prisma-planetscale.ts" + : "src/server/db/db-prisma.ts" + ); const clientDest = path.join(projectDir, "src/server/db.ts"); // add postinstall and push script to package.json diff --git a/cli/template/extras/prisma/schema/base-planetscale.prisma b/cli/template/extras/prisma/schema/base-planetscale.prisma new file mode 100644 index 0000000000..f44ad8451e --- /dev/null +++ b/cli/template/extras/prisma/schema/base-planetscale.prisma @@ -0,0 +1,21 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-js" + previewFeatures = ["driverAdapters"] +} + +datasource db { + provider = "sqlite" + url = env("DATABASE_URL") +} + +model Post { + id Int @id @default(autoincrement()) + name String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([name]) +} diff --git a/cli/template/extras/prisma/schema/with-auth-planetscale.prisma b/cli/template/extras/prisma/schema/with-auth-planetscale.prisma new file mode 100644 index 0000000000..54d69bb3e6 --- /dev/null +++ b/cli/template/extras/prisma/schema/with-auth-planetscale.prisma @@ -0,0 +1,74 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-js" + previewFeatures = ["driverAdapters"] +} + +datasource db { + provider = "sqlite" + // NOTE: When using mysql or sqlserver, uncomment the @db.Text annotations in model Account below + // Further reading: + // https://next-auth.js.org/adapters/prisma#create-the-prisma-schema + // https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#string + url = env("DATABASE_URL") +} + +model Post { + id Int @id @default(autoincrement()) + name String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + createdBy User @relation(fields: [createdById], references: [id]) + createdById String + + @@index([name]) +} + +// Necessary for Next auth +model Account { + id String @id @default(cuid()) + userId String + type String + provider String + providerAccountId String + refresh_token String? // @db.Text + access_token String? // @db.Text + expires_at Int? + token_type String? + scope String? + id_token String? // @db.Text + session_state String? + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([provider, providerAccountId]) +} + +model Session { + id String @id @default(cuid()) + sessionToken String @unique + userId String + expires DateTime + user User @relation(fields: [userId], references: [id], onDelete: Cascade) +} + +model User { + id String @id @default(cuid()) + name String? + email String? @unique + emailVerified DateTime? + image String? + accounts Account[] + sessions Session[] + posts Post[] +} + +model VerificationToken { + identifier String + token String @unique + expires DateTime + + @@unique([identifier, token]) +} diff --git a/cli/template/extras/src/server/db/db-prisma-planetscale.ts b/cli/template/extras/src/server/db/db-prisma-planetscale.ts new file mode 100644 index 0000000000..bc1aa2da5d --- /dev/null +++ b/cli/template/extras/src/server/db/db-prisma-planetscale.ts @@ -0,0 +1,22 @@ +// Import required dependencies +import { Client } from "@planetscale/database"; +import { PrismaPlanetScale } from "@prisma/adapter-planetscale"; +import { PrismaClient } from "@prisma/client/edge"; + +import { env } from "~/env.mjs"; + +const globalForPrisma = globalThis as unknown as { + prisma: PrismaClient | undefined; +}; + +const client = new Client({ url: env.DATABASE_URL }); + +export const db = + globalForPrisma.prisma ?? + new PrismaClient({ + log: + env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"], + adapter: new PrismaPlanetScale(client), + }); + +if (env.NODE_ENV !== "production") globalForPrisma.prisma = db; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0d9d398a32..21f8afb6b5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -108,6 +108,9 @@ importers: '@planetscale/database': specifier: ^1.11.0 version: 1.11.0 + '@prisma/adapter-planetscale': + specifier: ^5.6.0 + version: 5.6.0(@planetscale/database@1.11.0) '@prisma/client': specifier: ^5.1.1 version: 5.1.1(prisma@4.14.0) @@ -2337,6 +2340,17 @@ packages: engines: {node: '>=16'} dev: true + /@prisma/adapter-planetscale@5.6.0(@planetscale/database@1.11.0): + resolution: {integrity: sha512-VhBEXKcRDbkozzo3W0mXd4lIHxvpR6YJkZRCHhPTyAx7gba7kpu/lJ83y/P1+YXRiyN2Qmf91pDVYr1qcpfCwQ==} + peerDependencies: + '@planetscale/database': ^1.11.0 + dependencies: + '@planetscale/database': 1.11.0 + '@prisma/driver-adapter-utils': 5.6.0 + transitivePeerDependencies: + - supports-color + dev: true + /@prisma/client@5.1.1(prisma@4.14.0): resolution: {integrity: sha512-fxcCeK5pMQGcgCqCrWsi+I2rpIbk0rAhdrN+ke7f34tIrgPwA68ensrpin+9+fZvuV2OtzHmuipwduSY6HswdA==} engines: {node: '>=16.13'} @@ -2351,6 +2365,14 @@ packages: prisma: 4.14.0 dev: true + /@prisma/driver-adapter-utils@5.6.0: + resolution: {integrity: sha512-/TSrfCGLAQghNf+bwg5/e8iKAgecCYU/gMN0IyNra3183/VTQJneLFgbacuSK9bBXiIRUmpbuUIrJ6dhENzfjA==} + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + /@prisma/engines-version@5.1.1-1.6a3747c37ff169c90047725a05a6ef02e32ac97e: resolution: {integrity: sha512-owZqbY/wucbr65bXJ/ljrHPgQU5xXTSkmcE/JcbqE1kusuAXV/TLN3/exmz21SZ5rJ7WDkyk70J2G/n68iogbQ==} dev: true From 962adeb170eb5cb55d4fd6c1541a004f1edb46f0 Mon Sep 17 00:00:00 2001 From: Matvey Ryabchikov <35634442+ronanru@users.noreply.github.com> Date: Sun, 19 Nov 2023 15:58:50 +0300 Subject: [PATCH 15/30] fix prisma + planetscale --- cli/template/extras/src/server/db/db-prisma-planetscale.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/template/extras/src/server/db/db-prisma-planetscale.ts b/cli/template/extras/src/server/db/db-prisma-planetscale.ts index bc1aa2da5d..7984a43ab9 100644 --- a/cli/template/extras/src/server/db/db-prisma-planetscale.ts +++ b/cli/template/extras/src/server/db/db-prisma-planetscale.ts @@ -1,7 +1,7 @@ // Import required dependencies import { Client } from "@planetscale/database"; import { PrismaPlanetScale } from "@prisma/adapter-planetscale"; -import { PrismaClient } from "@prisma/client/edge"; +import { PrismaClient } from "@prisma/client"; import { env } from "~/env.mjs"; From 5a318d9f1050740cc06d8c1e6b00e08fc9032d5e Mon Sep 17 00:00:00 2001 From: Matvey Ryabchikov <35634442+ronanru@users.noreply.github.com> Date: Sun, 19 Nov 2023 16:15:21 +0300 Subject: [PATCH 16/30] fix eslint error --- cli/src/installers/envVars.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/cli/src/installers/envVars.ts b/cli/src/installers/envVars.ts index e192d3168c..c51bb955ad 100644 --- a/cli/src/installers/envVars.ts +++ b/cli/src/installers/envVars.ts @@ -25,14 +25,10 @@ export const envVariablesInstaller: Installer = ({ projectName ); - const envFile = - usingAuth && usingDb - ? "with-auth-db.js" - : usingAuth - ? "with-auth.js" - : usingDb - ? "with-db.js" - : ""; + let envFile = ""; + if (usingAuth && usingDb) envFile = "with-auth-db.js"; + else if (usingAuth) envFile = "with-auth.js"; + else if (usingDb) envFile = "with-db.js"; if (envFile !== "") { const envSchemaSrc = path.join( From 5f927921d99f0d3255f325680124739454b77bb2 Mon Sep 17 00:00:00 2001 From: Matvey Ryabchikov <35634442+ronanru@users.noreply.github.com> Date: Sun, 19 Nov 2023 17:11:37 +0300 Subject: [PATCH 17/30] fix db script --- cli/template/extras/start-database/mysql.sh | 2 +- cli/template/extras/start-database/postgres.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/template/extras/start-database/mysql.sh b/cli/template/extras/start-database/mysql.sh index fd22d78a58..48246c8570 100755 --- a/cli/template/extras/start-database/mysql.sh +++ b/cli/template/extras/start-database/mysql.sh @@ -17,7 +17,7 @@ if [ "$(docker ps -q -f name=$DB_CONTAINER_NAME)" ]; then docker start $DB_CONTAINER_NAME echo "Database container started" else - echo "Did not find a database container. Creating a new container..." + echo "Creating a new container..." docker run --name $DB_CONTAINER_NAME -e MYSQL_ROOT_PASSWORD=$DB_PASSWORD -e MYSQL_DATABASE=project1 -d -p 3306:3306 docker.io/mysql echo "Database container created and started" fi diff --git a/cli/template/extras/start-database/postgres.sh b/cli/template/extras/start-database/postgres.sh index 39283269ee..712e575e1f 100755 --- a/cli/template/extras/start-database/postgres.sh +++ b/cli/template/extras/start-database/postgres.sh @@ -17,7 +17,7 @@ if [ "$(docker ps -q -f name=$DB_CONTAINER_NAME)" ]; then docker start $DB_CONTAINER_NAME echo "Database container started" else - echo "Did not find a database container. Creating a new container..." + echo "Creating a new container..." docker run --name $DB_CONTAINER_NAME -e POSTGRES_PASSWORD=$DB_PASSWORD -e POSTGRES_DB=project1 -d -p 5432:5432 docker.io/postgres echo "Database container created and started" fi From 423d9fee01029a7b5a29fc26f005a527613d2572 Mon Sep 17 00:00:00 2001 From: Matvey Ryabchikov <35634442+ronanru@users.noreply.github.com> Date: Mon, 20 Nov 2023 03:04:48 +0300 Subject: [PATCH 18/30] Revert "fix prisma + planetscale" This reverts commit 962adeb170eb5cb55d4fd6c1541a004f1edb46f0. --- cli/template/extras/src/server/db/db-prisma-planetscale.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/template/extras/src/server/db/db-prisma-planetscale.ts b/cli/template/extras/src/server/db/db-prisma-planetscale.ts index e3c5a8b9eb..b5e6ae24cb 100644 --- a/cli/template/extras/src/server/db/db-prisma-planetscale.ts +++ b/cli/template/extras/src/server/db/db-prisma-planetscale.ts @@ -1,7 +1,7 @@ // Import required dependencies import { Client } from "@planetscale/database"; import { PrismaPlanetScale } from "@prisma/adapter-planetscale"; -import { PrismaClient } from "@prisma/client"; +import { PrismaClient } from "@prisma/client/edge"; import { env } from "~/env.js"; From 6f668e51ead0ba3ca72481764c64d0cec8e84da7 Mon Sep 17 00:00:00 2001 From: Matvey Ryabchikov <35634442+ronanru@users.noreply.github.com> Date: Mon, 20 Nov 2023 15:34:21 +0300 Subject: [PATCH 19/30] remove unnecessary comment --- cli/template/extras/src/server/db/db-prisma-planetscale.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/cli/template/extras/src/server/db/db-prisma-planetscale.ts b/cli/template/extras/src/server/db/db-prisma-planetscale.ts index b5e6ae24cb..8663f639f6 100644 --- a/cli/template/extras/src/server/db/db-prisma-planetscale.ts +++ b/cli/template/extras/src/server/db/db-prisma-planetscale.ts @@ -1,4 +1,3 @@ -// Import required dependencies import { Client } from "@planetscale/database"; import { PrismaPlanetScale } from "@prisma/adapter-planetscale"; import { PrismaClient } from "@prisma/client/edge"; From 84c4d4d3a5428af6d82ee7b0680649df0ea74339 Mon Sep 17 00:00:00 2001 From: Matvey Ryabchikov <35634442+ronanru@users.noreply.github.com> Date: Wed, 22 Nov 2023 14:16:47 +0300 Subject: [PATCH 20/30] move password generation to the start script --- cli/src/installers/envVars.ts | 9 ++---- cli/src/utils/randomString.ts | 15 ---------- cli/template/extras/start-database/mysql.sh | 28 ++++++++++++++----- .../extras/start-database/postgres.sh | 26 ++++++++++++----- 4 files changed, 42 insertions(+), 36 deletions(-) delete mode 100644 cli/src/utils/randomString.ts diff --git a/cli/src/installers/envVars.ts b/cli/src/installers/envVars.ts index c51bb955ad..0853563d01 100644 --- a/cli/src/installers/envVars.ts +++ b/cli/src/installers/envVars.ts @@ -3,7 +3,6 @@ import fs from "fs-extra"; import { PKG_ROOT } from "~/consts.js"; import { type DatabaseProvider, type Installer } from "~/installers/index.js"; -import { generateRandomString } from "~/utils/randomString.js"; export const envVariablesInstaller: Installer = ({ projectDir, @@ -82,13 +81,9 @@ const getEnvContent = ( # Change the query params at the end of the URL to "?ssl={"rejectUnauthorized":true}" DATABASE_URL='mysql://YOUR_MYSQL_URL_HERE?ssl={"rejectUnauthorized":true}'`; } else if (databaseProvider === "mysql") { - content += `DATABASE_URL="mysql://root:${generateRandomString( - 12 - )}@localhost:3306/${projectName}"`; + content += `DATABASE_URL="mysql://root:password@localhost:3306/${projectName}"`; } else if (databaseProvider === "postgres") { - content += `DATABASE_URL="postgresql://postgres:${generateRandomString( - 12 - )}@localhost:5432/${projectName}"`; + content += `DATABASE_URL="postgresql://postgres:password@localhost:5432/${projectName}"`; } else if (databaseProvider === "sqlite") { content += usingPrisma ? 'DATABASE_URL="file:./db.sqlite"' diff --git a/cli/src/utils/randomString.ts b/cli/src/utils/randomString.ts deleted file mode 100644 index 925d6f7a96..0000000000 --- a/cli/src/utils/randomString.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { webcrypto as crypto } from "node:crypto"; - -export const generateRandomString = (length: number) => { - const randomUint32Values = new Uint32Array(length); - crypto.getRandomValues(randomUint32Values); - const u32Max = 0xffffffff; - const alphabet = - "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - let result = ""; - for (const randomUnit32 of randomUint32Values) { - const rand = randomUnit32 / (u32Max + 1); - result += alphabet[Math.floor(alphabet.length * rand)]; - } - return result; -}; diff --git a/cli/template/extras/start-database/mysql.sh b/cli/template/extras/start-database/mysql.sh index 48246c8570..6d28e53737 100755 --- a/cli/template/extras/start-database/mysql.sh +++ b/cli/template/extras/start-database/mysql.sh @@ -1,4 +1,5 @@ #!/bin/bash +# Use this script to start a docker container for a local development database DB_CONTAINER_NAME="project1-mysql" @@ -7,18 +8,31 @@ if ! [ -x "$(command -v docker)" ]; then exit 1 fi +if [ "$(docker ps -q -f name=$DB_CONTAINER_NAME)" ]; then + docker start $DB_CONTAINER_NAME + echo "Database container started" + exit 0 +fi + # import env variables from .env set -a source .env DB_PASSWORD=$(echo $DATABASE_URL | awk -F':' '{print $3}' | awk -F'@' '{print $1}') -if [ "$(docker ps -q -f name=$DB_CONTAINER_NAME)" ]; then - docker start $DB_CONTAINER_NAME - echo "Database container started" -else - echo "Creating a new container..." - docker run --name $DB_CONTAINER_NAME -e MYSQL_ROOT_PASSWORD=$DB_PASSWORD -e MYSQL_DATABASE=project1 -d -p 3306:3306 docker.io/mysql - echo "Database container created and started" +if [ "$DB_PASSWORD" == "password" ]; then + echo "You are using the default database password" + read -p "Should we generate a random password for you? [y/N]: " -r REPLY + if ! [[ $REPLY =~ ^[Yy]$ ]]; then + echo "Please set a password in the .env file and try again" + exit 1 + fi + DB_PASSWORD=$(openssl rand -base64 12) + sed -i -e "s/:password@/:$DB_PASSWORD@/" .env fi +docker run --name $DB_CONTAINER_NAME -e MYSQL_ROOT_PASSWORD=$DB_PASSWORD -e MYSQL_DATABASE=project1 -d -p 3306:3306 docker.io/mysql + +echo "Database container was succesfuly created" + + diff --git a/cli/template/extras/start-database/postgres.sh b/cli/template/extras/start-database/postgres.sh index 712e575e1f..9bb21802d9 100755 --- a/cli/template/extras/start-database/postgres.sh +++ b/cli/template/extras/start-database/postgres.sh @@ -1,4 +1,5 @@ #!/bin/bash +# Use this script to start a docker container for a local development database DB_CONTAINER_NAME="project1-postgres" @@ -7,18 +8,29 @@ if ! [ -x "$(command -v docker)" ]; then exit 1 fi +if [ "$(docker ps -q -f name=$DB_CONTAINER_NAME)" ]; then + docker start $DB_CONTAINER_NAME + echo "Database container started" + exit 0 +fi + # import env variables from .env set -a source .env DB_PASSWORD=$(echo $DATABASE_URL | awk -F':' '{print $3}' | awk -F'@' '{print $1}') -if [ "$(docker ps -q -f name=$DB_CONTAINER_NAME)" ]; then - docker start $DB_CONTAINER_NAME - echo "Database container started" -else - echo "Creating a new container..." - docker run --name $DB_CONTAINER_NAME -e POSTGRES_PASSWORD=$DB_PASSWORD -e POSTGRES_DB=project1 -d -p 5432:5432 docker.io/postgres - echo "Database container created and started" +if [ "$DB_PASSWORD" = "password" ]; then + echo "You are using the default database password" + read -p "Should we generate a random password for you? [y/N]: " -r REPLY + if ! [[ $REPLY =~ ^[Yy]$ ]]; then + echo "Please set a password in the .env file and try again" + exit 1 + fi + DB_PASSWORD=$(openssl rand -base64 12) + sed -i -e "s/:password@/:$DB_PASSWORD@/" .env fi +docker run --name $DB_CONTAINER_NAME -e POSTGRES_PASSWORD=$DB_PASSWORD -e POSTGRES_DB=project1 -d -p 5432:5432 docker.io/postgres + +echo "Database container was succesfuly created" From 166f3b7e0c55bd81da9d0d109b6bf93d2fe4ce20 Mon Sep 17 00:00:00 2001 From: Matvey Ryabchikov <35634442+ronanru@users.noreply.github.com> Date: Wed, 22 Nov 2023 15:05:14 +0300 Subject: [PATCH 21/30] add foreign keys to drizzle schemas --- cli/src/installers/drizzle.ts | 20 ++-- .../db/schema-drizzle/base-planetscale.ts | 34 ++++++ .../db/schema-drizzle/with-auth-mysql.ts | 12 +- .../schema-drizzle/with-auth-planetscale.ts | 110 ++++++++++++++++++ .../db/schema-drizzle/with-auth-postgres.ts | 12 +- .../db/schema-drizzle/with-auth-sqlite.ts | 12 +- 6 files changed, 180 insertions(+), 20 deletions(-) create mode 100644 cli/template/extras/src/server/db/schema-drizzle/base-planetscale.ts create mode 100644 cli/template/extras/src/server/db/schema-drizzle/with-auth-planetscale.ts diff --git a/cli/src/installers/drizzle.ts b/cli/src/installers/drizzle.ts index 13825371ae..1492c1966f 100644 --- a/cli/src/installers/drizzle.ts +++ b/cli/src/installers/drizzle.ts @@ -38,15 +38,6 @@ export const drizzleInstaller: Installer = ({ devMode: false, }); - const dbType = ( - { - postgres: "postgres", - sqlite: "sqlite", - mysql: "mysql", - planetscale: "mysql", - } as const - )[databaseProvider]; - const extrasDir = path.join(PKG_ROOT, "template/extras"); const configFile = path.join( @@ -60,7 +51,9 @@ export const drizzleInstaller: Installer = ({ const schemaSrc = path.join( extrasDir, "src/server/db/schema-drizzle", - packages?.nextAuth.inUse ? `with-auth-${dbType}.ts` : `base-${dbType}.ts` + packages?.nextAuth.inUse + ? `with-auth-${databaseProvider}.ts` + : `base-${databaseProvider}.ts` ); const schemaDest = path.join(projectDir, "src/server/db/schema.ts"); @@ -88,7 +81,12 @@ export const drizzleInstaller: Installer = ({ packageJsonContent.scripts = { ...packageJsonContent.scripts, "db:push": `dotenv drizzle-kit push:${ - dbType === "postgres" ? "pg" : dbType + { + postgres: "pg", + sqlite: "sqlite", + mysql: "mysql", + planetscale: "mysql", + }[databaseProvider] }`, "db:studio": "dotenv drizzle-kit studio", }; diff --git a/cli/template/extras/src/server/db/schema-drizzle/base-planetscale.ts b/cli/template/extras/src/server/db/schema-drizzle/base-planetscale.ts new file mode 100644 index 0000000000..bda3089138 --- /dev/null +++ b/cli/template/extras/src/server/db/schema-drizzle/base-planetscale.ts @@ -0,0 +1,34 @@ +// Example model schema from the Drizzle docs +// https://orm.drizzle.team/docs/sql-schema-declaration + +import { sql } from "drizzle-orm"; +import { + bigint, + index, + mysqlTableCreator, + timestamp, + varchar, +} from "drizzle-orm/mysql-core"; + +/** + * This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same + * database instance for multiple projects. + * + * @see https://orm.drizzle.team/docs/goodies#multi-project-schema + */ +export const createTable = mysqlTableCreator((name) => `project1_${name}`); + +export const posts = createTable( + "post", + { + id: bigint("id", { mode: "number" }).primaryKey().autoincrement(), + name: varchar("name", { length: 256 }), + createdAt: timestamp("created_at") + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp("updatedAt").onUpdateNow(), + }, + (example) => ({ + nameIndex: index("name_idx").on(example.name), + }) +); diff --git a/cli/template/extras/src/server/db/schema-drizzle/with-auth-mysql.ts b/cli/template/extras/src/server/db/schema-drizzle/with-auth-mysql.ts index e53a950fbc..1b6cfce470 100644 --- a/cli/template/extras/src/server/db/schema-drizzle/with-auth-mysql.ts +++ b/cli/template/extras/src/server/db/schema-drizzle/with-auth-mysql.ts @@ -24,7 +24,9 @@ export const posts = createTable( { id: bigint("id", { mode: "number" }).primaryKey().autoincrement(), name: varchar("name", { length: 256 }), - createdById: varchar("createdById", { length: 255 }).notNull(), + createdById: varchar("createdById", { length: 255 }) + .notNull() + .references(() => users.id), createdAt: timestamp("created_at") .default(sql`CURRENT_TIMESTAMP`) .notNull(), @@ -55,7 +57,9 @@ export const usersRelations = relations(users, ({ many }) => ({ export const accounts = createTable( "account", { - userId: varchar("userId", { length: 255 }).notNull(), + userId: varchar("userId", { length: 255 }) + .notNull() + .references(() => users.id), type: varchar("type", { length: 255 }) .$type() .notNull(), @@ -85,7 +89,9 @@ export const sessions = createTable( sessionToken: varchar("sessionToken", { length: 255 }) .notNull() .primaryKey(), - userId: varchar("userId", { length: 255 }).notNull(), + userId: varchar("userId", { length: 255 }) + .notNull() + .references(() => users.id), expires: timestamp("expires", { mode: "date" }).notNull(), }, (session) => ({ diff --git a/cli/template/extras/src/server/db/schema-drizzle/with-auth-planetscale.ts b/cli/template/extras/src/server/db/schema-drizzle/with-auth-planetscale.ts new file mode 100644 index 0000000000..e53a950fbc --- /dev/null +++ b/cli/template/extras/src/server/db/schema-drizzle/with-auth-planetscale.ts @@ -0,0 +1,110 @@ +import { relations, sql } from "drizzle-orm"; +import { + bigint, + index, + int, + mysqlTableCreator, + primaryKey, + text, + timestamp, + varchar, +} from "drizzle-orm/mysql-core"; +import { type AdapterAccount } from "next-auth/adapters"; + +/** + * This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same + * database instance for multiple projects. + * + * @see https://orm.drizzle.team/docs/goodies#multi-project-schema + */ +export const createTable = mysqlTableCreator((name) => `project1_${name}`); + +export const posts = createTable( + "post", + { + id: bigint("id", { mode: "number" }).primaryKey().autoincrement(), + name: varchar("name", { length: 256 }), + createdById: varchar("createdById", { length: 255 }).notNull(), + createdAt: timestamp("created_at") + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp("updatedAt").onUpdateNow(), + }, + (example) => ({ + createdByIdIdx: index("createdById_idx").on(example.createdById), + nameIndex: index("name_idx").on(example.name), + }) +); + +export const users = createTable("user", { + id: varchar("id", { length: 255 }).notNull().primaryKey(), + name: varchar("name", { length: 255 }), + email: varchar("email", { length: 255 }).notNull(), + emailVerified: timestamp("emailVerified", { + mode: "date", + fsp: 3, + }).default(sql`CURRENT_TIMESTAMP(3)`), + image: varchar("image", { length: 255 }), +}); + +export const usersRelations = relations(users, ({ many }) => ({ + accounts: many(accounts), + sessions: many(sessions), +})); + +export const accounts = createTable( + "account", + { + userId: varchar("userId", { length: 255 }).notNull(), + type: varchar("type", { length: 255 }) + .$type() + .notNull(), + provider: varchar("provider", { length: 255 }).notNull(), + providerAccountId: varchar("providerAccountId", { length: 255 }).notNull(), + refresh_token: text("refresh_token"), + access_token: text("access_token"), + expires_at: int("expires_at"), + token_type: varchar("token_type", { length: 255 }), + scope: varchar("scope", { length: 255 }), + id_token: text("id_token"), + session_state: varchar("session_state", { length: 255 }), + }, + (account) => ({ + compoundKey: primaryKey(account.provider, account.providerAccountId), + userIdIdx: index("userId_idx").on(account.userId), + }) +); + +export const accountsRelations = relations(accounts, ({ one }) => ({ + user: one(users, { fields: [accounts.userId], references: [users.id] }), +})); + +export const sessions = createTable( + "session", + { + sessionToken: varchar("sessionToken", { length: 255 }) + .notNull() + .primaryKey(), + userId: varchar("userId", { length: 255 }).notNull(), + expires: timestamp("expires", { mode: "date" }).notNull(), + }, + (session) => ({ + userIdIdx: index("userId_idx").on(session.userId), + }) +); + +export const sessionsRelations = relations(sessions, ({ one }) => ({ + user: one(users, { fields: [sessions.userId], references: [users.id] }), +})); + +export const verificationTokens = createTable( + "verificationToken", + { + identifier: varchar("identifier", { length: 255 }).notNull(), + token: varchar("token", { length: 255 }).notNull(), + expires: timestamp("expires", { mode: "date" }).notNull(), + }, + (vt) => ({ + compoundKey: primaryKey(vt.identifier, vt.token), + }) +); diff --git a/cli/template/extras/src/server/db/schema-drizzle/with-auth-postgres.ts b/cli/template/extras/src/server/db/schema-drizzle/with-auth-postgres.ts index 9a482e2257..1ede1bebac 100644 --- a/cli/template/extras/src/server/db/schema-drizzle/with-auth-postgres.ts +++ b/cli/template/extras/src/server/db/schema-drizzle/with-auth-postgres.ts @@ -24,7 +24,9 @@ export const posts = createTable( { id: serial("id").primaryKey(), name: varchar("name", { length: 256 }), - createdById: varchar("createdById", { length: 255 }).notNull(), + createdById: varchar("createdById", { length: 255 }) + .notNull() + .references(() => users.id), createdAt: timestamp("created_at") .default(sql`CURRENT_TIMESTAMP`) .notNull(), @@ -53,7 +55,9 @@ export const usersRelations = relations(users, ({ many }) => ({ export const accounts = createTable( "account", { - userId: varchar("userId", { length: 255 }).notNull(), + userId: varchar("userId", { length: 255 }) + .notNull() + .references(() => users.id), type: varchar("type", { length: 255 }) .$type() .notNull(), @@ -83,7 +87,9 @@ export const sessions = createTable( sessionToken: varchar("sessionToken", { length: 255 }) .notNull() .primaryKey(), - userId: varchar("userId", { length: 255 }).notNull(), + userId: varchar("userId", { length: 255 }) + .notNull() + .references(() => users.id), expires: timestamp("expires", { mode: "date" }).notNull(), }, (session) => ({ diff --git a/cli/template/extras/src/server/db/schema-drizzle/with-auth-sqlite.ts b/cli/template/extras/src/server/db/schema-drizzle/with-auth-sqlite.ts index 346195041f..8cc1ec44fe 100644 --- a/cli/template/extras/src/server/db/schema-drizzle/with-auth-sqlite.ts +++ b/cli/template/extras/src/server/db/schema-drizzle/with-auth-sqlite.ts @@ -21,7 +21,9 @@ export const posts = createTable( { id: int("id", { mode: "number" }).primaryKey({ autoIncrement: true }), name: text("name", { length: 256 }), - createdById: text("createdById", { length: 255 }).notNull(), + createdById: text("createdById", { length: 255 }) + .notNull() + .references(() => users.id), createdAt: int("created_at", { mode: "timestamp" }) .default(sql`CURRENT_TIMESTAMP`) .notNull(), @@ -50,7 +52,9 @@ export const usersRelations = relations(users, ({ many }) => ({ export const accounts = createTable( "account", { - userId: text("userId", { length: 255 }).notNull(), + userId: text("userId", { length: 255 }) + .notNull() + .references(() => users.id), type: text("type", { length: 255 }) .$type() .notNull(), @@ -78,7 +82,9 @@ export const sessions = createTable( "session", { sessionToken: text("sessionToken", { length: 255 }).notNull().primaryKey(), - userId: text("userId", { length: 255 }).notNull(), + userId: text("userId", { length: 255 }) + .notNull() + .references(() => users.id), expires: int("expires", { mode: "timestamp" }).notNull(), }, (session) => ({ From 1e77b3af661bd335fe754b73910a8050812b8f38 Mon Sep 17 00:00:00 2001 From: Matvey Ryabchikov <35634442+ronanru@users.noreply.github.com> Date: Wed, 22 Nov 2023 16:03:21 +0300 Subject: [PATCH 22/30] docs: update first steps --- cli/src/cli/index.ts | 9 ++++----- www/src/pages/en/usage/first-steps.md | 4 ++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/cli/src/cli/index.ts b/cli/src/cli/index.ts index eeaf657eed..284d48e45f 100644 --- a/cli/src/cli/index.ts +++ b/cli/src/cli/index.ts @@ -284,12 +284,12 @@ export const runCli = async (): Promise => { return p.select({ message: "What database provider would you like to use?", options: [ + { value: "sqlite", label: "SQLite" }, { value: "mysql", label: "MySQL" }, { value: "postgres", label: "PostgreSQL" }, - { value: "sqlite", label: "SQLite" }, - { value: "planetscale", label: "Planetscale Serverless" }, + { value: "planetscale", label: "Planetscale" }, ], - initialValue: "mysql", + initialValue: "sqlite", }); }, ...(!cliResults.flags.noGit && { @@ -338,8 +338,7 @@ export const runCli = async (): Promise => { appName: project.name ?? cliResults.appName, packages, databaseProvider: - (project.databaseProvider as DatabaseProvider) || - (packages.includes("drizzle") ? "planetscale" : "sqlite"), + (project.databaseProvider as DatabaseProvider) || "sqlite", flags: { ...cliResults.flags, appRouter: project.appRouter ?? cliResults.flags.appRouter, diff --git a/www/src/pages/en/usage/first-steps.md b/www/src/pages/en/usage/first-steps.md index 776f3ce8ff..44e39074c4 100644 --- a/www/src/pages/en/usage/first-steps.md +++ b/www/src/pages/en/usage/first-steps.md @@ -9,6 +9,10 @@ You just scaffolded a new T3 App and are ready to go. Here is the bare minimum t ## Database +### MySQL, PostgreSQL + +If you chose MySQL or PostgreSQL as your database, your T3 app will come with a `start-database.sh` bash script that can create a docker container with a database for local development. If you already have a database, feel free to delete this file and put your database credentials in `.env`. On macOS, you can also use [DBngin](https://dbngin.com/) if you don't want to use docker. + ### Prisma If your app includes Prisma, make sure to run `npx prisma db push` from the root directory of your app. This command will sync your Prisma schema with your database and will generate the TypeScript types for the Prisma Client based on your schema. Note that you need to [restart the TypeScript server](https://tinytip.co/tips/vscode-restart-ts/) after doing this so that it can detect the generated types. From fde1ed49b9754dd8f54cc29a75b26a4031ae5963 Mon Sep 17 00:00:00 2001 From: Matvey Ryabchikov <35634442+ronanru@users.noreply.github.com> Date: Thu, 23 Nov 2023 13:14:57 +0300 Subject: [PATCH 23/30] fix planetscale+prisma schema --- .../prisma/schema/base-planetscale.prisma | 5 ++++- .../schema/with-auth-planetscale.prisma | 19 +++++++++++-------- .../src/server/db/db-prisma-planetscale.ts | 2 +- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/cli/template/extras/prisma/schema/base-planetscale.prisma b/cli/template/extras/prisma/schema/base-planetscale.prisma index f44ad8451e..0e57946b97 100644 --- a/cli/template/extras/prisma/schema/base-planetscale.prisma +++ b/cli/template/extras/prisma/schema/base-planetscale.prisma @@ -7,8 +7,11 @@ generator client { } datasource db { - provider = "sqlite" + provider = "mysql" url = env("DATABASE_URL") + + // Do not use foreign keys (PlanetScale does not support them) + relationMode = "prisma" } model Post { diff --git a/cli/template/extras/prisma/schema/with-auth-planetscale.prisma b/cli/template/extras/prisma/schema/with-auth-planetscale.prisma index 54d69bb3e6..00a24dd180 100644 --- a/cli/template/extras/prisma/schema/with-auth-planetscale.prisma +++ b/cli/template/extras/prisma/schema/with-auth-planetscale.prisma @@ -7,12 +7,11 @@ generator client { } datasource db { - provider = "sqlite" - // NOTE: When using mysql or sqlserver, uncomment the @db.Text annotations in model Account below - // Further reading: - // https://next-auth.js.org/adapters/prisma#create-the-prisma-schema - // https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#string + provider = "mysql" url = env("DATABASE_URL") + + // Do not use foreign keys (PlanetScale does not support them) + relationMode = "prisma" } model Post { @@ -25,6 +24,7 @@ model Post { createdById String @@index([name]) + @@index([createdById]) } // Necessary for Next auth @@ -34,16 +34,17 @@ model Account { type String provider String providerAccountId String - refresh_token String? // @db.Text - access_token String? // @db.Text + refresh_token String? @db.Text + access_token String? @db.Text expires_at Int? token_type String? scope String? - id_token String? // @db.Text + id_token String? @db.Text session_state String? user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([provider, providerAccountId]) + @@index([userId]) } model Session { @@ -52,6 +53,8 @@ model Session { userId String expires DateTime user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@index([userId]) } model User { diff --git a/cli/template/extras/src/server/db/db-prisma-planetscale.ts b/cli/template/extras/src/server/db/db-prisma-planetscale.ts index 8663f639f6..dbb9c2ed32 100644 --- a/cli/template/extras/src/server/db/db-prisma-planetscale.ts +++ b/cli/template/extras/src/server/db/db-prisma-planetscale.ts @@ -1,6 +1,6 @@ import { Client } from "@planetscale/database"; import { PrismaPlanetScale } from "@prisma/adapter-planetscale"; -import { PrismaClient } from "@prisma/client/edge"; +import { PrismaClient } from "@prisma/client"; import { env } from "~/env.js"; From 59a65ce45dc276499edfc23f16bbfa58edd09f66 Mon Sep 17 00:00:00 2001 From: Matvey Ryabchikov <35634442+ronanru@users.noreply.github.com> Date: Sun, 26 Nov 2023 22:39:34 +0300 Subject: [PATCH 24/30] add windows instructions to the db script --- cli/template/extras/start-database/mysql.sh | 8 ++++++++ cli/template/extras/start-database/postgres.sh | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/cli/template/extras/start-database/mysql.sh b/cli/template/extras/start-database/mysql.sh index 6d28e53737..5319e9286e 100755 --- a/cli/template/extras/start-database/mysql.sh +++ b/cli/template/extras/start-database/mysql.sh @@ -1,6 +1,14 @@ #!/bin/bash # Use this script to start a docker container for a local development database +# TO RUN ON WINDOWS: +# 1. Install WSL (Windows Subsystem for Linux) - https://learn.microsoft.com/en-us/windows/wsl/install +# 2. Install Docker Desktop for Windows - https://docs.docker.com/docker-for-windows/install/ +# 3. Open WSL - `wsl` +# 4. Run this script - `./start-database.sh` + +# On Lunux and macOS you can run this script directly - `./start-database.sh` + DB_CONTAINER_NAME="project1-mysql" if ! [ -x "$(command -v docker)" ]; then diff --git a/cli/template/extras/start-database/postgres.sh b/cli/template/extras/start-database/postgres.sh index 9bb21802d9..f873d4df62 100755 --- a/cli/template/extras/start-database/postgres.sh +++ b/cli/template/extras/start-database/postgres.sh @@ -1,6 +1,14 @@ #!/bin/bash # Use this script to start a docker container for a local development database +# TO RUN ON WINDOWS: +# 1. Install WSL (Windows Subsystem for Linux) - https://learn.microsoft.com/en-us/windows/wsl/install +# 2. Install Docker Desktop for Windows - https://docs.docker.com/docker-for-windows/install/ +# 3. Open WSL - `wsl` +# 4. Run this script - `./start-database.sh` + +# On Lunux and macOS you can run this script directly - `./start-database.sh` + DB_CONTAINER_NAME="project1-postgres" if ! [ -x "$(command -v docker)" ]; then From e28a41c77dad19173f713cd24a437b8d7b6ecc84 Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Thu, 4 Jan 2024 16:08:58 +0100 Subject: [PATCH 25/30] merge --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 2d92168bf6..c6ba810dd3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" }, "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true, From a0c20503e745e64a8ddae64f932aa1693c9dc3b5 Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Thu, 4 Jan 2024 16:21:15 +0100 Subject: [PATCH 26/30] fix schema --- .../src/server/db/schema-drizzle/with-auth-mysql.ts | 10 ++++++---- .../server/db/schema-drizzle/with-auth-planetscale.ts | 10 ++++++---- .../src/server/db/schema-drizzle/with-auth-postgres.ts | 10 ++++++---- .../src/server/db/schema-drizzle/with-auth-sqlite.ts | 10 ++++++---- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/cli/template/extras/src/server/db/schema-drizzle/with-auth-mysql.ts b/cli/template/extras/src/server/db/schema-drizzle/with-auth-mysql.ts index 1b6cfce470..da10c0756c 100644 --- a/cli/template/extras/src/server/db/schema-drizzle/with-auth-mysql.ts +++ b/cli/template/extras/src/server/db/schema-drizzle/with-auth-mysql.ts @@ -74,8 +74,10 @@ export const accounts = createTable( session_state: varchar("session_state", { length: 255 }), }, (account) => ({ - compoundKey: primaryKey(account.provider, account.providerAccountId), - userIdIdx: index("userId_idx").on(account.userId), + compoundKey: primaryKey({ + columns: [account.provider, account.providerAccountId], + }), + userIdIdx: index("account_userId_idx").on(account.userId), }) ); @@ -95,7 +97,7 @@ export const sessions = createTable( expires: timestamp("expires", { mode: "date" }).notNull(), }, (session) => ({ - userIdIdx: index("userId_idx").on(session.userId), + userIdIdx: index("session_userId_idx").on(session.userId), }) ); @@ -111,6 +113,6 @@ export const verificationTokens = createTable( expires: timestamp("expires", { mode: "date" }).notNull(), }, (vt) => ({ - compoundKey: primaryKey(vt.identifier, vt.token), + compoundKey: primaryKey({ columns: [vt.identifier, vt.token] }), }) ); diff --git a/cli/template/extras/src/server/db/schema-drizzle/with-auth-planetscale.ts b/cli/template/extras/src/server/db/schema-drizzle/with-auth-planetscale.ts index e53a950fbc..dcba89b31b 100644 --- a/cli/template/extras/src/server/db/schema-drizzle/with-auth-planetscale.ts +++ b/cli/template/extras/src/server/db/schema-drizzle/with-auth-planetscale.ts @@ -70,8 +70,10 @@ export const accounts = createTable( session_state: varchar("session_state", { length: 255 }), }, (account) => ({ - compoundKey: primaryKey(account.provider, account.providerAccountId), - userIdIdx: index("userId_idx").on(account.userId), + compoundKey: primaryKey({ + columns: [account.provider, account.providerAccountId], + }), + userIdIdx: index("accounts_userId_idx").on(account.userId), }) ); @@ -89,7 +91,7 @@ export const sessions = createTable( expires: timestamp("expires", { mode: "date" }).notNull(), }, (session) => ({ - userIdIdx: index("userId_idx").on(session.userId), + userIdIdx: index("session_userId_idx").on(session.userId), }) ); @@ -105,6 +107,6 @@ export const verificationTokens = createTable( expires: timestamp("expires", { mode: "date" }).notNull(), }, (vt) => ({ - compoundKey: primaryKey(vt.identifier, vt.token), + compoundKey: primaryKey({ columns: [vt.identifier, vt.token] }), }) ); diff --git a/cli/template/extras/src/server/db/schema-drizzle/with-auth-postgres.ts b/cli/template/extras/src/server/db/schema-drizzle/with-auth-postgres.ts index 1ede1bebac..7733f4f17b 100644 --- a/cli/template/extras/src/server/db/schema-drizzle/with-auth-postgres.ts +++ b/cli/template/extras/src/server/db/schema-drizzle/with-auth-postgres.ts @@ -72,8 +72,10 @@ export const accounts = createTable( session_state: varchar("session_state", { length: 255 }), }, (account) => ({ - compoundKey: primaryKey(account.provider, account.providerAccountId), - userIdIdx: index("userId_idx").on(account.userId), + compoundKey: primaryKey({ + columns: [account.provider, account.providerAccountId], + }), + userIdIdx: index("account_userId_idx").on(account.userId), }) ); @@ -93,7 +95,7 @@ export const sessions = createTable( expires: timestamp("expires", { mode: "date" }).notNull(), }, (session) => ({ - userIdIdx: index("userId_idx").on(session.userId), + userIdIdx: index("session_userId_idx").on(session.userId), }) ); @@ -109,6 +111,6 @@ export const verificationTokens = createTable( expires: timestamp("expires", { mode: "date" }).notNull(), }, (vt) => ({ - compoundKey: primaryKey(vt.identifier, vt.token), + compoundKey: primaryKey({ columns: [vt.identifier, vt.token] }), }) ); diff --git a/cli/template/extras/src/server/db/schema-drizzle/with-auth-sqlite.ts b/cli/template/extras/src/server/db/schema-drizzle/with-auth-sqlite.ts index 8cc1ec44fe..f739573bfc 100644 --- a/cli/template/extras/src/server/db/schema-drizzle/with-auth-sqlite.ts +++ b/cli/template/extras/src/server/db/schema-drizzle/with-auth-sqlite.ts @@ -69,8 +69,10 @@ export const accounts = createTable( session_state: text("session_state", { length: 255 }), }, (account) => ({ - compoundKey: primaryKey(account.provider, account.providerAccountId), - userIdIdx: index("userId_idx").on(account.userId), + compoundKey: primaryKey({ + columns: [account.provider, account.providerAccountId], + }), + userIdIdx: index("account_userId_idx").on(account.userId), }) ); @@ -88,7 +90,7 @@ export const sessions = createTable( expires: int("expires", { mode: "timestamp" }).notNull(), }, (session) => ({ - userIdIdx: index("userId_idx").on(session.userId), + userIdIdx: index("session_userId_idx").on(session.userId), }) ); @@ -104,6 +106,6 @@ export const verificationTokens = createTable( expires: int("expires", { mode: "timestamp" }).notNull(), }, (vt) => ({ - compoundKey: primaryKey(vt.identifier, vt.token), + compoundKey: primaryKey({ columns: [vt.identifier, vt.token] }), }) ); From 255988d17cbebb259962a27516ccc7732128691a Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Thu, 4 Jan 2024 16:32:06 +0100 Subject: [PATCH 27/30] add type cast for adapter to workaround version mismatch --- cli/template/extras/src/server/auth-app/with-drizzle.ts | 3 ++- cli/template/extras/src/server/auth-app/with-prisma.ts | 3 ++- cli/template/extras/src/server/auth-pages/with-drizzle.ts | 3 ++- cli/template/extras/src/server/auth-pages/with-prisma.ts | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/cli/template/extras/src/server/auth-app/with-drizzle.ts b/cli/template/extras/src/server/auth-app/with-drizzle.ts index 392437e28c..6c2dc4f826 100644 --- a/cli/template/extras/src/server/auth-app/with-drizzle.ts +++ b/cli/template/extras/src/server/auth-app/with-drizzle.ts @@ -4,6 +4,7 @@ import { type DefaultSession, type NextAuthOptions, } from "next-auth"; +import { type Adapter } from "next-auth/adapters"; import DiscordProvider from "next-auth/providers/discord"; import { env } from "~/env"; @@ -46,7 +47,7 @@ export const authOptions: NextAuthOptions = { }, }), }, - adapter: DrizzleAdapter(db, createTable), + adapter: DrizzleAdapter(db, createTable) as Adapter, providers: [ DiscordProvider({ clientId: env.DISCORD_CLIENT_ID, diff --git a/cli/template/extras/src/server/auth-app/with-prisma.ts b/cli/template/extras/src/server/auth-app/with-prisma.ts index d1f119f936..887c6461de 100644 --- a/cli/template/extras/src/server/auth-app/with-prisma.ts +++ b/cli/template/extras/src/server/auth-app/with-prisma.ts @@ -4,6 +4,7 @@ import { type DefaultSession, type NextAuthOptions, } from "next-auth"; +import { type Adapter } from "next-auth/adapters"; import DiscordProvider from "next-auth/providers/discord"; import { env } from "~/env"; @@ -45,7 +46,7 @@ export const authOptions: NextAuthOptions = { }, }), }, - adapter: PrismaAdapter(db), + adapter: PrismaAdapter(db) as Adapter, providers: [ DiscordProvider({ clientId: env.DISCORD_CLIENT_ID, diff --git a/cli/template/extras/src/server/auth-pages/with-drizzle.ts b/cli/template/extras/src/server/auth-pages/with-drizzle.ts index 26b5210bf0..6e444ee76b 100644 --- a/cli/template/extras/src/server/auth-pages/with-drizzle.ts +++ b/cli/template/extras/src/server/auth-pages/with-drizzle.ts @@ -5,6 +5,7 @@ import { type DefaultSession, type NextAuthOptions, } from "next-auth"; +import { type Adapter } from "next-auth/adapters"; import DiscordProvider from "next-auth/providers/discord"; import { env } from "~/env"; @@ -47,7 +48,7 @@ export const authOptions: NextAuthOptions = { }, }), }, - adapter: DrizzleAdapter(db, createTable), + adapter: DrizzleAdapter(db, createTable) as Adapter, providers: [ DiscordProvider({ clientId: env.DISCORD_CLIENT_ID, diff --git a/cli/template/extras/src/server/auth-pages/with-prisma.ts b/cli/template/extras/src/server/auth-pages/with-prisma.ts index 1c5d36808b..041a99d0ca 100644 --- a/cli/template/extras/src/server/auth-pages/with-prisma.ts +++ b/cli/template/extras/src/server/auth-pages/with-prisma.ts @@ -5,6 +5,7 @@ import { type DefaultSession, type NextAuthOptions, } from "next-auth"; +import { type Adapter } from "next-auth/adapters"; import DiscordProvider from "next-auth/providers/discord"; import { env } from "~/env"; @@ -46,7 +47,7 @@ export const authOptions: NextAuthOptions = { }, }), }, - adapter: PrismaAdapter(db), + adapter: PrismaAdapter(db) as Adapter, providers: [ DiscordProvider({ clientId: env.DISCORD_CLIENT_ID, From a72ea730aa4d927ea4d33be2fda9578a63588fd0 Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Thu, 4 Jan 2024 16:40:33 +0100 Subject: [PATCH 28/30] add mode --- cli/template/extras/src/server/db/index-drizzle/with-mysql.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/template/extras/src/server/db/index-drizzle/with-mysql.ts b/cli/template/extras/src/server/db/index-drizzle/with-mysql.ts index b74eb3887e..dab9d98200 100644 --- a/cli/template/extras/src/server/db/index-drizzle/with-mysql.ts +++ b/cli/template/extras/src/server/db/index-drizzle/with-mysql.ts @@ -8,5 +8,5 @@ export const db = drizzle( mysql.createPool({ uri: env.DATABASE_URL, }), - { schema } + { schema, mode: "default" } ); From ed731955a27d2dd301c895552338d31896261889 Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Thu, 4 Jan 2024 16:56:14 +0100 Subject: [PATCH 29/30] rm cast for prisma adapter --- cli/template/extras/src/server/auth-app/with-prisma.ts | 3 +-- cli/template/extras/src/server/auth-pages/with-prisma.ts | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/cli/template/extras/src/server/auth-app/with-prisma.ts b/cli/template/extras/src/server/auth-app/with-prisma.ts index 887c6461de..d1f119f936 100644 --- a/cli/template/extras/src/server/auth-app/with-prisma.ts +++ b/cli/template/extras/src/server/auth-app/with-prisma.ts @@ -4,7 +4,6 @@ import { type DefaultSession, type NextAuthOptions, } from "next-auth"; -import { type Adapter } from "next-auth/adapters"; import DiscordProvider from "next-auth/providers/discord"; import { env } from "~/env"; @@ -46,7 +45,7 @@ export const authOptions: NextAuthOptions = { }, }), }, - adapter: PrismaAdapter(db) as Adapter, + adapter: PrismaAdapter(db), providers: [ DiscordProvider({ clientId: env.DISCORD_CLIENT_ID, diff --git a/cli/template/extras/src/server/auth-pages/with-prisma.ts b/cli/template/extras/src/server/auth-pages/with-prisma.ts index 041a99d0ca..1c5d36808b 100644 --- a/cli/template/extras/src/server/auth-pages/with-prisma.ts +++ b/cli/template/extras/src/server/auth-pages/with-prisma.ts @@ -5,7 +5,6 @@ import { type DefaultSession, type NextAuthOptions, } from "next-auth"; -import { type Adapter } from "next-auth/adapters"; import DiscordProvider from "next-auth/providers/discord"; import { env } from "~/env"; @@ -47,7 +46,7 @@ export const authOptions: NextAuthOptions = { }, }), }, - adapter: PrismaAdapter(db) as Adapter, + adapter: PrismaAdapter(db), providers: [ DiscordProvider({ clientId: env.DISCORD_CLIENT_ID, From 60914c70750d34a285fc1c214969b10453e32cb7 Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Thu, 4 Jan 2024 17:00:13 +0100 Subject: [PATCH 30/30] fix env var string --- cli/src/installers/envVars.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cli/src/installers/envVars.ts b/cli/src/installers/envVars.ts index 0853563d01..38198d9ee4 100644 --- a/cli/src/installers/envVars.ts +++ b/cli/src/installers/envVars.ts @@ -77,9 +77,14 @@ const getEnvContent = ( if (usingPrisma || usingDrizzle) { if (databaseProvider === "planetscale") { - content += `Get the Database URL from the "prisma" dropdown selector in PlanetScale. + if (usingDrizzle) { + content += `Get the Database URL from the "prisma" dropdown selector in PlanetScale. # Change the query params at the end of the URL to "?ssl={"rejectUnauthorized":true}" DATABASE_URL='mysql://YOUR_MYSQL_URL_HERE?ssl={"rejectUnauthorized":true}'`; + } else { + content = `Get the Database URL from the "prisma" dropdown selector in PlanetScale. +DATABASE_URL='mysql://YOUR_MYSQL_URL_HERE?sslaccept=strict'`; + } } else if (databaseProvider === "mysql") { content += `DATABASE_URL="mysql://root:password@localhost:3306/${projectName}"`; } else if (databaseProvider === "postgres") {