@nordsym/apiclaw 1.0.0 → 1.1.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/AGENTS.md +74 -0
- package/HEARTBEAT.md +4 -0
- package/IDENTITY.md +22 -0
- package/README.md +197 -202
- package/SOUL.md +36 -0
- package/STATUS.md +237 -0
- package/TOOLS.md +36 -0
- package/USER.md +17 -0
- package/{backend/convex → convex}/_generated/api.d.ts +6 -6
- package/convex/credits.ts +211 -0
- package/convex/http.ts +490 -0
- package/convex/providers.ts +516 -0
- package/convex/purchases.ts +183 -0
- package/convex/schema.ts +180 -0
- package/convex.json +3 -0
- package/dist/credentials.d.ts +19 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +158 -0
- package/dist/credentials.js.map +1 -0
- package/dist/credits.d.ts +14 -11
- package/dist/credits.d.ts.map +1 -1
- package/dist/credits.js +151 -99
- package/dist/credits.js.map +1 -1
- package/dist/discovery.d.ts +7 -16
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +33 -40
- package/dist/discovery.js.map +1 -1
- package/dist/execute.d.ts +19 -0
- package/dist/execute.d.ts.map +1 -0
- package/dist/execute.js +285 -0
- package/dist/execute.js.map +1 -0
- package/dist/index.js +106 -30
- package/dist/index.js.map +1 -1
- package/dist/proxy.d.ts +6 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +19 -0
- package/dist/proxy.js.map +1 -0
- package/dist/registry/apis.json +95362 -202
- package/dist/registry/apis_expanded.json +100853 -0
- package/dist/stripe.d.ts +68 -0
- package/dist/stripe.d.ts.map +1 -0
- package/dist/stripe.js +196 -0
- package/dist/stripe.js.map +1 -0
- package/dist/test.d.ts +3 -2
- package/dist/test.d.ts.map +1 -1
- package/dist/test.js +105 -75
- package/dist/test.js.map +1 -1
- package/dist/types.d.ts +0 -28
- package/dist/types.d.ts.map +1 -1
- package/dist/webhook.d.ts +2 -0
- package/dist/webhook.d.ts.map +1 -0
- package/dist/webhook.js +90 -0
- package/dist/webhook.js.map +1 -0
- package/landing/DESIGN.md +343 -0
- package/landing/package-lock.json +1190 -40
- package/landing/package.json +5 -2
- package/landing/public/android-chrome-192x192.png +0 -0
- package/landing/public/android-chrome-512x512.png +0 -0
- package/landing/public/apple-touch-icon.png +0 -0
- package/landing/public/demo.gif +0 -0
- package/landing/public/demo.mp4 +0 -0
- package/landing/public/favicon-16x16.png +0 -0
- package/landing/public/favicon-32x32.png +0 -0
- package/landing/public/favicon.ico +0 -0
- package/landing/public/favicon.svg +3 -0
- package/landing/public/icon.svg +47 -0
- package/landing/public/logo-mono.svg +37 -0
- package/landing/public/logo-simple.svg +45 -0
- package/landing/public/logo.svg +84 -0
- package/landing/public/og-image.png +0 -0
- package/landing/public/og-template.html +184 -0
- package/landing/public/site.webmanifest +31 -0
- package/landing/scripts/generate-assets.js +284 -0
- package/landing/scripts/generate-pngs.js +48 -0
- package/landing/scripts/generate-stats.js +42 -0
- package/landing/src/app/admin/page.tsx +348 -0
- package/landing/src/app/api/auth/magic-link/route.ts +73 -0
- package/landing/src/app/api/auth/session/route.ts +38 -0
- package/landing/src/app/api/auth/verify/route.ts +43 -0
- package/landing/src/app/api/og/route.tsx +74 -0
- package/landing/src/app/globals.css +439 -100
- package/landing/src/app/layout.tsx +37 -9
- package/landing/src/app/page.tsx +640 -552
- package/landing/src/app/providers/dashboard/login/page.tsx +176 -0
- package/landing/src/app/providers/dashboard/page.tsx +589 -0
- package/landing/src/app/providers/dashboard/verify/page.tsx +106 -0
- package/landing/src/app/providers/layout.tsx +14 -0
- package/landing/src/app/providers/page.tsx +402 -0
- package/landing/src/app/providers/register/page.tsx +670 -0
- package/landing/src/components/ProviderDashboard.tsx +794 -0
- package/landing/src/hooks/useDashboardData.ts +99 -0
- package/landing/src/lib/apis.json +116054 -0
- package/landing/src/lib/convex-client.ts +106 -0
- package/landing/src/lib/mock-data.ts +285 -0
- package/landing/src/lib/stats.json +6 -0
- package/landing/tailwind.config.ts +12 -11
- package/landing/tsconfig.tsbuildinfo +1 -0
- package/package.json +21 -20
- package/scripts/SYMBOT-FIX.md +238 -0
- package/scripts/demo-simulation.py +177 -0
- package/scripts/expand-more.py +502 -0
- package/scripts/expand-registry.py +434 -0
- package/scripts/history-sanitizer.ts +272 -0
- package/scripts/mass-scrape.py +1308 -0
- package/scripts/sync-and-deploy.sh +36 -0
- package/src/credentials.ts +177 -0
- package/src/credits.ts +190 -122
- package/src/discovery.ts +45 -58
- package/src/execute.ts +350 -0
- package/src/index.ts +113 -31
- package/src/proxy.ts +24 -0
- package/src/registry/apis.json +95362 -202
- package/src/registry/apis_expanded.json +100853 -0
- package/src/stripe.ts +243 -0
- package/src/test.ts +127 -89
- package/src/types.ts +0 -34
- package/src/webhook.ts +107 -0
- package/.github/ISSUE_TEMPLATE/add-api.yml +0 -123
- package/BRIEFING.md +0 -30
- package/backend/convex/apiKeys.ts +0 -75
- package/backend/convex/purchases.ts +0 -74
- package/backend/convex/schema.ts +0 -45
- package/backend/convex/transactions.ts +0 -57
- package/backend/convex/users.ts +0 -94
- package/backend/package-lock.json +0 -521
- package/backend/package.json +0 -15
- package/dist/registry/parse_apis.py +0 -146
- package/dist/revenuecat.d.ts +0 -61
- package/dist/revenuecat.d.ts.map +0 -1
- package/dist/revenuecat.js +0 -166
- package/dist/revenuecat.js.map +0 -1
- package/dist/webhooks/revenuecat.d.ts +0 -48
- package/dist/webhooks/revenuecat.d.ts.map +0 -1
- package/dist/webhooks/revenuecat.js +0 -119
- package/dist/webhooks/revenuecat.js.map +0 -1
- package/docs/revenuecat-setup.md +0 -89
- package/landing/src/app/api/keys/route.ts +0 -71
- package/landing/src/app/api/log/route.ts +0 -37
- package/landing/src/app/api/stats/route.ts +0 -37
- package/landing/src/app/page.tsx.bak +0 -567
- package/landing/src/components/AddKeyModal.tsx +0 -159
- package/newsletter-template.html +0 -71
- package/outreach/OUTREACH-SYSTEM.md +0 -211
- package/outreach/email-template.html +0 -179
- package/outreach/targets.md +0 -133
- package/src/registry/parse_apis.py +0 -146
- package/src/revenuecat.ts +0 -239
- package/src/webhooks/revenuecat.ts +0 -187
- /package/{backend/convex → convex}/README.md +0 -0
- /package/{backend/convex → convex}/_generated/api.js +0 -0
- /package/{backend/convex → convex}/_generated/dataModel.d.ts +0 -0
- /package/{backend/convex → convex}/_generated/server.d.ts +0 -0
- /package/{backend/convex → convex}/_generated/server.js +0 -0
- /package/{backend/convex → convex}/tsconfig.json +0 -0
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
name: "🦞 Add Your API"
|
|
2
|
-
description: Submit your API to the APIClaw marketplace
|
|
3
|
-
title: "[API] "
|
|
4
|
-
labels: ["api-submission", "triage"]
|
|
5
|
-
body:
|
|
6
|
-
- type: markdown
|
|
7
|
-
attributes:
|
|
8
|
-
value: |
|
|
9
|
-
Thanks for submitting your API to APIClaw! 🦞
|
|
10
|
-
|
|
11
|
-
We'll review your submission and get back to you within a few days.
|
|
12
|
-
|
|
13
|
-
- type: input
|
|
14
|
-
id: api_name
|
|
15
|
-
attributes:
|
|
16
|
-
label: API Name
|
|
17
|
-
description: The name of your API
|
|
18
|
-
placeholder: "e.g., Acme Email API"
|
|
19
|
-
validations:
|
|
20
|
-
required: true
|
|
21
|
-
|
|
22
|
-
- type: input
|
|
23
|
-
id: api_url
|
|
24
|
-
attributes:
|
|
25
|
-
label: API URL
|
|
26
|
-
description: Link to your API documentation or homepage
|
|
27
|
-
placeholder: "https://api.example.com"
|
|
28
|
-
validations:
|
|
29
|
-
required: true
|
|
30
|
-
|
|
31
|
-
- type: textarea
|
|
32
|
-
id: description
|
|
33
|
-
attributes:
|
|
34
|
-
label: Description
|
|
35
|
-
description: What does your API do? (2-3 sentences)
|
|
36
|
-
placeholder: "Our API enables developers to..."
|
|
37
|
-
validations:
|
|
38
|
-
required: true
|
|
39
|
-
|
|
40
|
-
- type: dropdown
|
|
41
|
-
id: category
|
|
42
|
-
attributes:
|
|
43
|
-
label: Category
|
|
44
|
-
description: Primary category for your API
|
|
45
|
-
options:
|
|
46
|
-
- SMS/Messaging
|
|
47
|
-
- Email
|
|
48
|
-
- Search
|
|
49
|
-
- LLM/AI
|
|
50
|
-
- TTS/Voice
|
|
51
|
-
- Payments
|
|
52
|
-
- Storage
|
|
53
|
-
- Auth/Identity
|
|
54
|
-
- Analytics
|
|
55
|
-
- Other
|
|
56
|
-
validations:
|
|
57
|
-
required: true
|
|
58
|
-
|
|
59
|
-
- type: dropdown
|
|
60
|
-
id: auth_type
|
|
61
|
-
attributes:
|
|
62
|
-
label: Authentication Type
|
|
63
|
-
description: How do users authenticate?
|
|
64
|
-
options:
|
|
65
|
-
- API Key
|
|
66
|
-
- OAuth 2.0
|
|
67
|
-
- Bearer Token
|
|
68
|
-
- Basic Auth
|
|
69
|
-
- No Auth
|
|
70
|
-
- Other
|
|
71
|
-
validations:
|
|
72
|
-
required: true
|
|
73
|
-
|
|
74
|
-
- type: dropdown
|
|
75
|
-
id: pricing
|
|
76
|
-
attributes:
|
|
77
|
-
label: Pricing Model
|
|
78
|
-
description: How is your API priced?
|
|
79
|
-
options:
|
|
80
|
-
- Free
|
|
81
|
-
- Freemium
|
|
82
|
-
- Pay-as-you-go
|
|
83
|
-
- Subscription
|
|
84
|
-
- Enterprise only
|
|
85
|
-
validations:
|
|
86
|
-
required: true
|
|
87
|
-
|
|
88
|
-
- type: input
|
|
89
|
-
id: regions
|
|
90
|
-
attributes:
|
|
91
|
-
label: Supported Regions
|
|
92
|
-
description: Where is your API available?
|
|
93
|
-
placeholder: "Global, EU, US, etc."
|
|
94
|
-
validations:
|
|
95
|
-
required: false
|
|
96
|
-
|
|
97
|
-
- type: textarea
|
|
98
|
-
id: additional
|
|
99
|
-
attributes:
|
|
100
|
-
label: Additional Information
|
|
101
|
-
description: Anything else we should know?
|
|
102
|
-
placeholder: "Rate limits, compliance certifications, special features..."
|
|
103
|
-
validations:
|
|
104
|
-
required: false
|
|
105
|
-
|
|
106
|
-
- type: input
|
|
107
|
-
id: contact
|
|
108
|
-
attributes:
|
|
109
|
-
label: Contact Email
|
|
110
|
-
description: Email for follow-up questions
|
|
111
|
-
placeholder: "api-team@example.com"
|
|
112
|
-
validations:
|
|
113
|
-
required: true
|
|
114
|
-
|
|
115
|
-
- type: checkboxes
|
|
116
|
-
id: terms
|
|
117
|
-
attributes:
|
|
118
|
-
label: Confirmation
|
|
119
|
-
options:
|
|
120
|
-
- label: I confirm this API has public documentation
|
|
121
|
-
required: true
|
|
122
|
-
- label: I am authorized to submit this API on behalf of the provider
|
|
123
|
-
required: true
|
package/BRIEFING.md
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# APIClaw — Symbot Briefing
|
|
2
|
-
|
|
3
|
-
**Vad det är:** Agent-native API discovery & purchasing via MCP
|
|
4
|
-
**Status:** AKTIVT BYGGE — feedback välkommet
|
|
5
|
-
|
|
6
|
-
## Länkar
|
|
7
|
-
- **Landing:** https://landing-peach-delta.vercel.app
|
|
8
|
-
- **GitHub:** https://github.com/nordsym/apiclaw
|
|
9
|
-
- **Stripe Pro ($99/mo):** https://buy.stripe.com/fZu3cx3W48uC0mo1CMcMM0g
|
|
10
|
-
- **Convex Backend:** https://blessed-chicken-640.convex.cloud
|
|
11
|
-
|
|
12
|
-
## Vad det gör
|
|
13
|
-
1. Agenter frågar "I need SMS API"
|
|
14
|
-
2. APIClaw returnerar rankade alternativ med pricing
|
|
15
|
-
3. Agent köper access → får credentials direkt
|
|
16
|
-
4. Ingen dashboard, ingen signup
|
|
17
|
-
|
|
18
|
-
## Stats
|
|
19
|
-
- 1,400+ APIs i registret
|
|
20
|
-
- 51 kategorier
|
|
21
|
-
- Free (5% fee) / Pro $99/mo (2% fee)
|
|
22
|
-
- USDC on Base (Coinbase Agent Wallet)
|
|
23
|
-
|
|
24
|
-
## Om någon frågar
|
|
25
|
-
- Välkomna feedback!
|
|
26
|
-
- Logga deras intresse/frågor
|
|
27
|
-
- Erbjud att visa demo eller förklara mer
|
|
28
|
-
- Om de vill lista sin API → github.com/nordsym/apiclaw/issues/new?template=add-api.yml
|
|
29
|
-
|
|
30
|
-
## Del av OpenClaw-universumet 🦞
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { v } from "convex/values";
|
|
2
|
-
import { mutation, query } from "./_generated/server";
|
|
3
|
-
|
|
4
|
-
// Add a new API key
|
|
5
|
-
export const add = mutation({
|
|
6
|
-
args: {
|
|
7
|
-
userEmail: v.string(),
|
|
8
|
-
apiId: v.string(),
|
|
9
|
-
apiName: v.string(),
|
|
10
|
-
keyName: v.string(),
|
|
11
|
-
keyValue: v.string(),
|
|
12
|
-
},
|
|
13
|
-
handler: async (ctx, args) => {
|
|
14
|
-
const id = await ctx.db.insert("apiKeys", {
|
|
15
|
-
userEmail: args.userEmail,
|
|
16
|
-
apiId: args.apiId,
|
|
17
|
-
apiName: args.apiName,
|
|
18
|
-
keyName: args.keyName,
|
|
19
|
-
keyValue: args.keyValue,
|
|
20
|
-
createdAt: Date.now(),
|
|
21
|
-
});
|
|
22
|
-
return id;
|
|
23
|
-
},
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
// Get all keys for a user
|
|
27
|
-
export const listByEmail = query({
|
|
28
|
-
args: { userEmail: v.string() },
|
|
29
|
-
handler: async (ctx, args) => {
|
|
30
|
-
const keys = await ctx.db
|
|
31
|
-
.query("apiKeys")
|
|
32
|
-
.withIndex("by_email", (q) => q.eq("userEmail", args.userEmail))
|
|
33
|
-
.collect();
|
|
34
|
-
// Return without exposing full key values
|
|
35
|
-
return keys.map((k) => ({
|
|
36
|
-
...k,
|
|
37
|
-
keyValue: k.keyValue.slice(0, 4) + "..." + k.keyValue.slice(-4),
|
|
38
|
-
_fullKey: undefined, // never expose
|
|
39
|
-
}));
|
|
40
|
-
},
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
// Get a specific key (for agent use - requires auth in prod)
|
|
44
|
-
export const get = query({
|
|
45
|
-
args: {
|
|
46
|
-
userEmail: v.string(),
|
|
47
|
-
apiId: v.string(),
|
|
48
|
-
},
|
|
49
|
-
handler: async (ctx, args) => {
|
|
50
|
-
const keys = await ctx.db
|
|
51
|
-
.query("apiKeys")
|
|
52
|
-
.withIndex("by_email_and_api", (q) =>
|
|
53
|
-
q.eq("userEmail", args.userEmail).eq("apiId", args.apiId)
|
|
54
|
-
)
|
|
55
|
-
.collect();
|
|
56
|
-
return keys;
|
|
57
|
-
},
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// Delete a key
|
|
61
|
-
export const remove = mutation({
|
|
62
|
-
args: { id: v.id("apiKeys") },
|
|
63
|
-
handler: async (ctx, args) => {
|
|
64
|
-
await ctx.db.delete(args.id);
|
|
65
|
-
return { success: true };
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// Update last used timestamp
|
|
70
|
-
export const markUsed = mutation({
|
|
71
|
-
args: { id: v.id("apiKeys") },
|
|
72
|
-
handler: async (ctx, args) => {
|
|
73
|
-
await ctx.db.patch(args.id, { lastUsed: Date.now() });
|
|
74
|
-
},
|
|
75
|
-
});
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { query, mutation } from "./_generated/server";
|
|
2
|
-
import { v } from "convex/values";
|
|
3
|
-
|
|
4
|
-
// Create new API purchase
|
|
5
|
-
export const create = mutation({
|
|
6
|
-
args: {
|
|
7
|
-
userId: v.id("users"),
|
|
8
|
-
apiId: v.string(),
|
|
9
|
-
credentials: v.any(),
|
|
10
|
-
expiresAt: v.optional(v.number()),
|
|
11
|
-
},
|
|
12
|
-
handler: async (ctx, args) => {
|
|
13
|
-
const purchaseId = await ctx.db.insert("purchases", {
|
|
14
|
-
userId: args.userId,
|
|
15
|
-
apiId: args.apiId,
|
|
16
|
-
credentials: args.credentials,
|
|
17
|
-
status: "active",
|
|
18
|
-
purchasedAt: Date.now(),
|
|
19
|
-
expiresAt: args.expiresAt,
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
return purchaseId;
|
|
23
|
-
},
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
// List user's active purchases
|
|
27
|
-
export const listByUser = query({
|
|
28
|
-
args: {
|
|
29
|
-
userId: v.id("users"),
|
|
30
|
-
},
|
|
31
|
-
handler: async (ctx, args) => {
|
|
32
|
-
const purchases = await ctx.db
|
|
33
|
-
.query("purchases")
|
|
34
|
-
.withIndex("by_user", (q) => q.eq("userId", args.userId))
|
|
35
|
-
.collect();
|
|
36
|
-
|
|
37
|
-
// Filter active only
|
|
38
|
-
return purchases.filter((p) => p.status === "active");
|
|
39
|
-
},
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
// Get purchase by user and API
|
|
43
|
-
export const getByUserAndApi = query({
|
|
44
|
-
args: {
|
|
45
|
-
userId: v.id("users"),
|
|
46
|
-
apiId: v.string(),
|
|
47
|
-
},
|
|
48
|
-
handler: async (ctx, args) => {
|
|
49
|
-
return await ctx.db
|
|
50
|
-
.query("purchases")
|
|
51
|
-
.withIndex("by_user_and_api", (q) =>
|
|
52
|
-
q.eq("userId", args.userId).eq("apiId", args.apiId)
|
|
53
|
-
)
|
|
54
|
-
.first();
|
|
55
|
-
},
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// Update purchase status
|
|
59
|
-
export const updateStatus = mutation({
|
|
60
|
-
args: {
|
|
61
|
-
purchaseId: v.id("purchases"),
|
|
62
|
-
status: v.string(),
|
|
63
|
-
},
|
|
64
|
-
handler: async (ctx, args) => {
|
|
65
|
-
await ctx.db.patch(args.purchaseId, { status: args.status });
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// Get all purchases (for admin)
|
|
70
|
-
export const listAll = query({
|
|
71
|
-
handler: async (ctx) => {
|
|
72
|
-
return await ctx.db.query("purchases").collect();
|
|
73
|
-
},
|
|
74
|
-
});
|
package/backend/convex/schema.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { defineSchema, defineTable } from "convex/server";
|
|
2
|
-
import { v } from "convex/values";
|
|
3
|
-
|
|
4
|
-
export default defineSchema({
|
|
5
|
-
// Users
|
|
6
|
-
users: defineTable({
|
|
7
|
-
clerkId: v.optional(v.string()),
|
|
8
|
-
email: v.string(),
|
|
9
|
-
credits: v.number(), // in cents
|
|
10
|
-
createdAt: v.number(),
|
|
11
|
-
}).index("by_email", ["email"])
|
|
12
|
-
.index("by_clerkId", ["clerkId"]),
|
|
13
|
-
|
|
14
|
-
// API Purchases
|
|
15
|
-
purchases: defineTable({
|
|
16
|
-
userId: v.id("users"),
|
|
17
|
-
apiId: v.string(), // "46elks", "resend", etc
|
|
18
|
-
credentials: v.any(), // encrypted or reference
|
|
19
|
-
status: v.string(), // "active", "expired"
|
|
20
|
-
purchasedAt: v.number(),
|
|
21
|
-
expiresAt: v.optional(v.number()),
|
|
22
|
-
}).index("by_user", ["userId"])
|
|
23
|
-
.index("by_user_and_api", ["userId", "apiId"]),
|
|
24
|
-
|
|
25
|
-
// Transactions
|
|
26
|
-
transactions: defineTable({
|
|
27
|
-
userId: v.id("users"),
|
|
28
|
-
type: v.string(), // "credit_add", "api_purchase"
|
|
29
|
-
amount: v.number(),
|
|
30
|
-
description: v.string(),
|
|
31
|
-
createdAt: v.number(),
|
|
32
|
-
}).index("by_user", ["userId"]),
|
|
33
|
-
|
|
34
|
-
// BYOK - Bring Your Own Keys
|
|
35
|
-
apiKeys: defineTable({
|
|
36
|
-
userEmail: v.string(),
|
|
37
|
-
apiId: v.string(),
|
|
38
|
-
apiName: v.string(),
|
|
39
|
-
keyName: v.string(), // "API Key", "Secret Key", etc
|
|
40
|
-
keyValue: v.string(), // the actual key (encrypted in prod)
|
|
41
|
-
createdAt: v.number(),
|
|
42
|
-
lastUsed: v.optional(v.number()),
|
|
43
|
-
}).index("by_email", ["userEmail"])
|
|
44
|
-
.index("by_email_and_api", ["userEmail", "apiId"]),
|
|
45
|
-
});
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { query, mutation } from "./_generated/server";
|
|
2
|
-
import { v } from "convex/values";
|
|
3
|
-
|
|
4
|
-
// Log a transaction
|
|
5
|
-
export const log = mutation({
|
|
6
|
-
args: {
|
|
7
|
-
userId: v.id("users"),
|
|
8
|
-
type: v.string(), // "credit_add", "api_purchase", "refund"
|
|
9
|
-
amount: v.number(),
|
|
10
|
-
description: v.string(),
|
|
11
|
-
},
|
|
12
|
-
handler: async (ctx, args) => {
|
|
13
|
-
const transactionId = await ctx.db.insert("transactions", {
|
|
14
|
-
userId: args.userId,
|
|
15
|
-
type: args.type,
|
|
16
|
-
amount: args.amount,
|
|
17
|
-
description: args.description,
|
|
18
|
-
createdAt: Date.now(),
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
return transactionId;
|
|
22
|
-
},
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
// Get transaction history for user
|
|
26
|
-
export const listByUser = query({
|
|
27
|
-
args: {
|
|
28
|
-
userId: v.id("users"),
|
|
29
|
-
limit: v.optional(v.number()),
|
|
30
|
-
},
|
|
31
|
-
handler: async (ctx, args) => {
|
|
32
|
-
const query = ctx.db
|
|
33
|
-
.query("transactions")
|
|
34
|
-
.withIndex("by_user", (q) => q.eq("userId", args.userId))
|
|
35
|
-
.order("desc");
|
|
36
|
-
|
|
37
|
-
if (args.limit) {
|
|
38
|
-
return await query.take(args.limit);
|
|
39
|
-
}
|
|
40
|
-
return await query.collect();
|
|
41
|
-
},
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
// Get all transactions (for admin)
|
|
45
|
-
export const listAll = query({
|
|
46
|
-
args: {
|
|
47
|
-
limit: v.optional(v.number()),
|
|
48
|
-
},
|
|
49
|
-
handler: async (ctx, args) => {
|
|
50
|
-
const query = ctx.db.query("transactions").order("desc");
|
|
51
|
-
|
|
52
|
-
if (args.limit) {
|
|
53
|
-
return await query.take(args.limit);
|
|
54
|
-
}
|
|
55
|
-
return await query.collect();
|
|
56
|
-
},
|
|
57
|
-
});
|
package/backend/convex/users.ts
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { query, mutation } from "./_generated/server";
|
|
2
|
-
import { v } from "convex/values";
|
|
3
|
-
|
|
4
|
-
// Get user by email, create if not exists
|
|
5
|
-
export const getOrCreate = mutation({
|
|
6
|
-
args: {
|
|
7
|
-
email: v.string(),
|
|
8
|
-
clerkId: v.optional(v.string()),
|
|
9
|
-
},
|
|
10
|
-
handler: async (ctx, args) => {
|
|
11
|
-
// Try to find existing user
|
|
12
|
-
const existing = await ctx.db
|
|
13
|
-
.query("users")
|
|
14
|
-
.withIndex("by_email", (q) => q.eq("email", args.email))
|
|
15
|
-
.first();
|
|
16
|
-
|
|
17
|
-
if (existing) {
|
|
18
|
-
// Update clerkId if provided and not set
|
|
19
|
-
if (args.clerkId && !existing.clerkId) {
|
|
20
|
-
await ctx.db.patch(existing._id, { clerkId: args.clerkId });
|
|
21
|
-
}
|
|
22
|
-
return existing._id;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Create new user
|
|
26
|
-
const userId = await ctx.db.insert("users", {
|
|
27
|
-
email: args.email,
|
|
28
|
-
clerkId: args.clerkId,
|
|
29
|
-
credits: 0,
|
|
30
|
-
createdAt: Date.now(),
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
return userId;
|
|
34
|
-
},
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
// Add credits to user
|
|
38
|
-
export const addCredits = mutation({
|
|
39
|
-
args: {
|
|
40
|
-
userId: v.id("users"),
|
|
41
|
-
amount: v.number(), // in cents, positive or negative
|
|
42
|
-
},
|
|
43
|
-
handler: async (ctx, args) => {
|
|
44
|
-
const user = await ctx.db.get(args.userId);
|
|
45
|
-
if (!user) {
|
|
46
|
-
throw new Error("User not found");
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const newBalance = user.credits + args.amount;
|
|
50
|
-
if (newBalance < 0) {
|
|
51
|
-
throw new Error("Insufficient credits");
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
await ctx.db.patch(args.userId, { credits: newBalance });
|
|
55
|
-
return newBalance;
|
|
56
|
-
},
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// Get current balance
|
|
60
|
-
export const getBalance = query({
|
|
61
|
-
args: {
|
|
62
|
-
userId: v.id("users"),
|
|
63
|
-
},
|
|
64
|
-
handler: async (ctx, args) => {
|
|
65
|
-
const user = await ctx.db.get(args.userId);
|
|
66
|
-
if (!user) {
|
|
67
|
-
throw new Error("User not found");
|
|
68
|
-
}
|
|
69
|
-
return user.credits;
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
// Get user by ID
|
|
74
|
-
export const get = query({
|
|
75
|
-
args: {
|
|
76
|
-
userId: v.id("users"),
|
|
77
|
-
},
|
|
78
|
-
handler: async (ctx, args) => {
|
|
79
|
-
return await ctx.db.get(args.userId);
|
|
80
|
-
},
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
// Get user by email
|
|
84
|
-
export const getByEmail = query({
|
|
85
|
-
args: {
|
|
86
|
-
email: v.string(),
|
|
87
|
-
},
|
|
88
|
-
handler: async (ctx, args) => {
|
|
89
|
-
return await ctx.db
|
|
90
|
-
.query("users")
|
|
91
|
-
.withIndex("by_email", (q) => q.eq("email", args.email))
|
|
92
|
-
.first();
|
|
93
|
-
},
|
|
94
|
-
});
|