@sansavision/create-pulse 0.4.2 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/templates/nextjs-auth-demo/README.md +61 -10
- package/templates/nextjs-auth-demo/package.json +2 -0
- package/templates/nextjs-auth-demo/src/lib/auth.ts +2 -1
- package/templates/nextjs-auth-demo/src/lib/db.ts +2 -1
- package/templates/nextjs-auth-demo/src/lib/schema.ts +107 -0
package/package.json
CHANGED
|
@@ -13,26 +13,58 @@ An **investor-ready** demo application showcasing the full capabilities of [Puls
|
|
|
13
13
|
|
|
14
14
|
## Quick Start
|
|
15
15
|
|
|
16
|
+
### 1. Install dependencies
|
|
17
|
+
|
|
16
18
|
```bash
|
|
17
|
-
# 1. Install dependencies
|
|
18
19
|
npm install
|
|
20
|
+
```
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
### 2. Configure environment
|
|
23
|
+
|
|
24
|
+
```bash
|
|
21
25
|
cp .env.example .env
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Edit `.env` and set your secret (generate one with `openssl rand -base64 32`):
|
|
29
|
+
|
|
30
|
+
```env
|
|
31
|
+
BETTER_AUTH_SECRET=your-generated-secret-here
|
|
32
|
+
BETTER_AUTH_URL=http://localhost:3000
|
|
33
|
+
NEXT_PUBLIC_PULSE_URL=ws://localhost:4001
|
|
34
|
+
```
|
|
22
35
|
|
|
23
|
-
|
|
24
|
-
openssl rand -base64 32
|
|
36
|
+
> **Note:** `BETTER_AUTH_URL` must match the port your app runs on. If you use `--port 3444`, set it to `http://localhost:3444`.
|
|
25
37
|
|
|
26
|
-
|
|
27
|
-
npx @auth/cli migrate
|
|
38
|
+
### 3. Create database tables
|
|
28
39
|
|
|
29
|
-
|
|
30
|
-
npx @sansavision/pulse-cli serve --auth-mode webhook --auth-webhook http://localhost:3000/api/pulse/verify
|
|
40
|
+
The auth schema is pre-generated in `src/lib/schema.ts`. Push it to SQLite:
|
|
31
41
|
|
|
32
|
-
|
|
42
|
+
```bash
|
|
43
|
+
npm run db:push
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
> This creates the `user`, `session`, `account`, and `verification` tables in `./sqlite.db`.
|
|
47
|
+
|
|
48
|
+
### 4. Start the Pulse relay
|
|
49
|
+
|
|
50
|
+
In a separate terminal, start the relay with webhook auth:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx @sansavision/pulse-cli serve \
|
|
54
|
+
--auth-mode webhook \
|
|
55
|
+
--auth-webhook http://localhost:3000/api/pulse/verify
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
> If your app runs on a different port, update the `--auth-webhook` URL accordingly.
|
|
59
|
+
|
|
60
|
+
### 5. Start the app
|
|
61
|
+
|
|
62
|
+
```bash
|
|
33
63
|
npm run dev
|
|
34
64
|
```
|
|
35
65
|
|
|
66
|
+
Open [http://localhost:3000](http://localhost:3000) in your browser.
|
|
67
|
+
|
|
36
68
|
## Multi-User Testing
|
|
37
69
|
|
|
38
70
|
1. Open `http://localhost:3000` in a browser
|
|
@@ -70,5 +102,24 @@ Next.js App (port 3000) Pulse Relay (port 4001)
|
|
|
70
102
|
| Variable | Description | Default |
|
|
71
103
|
|----------|-------------|---------|
|
|
72
104
|
| `BETTER_AUTH_SECRET` | Auth encryption secret (32+ chars) | — |
|
|
73
|
-
| `BETTER_AUTH_URL` | Base URL of the app | `http://localhost:3000` |
|
|
105
|
+
| `BETTER_AUTH_URL` | Base URL of the app (must match your port) | `http://localhost:3000` |
|
|
74
106
|
| `NEXT_PUBLIC_PULSE_URL` | Pulse relay WebSocket URL | `ws://localhost:4001` |
|
|
107
|
+
|
|
108
|
+
## Troubleshooting
|
|
109
|
+
|
|
110
|
+
### "Invalid origin" error on signup
|
|
111
|
+
|
|
112
|
+
Your `BETTER_AUTH_URL` doesn't match the port the app is running on. For example, if you start the app with `--port 3444`, set `BETTER_AUTH_URL=http://localhost:3444` in `.env`.
|
|
113
|
+
|
|
114
|
+
### "Connecting to Pulse relay..." stays loading
|
|
115
|
+
|
|
116
|
+
Make sure the Pulse relay is running in a separate terminal with `--auth-mode webhook`. Without auth flags, the relay won't accept authenticated connections.
|
|
117
|
+
|
|
118
|
+
### Database errors
|
|
119
|
+
|
|
120
|
+
If you see table-related errors, re-run the schema push:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
rm -f sqlite.db
|
|
124
|
+
npm run db:push
|
|
125
|
+
```
|
|
@@ -2,9 +2,10 @@ import { betterAuth } from "better-auth";
|
|
|
2
2
|
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
|
3
3
|
import { bearer } from "better-auth/plugins";
|
|
4
4
|
import { db } from "./db";
|
|
5
|
+
import * as schema from "./schema";
|
|
5
6
|
|
|
6
7
|
export const auth = betterAuth({
|
|
7
|
-
database: drizzleAdapter(db, { provider: "sqlite" }),
|
|
8
|
+
database: drizzleAdapter(db, { provider: "sqlite", schema }),
|
|
8
9
|
emailAndPassword: {
|
|
9
10
|
enabled: true,
|
|
10
11
|
},
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { relations, sql } from "drizzle-orm";
|
|
2
|
+
import { sqliteTable, text, integer, index } from "drizzle-orm/sqlite-core";
|
|
3
|
+
|
|
4
|
+
export const user = sqliteTable("user", {
|
|
5
|
+
id: text("id").primaryKey(),
|
|
6
|
+
name: text("name").notNull(),
|
|
7
|
+
email: text("email").notNull().unique(),
|
|
8
|
+
emailVerified: integer("email_verified", { mode: "boolean" })
|
|
9
|
+
.default(false)
|
|
10
|
+
.notNull(),
|
|
11
|
+
image: text("image"),
|
|
12
|
+
createdAt: integer("created_at", { mode: "timestamp_ms" })
|
|
13
|
+
.default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
|
|
14
|
+
.notNull(),
|
|
15
|
+
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
|
|
16
|
+
.default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
|
|
17
|
+
.$onUpdate(() => /* @__PURE__ */ new Date())
|
|
18
|
+
.notNull(),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const session = sqliteTable(
|
|
22
|
+
"session",
|
|
23
|
+
{
|
|
24
|
+
id: text("id").primaryKey(),
|
|
25
|
+
expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
|
|
26
|
+
token: text("token").notNull().unique(),
|
|
27
|
+
createdAt: integer("created_at", { mode: "timestamp_ms" })
|
|
28
|
+
.default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
|
|
29
|
+
.notNull(),
|
|
30
|
+
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
|
|
31
|
+
.$onUpdate(() => /* @__PURE__ */ new Date())
|
|
32
|
+
.notNull(),
|
|
33
|
+
ipAddress: text("ip_address"),
|
|
34
|
+
userAgent: text("user_agent"),
|
|
35
|
+
userId: text("user_id")
|
|
36
|
+
.notNull()
|
|
37
|
+
.references(() => user.id, { onDelete: "cascade" }),
|
|
38
|
+
},
|
|
39
|
+
(table) => [index("session_userId_idx").on(table.userId)],
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
export const account = sqliteTable(
|
|
43
|
+
"account",
|
|
44
|
+
{
|
|
45
|
+
id: text("id").primaryKey(),
|
|
46
|
+
accountId: text("account_id").notNull(),
|
|
47
|
+
providerId: text("provider_id").notNull(),
|
|
48
|
+
userId: text("user_id")
|
|
49
|
+
.notNull()
|
|
50
|
+
.references(() => user.id, { onDelete: "cascade" }),
|
|
51
|
+
accessToken: text("access_token"),
|
|
52
|
+
refreshToken: text("refresh_token"),
|
|
53
|
+
idToken: text("id_token"),
|
|
54
|
+
accessTokenExpiresAt: integer("access_token_expires_at", {
|
|
55
|
+
mode: "timestamp_ms",
|
|
56
|
+
}),
|
|
57
|
+
refreshTokenExpiresAt: integer("refresh_token_expires_at", {
|
|
58
|
+
mode: "timestamp_ms",
|
|
59
|
+
}),
|
|
60
|
+
scope: text("scope"),
|
|
61
|
+
password: text("password"),
|
|
62
|
+
createdAt: integer("created_at", { mode: "timestamp_ms" })
|
|
63
|
+
.default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
|
|
64
|
+
.notNull(),
|
|
65
|
+
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
|
|
66
|
+
.$onUpdate(() => /* @__PURE__ */ new Date())
|
|
67
|
+
.notNull(),
|
|
68
|
+
},
|
|
69
|
+
(table) => [index("account_userId_idx").on(table.userId)],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
export const verification = sqliteTable(
|
|
73
|
+
"verification",
|
|
74
|
+
{
|
|
75
|
+
id: text("id").primaryKey(),
|
|
76
|
+
identifier: text("identifier").notNull(),
|
|
77
|
+
value: text("value").notNull(),
|
|
78
|
+
expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
|
|
79
|
+
createdAt: integer("created_at", { mode: "timestamp_ms" })
|
|
80
|
+
.default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
|
|
81
|
+
.notNull(),
|
|
82
|
+
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
|
|
83
|
+
.default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
|
|
84
|
+
.$onUpdate(() => /* @__PURE__ */ new Date())
|
|
85
|
+
.notNull(),
|
|
86
|
+
},
|
|
87
|
+
(table) => [index("verification_identifier_idx").on(table.identifier)],
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
export const userRelations = relations(user, ({ many }) => ({
|
|
91
|
+
sessions: many(session),
|
|
92
|
+
accounts: many(account),
|
|
93
|
+
}));
|
|
94
|
+
|
|
95
|
+
export const sessionRelations = relations(session, ({ one }) => ({
|
|
96
|
+
user: one(user, {
|
|
97
|
+
fields: [session.userId],
|
|
98
|
+
references: [user.id],
|
|
99
|
+
}),
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
export const accountRelations = relations(account, ({ one }) => ({
|
|
103
|
+
user: one(user, {
|
|
104
|
+
fields: [account.userId],
|
|
105
|
+
references: [user.id],
|
|
106
|
+
}),
|
|
107
|
+
}));
|