Skip to content

Commit 1ca76b2

Browse files
Implement comprehensive auth template system
1 parent fbdf12f commit 1ca76b2

File tree

22 files changed

+394
-198
lines changed

22 files changed

+394
-198
lines changed

apps/cli/src/helpers/auth-setup.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@ export async function setupAuth(
1818
projectDir: string,
1919
enableAuth: boolean,
2020
): Promise<void> {
21+
if (!enableAuth) {
22+
return;
23+
}
24+
2125
const serverDir = path.join(projectDir, "packages/server");
2226
const clientDir = path.join(projectDir, "packages/client");
2327

2428
try {
25-
if (!enableAuth) {
26-
return;
27-
}
2829
addPackageDependency({
2930
dependencies: ["better-auth"],
3031
projectDir: serverDir,

apps/cli/src/helpers/create-project.ts

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,65 @@ export async function createProject(options: ProjectConfig): Promise<string> {
4040

4141
if (await fs.pathExists(ormTemplateDir)) {
4242
await fs.copy(ormTemplateDir, projectDir, { overwrite: true });
43+
44+
const serverSrcPath = path.join(projectDir, "packages/server/src");
45+
const baseLibPath = path.join(serverSrcPath, "lib");
46+
const withAuthLibPath = path.join(serverSrcPath, "with-auth-lib");
47+
48+
if (options.auth) {
49+
await fs.remove(baseLibPath);
50+
await fs.move(withAuthLibPath, baseLibPath);
51+
52+
if (options.orm === "prisma") {
53+
const schemaPath = path.join(
54+
projectDir,
55+
"packages/server/prisma/schema.prisma",
56+
);
57+
const withAuthSchemaPath = path.join(
58+
projectDir,
59+
"packages/server/prisma/with-auth-schema.prisma",
60+
);
61+
62+
if (await fs.pathExists(withAuthSchemaPath)) {
63+
await fs.remove(schemaPath);
64+
await fs.move(withAuthSchemaPath, schemaPath);
65+
}
66+
} else if (options.orm === "drizzle") {
67+
const schemaPath = path.join(
68+
projectDir,
69+
"packages/server/src/db/schema.ts",
70+
);
71+
const withAuthSchemaPath = path.join(
72+
projectDir,
73+
"packages/server/src/db/with-auth-schema.ts",
74+
);
75+
76+
if (await fs.pathExists(withAuthSchemaPath)) {
77+
await fs.remove(schemaPath);
78+
await fs.move(withAuthSchemaPath, schemaPath);
79+
}
80+
}
81+
} else {
82+
await fs.remove(withAuthLibPath);
83+
84+
if (options.orm === "prisma") {
85+
const withAuthSchema = path.join(
86+
projectDir,
87+
"packages/server/prisma/with-auth-schema.prisma",
88+
);
89+
if (await fs.pathExists(withAuthSchema)) {
90+
await fs.remove(withAuthSchema);
91+
}
92+
} else if (options.orm === "drizzle") {
93+
const withAuthSchema = path.join(
94+
projectDir,
95+
"packages/server/src/db/with-auth-schema.ts",
96+
);
97+
if (await fs.pathExists(withAuthSchema)) {
98+
await fs.remove(withAuthSchema);
99+
}
100+
}
101+
}
43102
}
44103
}
45104

@@ -51,7 +110,6 @@ export async function createProject(options: ProjectConfig): Promise<string> {
51110
);
52111

53112
await setupAuth(projectDir, options.auth);
54-
55113
await setupEnvironmentVariables(projectDir, options);
56114

57115
if (options.git) {
@@ -63,7 +121,6 @@ export async function createProject(options: ProjectConfig): Promise<string> {
63121
}
64122

65123
await updatePackageConfigurations(projectDir, options);
66-
67124
await createReadme(projectDir, options);
68125

69126
displayPostInstallInstructions(

apps/cli/src/helpers/env-setup.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ export async function setupEnvironmentVariables(
1717
envContent = await fs.readFile(envPath, "utf8");
1818
}
1919

20+
if (!envContent.includes("CORS_ORIGIN")) {
21+
envContent += "\nCORS_ORIGIN=http://localhost:3001";
22+
}
23+
2024
if (options.auth) {
2125
if (!envContent.includes("BETTER_AUTH_SECRET")) {
2226
envContent += `\nBETTER_AUTH_SECRET=${generateAuthSecret()}`;
@@ -25,16 +29,6 @@ export async function setupEnvironmentVariables(
2529
if (!envContent.includes("BETTER_AUTH_URL")) {
2630
envContent += "\nBETTER_AUTH_URL=http://localhost:3000";
2731
}
28-
29-
if (!envContent.includes("CORS_ORIGIN")) {
30-
envContent += "\nCORS_ORIGIN=http://localhost:3001";
31-
}
32-
33-
const clientEnvPath = path.join(clientDir, ".env");
34-
if (!(await fs.pathExists(clientEnvPath))) {
35-
const clientEnvContent = "VITE_SERVER_URL=http://localhost:3000\n";
36-
await fs.writeFile(clientEnvPath, clientEnvContent);
37-
}
3832
}
3933

4034
if (options.database !== "none") {
@@ -54,4 +48,17 @@ export async function setupEnvironmentVariables(
5448
}
5549

5650
await fs.writeFile(envPath, envContent.trim());
51+
52+
const clientEnvPath = path.join(clientDir, ".env");
53+
let clientEnvContent = "";
54+
55+
if (await fs.pathExists(clientEnvPath)) {
56+
clientEnvContent = await fs.readFile(clientEnvPath, "utf8");
57+
}
58+
59+
if (!clientEnvContent.includes("VITE_SERVER_URL")) {
60+
clientEnvContent += "VITE_SERVER_URL=http://localhost:3000\n";
61+
}
62+
63+
await fs.writeFile(clientEnvPath, clientEnvContent.trim());
5764
}

apps/cli/src/helpers/post-installation.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,32 +33,30 @@ function getDatabaseInstructions(
3333
const instructions = [];
3434

3535
if (orm === "prisma") {
36-
instructions.push(
37-
`${pc.cyan("•")} Apply schema: ${pc.dim(`${runCmd} db:push`)}`,
38-
);
39-
instructions.push(
40-
`${pc.cyan("•")} Database UI: ${pc.dim(`${runCmd} db:studio`)}`,
41-
);
42-
4336
if (database === "sqlite") {
4437
instructions.push(
4538
`${pc.yellow("NOTE:")} Turso support with Prisma is in Early Access and requires additional setup.`,
4639
`${pc.dim("Learn more at: https://www.prisma.io/docs/orm/overview/databases/turso")}`,
4740
);
4841
}
49-
} else if (orm === "drizzle") {
5042
instructions.push(
5143
`${pc.cyan("•")} Apply schema: ${pc.dim(`${runCmd} db:push`)}`,
5244
);
5345
instructions.push(
5446
`${pc.cyan("•")} Database UI: ${pc.dim(`${runCmd} db:studio`)}`,
5547
);
56-
48+
} else if (orm === "drizzle") {
5749
if (database === "sqlite") {
5850
instructions.push(
5951
`${pc.cyan("•")} Start local DB: ${pc.dim(`cd packages/server && ${runCmd} db:local`)}`,
6052
);
6153
}
54+
instructions.push(
55+
`${pc.cyan("•")} Apply schema: ${pc.dim(`${runCmd} db:push`)}`,
56+
);
57+
instructions.push(
58+
`${pc.cyan("•")} Database UI: ${pc.dim(`${runCmd} db:studio`)}`,
59+
);
6260
}
6361

6462
return instructions.length

apps/cli/template/base/packages/client/src/main.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,6 @@ const trpcClient = trpc.createClient({
3131
links: [
3232
httpBatchLink({
3333
url: `${import.meta.env.VITE_SERVER_URL}/trpc`,
34-
fetch(url, options) {
35-
return fetch(url, {
36-
...options,
37-
credentials: "include",
38-
});
39-
},
4034
}),
4135
],
4236
});
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import {
2+
QueryCache,
3+
QueryClient,
4+
QueryClientProvider,
5+
} from "@tanstack/react-query";
6+
import { RouterProvider, createRouter } from "@tanstack/react-router";
7+
import { httpBatchLink } from "@trpc/client";
8+
import { createTRPCQueryUtils } from "@trpc/react-query";
9+
import ReactDOM from "react-dom/client";
10+
import { toast } from "sonner";
11+
import Loader from "./components/loader";
12+
import { routeTree } from "./routeTree.gen";
13+
import { trpc } from "./utils/trpc";
14+
15+
const queryClient = new QueryClient({
16+
queryCache: new QueryCache({
17+
onError: (error) => {
18+
toast.error(error.message, {
19+
action: {
20+
label: "retry",
21+
onClick: () => {
22+
queryClient.invalidateQueries();
23+
},
24+
},
25+
});
26+
},
27+
}),
28+
});
29+
30+
const trpcClient = trpc.createClient({
31+
links: [
32+
httpBatchLink({
33+
url: `${import.meta.env.VITE_SERVER_URL}/trpc`,
34+
fetch(url, options) {
35+
return fetch(url, {
36+
...options,
37+
credentials: "include",
38+
});
39+
},
40+
}),
41+
],
42+
});
43+
44+
export const trpcQueryUtils = createTRPCQueryUtils({
45+
queryClient,
46+
client: trpcClient,
47+
});
48+
49+
const router = createRouter({
50+
routeTree,
51+
defaultPreload: "intent",
52+
context: { trpcQueryUtils },
53+
defaultPendingComponent: () => <Loader />,
54+
Wrap: function WrapComponent({ children }) {
55+
return (
56+
<trpc.Provider client={trpcClient} queryClient={queryClient}>
57+
<QueryClientProvider client={queryClient}>
58+
{children}
59+
</QueryClientProvider>
60+
</trpc.Provider>
61+
);
62+
},
63+
});
64+
65+
// Register things for typesafety
66+
declare module "@tanstack/react-router" {
67+
interface Register {
68+
router: typeof router;
69+
}
70+
}
71+
72+
const rootElement = document.getElementById("app")!;
73+
74+
if (!rootElement.innerHTML) {
75+
const root = ReactDOM.createRoot(rootElement);
76+
root.render(<RouterProvider router={router} />);
77+
}
Lines changed: 5 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,7 @@
11
import { pgTable, text, integer, timestamp, boolean } from "drizzle-orm/pg-core";
22

3-
export const user = pgTable("user", {
4-
id: text("id").primaryKey(),
5-
name: text('name').notNull(),
6-
email: text('email').notNull().unique(),
7-
emailVerified: boolean('email_verified').notNull(),
8-
image: text('image'),
9-
createdAt: timestamp('created_at').notNull(),
10-
updatedAt: timestamp('updated_at').notNull()
11-
});
12-
13-
export const session = pgTable("session", {
14-
id: text("id").primaryKey(),
15-
expiresAt: timestamp('expires_at').notNull(),
16-
token: text('token').notNull().unique(),
17-
createdAt: timestamp('created_at').notNull(),
18-
updatedAt: timestamp('updated_at').notNull(),
19-
ipAddress: text('ip_address'),
20-
userAgent: text('user_agent'),
21-
userId: text('user_id').notNull().references(()=> user.id, { onDelete: 'cascade' })
22-
});
23-
24-
export const account = pgTable("account", {
25-
id: text("id").primaryKey(),
26-
accountId: text('account_id').notNull(),
27-
providerId: text('provider_id').notNull(),
28-
userId: text('user_id').notNull().references(()=> user.id, { onDelete: 'cascade' }),
29-
accessToken: text('access_token'),
30-
refreshToken: text('refresh_token'),
31-
idToken: text('id_token'),
32-
accessTokenExpiresAt: timestamp('access_token_expires_at'),
33-
refreshTokenExpiresAt: timestamp('refresh_token_expires_at'),
34-
scope: text('scope'),
35-
password: text('password'),
36-
createdAt: timestamp('created_at').notNull(),
37-
updatedAt: timestamp('updated_at').notNull()
38-
});
39-
40-
export const verification = pgTable("verification", {
41-
id: text("id").primaryKey(),
42-
identifier: text('identifier').notNull(),
43-
value: text('value').notNull(),
44-
expiresAt: timestamp('expires_at').notNull(),
45-
createdAt: timestamp('created_at'),
46-
updatedAt: timestamp('updated_at')
47-
});
3+
export const todo = pgTable("todo", {
4+
id: serial("id").primaryKey(),
5+
text: text("text").notNull(),
6+
completed: boolean("completed").default(false).notNull()
7+
});
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { pgTable, text, integer, timestamp, boolean } from "drizzle-orm/pg-core";
2+
3+
export const todo = pgTable("todo", {
4+
id: serial("id").primaryKey(),
5+
text: text("text").notNull(),
6+
completed: boolean("completed").default(false).notNull()
7+
});
8+
9+
export const user = pgTable("user", {
10+
id: text("id").primaryKey(),
11+
name: text('name').notNull(),
12+
email: text('email').notNull().unique(),
13+
emailVerified: boolean('email_verified').notNull(),
14+
image: text('image'),
15+
createdAt: timestamp('created_at').notNull(),
16+
updatedAt: timestamp('updated_at').notNull()
17+
});
18+
19+
export const session = pgTable("session", {
20+
id: text("id").primaryKey(),
21+
expiresAt: timestamp('expires_at').notNull(),
22+
token: text('token').notNull().unique(),
23+
createdAt: timestamp('created_at').notNull(),
24+
updatedAt: timestamp('updated_at').notNull(),
25+
ipAddress: text('ip_address'),
26+
userAgent: text('user_agent'),
27+
userId: text('user_id').notNull().references(()=> user.id, { onDelete: 'cascade' })
28+
});
29+
30+
export const account = pgTable("account", {
31+
id: text("id").primaryKey(),
32+
accountId: text('account_id').notNull(),
33+
providerId: text('provider_id').notNull(),
34+
userId: text('user_id').notNull().references(()=> user.id, { onDelete: 'cascade' }),
35+
accessToken: text('access_token'),
36+
refreshToken: text('refresh_token'),
37+
idToken: text('id_token'),
38+
accessTokenExpiresAt: timestamp('access_token_expires_at'),
39+
refreshTokenExpiresAt: timestamp('refresh_token_expires_at'),
40+
scope: text('scope'),
41+
password: text('password'),
42+
createdAt: timestamp('created_at').notNull(),
43+
updatedAt: timestamp('updated_at').notNull()
44+
});
45+
46+
export const verification = pgTable("verification", {
47+
id: text("id").primaryKey(),
48+
identifier: text('identifier').notNull(),
49+
value: text('value').notNull(),
50+
expiresAt: timestamp('expires_at').notNull(),
51+
createdAt: timestamp('created_at'),
52+
updatedAt: timestamp('updated_at')
53+
});

apps/cli/template/with-drizzle-sqlite/packages/server/src/lib/auth.ts renamed to apps/cli/template/with-drizzle-postgres/packages/server/src/with-auth-lib/auth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as schema from "../db/schema";
55

66
export const auth = betterAuth({
77
database: drizzleAdapter(db, {
8-
provider: "sqlite",
8+
provider: "pg",
99
schema: schema,
1010
}),
1111
trustedOrigins: [process.env.CORS_ORIGIN!],

0 commit comments

Comments
 (0)