mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
add postgres support
This commit is contained in:
5
.changeset/fifty-rocks-try.md
Normal file
5
.changeset/fifty-rocks-try.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"create-better-t-stack": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Add postgres support
|
||||||
@@ -52,9 +52,11 @@ export async function configureAuth(
|
|||||||
const envPath = path.join(serverDir, ".env");
|
const envPath = path.join(serverDir, ".env");
|
||||||
const templateEnvPath = path.join(
|
const templateEnvPath = path.join(
|
||||||
PKG_ROOT,
|
PKG_ROOT,
|
||||||
options.orm === "drizzle"
|
getOrmTemplatePath(
|
||||||
? "template/with-drizzle/packages/server/_env"
|
options.orm,
|
||||||
: "template/base/packages/server/_env",
|
options.database,
|
||||||
|
"packages/server/_env",
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!(await fs.pathExists(envPath))) {
|
if (!(await fs.pathExists(envPath))) {
|
||||||
@@ -108,7 +110,11 @@ ${options.orm === "prisma" ? 'DATABASE_URL="file:./dev.db"' : ""}
|
|||||||
const prismaAuthPath = path.join(serverDir, "src/lib/auth.ts");
|
const prismaAuthPath = path.join(serverDir, "src/lib/auth.ts");
|
||||||
const defaultPrismaAuthPath = path.join(
|
const defaultPrismaAuthPath = path.join(
|
||||||
PKG_ROOT,
|
PKG_ROOT,
|
||||||
"template/with-prisma/packages/server/src/lib/auth.ts",
|
getOrmTemplatePath(
|
||||||
|
options.orm,
|
||||||
|
options.database,
|
||||||
|
"packages/server/src/lib/auth.ts",
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -147,7 +153,11 @@ ${options.orm === "prisma" ? 'DATABASE_URL="file:./dev.db"' : ""}
|
|||||||
const drizzleAuthPath = path.join(serverDir, "src/lib/auth.ts");
|
const drizzleAuthPath = path.join(serverDir, "src/lib/auth.ts");
|
||||||
const defaultDrizzleAuthPath = path.join(
|
const defaultDrizzleAuthPath = path.join(
|
||||||
PKG_ROOT,
|
PKG_ROOT,
|
||||||
"template/with-drizzle/packages/server/src/lib/auth.ts",
|
getOrmTemplatePath(
|
||||||
|
options.orm,
|
||||||
|
options.database,
|
||||||
|
"packages/server/src/lib/auth.ts",
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -180,6 +190,24 @@ ${options.orm === "prisma" ? 'DATABASE_URL="file:./dev.db"' : ""}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getOrmTemplatePath(
|
||||||
|
orm: string,
|
||||||
|
database: string,
|
||||||
|
relativePath: string,
|
||||||
|
): string {
|
||||||
|
if (orm === "drizzle") {
|
||||||
|
return database === "sqlite"
|
||||||
|
? `template/with-drizzle-sqlite/${relativePath}`
|
||||||
|
: `template/with-drizzle-postgres/${relativePath}`;
|
||||||
|
}
|
||||||
|
if (orm === "prisma") {
|
||||||
|
return database === "sqlite"
|
||||||
|
? `template/with-prisma-sqlite/${relativePath}`
|
||||||
|
: `template/with-prisma-postgres/${relativePath}`;
|
||||||
|
}
|
||||||
|
return `template/base/${relativePath}`;
|
||||||
|
}
|
||||||
|
|
||||||
function generateAuthSecret(length = 32): string {
|
function generateAuthSecret(length = 32): string {
|
||||||
const characters =
|
const characters =
|
||||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
|||||||
@@ -27,9 +27,7 @@ export async function createProject(options: ProjectConfig): Promise<string> {
|
|||||||
if (options.orm !== "none" && options.database !== "none") {
|
if (options.orm !== "none" && options.database !== "none") {
|
||||||
const ormTemplateDir = path.join(
|
const ormTemplateDir = path.join(
|
||||||
PKG_ROOT,
|
PKG_ROOT,
|
||||||
options.orm === "drizzle"
|
getOrmTemplateDir(options.orm, options.database),
|
||||||
? "template/with-drizzle"
|
|
||||||
: "template/with-prisma",
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (await fs.pathExists(ormTemplateDir)) {
|
if (await fs.pathExists(ormTemplateDir)) {
|
||||||
@@ -143,3 +141,19 @@ export async function createProject(options: ProjectConfig): Promise<string> {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getOrmTemplateDir(orm: string, database: string): string {
|
||||||
|
if (orm === "drizzle") {
|
||||||
|
return database === "sqlite"
|
||||||
|
? "template/with-drizzle-sqlite"
|
||||||
|
: "template/with-drizzle-postgres";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orm === "prisma") {
|
||||||
|
return database === "sqlite"
|
||||||
|
? "template/with-prisma-sqlite"
|
||||||
|
: "template/with-prisma-postgres";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "template/base";
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,24 +21,21 @@ export async function setupDatabase(
|
|||||||
try {
|
try {
|
||||||
if (databaseType === "sqlite") {
|
if (databaseType === "sqlite") {
|
||||||
if (orm === "drizzle") {
|
if (orm === "drizzle") {
|
||||||
await setupDrizzleDependencies(projectDir);
|
await setupDrizzleDependencies(projectDir, "sqlite");
|
||||||
await setupTurso(projectDir, setupTursoDb);
|
if (setupTursoDb) {
|
||||||
|
await setupTurso(projectDir, true);
|
||||||
|
}
|
||||||
} else if (orm === "prisma") {
|
} else if (orm === "prisma") {
|
||||||
await setupPrismaDependencies(projectDir);
|
await setupPrismaDependencies(projectDir, "sqlite");
|
||||||
await setupTurso(projectDir, setupTursoDb);
|
if (setupTursoDb) {
|
||||||
|
await setupTurso(projectDir, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (databaseType === "postgres") {
|
} else if (databaseType === "postgres") {
|
||||||
log.info(
|
|
||||||
pc.blue(
|
|
||||||
"PostgreSQL setup is coming in a future update. Using SQLite configuration for now.",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (orm === "drizzle") {
|
if (orm === "drizzle") {
|
||||||
await setupDrizzleDependencies(projectDir);
|
await setupDrizzleDependencies(projectDir, "postgres");
|
||||||
await setupTurso(projectDir, setupTursoDb);
|
|
||||||
} else if (orm === "prisma") {
|
} else if (orm === "prisma") {
|
||||||
await setupPrismaDependencies(projectDir);
|
await setupPrismaDependencies(projectDir, "postgres");
|
||||||
await setupTurso(projectDir, setupTursoDb);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -50,7 +47,10 @@ export async function setupDatabase(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setupDrizzleDependencies(projectDir: string): Promise<void> {
|
async function setupDrizzleDependencies(
|
||||||
|
projectDir: string,
|
||||||
|
dbType: string,
|
||||||
|
): Promise<void> {
|
||||||
const serverDir = path.join(projectDir, "packages/server");
|
const serverDir = path.join(projectDir, "packages/server");
|
||||||
|
|
||||||
const packageJsonPath = path.join(serverDir, "package.json");
|
const packageJsonPath = path.join(serverDir, "package.json");
|
||||||
@@ -60,9 +60,14 @@ async function setupDrizzleDependencies(projectDir: string): Promise<void> {
|
|||||||
packageJson.dependencies = {
|
packageJson.dependencies = {
|
||||||
...packageJson.dependencies,
|
...packageJson.dependencies,
|
||||||
"drizzle-orm": "^0.38.4",
|
"drizzle-orm": "^0.38.4",
|
||||||
"@libsql/client": "^0.14.0",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (dbType === "sqlite") {
|
||||||
|
packageJson.dependencies["@libsql/client"] = "^0.14.0";
|
||||||
|
} else if (dbType === "postgres") {
|
||||||
|
packageJson.dependencies.postgres = "^3.4.5";
|
||||||
|
}
|
||||||
|
|
||||||
packageJson.devDependencies = {
|
packageJson.devDependencies = {
|
||||||
...packageJson.devDependencies,
|
...packageJson.devDependencies,
|
||||||
"drizzle-kit": "^0.30.4",
|
"drizzle-kit": "^0.30.4",
|
||||||
@@ -79,7 +84,10 @@ async function setupDrizzleDependencies(projectDir: string): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setupPrismaDependencies(projectDir: string): Promise<void> {
|
async function setupPrismaDependencies(
|
||||||
|
projectDir: string,
|
||||||
|
dbType: string,
|
||||||
|
): Promise<void> {
|
||||||
const serverDir = path.join(projectDir, "packages/server");
|
const serverDir = path.join(projectDir, "packages/server");
|
||||||
|
|
||||||
const packageJsonPath = path.join(serverDir, "package.json");
|
const packageJsonPath = path.join(serverDir, "package.json");
|
||||||
@@ -89,10 +97,15 @@ async function setupPrismaDependencies(projectDir: string): Promise<void> {
|
|||||||
packageJson.dependencies = {
|
packageJson.dependencies = {
|
||||||
...packageJson.dependencies,
|
...packageJson.dependencies,
|
||||||
"@prisma/client": "^5.7.1",
|
"@prisma/client": "^5.7.1",
|
||||||
"@prisma/adapter-libsql": "^5.7.1",
|
|
||||||
"@libsql/client": "^0.14.0",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (dbType === "sqlite") {
|
||||||
|
packageJson.dependencies["@prisma/adapter-libsql"] = "^5.7.1";
|
||||||
|
packageJson.dependencies["@libsql/client"] = "^0.14.0";
|
||||||
|
} else if (dbType === "postgres") {
|
||||||
|
// PostgreSQL specific dependencies if needed
|
||||||
|
}
|
||||||
|
|
||||||
packageJson.devDependencies = {
|
packageJson.devDependencies = {
|
||||||
...packageJson.devDependencies,
|
...packageJson.devDependencies,
|
||||||
prisma: "^5.7.1",
|
prisma: "^5.7.1",
|
||||||
@@ -112,7 +125,10 @@ async function setupPrismaDependencies(projectDir: string): Promise<void> {
|
|||||||
if (await fs.pathExists(envPath)) {
|
if (await fs.pathExists(envPath)) {
|
||||||
const envContent = await fs.readFile(envPath, "utf8");
|
const envContent = await fs.readFile(envPath, "utf8");
|
||||||
if (!envContent.includes("DATABASE_URL")) {
|
if (!envContent.includes("DATABASE_URL")) {
|
||||||
const databaseUrlLine = `\nDATABASE_URL="file:./dev.db"`;
|
const databaseUrlLine =
|
||||||
|
dbType === "sqlite"
|
||||||
|
? `\nDATABASE_URL="file:./dev.db"`
|
||||||
|
: `\nDATABASE_URL="postgresql://postgres:postgres@localhost:5432/mydb?schema=public"`;
|
||||||
await fs.appendFile(envPath, databaseUrlLine);
|
await fs.appendFile(envPath, databaseUrlLine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { defineConfig } from "drizzle-kit";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
schema: "./src/db/schema.ts",
|
||||||
|
out: "./migrations",
|
||||||
|
dialect: "postgresql",
|
||||||
|
dbCredentials: {
|
||||||
|
url: process.env.POSTGRES_URL!,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { drizzle } from "drizzle-orm/postgres-js";
|
||||||
|
import postgres from "postgres";
|
||||||
|
|
||||||
|
const queryClient = postgres(process.env.DATABASE_URL);
|
||||||
|
const db = drizzle({ client: queryClient });
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { pgTable, text, integer, timestamp, boolean } from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
|
export const user = pgTable("user", {
|
||||||
|
id: text("id").primaryKey(),
|
||||||
|
name: text('name').notNull(),
|
||||||
|
email: text('email').notNull().unique(),
|
||||||
|
emailVerified: boolean('email_verified').notNull(),
|
||||||
|
image: text('image'),
|
||||||
|
createdAt: timestamp('created_at').notNull(),
|
||||||
|
updatedAt: timestamp('updated_at').notNull()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const session = pgTable("session", {
|
||||||
|
id: text("id").primaryKey(),
|
||||||
|
expiresAt: timestamp('expires_at').notNull(),
|
||||||
|
token: text('token').notNull().unique(),
|
||||||
|
createdAt: timestamp('created_at').notNull(),
|
||||||
|
updatedAt: timestamp('updated_at').notNull(),
|
||||||
|
ipAddress: text('ip_address'),
|
||||||
|
userAgent: text('user_agent'),
|
||||||
|
userId: text('user_id').notNull().references(()=> user.id, { onDelete: 'cascade' })
|
||||||
|
});
|
||||||
|
|
||||||
|
export const account = pgTable("account", {
|
||||||
|
id: text("id").primaryKey(),
|
||||||
|
accountId: text('account_id').notNull(),
|
||||||
|
providerId: text('provider_id').notNull(),
|
||||||
|
userId: text('user_id').notNull().references(()=> user.id, { onDelete: 'cascade' }),
|
||||||
|
accessToken: text('access_token'),
|
||||||
|
refreshToken: text('refresh_token'),
|
||||||
|
idToken: text('id_token'),
|
||||||
|
accessTokenExpiresAt: timestamp('access_token_expires_at'),
|
||||||
|
refreshTokenExpiresAt: timestamp('refresh_token_expires_at'),
|
||||||
|
scope: text('scope'),
|
||||||
|
password: text('password'),
|
||||||
|
createdAt: timestamp('created_at').notNull(),
|
||||||
|
updatedAt: timestamp('updated_at').notNull()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const verification = pgTable("verification", {
|
||||||
|
id: text("id").primaryKey(),
|
||||||
|
identifier: text('identifier').notNull(),
|
||||||
|
value: text('value').notNull(),
|
||||||
|
expiresAt: timestamp('expires_at').notNull(),
|
||||||
|
createdAt: timestamp('created_at'),
|
||||||
|
updatedAt: timestamp('updated_at')
|
||||||
|
});
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
BETTER_AUTH_SECRET=jdUstNuiIZLVh897KOMMS8EmTP0QkD32
|
||||||
|
BETTER_AUTH_URL=http://localhost:3000
|
||||||
|
TURSO_CONNECTION_URL=http://127.0.0.1:8080
|
||||||
|
CORS_ORIGIN=http://localhost:3001
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { betterAuth } from "better-auth";
|
||||||
|
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||||
|
import { db } from "../db";
|
||||||
|
import * as schema from "../db/schema";
|
||||||
|
|
||||||
|
export const auth = betterAuth({
|
||||||
|
database: drizzleAdapter(db, {
|
||||||
|
provider: "sqlite",
|
||||||
|
schema: schema,
|
||||||
|
}),
|
||||||
|
trustedOrigins: [process.env.CORS_ORIGIN!],
|
||||||
|
emailAndPassword: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { PrismaClient } from "@prisma/client";
|
||||||
|
|
||||||
|
let prisma = new PrismaClient();
|
||||||
|
|
||||||
|
export default prisma;
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
// This is your Prisma schema file,
|
||||||
|
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||||
|
|
||||||
|
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
|
||||||
|
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
|
||||||
|
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgres"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
model User {
|
||||||
|
id String @id @map("_id")
|
||||||
|
name String
|
||||||
|
email String
|
||||||
|
emailVerified Boolean
|
||||||
|
image String?
|
||||||
|
createdAt DateTime
|
||||||
|
updatedAt DateTime
|
||||||
|
sessions Session[]
|
||||||
|
accounts Account[]
|
||||||
|
|
||||||
|
@@unique([email])
|
||||||
|
@@map("user")
|
||||||
|
}
|
||||||
|
|
||||||
|
model Session {
|
||||||
|
id String @id @map("_id")
|
||||||
|
expiresAt DateTime
|
||||||
|
token String
|
||||||
|
createdAt DateTime
|
||||||
|
updatedAt DateTime
|
||||||
|
ipAddress String?
|
||||||
|
userAgent String?
|
||||||
|
userId String
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@unique([token])
|
||||||
|
@@map("session")
|
||||||
|
}
|
||||||
|
|
||||||
|
model Account {
|
||||||
|
id String @id @map("_id")
|
||||||
|
accountId String
|
||||||
|
providerId String
|
||||||
|
userId String
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
accessToken String?
|
||||||
|
refreshToken String?
|
||||||
|
idToken String?
|
||||||
|
accessTokenExpiresAt DateTime?
|
||||||
|
refreshTokenExpiresAt DateTime?
|
||||||
|
scope String?
|
||||||
|
password String?
|
||||||
|
createdAt DateTime
|
||||||
|
updatedAt DateTime
|
||||||
|
|
||||||
|
@@map("account")
|
||||||
|
}
|
||||||
|
|
||||||
|
model Verification {
|
||||||
|
id String @id @map("_id")
|
||||||
|
identifier String
|
||||||
|
value String
|
||||||
|
expiresAt DateTime
|
||||||
|
createdAt DateTime?
|
||||||
|
updatedAt DateTime?
|
||||||
|
|
||||||
|
@@map("verification")
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { betterAuth } from "better-auth";
|
||||||
|
import { prismaAdapter } from "better-auth/adapters/prisma";
|
||||||
|
import prisma from "../db";
|
||||||
|
|
||||||
|
export const auth = betterAuth({
|
||||||
|
database: prismaAdapter(prisma, {
|
||||||
|
provider: "pg",
|
||||||
|
}),
|
||||||
|
});
|
||||||
4
bun.lock
4
bun.lock
@@ -7,14 +7,14 @@
|
|||||||
"@biomejs/biome": "1.9.4",
|
"@biomejs/biome": "1.9.4",
|
||||||
"@changesets/cli": "^2.28.1",
|
"@changesets/cli": "^2.28.1",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"lint-staged": "^15.4.3",
|
"lint-staged": "^15.5.0",
|
||||||
"turbo": "^2.4.4",
|
"turbo": "^2.4.4",
|
||||||
"typescript": "5.7.3",
|
"typescript": "5.7.3",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"apps/cli": {
|
"apps/cli": {
|
||||||
"name": "create-better-t-stack",
|
"name": "create-better-t-stack",
|
||||||
"version": "0.11.0",
|
"version": "0.11.1",
|
||||||
"bin": {
|
"bin": {
|
||||||
"create-better-t-stack": "dist/index.js",
|
"create-better-t-stack": "dist/index.js",
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user