@riligar/agents-kit 1.20.0 → 1.21.0
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/.agent/rules/naming-conventions.md +17 -17
- package/.agent/skills/riligar-design-system/SKILL.md +97 -32
- package/.agent/skills/riligar-design-system/assets/theme.js +29 -15
- package/.agent/skills/riligar-design-system/references/anti-patterns.md +67 -18
- package/.agent/skills/riligar-design-system/references/design-system.md +125 -61
- package/.agent/skills/riligar-design-system/references/master-patterns.md +181 -13
- package/.agent/skills/riligar-design-system/references/visual-references.md +103 -43
- package/.agent/skills/riligar-dev-database/SKILL.md +15 -15
- package/.agent/skills/riligar-dev-database/references/connection.md +6 -8
- package/.agent/skills/riligar-dev-database/references/queries.md +25 -29
- package/.agent/skills/riligar-dev-database/references/schema.md +32 -18
- package/.agent/skills/riligar-dev-manager/SKILL.md +29 -29
- package/.agent/skills/riligar-infra-stripe/SKILL.md +46 -42
- package/.agent/skills/riligar-infra-stripe/references/stripe-database.md +62 -68
- package/package.json +1 -1
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: riligar-dev-database
|
|
3
|
-
description:
|
|
3
|
+
description: database patterns for RiLiGar using Drizzle ORM + bun:sqlite. Use when setting up database connections, defining schemas, creating migrations, or writing queries. Covers SQLite on Fly.io volumes with the drizzle-kit workflow.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# database — Drizzle + bun:sqlite
|
|
7
7
|
|
|
8
8
|
> SQLite nativo no Bun. Zero drivers externos. Base de dados no volume do Fly.io (`/app/data`).
|
|
9
9
|
|
|
10
10
|
## Referências
|
|
11
11
|
|
|
12
|
-
| Arquivo
|
|
13
|
-
|
|
|
14
|
-
| [connection.md](references/connection.md) | Setup inicial: instalação, db.js, drizzle.config
|
|
15
|
-
| [schema.md](references/schema.md)
|
|
16
|
-
| [migrations.md](references/migrations.md) | Criar e executar migrations com drizzle-kit
|
|
17
|
-
| [queries.md](references/queries.md)
|
|
12
|
+
| Arquivo | Quando usar |
|
|
13
|
+
| ----------------------------------------- | ---------------------------------------------------- |
|
|
14
|
+
| [connection.md](references/connection.md) | Setup inicial: instalação, db.js, drizzle.config |
|
|
15
|
+
| [schema.md](references/schema.md) | Definir tabelas, tipos de colunas, relações |
|
|
16
|
+
| [migrations.md](references/migrations.md) | Criar e executar migrations com drizzle-kit |
|
|
17
|
+
| [queries.md](references/queries.md) | Select, insert, update, delete, queries com relações |
|
|
18
18
|
|
|
19
19
|
## Quick Start
|
|
20
20
|
|
|
21
21
|
```javascript
|
|
22
22
|
// database/db.js
|
|
23
23
|
import { drizzle } from 'drizzle-orm/bun-sqlite'
|
|
24
|
-
import
|
|
24
|
+
import database from 'bun:sqlite'
|
|
25
25
|
|
|
26
|
-
const sqlite = new
|
|
26
|
+
const sqlite = new database(process.env.DB_PATH ?? './database/database.db')
|
|
27
27
|
const db = drizzle({ client: sqlite })
|
|
28
28
|
|
|
29
29
|
export { db }
|
|
@@ -31,15 +31,15 @@ export { db }
|
|
|
31
31
|
|
|
32
32
|
## Regras
|
|
33
33
|
|
|
34
|
-
- **Caminho do banco:** `/app/data/database.db` em produção (volume Fly.io). `./
|
|
34
|
+
- **Caminho do banco:** `/app/data/database.db` em produção (volume Fly.io). `./database/database.db` em desenvolvimento.
|
|
35
35
|
- **Migrations sempre:** Use `drizzle-kit generate` + `drizzle-kit migrate`. Nunca edite migrations à mão.
|
|
36
36
|
- **Schema único:** Todas as tabelas em `database/schema.js`.
|
|
37
37
|
- **Migrations no startup:** Use `migrate()` no `index.js` antes de `.listen()`.
|
|
38
38
|
|
|
39
39
|
## Related Skills
|
|
40
40
|
|
|
41
|
-
| Need
|
|
42
|
-
|
|
|
43
|
-
| **Backend (Elysia)**
|
|
41
|
+
| Need | Skill |
|
|
42
|
+
| --------------------- | ------------------------------------- |
|
|
43
|
+
| **Backend (Elysia)** | @[.agent/skills/riligar-dev-manager] |
|
|
44
44
|
| **Payments (Stripe)** | @[.agent/skills/riligar-infra-stripe] |
|
|
45
|
-
| **Infrastructure**
|
|
45
|
+
| **Infrastructure** | @[.agent/skills/riligar-infra-fly] |
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# database Connection
|
|
2
2
|
|
|
3
3
|
## Installation
|
|
4
4
|
|
|
@@ -14,15 +14,15 @@ No additional drivers needed — `bun:sqlite` is built into Bun.
|
|
|
14
14
|
```javascript
|
|
15
15
|
// database/db.js
|
|
16
16
|
import { drizzle } from 'drizzle-orm/bun-sqlite'
|
|
17
|
-
import
|
|
17
|
+
import database from 'bun:sqlite'
|
|
18
18
|
|
|
19
|
-
const sqlite = new
|
|
19
|
+
const sqlite = new database(process.env.DB_PATH ?? './database/database.db')
|
|
20
20
|
const db = drizzle({ client: sqlite })
|
|
21
21
|
|
|
22
22
|
export { db }
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
- Em **desenvolvimento**: `DB_PATH` não é definido → usa `./
|
|
25
|
+
- Em **desenvolvimento**: `DB_PATH` não é definido → usa `./database/database.db`
|
|
26
26
|
- Em **produção** (Fly.io): `fly secrets set DB_PATH=/app/data/database.db`
|
|
27
27
|
|
|
28
28
|
## drizzle.config.js
|
|
@@ -36,7 +36,7 @@ export default defineConfig({
|
|
|
36
36
|
schema: './database/schema.js',
|
|
37
37
|
out: './database/migrations',
|
|
38
38
|
dbCredentials: {
|
|
39
|
-
url: process.env.DB_PATH ?? './
|
|
39
|
+
url: process.env.DB_PATH ?? './database/database.db',
|
|
40
40
|
},
|
|
41
41
|
})
|
|
42
42
|
```
|
|
@@ -53,9 +53,7 @@ import { db } from './database/db'
|
|
|
53
53
|
await migrate(db, { migrationsFolder: './database/migrations' })
|
|
54
54
|
|
|
55
55
|
// ... resto do setup
|
|
56
|
-
const app = new Elysia()
|
|
57
|
-
.use(routes)
|
|
58
|
-
.listen(3000)
|
|
56
|
+
const app = new Elysia().use(routes).listen(3000)
|
|
59
57
|
```
|
|
60
58
|
|
|
61
59
|
Isso garante que o banco esteja sempre atualizado quando o servidor inicia no Fly.io.
|
|
@@ -17,10 +17,12 @@ import { eq, and, or, desc, asc } from 'drizzle-orm'
|
|
|
17
17
|
const allUsers = await db.select().from(users)
|
|
18
18
|
|
|
19
19
|
// Campos específicos
|
|
20
|
-
const names = await db
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
const names = await db
|
|
21
|
+
.select({
|
|
22
|
+
id: users.id,
|
|
23
|
+
name: users.name,
|
|
24
|
+
})
|
|
25
|
+
.from(users)
|
|
24
26
|
```
|
|
25
27
|
|
|
26
28
|
### Filtrar
|
|
@@ -30,23 +32,24 @@ const names = await db.select({
|
|
|
30
32
|
const [user] = await db.select().from(users).where(eq(users.id, userId)).limit(1)
|
|
31
33
|
|
|
32
34
|
// Múltiplos filtros (AND)
|
|
33
|
-
const results = await db
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
)
|
|
38
|
-
)
|
|
35
|
+
const results = await db
|
|
36
|
+
.select()
|
|
37
|
+
.from(users)
|
|
38
|
+
.where(and(eq(users.active, true), eq(users.plan, 'pro')))
|
|
39
39
|
|
|
40
40
|
// OR
|
|
41
|
-
const results = await db
|
|
42
|
-
|
|
43
|
-
)
|
|
41
|
+
const results = await db
|
|
42
|
+
.select()
|
|
43
|
+
.from(users)
|
|
44
|
+
.where(or(eq(users.id, '1'), eq(users.id, '2')))
|
|
44
45
|
```
|
|
45
46
|
|
|
46
47
|
### Ordenar e Paginar
|
|
47
48
|
|
|
48
49
|
```javascript
|
|
49
|
-
const page = await db
|
|
50
|
+
const page = await db
|
|
51
|
+
.select()
|
|
52
|
+
.from(posts)
|
|
50
53
|
.orderBy(desc(posts.createdAt))
|
|
51
54
|
.limit(10)
|
|
52
55
|
.offset(pageIndex * 10)
|
|
@@ -59,9 +62,7 @@ const page = await db.select().from(posts)
|
|
|
59
62
|
await db.insert(users).values({ name: 'Dan', email: 'dan@email.com' })
|
|
60
63
|
|
|
61
64
|
// Com retorno
|
|
62
|
-
const [user] = await db.insert(users)
|
|
63
|
-
.values({ name: 'Dan', email: 'dan@email.com' })
|
|
64
|
-
.returning()
|
|
65
|
+
const [user] = await db.insert(users).values({ name: 'Dan', email: 'dan@email.com' }).returning()
|
|
65
66
|
|
|
66
67
|
// Múltiplos
|
|
67
68
|
await db.insert(users).values([
|
|
@@ -70,7 +71,8 @@ await db.insert(users).values([
|
|
|
70
71
|
])
|
|
71
72
|
|
|
72
73
|
// Upsert (conflict handling)
|
|
73
|
-
await db
|
|
74
|
+
await db
|
|
75
|
+
.insert(users)
|
|
74
76
|
.values({ id: '1', name: 'Dan' })
|
|
75
77
|
.onConflictDoUpdate({
|
|
76
78
|
target: users.id,
|
|
@@ -81,15 +83,10 @@ await db.insert(users)
|
|
|
81
83
|
## Update
|
|
82
84
|
|
|
83
85
|
```javascript
|
|
84
|
-
await db.update(users)
|
|
85
|
-
.set({ name: 'Mr. Dan', updatedAt: new Date() })
|
|
86
|
-
.where(eq(users.id, userId))
|
|
86
|
+
await db.update(users).set({ name: 'Mr. Dan', updatedAt: new Date() }).where(eq(users.id, userId))
|
|
87
87
|
|
|
88
88
|
// Com retorno
|
|
89
|
-
const [updated] = await db.update(users)
|
|
90
|
-
.set({ plan: 'pro' })
|
|
91
|
-
.where(eq(users.id, userId))
|
|
92
|
-
.returning()
|
|
89
|
+
const [updated] = await db.update(users).set({ plan: 'pro' }).where(eq(users.id, userId)).returning()
|
|
93
90
|
```
|
|
94
91
|
|
|
95
92
|
## Delete
|
|
@@ -98,9 +95,7 @@ const [updated] = await db.update(users)
|
|
|
98
95
|
await db.delete(users).where(eq(users.id, userId))
|
|
99
96
|
|
|
100
97
|
// Com retorno
|
|
101
|
-
const [deleted] = await db.delete(users)
|
|
102
|
-
.where(eq(users.id, userId))
|
|
103
|
-
.returning()
|
|
98
|
+
const [deleted] = await db.delete(users).where(eq(users.id, userId)).returning()
|
|
104
99
|
```
|
|
105
100
|
|
|
106
101
|
## Queries com Relações
|
|
@@ -159,7 +154,8 @@ export async function createUser(data) {
|
|
|
159
154
|
}
|
|
160
155
|
|
|
161
156
|
export async function updateUser(id, data) {
|
|
162
|
-
const [user] = await db
|
|
157
|
+
const [user] = await db
|
|
158
|
+
.update(users)
|
|
163
159
|
.set({ ...data, updatedAt: new Date() })
|
|
164
160
|
.where(eq(users.id, id))
|
|
165
161
|
.returning()
|
|
@@ -8,37 +8,41 @@ import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
|
|
|
8
8
|
import { relations } from 'drizzle-orm'
|
|
9
9
|
|
|
10
10
|
export const users = sqliteTable('users', {
|
|
11
|
-
id: text('id')
|
|
11
|
+
id: text('id')
|
|
12
|
+
.primaryKey()
|
|
13
|
+
.$defaultFn(() => crypto.randomUUID()),
|
|
12
14
|
email: text('email').notNull().unique(),
|
|
13
15
|
name: text('name'),
|
|
14
|
-
createdAt: integer('created_at', { mode: 'timestamp' })
|
|
16
|
+
createdAt: integer('created_at', { mode: 'timestamp' })
|
|
17
|
+
.notNull()
|
|
18
|
+
.$defaultFn(() => new Date()),
|
|
15
19
|
updatedAt: integer('updated_at', { mode: 'timestamp' }),
|
|
16
20
|
})
|
|
17
21
|
```
|
|
18
22
|
|
|
19
23
|
## Tipos de Colunas
|
|
20
24
|
|
|
21
|
-
| Tipo
|
|
22
|
-
|
|
|
23
|
-
| `text()`
|
|
24
|
-
| `integer()` | Números, booleans, timestamps | `integer('age')`
|
|
25
|
-
| `real()`
|
|
26
|
-
| `blob()`
|
|
27
|
-
| `numeric()` | Valores numéricos precisos
|
|
25
|
+
| Tipo | Uso | Exemplo |
|
|
26
|
+
| ----------- | ----------------------------- | ------------------------ |
|
|
27
|
+
| `text()` | Strings, UUIDs, JSON | `text('name').notNull()` |
|
|
28
|
+
| `integer()` | Números, booleans, timestamps | `integer('age')` |
|
|
29
|
+
| `real()` | Decimais (float) | `real('price')` |
|
|
30
|
+
| `blob()` | Dados binários | `blob('avatar')` |
|
|
31
|
+
| `numeric()` | Valores numéricos precisos | `numeric('amount')` |
|
|
28
32
|
|
|
29
33
|
### Modes do `integer()`
|
|
30
34
|
|
|
31
35
|
```javascript
|
|
32
|
-
integer('count')
|
|
33
|
-
integer('active', { mode: 'boolean' })
|
|
34
|
-
integer('created_at', { mode: 'timestamp' })
|
|
36
|
+
integer('count') // número
|
|
37
|
+
integer('active', { mode: 'boolean' }) // boolean (0/1)
|
|
38
|
+
integer('created_at', { mode: 'timestamp' }) // Date (segundos)
|
|
35
39
|
integer('created_at', { mode: 'timestamp_ms' }) // Date (milissegundos)
|
|
36
40
|
```
|
|
37
41
|
|
|
38
42
|
### JSON via `text()`
|
|
39
43
|
|
|
40
44
|
```javascript
|
|
41
|
-
text('metadata', { mode: 'json' })
|
|
45
|
+
text('metadata', { mode: 'json' }) // armazena JSON como text
|
|
42
46
|
```
|
|
43
47
|
|
|
44
48
|
## Primary Key
|
|
@@ -55,11 +59,17 @@ id: integer('id').primaryKey({ autoIncrement: true }),
|
|
|
55
59
|
|
|
56
60
|
```javascript
|
|
57
61
|
export const posts = sqliteTable('posts', {
|
|
58
|
-
id: text('id')
|
|
59
|
-
|
|
62
|
+
id: text('id')
|
|
63
|
+
.primaryKey()
|
|
64
|
+
.$defaultFn(() => crypto.randomUUID()),
|
|
65
|
+
authorId: text('author_id')
|
|
66
|
+
.notNull()
|
|
67
|
+
.references(() => users.id),
|
|
60
68
|
title: text('title').notNull(),
|
|
61
69
|
body: text('body'),
|
|
62
|
-
createdAt: integer('created_at', { mode: 'timestamp' })
|
|
70
|
+
createdAt: integer('created_at', { mode: 'timestamp' })
|
|
71
|
+
.notNull()
|
|
72
|
+
.$defaultFn(() => new Date()),
|
|
63
73
|
})
|
|
64
74
|
```
|
|
65
75
|
|
|
@@ -84,8 +94,12 @@ export const postsRelations = relations(posts, ({ one }) => ({
|
|
|
84
94
|
|
|
85
95
|
```javascript
|
|
86
96
|
export const usersToGroups = sqliteTable('users_to_groups', {
|
|
87
|
-
userId: text('user_id')
|
|
88
|
-
|
|
97
|
+
userId: text('user_id')
|
|
98
|
+
.notNull()
|
|
99
|
+
.references(() => users.id),
|
|
100
|
+
groupId: text('group_id')
|
|
101
|
+
.notNull()
|
|
102
|
+
.references(() => groups.id),
|
|
89
103
|
})
|
|
90
104
|
|
|
91
105
|
export const usersRelations = relations(users, ({ many }) => ({
|
|
@@ -22,21 +22,21 @@ const app = new Elysia()
|
|
|
22
22
|
.post('/users', ({ body }) => createUser(body), {
|
|
23
23
|
body: t.Object({
|
|
24
24
|
name: t.String(),
|
|
25
|
-
email: t.String({ format: 'email' })
|
|
26
|
-
})
|
|
25
|
+
email: t.String({ format: 'email' }),
|
|
26
|
+
}),
|
|
27
27
|
})
|
|
28
28
|
.listen(3000)
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
## Content Map
|
|
32
32
|
|
|
33
|
-
| File
|
|
34
|
-
|
|
|
35
|
-
| [elysia-basics.md](references/elysia-basics.md)
|
|
36
|
-
| [elysia-plugins.md](references/elysia-plugins.md)
|
|
37
|
-
| [elysia-validation.md](references/elysia-validation.md) | TypeBox validation (body, query, params) | Input validation
|
|
38
|
-
| [elysia-lifecycle.md](references/elysia-lifecycle.md)
|
|
39
|
-
| [elysia-patterns.md](references/elysia-patterns.md)
|
|
33
|
+
| File | Description | When to Read |
|
|
34
|
+
| ------------------------------------------------------- | ---------------------------------------- | ----------------------- |
|
|
35
|
+
| [elysia-basics.md](references/elysia-basics.md) | Setup, routes, handlers, context | Starting new project |
|
|
36
|
+
| [elysia-plugins.md](references/elysia-plugins.md) | Plugins, guards, modular design | Organizing code |
|
|
37
|
+
| [elysia-validation.md](references/elysia-validation.md) | TypeBox validation (body, query, params) | Input validation |
|
|
38
|
+
| [elysia-lifecycle.md](references/elysia-lifecycle.md) | Hooks (onBeforeHandle, onError, etc.) | Middleware, auth checks |
|
|
39
|
+
| [elysia-patterns.md](references/elysia-patterns.md) | REST patterns, responses, pagination | API design |
|
|
40
40
|
|
|
41
41
|
## Project Structure
|
|
42
42
|
|
|
@@ -61,13 +61,13 @@ src/
|
|
|
61
61
|
|
|
62
62
|
## Dependencies
|
|
63
63
|
|
|
64
|
-
| Pacote
|
|
65
|
-
|
|
66
|
-
| `bun`
|
|
67
|
-
| `elysia`
|
|
68
|
-
| `bun:sqlite`
|
|
69
|
-
| `drizzle-orm` | latest
|
|
70
|
-
| `bun:s3`
|
|
64
|
+
| Pacote | Versão | Descrição |
|
|
65
|
+
| ------------- | ------- | -------------- |
|
|
66
|
+
| `bun` | latest | Runtime |
|
|
67
|
+
| `elysia` | latest | Framework HTTP |
|
|
68
|
+
| `bun:sqlite` | builtin | SQLite driver |
|
|
69
|
+
| `drizzle-orm` | latest | ORM |
|
|
70
|
+
| `bun:s3` | latest | S3/R2 Storage |
|
|
71
71
|
|
|
72
72
|
## Core Patterns
|
|
73
73
|
|
|
@@ -84,8 +84,8 @@ export const userRoutes = new Elysia({ prefix: '/users' })
|
|
|
84
84
|
.post('/', ({ body }) => createUser(body), {
|
|
85
85
|
body: t.Object({
|
|
86
86
|
name: t.String({ minLength: 1 }),
|
|
87
|
-
email: t.String({ format: 'email' })
|
|
88
|
-
})
|
|
87
|
+
email: t.String({ format: 'email' }),
|
|
88
|
+
}),
|
|
89
89
|
})
|
|
90
90
|
```
|
|
91
91
|
|
|
@@ -112,11 +112,11 @@ console.log(`Server running at ${app.server?.url}`)
|
|
|
112
112
|
|
|
113
113
|
## Related Skills
|
|
114
114
|
|
|
115
|
-
| Need
|
|
116
|
-
|
|
|
115
|
+
| Need | Skill |
|
|
116
|
+
| ------------------ | ---------------------------------------- |
|
|
117
117
|
| **Authentication** | @[.agent/skills/riligar-dev-auth-elysia] |
|
|
118
|
-
| **
|
|
119
|
-
| **Infrastructure** | @[.agent/skills/riligar-infra-fly]
|
|
118
|
+
| **database** | @[.agent/skills/riligar-dev-database] |
|
|
119
|
+
| **Infrastructure** | @[.agent/skills/riligar-infra-fly] |
|
|
120
120
|
|
|
121
121
|
## Decision Checklist
|
|
122
122
|
|
|
@@ -126,14 +126,14 @@ Before building an API:
|
|
|
126
126
|
- [ ] Planned validation for all inputs?
|
|
127
127
|
- [ ] Error handling configured?
|
|
128
128
|
- [ ] Auth middleware needed? → Use `riligar-dev-auth-elysia`
|
|
129
|
-
- [ ]
|
|
129
|
+
- [ ] database connection setup? → Use `riligar-dev-database`
|
|
130
130
|
|
|
131
131
|
## Anti-Patterns
|
|
132
132
|
|
|
133
|
-
| Don't
|
|
134
|
-
|
|
|
135
|
-
| Put business logic in handlers
|
|
136
|
-
| Skip input validation
|
|
137
|
-
| Ignore error handling
|
|
138
|
-
| Create monolithic files
|
|
133
|
+
| Don't | Do |
|
|
134
|
+
| -------------------------------- | ------------------------ |
|
|
135
|
+
| Put business logic in handlers | Extract to `services/` |
|
|
136
|
+
| Skip input validation | Use TypeBox (`t.Object`) |
|
|
137
|
+
| Ignore error handling | Use `onError` lifecycle |
|
|
138
|
+
| Create monolithic files | Split into plugins |
|
|
139
139
|
| Use verbs in routes (`/getUser`) | Use nouns (`/users/:id`) |
|
|
@@ -21,8 +21,9 @@ Pergunte ao usuário:
|
|
|
21
21
|
> Você pode encontrá-las em: https://dashboard.stripe.com/apikeys
|
|
22
22
|
>
|
|
23
23
|
> Por favor, me forneça:
|
|
24
|
-
>
|
|
25
|
-
>
|
|
24
|
+
>
|
|
25
|
+
> 1. **Publishable Key** (pk*live*... ou pk*test*...)
|
|
26
|
+
> 2. **Secret Key** (sk*live*... ou sk*test*...)
|
|
26
27
|
|
|
27
28
|
Aguarde as chaves antes de prosseguir.
|
|
28
29
|
|
|
@@ -36,6 +37,7 @@ Após receber as chaves, pergunte:
|
|
|
36
37
|
> 2. **Quais planos/produtos** você quer oferecer?
|
|
37
38
|
>
|
|
38
39
|
> Exemplo de resposta:
|
|
40
|
+
>
|
|
39
41
|
> - Assinatura mensal
|
|
40
42
|
> - Plano Starter: R$ 29/mês (5 projetos, suporte email)
|
|
41
43
|
> - Plano Pro: R$ 99/mês (ilimitado, suporte prioritário)
|
|
@@ -57,17 +59,17 @@ async function setupProducts() {
|
|
|
57
59
|
{
|
|
58
60
|
name: 'Plano Starter',
|
|
59
61
|
description: '5 projetos, suporte email',
|
|
60
|
-
price: 2900,
|
|
62
|
+
price: 2900, // R$ 29,00 em centavos
|
|
61
63
|
interval: 'month',
|
|
62
|
-
features: ['5 projetos', 'Suporte email', '1GB storage']
|
|
64
|
+
features: ['5 projetos', 'Suporte email', '1GB storage'],
|
|
63
65
|
},
|
|
64
66
|
{
|
|
65
67
|
name: 'Plano Pro',
|
|
66
68
|
description: 'Ilimitado, suporte prioritário',
|
|
67
|
-
price: 9900,
|
|
69
|
+
price: 9900, // R$ 99,00 em centavos
|
|
68
70
|
interval: 'month',
|
|
69
|
-
features: ['Projetos ilimitados', 'Suporte prioritário', '10GB storage']
|
|
70
|
-
}
|
|
71
|
+
features: ['Projetos ilimitados', 'Suporte prioritário', '10GB storage'],
|
|
72
|
+
},
|
|
71
73
|
]
|
|
72
74
|
|
|
73
75
|
console.log('Criando produtos no Stripe...\n')
|
|
@@ -76,14 +78,14 @@ async function setupProducts() {
|
|
|
76
78
|
const stripeProduct = await stripe.products.create({
|
|
77
79
|
name: product.name,
|
|
78
80
|
description: product.description,
|
|
79
|
-
metadata: { features: JSON.stringify(product.features) }
|
|
81
|
+
metadata: { features: JSON.stringify(product.features) },
|
|
80
82
|
})
|
|
81
83
|
|
|
82
84
|
const stripePrice = await stripe.prices.create({
|
|
83
85
|
product: stripeProduct.id,
|
|
84
86
|
unit_amount: product.price,
|
|
85
87
|
currency: 'brl',
|
|
86
|
-
recurring: product.interval ? { interval: product.interval } : undefined
|
|
88
|
+
recurring: product.interval ? { interval: product.interval } : undefined,
|
|
87
89
|
})
|
|
88
90
|
|
|
89
91
|
console.log(`✓ ${product.name}`)
|
|
@@ -111,6 +113,7 @@ Após executar o script, peça:
|
|
|
111
113
|
Com as chaves e Price IDs, configure os arquivos de ambiente:
|
|
112
114
|
|
|
113
115
|
**Backend: `.env.development` e `.env.production`**
|
|
116
|
+
|
|
114
117
|
```bash
|
|
115
118
|
# .env.development (chaves de teste)
|
|
116
119
|
STRIPE_SECRET_KEY=sk_test_...
|
|
@@ -122,6 +125,7 @@ STRIPE_WEBHOOK_SECRET=whsec_...
|
|
|
122
125
|
```
|
|
123
126
|
|
|
124
127
|
**Frontend: `.env.development` e `.env.production`**
|
|
128
|
+
|
|
125
129
|
```bash
|
|
126
130
|
# .env.development
|
|
127
131
|
VITE_STRIPE_PUBLISHABLE_KEY=pk_test_...
|
|
@@ -130,7 +134,7 @@ VITE_STRIPE_PUBLISHABLE_KEY=pk_test_...
|
|
|
130
134
|
VITE_STRIPE_PUBLISHABLE_KEY=pk_live_...
|
|
131
135
|
```
|
|
132
136
|
|
|
133
|
-
### Step 6: Configurar
|
|
137
|
+
### Step 6: Configurar database
|
|
134
138
|
|
|
135
139
|
Gere a migration para adicionar campos do Stripe:
|
|
136
140
|
|
|
@@ -153,29 +157,29 @@ Instrua o usuário:
|
|
|
153
157
|
> 2. Clique em "Add endpoint"
|
|
154
158
|
> 3. URL: `https://seu-dominio.com/api/webhook`
|
|
155
159
|
> 4. Selecione os eventos:
|
|
156
|
-
>
|
|
157
|
-
>
|
|
158
|
-
>
|
|
159
|
-
>
|
|
160
|
-
>
|
|
161
|
-
> 5. Copie o "Signing secret" (
|
|
160
|
+
> - `checkout.session.completed`
|
|
161
|
+
> - `customer.subscription.updated`
|
|
162
|
+
> - `customer.subscription.deleted`
|
|
163
|
+
> - `invoice.paid`
|
|
164
|
+
> - `invoice.payment_failed`
|
|
165
|
+
> 5. Copie o "Signing secret" (whsec\_...)
|
|
162
166
|
> 6. Adicione ao `.env.development` e `.env.production`
|
|
163
167
|
|
|
164
168
|
### Step 8: Gerar Código
|
|
165
169
|
|
|
166
170
|
Gere todos os arquivos necessários usando os templates de [assets/](assets/):
|
|
167
171
|
|
|
168
|
-
| Arquivo
|
|
169
|
-
|
|
|
170
|
-
| `plugins/stripe.js`
|
|
171
|
-
| `routes/billing.js`
|
|
172
|
-
| `routes/webhook.js`
|
|
173
|
-
| `services/billing.js`
|
|
174
|
-
| `config/stripe-prices.js`
|
|
175
|
-
| `config/plans.js`
|
|
176
|
-
| `pages/Pricing.jsx`
|
|
177
|
-
| `components/BillingSettings.jsx` | stripe-client.js (seção 4)
|
|
178
|
-
| `hooks/useSubscription.js`
|
|
172
|
+
| Arquivo | Baseado em |
|
|
173
|
+
| -------------------------------- | ------------------------------- |
|
|
174
|
+
| `plugins/stripe.js` | stripe-server.js (seção 1) |
|
|
175
|
+
| `routes/billing.js` | stripe-server.js (seção 2) |
|
|
176
|
+
| `routes/webhook.js` | stripe-server.js (seção 3) |
|
|
177
|
+
| `services/billing.js` | stripe-server.js (seção 4) |
|
|
178
|
+
| `config/stripe-prices.js` | Price IDs coletados (Step 9) |
|
|
179
|
+
| `config/plans.js` | PLAN_MAP + PLAN_LIMITS (Step 9) |
|
|
180
|
+
| `pages/Pricing.jsx` | stripe-client.js (seção 3) |
|
|
181
|
+
| `components/BillingSettings.jsx` | stripe-client.js (seção 4) |
|
|
182
|
+
| `hooks/useSubscription.js` | stripe-client.js (seção 2) |
|
|
179
183
|
|
|
180
184
|
### Step 9: Criar Configs de Planos e Preços
|
|
181
185
|
|
|
@@ -188,24 +192,24 @@ export const STRIPE_PRICES = {
|
|
|
188
192
|
priceId: 'price_COLETADO_STARTER',
|
|
189
193
|
name: 'Starter',
|
|
190
194
|
price: 29,
|
|
191
|
-
features: ['5 projetos', 'Suporte email', '1GB storage']
|
|
195
|
+
features: ['5 projetos', 'Suporte email', '1GB storage'],
|
|
192
196
|
},
|
|
193
197
|
pro: {
|
|
194
198
|
priceId: 'price_COLETADO_PRO',
|
|
195
199
|
name: 'Pro',
|
|
196
200
|
price: 99,
|
|
197
|
-
features: ['Projetos ilimitados', 'Suporte prioritário', '10GB storage']
|
|
201
|
+
features: ['Projetos ilimitados', 'Suporte prioritário', '10GB storage'],
|
|
198
202
|
},
|
|
199
203
|
enterprise: {
|
|
200
204
|
priceId: 'price_COLETADO_ENTERPRISE',
|
|
201
205
|
name: 'Enterprise',
|
|
202
206
|
price: 299,
|
|
203
|
-
features: ['Tudo do Pro', 'Storage ilimitado', 'SLA garantido']
|
|
204
|
-
}
|
|
207
|
+
features: ['Tudo do Pro', 'Storage ilimitado', 'SLA garantido'],
|
|
208
|
+
},
|
|
205
209
|
}
|
|
206
210
|
|
|
207
|
-
export const getPrice =
|
|
208
|
-
export const getPriceId =
|
|
211
|
+
export const getPrice = plan => STRIPE_PRICES[plan]
|
|
212
|
+
export const getPriceId = plan => STRIPE_PRICES[plan]?.priceId
|
|
209
213
|
```
|
|
210
214
|
|
|
211
215
|
**B) Arquivo de mapeamento e limites (config/plans.js):**
|
|
@@ -215,9 +219,9 @@ export const getPriceId = (plan) => STRIPE_PRICES[plan]?.priceId
|
|
|
215
219
|
|
|
216
220
|
// Mapeia Price IDs do Stripe para nomes de planos internos
|
|
217
221
|
export const PLAN_MAP = {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
222
|
+
price_COLETADO_STARTER: 'starter',
|
|
223
|
+
price_COLETADO_PRO: 'pro',
|
|
224
|
+
price_COLETADO_ENTERPRISE: 'enterprise',
|
|
221
225
|
}
|
|
222
226
|
|
|
223
227
|
// Define limites de features por plano
|
|
@@ -283,12 +287,12 @@ stripe trigger checkout.session.completed
|
|
|
283
287
|
|
|
284
288
|
## Specialized Guides
|
|
285
289
|
|
|
286
|
-
| Guide
|
|
287
|
-
|
|
|
288
|
-
| [stripe-elysia.md](references/stripe-elysia.md)
|
|
289
|
-
| [stripe-react.md](references/stripe-react.md)
|
|
290
|
-
| [stripe-webhooks.md](references/stripe-webhooks.md) | Handlers de eventos
|
|
291
|
-
| [stripe-database.md](references/stripe-database.md) | Schema Drizzle
|
|
290
|
+
| Guide | Content |
|
|
291
|
+
| --------------------------------------------------- | ------------------------- |
|
|
292
|
+
| [stripe-elysia.md](references/stripe-elysia.md) | Backend routes completas |
|
|
293
|
+
| [stripe-react.md](references/stripe-react.md) | Componentes React/Mantine |
|
|
294
|
+
| [stripe-webhooks.md](references/stripe-webhooks.md) | Handlers de eventos |
|
|
295
|
+
| [stripe-database.md](references/stripe-database.md) | Schema Drizzle |
|
|
292
296
|
|
|
293
297
|
---
|
|
294
298
|
|